@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.
Files changed (238) hide show
  1. package/.agent-src/commands/agents.md +1 -1
  2. package/.agent-src/commands/bug-fix.md +1 -1
  3. package/.agent-src/commands/bug-investigate.md +2 -2
  4. package/.agent-src/commands/chat-history/import.md +60 -64
  5. package/.agent-src/commands/compress.md +12 -0
  6. package/.agent-src/commands/context/create.md +2 -2
  7. package/.agent-src/commands/context.md +1 -1
  8. package/.agent-src/commands/copilot-agents.md +1 -1
  9. package/.agent-src/commands/council/default.md +17 -5
  10. package/.agent-src/commands/council.md +1 -1
  11. package/.agent-src/commands/e2e-heal.md +1 -1
  12. package/.agent-src/commands/e2e-plan.md +1 -1
  13. package/.agent-src/commands/feature/dev.md +3 -3
  14. package/.agent-src/commands/feature.md +1 -1
  15. package/.agent-src/commands/fix/seeder.md +2 -2
  16. package/.agent-src/commands/fix.md +1 -1
  17. package/.agent-src/commands/jira-ticket.md +1 -1
  18. package/.agent-src/commands/judge.md +2 -2
  19. package/.agent-src/commands/memory.md +1 -1
  20. package/.agent-src/commands/mode.md +5 -5
  21. package/.agent-src/commands/module.md +1 -1
  22. package/.agent-src/commands/onboard.md +4 -4
  23. package/.agent-src/commands/optimize/augmentignore.md +1 -1
  24. package/.agent-src/commands/optimize-prompt.md +61 -0
  25. package/.agent-src/commands/optimize.md +1 -1
  26. package/.agent-src/commands/override.md +1 -1
  27. package/.agent-src/commands/review-changes.md +1 -1
  28. package/.agent-src/commands/review-routing.md +1 -1
  29. package/.agent-src/commands/roadmap.md +1 -1
  30. package/.agent-src/commands/set-cost-profile.md +3 -3
  31. package/.agent-src/commands/sync-agent-settings.md +2 -2
  32. package/.agent-src/commands/tests/create.md +2 -2
  33. package/.agent-src/commands/tests.md +1 -1
  34. package/.agent-src/commands/threat-model.md +4 -4
  35. package/.agent-src/contexts/authority/commit-mechanics.md +14 -1
  36. package/.agent-src/contexts/authority/destructive-mechanics.md +14 -1
  37. package/.agent-src/contexts/authority/scope-mechanics.md +5 -0
  38. package/.agent-src/contexts/communication/rules-auto/guidelines-mechanics.md +76 -0
  39. package/.agent-src/contexts/communication/rules-auto/slash-command-routing-policy-mechanics.md +1 -1
  40. package/.agent-src/contexts/communication/rules-auto/think-before-action-mechanics.md +98 -0
  41. package/.agent-src/contexts/communication/rules-auto/token-efficiency-mechanics.md +93 -0
  42. package/.agent-src/contexts/communication/rules-auto/user-interaction-mechanics.md +128 -5
  43. package/.agent-src/contexts/execution/autonomy-mechanics.md +44 -0
  44. package/.agent-src/contexts/model-recommendations.md +2 -2
  45. package/.agent-src/contexts/override-system.md +1 -1
  46. package/.agent-src/personas/product-owner.md +2 -2
  47. package/.agent-src/personas/qa.md +1 -1
  48. package/.agent-src/rules/agent-authority.md +5 -6
  49. package/.agent-src/rules/agent-docs.md +11 -53
  50. package/.agent-src/rules/analysis-skill-routing.md +10 -40
  51. package/.agent-src/rules/architecture.md +6 -1
  52. package/.agent-src/rules/artifact-drafting-protocol.md +5 -0
  53. package/.agent-src/rules/artifact-engagement-recording.md +23 -59
  54. package/.agent-src/rules/ask-when-uncertain.md +24 -47
  55. package/.agent-src/rules/augment-portability.md +14 -62
  56. package/.agent-src/rules/augment-source-of-truth.md +10 -1
  57. package/.agent-src/rules/autonomous-execution.md +17 -98
  58. package/.agent-src/rules/capture-learnings.md +9 -80
  59. package/.agent-src/rules/cli-output-handling.md +12 -42
  60. package/.agent-src/rules/command-suggestion-policy.md +25 -73
  61. package/.agent-src/rules/commit-conventions.md +9 -58
  62. package/.agent-src/rules/commit-policy.md +16 -47
  63. package/.agent-src/rules/context-hygiene.md +5 -0
  64. package/.agent-src/rules/direct-answers.md +21 -50
  65. package/.agent-src/rules/docker-commands.md +11 -45
  66. package/.agent-src/rules/docs-sync.md +10 -56
  67. package/.agent-src/rules/downstream-changes.md +5 -0
  68. package/.agent-src/rules/e2e-testing.md +9 -44
  69. package/.agent-src/rules/guidelines.md +13 -75
  70. package/.agent-src/rules/improve-before-implement.md +10 -2
  71. package/.agent-src/rules/language-and-tone.md +41 -106
  72. package/.agent-src/rules/laravel-translations.md +11 -40
  73. package/.agent-src/rules/markdown-safe-codeblocks.md +4 -0
  74. package/.agent-src/rules/minimal-safe-diff.md +4 -0
  75. package/.agent-src/rules/missing-tool-handling.md +4 -0
  76. package/.agent-src/rules/model-recommendation.md +9 -61
  77. package/.agent-src/rules/no-attribution-footers.md +5 -0
  78. package/.agent-src/rules/no-cheap-questions.md +11 -27
  79. package/.agent-src/rules/no-council-references.md +76 -0
  80. package/.agent-src/rules/no-roadmap-references.md +7 -0
  81. package/.agent-src/rules/non-destructive-by-default.md +13 -43
  82. package/.agent-src/rules/onboarding-gate.md +9 -117
  83. package/.agent-src/rules/package-ci-checks.md +10 -37
  84. package/.agent-src/rules/php-coding.md +10 -55
  85. package/.agent-src/rules/preservation-guard.md +9 -0
  86. package/.agent-src/rules/review-routing-awareness.md +9 -97
  87. package/.agent-src/rules/reviewer-awareness.md +8 -83
  88. package/.agent-src/rules/roadmap-progress-sync.md +7 -170
  89. package/.agent-src/rules/role-mode-adherence.md +6 -2
  90. package/.agent-src/rules/rule-type-governance.md +8 -66
  91. package/.agent-src/rules/runtime-safety.md +5 -0
  92. package/.agent-src/rules/scope-control.md +17 -62
  93. package/.agent-src/rules/security-sensitive-stop.md +7 -1
  94. package/.agent-src/rules/size-enforcement.md +6 -1
  95. package/.agent-src/rules/skill-improvement-trigger.md +9 -49
  96. package/.agent-src/rules/skill-quality.md +7 -113
  97. package/.agent-src/rules/slash-command-routing-policy.md +11 -63
  98. package/.agent-src/rules/think-before-action.md +22 -87
  99. package/.agent-src/rules/token-efficiency.md +10 -74
  100. package/.agent-src/rules/token-optimizer-maintenance.md +68 -0
  101. package/.agent-src/rules/tool-safety.md +4 -0
  102. package/.agent-src/rules/ui-audit-gate.md +25 -61
  103. package/.agent-src/rules/upstream-proposal.md +9 -67
  104. package/.agent-src/rules/user-interaction.md +22 -108
  105. package/.agent-src/rules/verify-before-complete.md +1 -1
  106. package/.agent-src/skills/agent-docs-writing/SKILL.md +1 -1
  107. package/.agent-src/skills/ai-council/SKILL.md +65 -0
  108. package/.agent-src/skills/analysis-autonomous-mode/SKILL.md +1 -1
  109. package/.agent-src/skills/analysis-skill-router/SKILL.md +3 -3
  110. package/.agent-src/skills/artisan-commands/SKILL.md +2 -2
  111. package/.agent-src/skills/authz-review/SKILL.md +1 -1
  112. package/.agent-src/skills/aws-infrastructure/SKILL.md +5 -5
  113. package/.agent-src/skills/blast-radius-analyzer/SKILL.md +8 -8
  114. package/.agent-src/skills/bug-analyzer/SKILL.md +5 -5
  115. package/.agent-src/skills/code-refactoring/SKILL.md +4 -4
  116. package/.agent-src/skills/code-review/SKILL.md +2 -2
  117. package/.agent-src/skills/command-writing/SKILL.md +11 -0
  118. package/.agent-src/skills/composer-packages/SKILL.md +2 -2
  119. package/.agent-src/skills/context-authoring/SKILL.md +11 -0
  120. package/.agent-src/skills/context-document/SKILL.md +1 -1
  121. package/.agent-src/skills/copilot-agents-optimization/SKILL.md +23 -0
  122. package/.agent-src/skills/copilot-config/SKILL.md +1 -1
  123. package/.agent-src/skills/dependency-upgrade/SKILL.md +2 -2
  124. package/.agent-src/skills/devcontainer/SKILL.md +2 -2
  125. package/.agent-src/skills/developer-like-execution/SKILL.md +1 -1
  126. package/.agent-src/skills/docker/SKILL.md +1 -1
  127. package/.agent-src/skills/dto-creator/SKILL.md +1 -1
  128. package/.agent-src/skills/estimate-ticket/SKILL.md +2 -2
  129. package/.agent-src/skills/fe-design/SKILL.md +4 -4
  130. package/.agent-src/skills/feature-planning/SKILL.md +5 -5
  131. package/.agent-src/skills/funnel-analysis/SKILL.md +1 -1
  132. package/.agent-src/skills/laravel/SKILL.md +1 -1
  133. package/.agent-src/skills/laravel-notifications/SKILL.md +5 -5
  134. package/.agent-src/skills/laravel-pennant/SKILL.md +1 -1
  135. package/.agent-src/skills/laravel-pulse/SKILL.md +4 -4
  136. package/.agent-src/skills/laravel-reverb/SKILL.md +2 -2
  137. package/.agent-src/skills/laravel-scheduling/SKILL.md +1 -1
  138. package/.agent-src/skills/migration-creator/SKILL.md +7 -7
  139. package/.agent-src/skills/multi-tenancy/SKILL.md +8 -8
  140. package/.agent-src/skills/performance-analysis/SKILL.md +3 -3
  141. package/.agent-src/skills/pest-testing/SKILL.md +6 -6
  142. package/.agent-src/skills/php-service/SKILL.md +2 -2
  143. package/.agent-src/skills/project-analysis-hypothesis-driven/SKILL.md +3 -3
  144. package/.agent-src/skills/project-analysis-react/SKILL.md +1 -1
  145. package/.agent-src/skills/project-analysis-symfony/SKILL.md +1 -1
  146. package/.agent-src/skills/project-analysis-zend-laminas/SKILL.md +2 -2
  147. package/.agent-src/skills/project-analyzer/SKILL.md +4 -4
  148. package/.agent-src/skills/prompt-optimizer/SKILL.md +108 -0
  149. package/.agent-src/skills/readme-reviewer/SKILL.md +1 -1
  150. package/.agent-src/skills/rule-writing/SKILL.md +33 -0
  151. package/.agent-src/skills/sentry-integration/SKILL.md +1 -1
  152. package/.agent-src/skills/skill-writing/SKILL.md +14 -0
  153. package/.agent-src/skills/terraform/SKILL.md +2 -2
  154. package/.agent-src/skills/terragrunt/SKILL.md +8 -8
  155. package/.agent-src/skills/test-performance/SKILL.md +5 -5
  156. package/.agent-src/skills/threat-modeling/SKILL.md +2 -2
  157. package/.agent-src/skills/token-optimizer/SKILL.md +110 -0
  158. package/.agent-src/skills/universal-project-analysis/SKILL.md +1 -1
  159. package/.agent-src/templates/AGENTS.md +1 -1
  160. package/.agent-src/templates/agent-settings.md +21 -16
  161. package/.agent-src/templates/contexts/tenant-boundaries.md +2 -2
  162. package/.agent-src/templates/contexts.md +1 -1
  163. package/.agent-src/templates/copilot-instructions.md +21 -0
  164. package/.agent-src/templates/copilot-review-instructions.md +76 -0
  165. package/.agent-src/templates/features.md +1 -1
  166. package/.agent-src/templates/rule.md +127 -0
  167. package/.claude-plugin/marketplace.json +4 -1
  168. package/AGENTS.md +32 -5
  169. package/CHANGELOG.md +69 -3
  170. package/README.md +22 -21
  171. package/config/agent-settings.template.yml +44 -10
  172. package/config/gitignore-block.txt +7 -0
  173. package/docs/architecture.md +86 -5
  174. package/docs/catalog.md +16 -6
  175. package/docs/contracts/agent-memory-contract.md +1 -1
  176. package/docs/contracts/context-paths.md +2 -1
  177. package/docs/contracts/file-ownership-matrix.json +354 -500
  178. package/docs/contracts/iron-law-overrides.txt +25 -0
  179. package/docs/contracts/kernel-membership.md +273 -0
  180. package/docs/contracts/load-context-schema.md +26 -11
  181. package/docs/contracts/pilot/agent-authority.md +24 -0
  182. package/docs/contracts/pilot/direct-answers.md +70 -0
  183. package/docs/contracts/pilot/language-and-tone.md +63 -0
  184. package/docs/contracts/rule-classification.md +170 -0
  185. package/docs/contracts/rule-router.md +153 -0
  186. package/docs/customization.md +17 -6
  187. package/docs/decisions/ADR-001-kernel-swap-deferred.md +109 -0
  188. package/docs/decisions/ADR-002-kernel-bucket-overrides.md +124 -0
  189. package/docs/decisions/ADR-rule-kernel-and-router.md +122 -0
  190. package/docs/getting-started.md +2 -2
  191. package/docs/guidelines/agent-infra/roadmap-progress-mechanics.md +176 -0
  192. package/docs/guidelines/agent-infra/rule-type-governance.md +73 -0
  193. package/docs/guidelines/agent-infra/size-and-scope.md +13 -2
  194. package/docs/guidelines/agent-infra/skill-quality-checklist.md +119 -0
  195. package/docs/guidelines/augment-portability-patterns.md +68 -0
  196. package/docs/guidelines/php/php-coding-patterns.md +62 -0
  197. package/package.json +1 -1
  198. package/scripts/_p43_bodies.py +235 -0
  199. package/scripts/_p43_compress.py +118 -0
  200. package/scripts/_p4_migrate.py +199 -0
  201. package/scripts/_pilot_council_question.py +57 -0
  202. package/scripts/_pilot_measure.py +53 -0
  203. package/scripts/ai_council/session.py +107 -5
  204. package/scripts/build_linear_digest.py +3 -5
  205. package/scripts/check_always_budget.py +39 -6
  206. package/scripts/check_compressed_paths.py +213 -0
  207. package/scripts/check_compression.py +15 -0
  208. package/scripts/check_context_paths.py +1 -0
  209. package/scripts/check_council_layout.py +105 -0
  210. package/scripts/check_council_references.py +145 -0
  211. package/scripts/check_portability.py +2 -0
  212. package/scripts/check_references.py +2 -0
  213. package/scripts/check_token_optimizer_freshness.py +131 -0
  214. package/scripts/compile_router.py +148 -0
  215. package/scripts/compress.py +219 -11
  216. package/scripts/council_cli.py +9 -5
  217. package/scripts/council_prune.py +81 -0
  218. package/scripts/count_token_optimizer_usage.sh +54 -0
  219. package/scripts/install.sh +44 -2
  220. package/scripts/iron_law_sha.py +98 -0
  221. package/scripts/lint_load_context.py +35 -5
  222. package/scripts/measure_rule_budget.py +314 -0
  223. package/scripts/prototype_lint_contradictions.py +150 -0
  224. package/scripts/schemas/rule.schema.json +55 -6
  225. package/scripts/skill_linter.py +196 -6
  226. package/scripts/smoke_path_resolution.py +93 -0
  227. package/scripts/validate_frontmatter.py +41 -1
  228. package/.agent-src/contexts/communication/rules-auto/artifact-engagement-recording-mechanics.md +0 -72
  229. package/.agent-src/contexts/communication/rules-auto/augment-portability-mechanics.md +0 -79
  230. package/.agent-src/contexts/communication/rules-auto/cli-output-handling-mechanics.md +0 -87
  231. package/.agent-src/contexts/communication/rules-auto/command-suggestion-policy-mechanics.md +0 -62
  232. package/.agent-src/contexts/communication/rules-auto/docs-sync-mechanics.md +0 -78
  233. package/.agent-src/contexts/communication/rules-auto/package-ci-checks-mechanics.md +0 -85
  234. package/.agent-src/contexts/communication/rules-auto/review-routing-awareness-mechanics.md +0 -65
  235. package/.agent-src/contexts/communication/rules-auto/roadmap-progress-sync-mechanics.md +0 -78
  236. package/.agent-src/contexts/communication/rules-auto/ui-audit-gate-mechanics.md +0 -53
  237. /package/{docs → .agent-src/contexts}/contracts/artifact-engagement-flow.md +0 -0
  238. /package/{docs → .agent-src/contexts}/contracts/command-suggestion-flow.md +0 -0
@@ -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
 
@@ -103,6 +103,8 @@ EXAMPLE_PATH_PATTERNS = [
103
103
  re.compile(r"skills/[\w-]+/SKILL\.md"), # example skill paths in commands
104
104
  re.compile(r"\{"), # template placeholders like {module}
105
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
106
108
  # Forward references inside in-flight planning docs (road-to-
107
109
  # structural-optimization.md and its companion spike protocols).
108
110
  # Each pattern below is removed once the matching phase lands.
@@ -0,0 +1,131 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Token-Optimizer freshness validator.
4
+
5
+ Per `road-to-token-optimization.md` P1.3: parses the catalog table inside
6
+ `.agent-src.uncompressed/skills/token-optimizer/SKILL.md`, verifies every
7
+ cited internal asset exists, and `grep`s the trigger keywords against
8
+ each target file. Fails on missing target OR keyword mismatch.
9
+
10
+ Authoritative-link rows (upstream URLs, planned commands marked TBD) are
11
+ recorded but not fetched — they live and die with their upstream and are
12
+ out of scope for this CI gate.
13
+
14
+ Acceptance: stdlib-only, deterministic, exit 0 = clean / exit 1 = drift.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import re
20
+ import sys
21
+ from pathlib import Path
22
+
23
+ REPO_ROOT = Path(__file__).resolve().parent.parent
24
+ SKILL = REPO_ROOT / ".agent-src.uncompressed" / "skills" / "token-optimizer" / "SKILL.md"
25
+
26
+ # Catalog row pattern: | name | path | keywords | description |
27
+ ROW_RE = re.compile(
28
+ r"^\|\s*`?(?P<name>[^`|]+?)`?\s*\|\s*"
29
+ r"(?P<path>[^|]+?)\s*\|\s*"
30
+ r"(?P<keywords>[^|]+?)\s*\|\s*"
31
+ r"(?P<desc>[^|]+?)\s*\|\s*$"
32
+ )
33
+ KW_RE = re.compile(r"`([^`]+)`")
34
+
35
+
36
+ def parse_catalog(text: str) -> list[dict[str, str]]:
37
+ rows: list[dict[str, str]] = []
38
+ in_catalog = False
39
+ for line in text.splitlines():
40
+ if line.strip().startswith("## "):
41
+ in_catalog = line.strip() == "## Catalog"
42
+ continue
43
+ if not in_catalog:
44
+ continue
45
+ if line.startswith("|---") or line.startswith("| Asset"):
46
+ continue
47
+ m = ROW_RE.match(line)
48
+ if not m:
49
+ continue
50
+ rows.append(
51
+ {
52
+ "name": m["name"].strip(),
53
+ "path": m["path"].strip(),
54
+ "keywords": m["keywords"].strip(),
55
+ "desc": m["desc"].strip(),
56
+ }
57
+ )
58
+ return rows
59
+
60
+
61
+ def is_external(path: str) -> bool:
62
+ p = path.lower()
63
+ return (
64
+ p.startswith("upstream:")
65
+ or p.startswith("http://")
66
+ or p.startswith("https://")
67
+ or p.startswith("tbd")
68
+ or "github.com" in p
69
+ )
70
+
71
+
72
+ def resolve(path: str) -> Path | None:
73
+ if is_external(path):
74
+ return None
75
+ cleaned = path.strip().lstrip("`").rstrip("`")
76
+ cleaned = cleaned.split(")")[0].lstrip("[(")
77
+ candidate = (REPO_ROOT / cleaned).resolve()
78
+ return candidate
79
+
80
+
81
+ def check_row(row: dict[str, str]) -> list[str]:
82
+ errs: list[str] = []
83
+ if is_external(row["path"]):
84
+ return errs
85
+ target = resolve(row["path"])
86
+ if target is None or not target.exists():
87
+ errs.append(f"[{row['name']}] target missing: {row['path']}")
88
+ return errs
89
+ body = target.read_text(encoding="utf-8", errors="replace").lower()
90
+ for kw in KW_RE.findall(row["keywords"]):
91
+ kw_lc = kw.strip().lower()
92
+ if not kw_lc:
93
+ continue
94
+ if kw_lc not in body:
95
+ errs.append(
96
+ f"[{row['name']}] trigger keyword '{kw}' not found in "
97
+ f"{row['path']} — catalog row may be stale"
98
+ )
99
+ return errs
100
+
101
+
102
+ def main() -> int:
103
+ if not SKILL.exists():
104
+ print(f"ERROR: token-optimizer skill not found at {SKILL}", file=sys.stderr)
105
+ return 1
106
+ text = SKILL.read_text(encoding="utf-8")
107
+ rows = parse_catalog(text)
108
+ if not rows:
109
+ print(
110
+ "ERROR: token-optimizer SKILL.md has no parseable catalog rows",
111
+ file=sys.stderr,
112
+ )
113
+ return 1
114
+ all_errs: list[str] = []
115
+ checked = 0
116
+ for row in rows:
117
+ errs = check_row(row)
118
+ all_errs.extend(errs)
119
+ if not is_external(row["path"]):
120
+ checked += 1
121
+ print(
122
+ f"token-optimizer freshness: {len(rows)} catalog rows, "
123
+ f"{checked} internal targets checked, {len(all_errs)} drift signal(s)"
124
+ )
125
+ for e in all_errs:
126
+ print(f" FAIL {e}")
127
+ return 1 if all_errs else 0
128
+
129
+
130
+ if __name__ == "__main__":
131
+ sys.exit(main())
@@ -0,0 +1,148 @@
1
+ #!/usr/bin/env python3
2
+ """Compile rule frontmatter into ``router.json``.
3
+
4
+ Reads ``.agent-src.uncompressed/rules/*.md``; produces deterministic JSON
5
+ mapping kernel + tier-1 + tier-2 rules to their triggers and routed
6
+ artifacts, per ``docs/contracts/rule-router.md``.
7
+
8
+ Stdlib-only, deterministic (sorted keys + sorted lists), idempotent.
9
+ Wired into ``task generate-tools`` after the compress step.
10
+ """
11
+ from __future__ import annotations
12
+
13
+ import json
14
+ import sys
15
+ from pathlib import Path
16
+
17
+ ROOT = Path(__file__).resolve().parent.parent
18
+ RULES_DIR = ROOT / ".agent-src.uncompressed" / "rules"
19
+ OUT_PATH = ROOT / "router.json"
20
+ SCHEMA_VERSION = 1
21
+
22
+ # Maps legacy tier values to the router-canonical names. See
23
+ # docs/contracts/rule-router.md § Backward compatibility.
24
+ LEGACY_TIER_MAP = {
25
+ "1": "tier-1",
26
+ "2": "tier-2",
27
+ "2a": "tier-2",
28
+ "3": "tier-1",
29
+ "mechanical-already": "tier-1",
30
+ "kernel": "kernel",
31
+ "tier-1": "tier-1",
32
+ "tier-2": "tier-2",
33
+ }
34
+
35
+ ALLOWED_TIERS = {"kernel", "tier-1", "tier-2"}
36
+ ALLOWED_PROFILES = {"minimal", "balanced", "full"}
37
+ ALLOWED_TRIGGER_KEYS = {"keyword", "phrase", "intent", "file_pattern",
38
+ "path_prefix", "command"}
39
+
40
+
41
+ def _parse_frontmatter(text: str) -> dict:
42
+ if not text.startswith("---\n"):
43
+ return {}
44
+ end = text.find("\n---", 4)
45
+ if end < 0:
46
+ return {}
47
+ block = text[4:end]
48
+ try:
49
+ import yaml # type: ignore
50
+ data = yaml.safe_load(block) or {}
51
+ return data if isinstance(data, dict) else {}
52
+ except ImportError:
53
+ return _parse_frontmatter_minimal(block)
54
+
55
+
56
+ def _parse_frontmatter_minimal(block: str) -> dict:
57
+ """Minimal YAML parser fallback (flat scalars + simple lists)."""
58
+ out: dict = {}
59
+ cur_key = None
60
+ for raw in block.splitlines():
61
+ line = raw.rstrip()
62
+ if not line or line.lstrip().startswith("#"):
63
+ continue
64
+ if line.startswith(" - ") and cur_key:
65
+ out.setdefault(cur_key, []).append(line[4:].strip())
66
+ elif ":" in line and not line.startswith(" "):
67
+ k, _, v = line.partition(":")
68
+ cur_key = k.strip()
69
+ v = v.strip()
70
+ if v in ("", "[]"):
71
+ out[cur_key] = [] if v == "[]" else None
72
+ else:
73
+ out[cur_key] = v.strip('"').strip("'")
74
+ return out
75
+
76
+
77
+ def _resolve_tier(rule_type: str, raw_tier: str) -> str:
78
+ if rule_type == "always":
79
+ return "kernel"
80
+ return LEGACY_TIER_MAP.get(str(raw_tier), "tier-2")
81
+
82
+
83
+ def _normalize_trigger(item) -> dict | None:
84
+ if not isinstance(item, dict):
85
+ return None
86
+ keys = [k for k in item if k in ALLOWED_TRIGGER_KEYS]
87
+ if len(keys) != 1:
88
+ return None
89
+ return {keys[0]: str(item[keys[0]])}
90
+
91
+
92
+ def _collect(rules_dir: Path) -> dict:
93
+ kernel: list[str] = []
94
+ tiered: dict[str, list[dict]] = {"tier-1": [], "tier-2": []}
95
+ for path in sorted(rules_dir.glob("*.md")):
96
+ fm = _parse_frontmatter(path.read_text(encoding="utf-8"))
97
+ if not fm:
98
+ continue
99
+ rule_id = path.stem
100
+ rule_type = str(fm.get("type", "auto"))
101
+ tier = _resolve_tier(rule_type, fm.get("tier", ""))
102
+ if tier not in ALLOWED_TIERS:
103
+ continue
104
+ if tier == "kernel":
105
+ kernel.append(rule_id)
106
+ continue
107
+ triggers_raw = fm.get("triggers") or []
108
+ triggers = [t for t in (_normalize_trigger(x) for x in triggers_raw) if t]
109
+ routes_to = sorted(str(x) for x in (fm.get("routes_to") or []))
110
+ entry = {"id": rule_id, "triggers": triggers, "routes_to": routes_to}
111
+ tiered[tier].append(entry)
112
+ for k in tiered:
113
+ tiered[k].sort(key=lambda x: x["id"])
114
+ return {"kernel": sorted(kernel), **{k.replace("-", "_"): v for k, v in tiered.items()}}
115
+
116
+
117
+ def build() -> dict:
118
+ collected = _collect(RULES_DIR)
119
+ return {
120
+ "schema_version": SCHEMA_VERSION,
121
+ "kernel": collected["kernel"],
122
+ "tier_1": collected["tier_1"],
123
+ "tier_2": collected["tier_2"],
124
+ "profiles": {
125
+ "minimal": ["__kernel__"],
126
+ "balanced": ["__kernel__", "__tier_1__"],
127
+ "full": ["__kernel__", "__tier_1__", "__tier_2__"],
128
+ },
129
+ }
130
+
131
+
132
+ def main(argv: list[str]) -> int:
133
+ out = build()
134
+ text = json.dumps(out, indent=2, sort_keys=False) + "\n"
135
+ if "--check" in argv:
136
+ if not OUT_PATH.exists() or OUT_PATH.read_text(encoding="utf-8") != text:
137
+ print("router.json out of date — run scripts/compile_router.py", file=sys.stderr)
138
+ return 1
139
+ print("✅ router.json is up to date")
140
+ return 0
141
+ OUT_PATH.write_text(text, encoding="utf-8")
142
+ counts = (len(out["kernel"]), len(out["tier_1"]), len(out["tier_2"]))
143
+ print(f"✅ router.json — kernel={counts[0]} tier-1={counts[1]} tier-2={counts[2]}")
144
+ return 0
145
+
146
+
147
+ if __name__ == "__main__":
148
+ sys.exit(main(sys.argv[1:]))