@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.
Files changed (297) hide show
  1. package/.agent-src/commands/agent-handoff.md +14 -10
  2. package/.agent-src/commands/agents.md +1 -1
  3. package/.agent-src/commands/bug-fix.md +1 -1
  4. package/.agent-src/commands/bug-investigate.md +2 -2
  5. package/.agent-src/commands/chat-history/import.md +166 -0
  6. package/.agent-src/commands/chat-history/learn.md +178 -0
  7. package/.agent-src/commands/chat-history/show.md +17 -18
  8. package/.agent-src/commands/chat-history.md +26 -25
  9. package/.agent-src/commands/compress.md +12 -0
  10. package/.agent-src/commands/context/create.md +2 -2
  11. package/.agent-src/commands/context.md +1 -1
  12. package/.agent-src/commands/copilot-agents.md +1 -1
  13. package/.agent-src/commands/council/default.md +21 -12
  14. package/.agent-src/commands/council.md +1 -1
  15. package/.agent-src/commands/create-pr.md +28 -8
  16. package/.agent-src/commands/e2e-heal.md +1 -1
  17. package/.agent-src/commands/e2e-plan.md +1 -1
  18. package/.agent-src/commands/feature/dev.md +3 -3
  19. package/.agent-src/commands/feature.md +1 -1
  20. package/.agent-src/commands/fix/seeder.md +2 -2
  21. package/.agent-src/commands/fix.md +1 -1
  22. package/.agent-src/commands/jira-ticket.md +1 -1
  23. package/.agent-src/commands/judge.md +2 -2
  24. package/.agent-src/commands/memory.md +1 -1
  25. package/.agent-src/commands/mode.md +5 -5
  26. package/.agent-src/commands/module.md +1 -1
  27. package/.agent-src/commands/onboard.md +4 -4
  28. package/.agent-src/commands/optimize/augmentignore.md +1 -1
  29. package/.agent-src/commands/optimize-prompt.md +61 -0
  30. package/.agent-src/commands/optimize.md +1 -1
  31. package/.agent-src/commands/override.md +1 -1
  32. package/.agent-src/commands/review-changes.md +1 -1
  33. package/.agent-src/commands/review-routing.md +1 -1
  34. package/.agent-src/commands/roadmap.md +1 -1
  35. package/.agent-src/commands/set-cost-profile.md +3 -3
  36. package/.agent-src/commands/sync-agent-settings.md +2 -2
  37. package/.agent-src/commands/sync-gitignore.md +1 -1
  38. package/.agent-src/commands/tests/create.md +2 -2
  39. package/.agent-src/commands/tests.md +1 -1
  40. package/.agent-src/commands/threat-model.md +4 -4
  41. package/.agent-src/contexts/authority/commit-mechanics.md +14 -1
  42. package/.agent-src/contexts/authority/destructive-mechanics.md +14 -1
  43. package/.agent-src/contexts/authority/scope-mechanics.md +5 -0
  44. package/.agent-src/contexts/communication/rules-auto/guidelines-mechanics.md +76 -0
  45. package/.agent-src/contexts/communication/rules-auto/skill-quality-mechanics.md +76 -0
  46. package/.agent-src/contexts/communication/rules-auto/slash-command-routing-policy-mechanics.md +4 -4
  47. package/.agent-src/contexts/communication/rules-auto/think-before-action-mechanics.md +98 -0
  48. package/.agent-src/contexts/communication/rules-auto/token-efficiency-mechanics.md +93 -0
  49. package/.agent-src/contexts/communication/rules-auto/user-interaction-mechanics.md +125 -9
  50. package/.agent-src/contexts/execution/autonomy-mechanics.md +44 -0
  51. package/.agent-src/contexts/model-recommendations.md +2 -2
  52. package/.agent-src/contexts/override-system.md +1 -1
  53. package/.agent-src/personas/product-owner.md +2 -2
  54. package/.agent-src/personas/qa.md +1 -1
  55. package/.agent-src/rules/agent-authority.md +5 -6
  56. package/.agent-src/rules/agent-docs.md +11 -53
  57. package/.agent-src/rules/analysis-skill-routing.md +10 -40
  58. package/.agent-src/rules/architecture.md +6 -1
  59. package/.agent-src/rules/artifact-drafting-protocol.md +5 -0
  60. package/.agent-src/rules/artifact-engagement-recording.md +23 -59
  61. package/.agent-src/rules/ask-when-uncertain.md +24 -47
  62. package/.agent-src/rules/augment-portability.md +14 -62
  63. package/.agent-src/rules/augment-source-of-truth.md +10 -1
  64. package/.agent-src/rules/autonomous-execution.md +17 -98
  65. package/.agent-src/rules/capture-learnings.md +9 -80
  66. package/.agent-src/rules/cli-output-handling.md +12 -42
  67. package/.agent-src/rules/command-suggestion-policy.md +25 -73
  68. package/.agent-src/rules/commit-conventions.md +9 -58
  69. package/.agent-src/rules/commit-policy.md +16 -47
  70. package/.agent-src/rules/context-hygiene.md +5 -0
  71. package/.agent-src/rules/direct-answers.md +21 -42
  72. package/.agent-src/rules/docker-commands.md +11 -45
  73. package/.agent-src/rules/docs-sync.md +10 -56
  74. package/.agent-src/rules/downstream-changes.md +5 -0
  75. package/.agent-src/rules/e2e-testing.md +9 -44
  76. package/.agent-src/rules/guidelines.md +13 -75
  77. package/.agent-src/rules/improve-before-implement.md +10 -2
  78. package/.agent-src/rules/language-and-tone.md +35 -69
  79. package/.agent-src/rules/laravel-translations.md +11 -40
  80. package/.agent-src/rules/markdown-safe-codeblocks.md +4 -0
  81. package/.agent-src/rules/minimal-safe-diff.md +4 -0
  82. package/.agent-src/rules/missing-tool-handling.md +4 -0
  83. package/.agent-src/rules/model-recommendation.md +9 -61
  84. package/.agent-src/rules/no-attribution-footers.md +53 -0
  85. package/.agent-src/rules/no-cheap-questions.md +11 -27
  86. package/.agent-src/rules/no-council-references.md +76 -0
  87. package/.agent-src/rules/no-roadmap-references.md +8 -1
  88. package/.agent-src/rules/non-destructive-by-default.md +13 -43
  89. package/.agent-src/rules/onboarding-gate.md +9 -117
  90. package/.agent-src/rules/package-ci-checks.md +10 -37
  91. package/.agent-src/rules/php-coding.md +10 -55
  92. package/.agent-src/rules/preservation-guard.md +9 -0
  93. package/.agent-src/rules/review-routing-awareness.md +9 -97
  94. package/.agent-src/rules/reviewer-awareness.md +8 -83
  95. package/.agent-src/rules/roadmap-progress-sync.md +7 -170
  96. package/.agent-src/rules/role-mode-adherence.md +6 -2
  97. package/.agent-src/rules/rule-type-governance.md +8 -66
  98. package/.agent-src/rules/runtime-safety.md +5 -0
  99. package/.agent-src/rules/scope-control.md +17 -62
  100. package/.agent-src/rules/security-sensitive-stop.md +7 -1
  101. package/.agent-src/rules/size-enforcement.md +6 -1
  102. package/.agent-src/rules/skill-improvement-trigger.md +9 -49
  103. package/.agent-src/rules/skill-quality.md +7 -64
  104. package/.agent-src/rules/slash-command-routing-policy.md +11 -63
  105. package/.agent-src/rules/think-before-action.md +22 -87
  106. package/.agent-src/rules/token-efficiency.md +10 -74
  107. package/.agent-src/rules/token-optimizer-maintenance.md +68 -0
  108. package/.agent-src/rules/tool-safety.md +4 -0
  109. package/.agent-src/rules/ui-audit-gate.md +25 -61
  110. package/.agent-src/rules/upstream-proposal.md +9 -67
  111. package/.agent-src/rules/user-interaction.md +25 -95
  112. package/.agent-src/rules/verify-before-complete.md +1 -1
  113. package/.agent-src/skills/agent-docs-writing/SKILL.md +1 -1
  114. package/.agent-src/skills/ai-council/SKILL.md +69 -5
  115. package/.agent-src/skills/analysis-autonomous-mode/SKILL.md +1 -1
  116. package/.agent-src/skills/analysis-skill-router/SKILL.md +3 -3
  117. package/.agent-src/skills/artisan-commands/SKILL.md +2 -2
  118. package/.agent-src/skills/authz-review/SKILL.md +1 -1
  119. package/.agent-src/skills/aws-infrastructure/SKILL.md +5 -5
  120. package/.agent-src/skills/blast-radius-analyzer/SKILL.md +8 -8
  121. package/.agent-src/skills/bug-analyzer/SKILL.md +5 -5
  122. package/.agent-src/skills/code-refactoring/SKILL.md +4 -4
  123. package/.agent-src/skills/code-review/SKILL.md +2 -2
  124. package/.agent-src/skills/command-writing/SKILL.md +11 -0
  125. package/.agent-src/skills/composer-packages/SKILL.md +2 -2
  126. package/.agent-src/skills/context-authoring/SKILL.md +11 -0
  127. package/.agent-src/skills/context-document/SKILL.md +1 -1
  128. package/.agent-src/skills/copilot-agents-optimization/SKILL.md +23 -0
  129. package/.agent-src/skills/copilot-config/SKILL.md +1 -1
  130. package/.agent-src/skills/dcf-modeling/SKILL.md +89 -0
  131. package/.agent-src/skills/dependency-upgrade/SKILL.md +2 -2
  132. package/.agent-src/skills/devcontainer/SKILL.md +2 -2
  133. package/.agent-src/skills/developer-like-execution/SKILL.md +1 -1
  134. package/.agent-src/skills/docker/SKILL.md +1 -1
  135. package/.agent-src/skills/dto-creator/SKILL.md +1 -1
  136. package/.agent-src/skills/estimate-ticket/SKILL.md +2 -2
  137. package/.agent-src/skills/fe-design/SKILL.md +4 -4
  138. package/.agent-src/skills/feature-planning/SKILL.md +5 -5
  139. package/.agent-src/skills/funnel-analysis/SKILL.md +100 -0
  140. package/.agent-src/skills/laravel/SKILL.md +1 -1
  141. package/.agent-src/skills/laravel-notifications/SKILL.md +5 -5
  142. package/.agent-src/skills/laravel-pennant/SKILL.md +1 -1
  143. package/.agent-src/skills/laravel-pulse/SKILL.md +4 -4
  144. package/.agent-src/skills/laravel-reverb/SKILL.md +2 -2
  145. package/.agent-src/skills/laravel-scheduling/SKILL.md +1 -1
  146. package/.agent-src/skills/md-language-check/SKILL.md +1 -1
  147. package/.agent-src/skills/migration-creator/SKILL.md +7 -7
  148. package/.agent-src/skills/multi-tenancy/SKILL.md +8 -8
  149. package/.agent-src/skills/okr-tree-modeling/SKILL.md +93 -0
  150. package/.agent-src/skills/performance-analysis/SKILL.md +3 -3
  151. package/.agent-src/skills/pest-testing/SKILL.md +6 -6
  152. package/.agent-src/skills/php-service/SKILL.md +2 -2
  153. package/.agent-src/skills/project-analysis-hypothesis-driven/SKILL.md +3 -3
  154. package/.agent-src/skills/project-analysis-react/SKILL.md +1 -1
  155. package/.agent-src/skills/project-analysis-symfony/SKILL.md +1 -1
  156. package/.agent-src/skills/project-analysis-zend-laminas/SKILL.md +2 -2
  157. package/.agent-src/skills/project-analyzer/SKILL.md +4 -4
  158. package/.agent-src/skills/prompt-optimizer/SKILL.md +108 -0
  159. package/.agent-src/skills/readme-reviewer/SKILL.md +1 -1
  160. package/.agent-src/skills/rice-prioritization/SKILL.md +100 -0
  161. package/.agent-src/skills/rule-writing/SKILL.md +33 -0
  162. package/.agent-src/skills/sentry-integration/SKILL.md +1 -1
  163. package/.agent-src/skills/skill-writing/SKILL.md +14 -0
  164. package/.agent-src/skills/subagent-orchestration/SKILL.md +34 -2
  165. package/.agent-src/skills/terraform/SKILL.md +2 -2
  166. package/.agent-src/skills/terragrunt/SKILL.md +8 -8
  167. package/.agent-src/skills/test-performance/SKILL.md +5 -5
  168. package/.agent-src/skills/threat-modeling/SKILL.md +2 -2
  169. package/.agent-src/skills/token-optimizer/SKILL.md +110 -0
  170. package/.agent-src/skills/unit-economics-modeling/SKILL.md +104 -0
  171. package/.agent-src/skills/universal-project-analysis/SKILL.md +1 -1
  172. package/.agent-src/skills/using-git-worktrees/SKILL.md +1 -0
  173. package/.agent-src/templates/AGENTS.md +1 -1
  174. package/.agent-src/templates/agent-settings.md +25 -41
  175. package/.agent-src/templates/contexts/tenant-boundaries.md +2 -2
  176. package/.agent-src/templates/contexts.md +1 -1
  177. package/.agent-src/templates/copilot-instructions.md +21 -0
  178. package/.agent-src/templates/copilot-review-instructions.md +76 -0
  179. package/.agent-src/templates/features.md +1 -1
  180. package/.agent-src/templates/rule.md +127 -0
  181. package/.agent-src/templates/scripts/work_engine/hook_bootstrap.py +7 -5
  182. package/.agent-src/templates/scripts/work_engine/hooks/__init__.py +0 -4
  183. package/.agent-src/templates/scripts/work_engine/hooks/builtin/__init__.py +0 -4
  184. package/.agent-src/templates/scripts/work_engine/hooks/builtin/_chat_history_base.py +7 -51
  185. package/.agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_append.py +1 -2
  186. package/.agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_halt_append.py +1 -2
  187. package/.agent-src/templates/scripts/work_engine/hooks/builtin/memory_visibility.py +2 -3
  188. package/.agent-src/templates/skill.md +30 -1
  189. package/.claude-plugin/marketplace.json +11 -4
  190. package/AGENTS.md +71 -3
  191. package/CHANGELOG.md +180 -3
  192. package/README.md +24 -23
  193. package/config/agent-settings.template.yml +63 -23
  194. package/config/gitignore-block.txt +11 -4
  195. package/docs/architecture.md +84 -3
  196. package/docs/catalog.md +23 -11
  197. package/docs/contracts/adr-chat-history-split.md +10 -1
  198. package/docs/contracts/agent-memory-contract.md +1 -1
  199. package/docs/contracts/command-clusters.md +1 -1
  200. package/docs/contracts/context-paths.md +2 -1
  201. package/docs/contracts/cross-wing-handoff.md +133 -0
  202. package/docs/contracts/file-ownership-matrix.json +678 -609
  203. package/docs/contracts/hook-architecture-v1.md +8 -1
  204. package/docs/contracts/iron-law-overrides.txt +25 -0
  205. package/docs/contracts/kernel-membership.md +273 -0
  206. package/docs/contracts/load-context-schema.md +26 -11
  207. package/docs/contracts/memory-visibility-v1.md +8 -24
  208. package/docs/contracts/pilot/agent-authority.md +24 -0
  209. package/docs/contracts/pilot/direct-answers.md +70 -0
  210. package/docs/contracts/pilot/language-and-tone.md +63 -0
  211. package/docs/contracts/rule-classification.md +170 -0
  212. package/docs/contracts/rule-router.md +153 -0
  213. package/docs/customization.md +18 -7
  214. package/docs/decisions/ADR-001-kernel-swap-deferred.md +109 -0
  215. package/docs/decisions/ADR-002-kernel-bucket-overrides.md +124 -0
  216. package/docs/decisions/ADR-rule-kernel-and-router.md +122 -0
  217. package/docs/getting-started.md +19 -27
  218. package/docs/guidelines/agent-infra/ask-when-uncertain-demos.md +1 -1
  219. package/docs/guidelines/agent-infra/roadmap-progress-mechanics.md +176 -0
  220. package/docs/guidelines/agent-infra/rule-type-governance.md +73 -0
  221. package/docs/guidelines/agent-infra/size-and-scope.md +13 -2
  222. package/docs/guidelines/agent-infra/skill-quality-checklist.md +119 -0
  223. package/docs/guidelines/augment-portability-patterns.md +68 -0
  224. package/docs/guidelines/php/php-coding-patterns.md +62 -0
  225. package/docs/hook-payload-capture.md +221 -0
  226. package/docs/migrations/commands-1.15.0.md +17 -12
  227. package/docs/skills-catalog.md +5 -4
  228. package/llms.txt +4 -3
  229. package/package.json +1 -1
  230. package/scripts/_p43_bodies.py +235 -0
  231. package/scripts/_p43_compress.py +118 -0
  232. package/scripts/_p4_migrate.py +199 -0
  233. package/scripts/_pilot_council_question.py +57 -0
  234. package/scripts/_pilot_measure.py +53 -0
  235. package/scripts/agent-config +1 -1
  236. package/scripts/ai_council/_default_prices.py +4 -4
  237. package/scripts/ai_council/clients.py +1 -1
  238. package/scripts/ai_council/modes.py +3 -4
  239. package/scripts/ai_council/pricing.py +10 -9
  240. package/scripts/ai_council/session.py +107 -5
  241. package/scripts/build_linear_digest.py +3 -5
  242. package/scripts/build_rule_trigger_matrix.py +1 -9
  243. package/scripts/chat_history.py +952 -596
  244. package/scripts/check_always_budget.py +39 -6
  245. package/scripts/check_compressed_paths.py +213 -0
  246. package/scripts/check_compression.py +15 -0
  247. package/scripts/check_context_paths.py +1 -0
  248. package/scripts/check_council_layout.py +105 -0
  249. package/scripts/check_council_references.py +145 -0
  250. package/scripts/check_portability.py +2 -0
  251. package/scripts/check_references.py +14 -2
  252. package/scripts/check_token_optimizer_freshness.py +131 -0
  253. package/scripts/compile_router.py +148 -0
  254. package/scripts/compress.py +219 -11
  255. package/scripts/council_cli.py +63 -9
  256. package/scripts/council_prune.py +81 -0
  257. package/scripts/count_token_optimizer_usage.sh +54 -0
  258. package/scripts/hook_manifest.yaml +33 -0
  259. package/scripts/hooks/augment-chat-history.sh +10 -0
  260. package/scripts/hooks/cowork-dispatcher.sh +98 -0
  261. package/scripts/hooks/dispatch_hook.py +35 -0
  262. package/scripts/hooks_status.py +12 -1
  263. package/scripts/install-hooks.sh +2 -2
  264. package/scripts/install.sh +81 -2
  265. package/scripts/iron_law_sha.py +98 -0
  266. package/scripts/lint_handoffs.py +214 -0
  267. package/scripts/lint_hook_manifest.py +2 -1
  268. package/scripts/lint_load_context.py +35 -5
  269. package/scripts/measure_rule_budget.py +314 -0
  270. package/scripts/prototype_lint_contradictions.py +150 -0
  271. package/scripts/redact_hook_capture.py +148 -0
  272. package/scripts/schemas/rule.schema.json +55 -6
  273. package/scripts/schemas/skill.schema.json +5 -0
  274. package/scripts/skill_linter.py +359 -7
  275. package/scripts/smoke_path_resolution.py +93 -0
  276. package/scripts/update_prices.py +3 -3
  277. package/scripts/validate_frontmatter.py +41 -1
  278. package/.agent-src/commands/chat-history/checkpoint.md +0 -126
  279. package/.agent-src/commands/chat-history/clear.md +0 -103
  280. package/.agent-src/commands/chat-history/resume.md +0 -183
  281. package/.agent-src/contexts/communication/rules-auto/artifact-engagement-recording-mechanics.md +0 -72
  282. package/.agent-src/contexts/communication/rules-auto/augment-portability-mechanics.md +0 -79
  283. package/.agent-src/contexts/communication/rules-auto/cli-output-handling-mechanics.md +0 -87
  284. package/.agent-src/contexts/communication/rules-auto/command-suggestion-policy-mechanics.md +0 -62
  285. package/.agent-src/contexts/communication/rules-auto/docs-sync-mechanics.md +0 -78
  286. package/.agent-src/contexts/communication/rules-auto/package-ci-checks-mechanics.md +0 -85
  287. package/.agent-src/contexts/communication/rules-auto/review-routing-awareness-mechanics.md +0 -65
  288. package/.agent-src/contexts/communication/rules-auto/roadmap-progress-sync-mechanics.md +0 -78
  289. package/.agent-src/contexts/communication/rules-auto/ui-audit-gate-mechanics.md +0 -53
  290. package/.agent-src/rules/chat-history-cadence.md +0 -143
  291. package/.agent-src/rules/chat-history-ownership.md +0 -124
  292. package/.agent-src/rules/chat-history-visibility.md +0 -97
  293. package/.agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_heartbeat.py +0 -50
  294. package/.agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_turn_check.py +0 -49
  295. package/scripts/check_phase_coupling.py +0 -148
  296. /package/{docs → .agent-src/contexts}/contracts/artifact-engagement-flow.md +0 -0
  297. /package/{docs → .agent-src/contexts}/contracts/command-suggestion-flow.md +0 -0
@@ -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:]))
@@ -1,7 +1,9 @@
1
1
  #!/usr/bin/env python3
2
2
  """
3
3
  Agent-config sync — compress .agent-src.uncompressed/ → .agent-src/
4
- and project .agent-src/ → .augment/ (copies for rules, symlinks for the rest).
4
+ and project .agent-src/ → .augment/ (copies for rules by default,
5
+ symlinks for the rest; opt into rule symlinks via
6
+ augment.rules_use_symlinks in .agent-settings.yml).
5
7
 
6
8
  Copies non-.md files as-is. Lists .md files that need compression (done by the
7
9
  Augment agent interactively). Tracks SHA-256 hashes of source files to detect
@@ -19,6 +21,7 @@ Usage:
19
21
 
20
22
  import hashlib
21
23
  import json
24
+ import re
22
25
  import shutil
23
26
  import sys
24
27
  from pathlib import Path
@@ -28,11 +31,41 @@ SOURCE_DIR = PROJECT_ROOT / ".agent-src.uncompressed"
28
31
  TARGET_DIR = PROJECT_ROOT / ".agent-src"
29
32
  AUGMENT_DIR = PROJECT_ROOT / ".augment"
30
33
  HASH_FILE = PROJECT_ROOT / ".compression-hashes.json"
34
+ SETTINGS_FILE = PROJECT_ROOT / ".agent-settings.yml"
31
35
 
32
36
  # Files to copy as-is even if .md (not compressed by agent)
33
37
  COPY_AS_IS = {"README.md"}
34
38
 
35
39
 
40
+ def _read_augment_rules_use_symlinks() -> bool:
41
+ """Read augment.rules_use_symlinks from .agent-settings.yml.
42
+
43
+ Returns True only when the setting is present under the top-level
44
+ ``augment:`` block and resolves to a truthy YAML scalar
45
+ (true/yes/on/1, case-insensitive). Missing file, missing block, or
46
+ any other value → False (preserve copy default).
47
+ """
48
+ if not SETTINGS_FILE.exists():
49
+ return False
50
+ try:
51
+ text = SETTINGS_FILE.read_text(encoding="utf-8")
52
+ except OSError:
53
+ return False
54
+ in_augment = False
55
+ for line in text.splitlines():
56
+ stripped = line.lstrip()
57
+ if not stripped or stripped.startswith("#"):
58
+ continue
59
+ if not line.startswith((" ", "\t")):
60
+ in_augment = stripped.startswith("augment:")
61
+ continue
62
+ if in_augment:
63
+ m = re.match(r"^\s+rules_use_symlinks\s*:\s*([^\s#]+)", line)
64
+ if m:
65
+ return m.group(1).strip().lower() in ("true", "yes", "on", "1")
66
+ return False
67
+
68
+
36
69
 
37
70
 
38
71
  def file_hash(filepath: Path) -> str:
@@ -59,17 +92,42 @@ def save_hashes(hashes: dict) -> None:
59
92
 
60
93
 
61
94
  def mark_done(relative_path: str) -> None:
62
- """Mark a single file as compressed by storing its current source hash."""
95
+ """Mark a single file as compressed by storing its current source hash.
96
+
97
+ Also runs the path rewriter on the just-written `.agent-src/<path>` so
98
+ logical names from the source frontmatter resolve to deployment-correct
99
+ relative paths in the shipped layer (P1 of road-to-path-fixes.md).
100
+ Idempotent — re-running is a no-op.
101
+ """
63
102
  source_file = SOURCE_DIR / relative_path
64
103
  if not source_file.exists():
65
104
  print(f"❌ Source file not found: {relative_path}")
66
105
  sys.exit(1)
106
+ apply_path_rewriter(relative_path)
67
107
  hashes = load_hashes()
68
108
  hashes[relative_path] = file_hash(source_file)
69
109
  save_hashes(hashes)
70
110
  print(f"✅ Marked as compressed: {relative_path}")
71
111
 
72
112
 
113
+ def apply_path_rewriter(relative_path: str) -> bool:
114
+ """Apply `_rewrite_paths` to `.agent-src/<relative_path>` in-place.
115
+
116
+ Returns True if the file was modified, False otherwise. Silently
117
+ returns False if the target doesn't exist (compression hasn't run
118
+ yet) — `--mark-done` is also valid before content exists.
119
+ """
120
+ target = TARGET_DIR / relative_path
121
+ if not target.exists() or not relative_path.endswith(".md"):
122
+ return False
123
+ original = target.read_text(encoding="utf-8")
124
+ rewritten = _rewrite_paths(original, relative_path)
125
+ if rewritten == original:
126
+ return False
127
+ target.write_text(rewritten, encoding="utf-8")
128
+ return True
129
+
130
+
73
131
  def mark_all_done() -> None:
74
132
  """Mark ALL .md files as compressed (e.g. after initial full compression)."""
75
133
  hashes = load_hashes()
@@ -250,6 +308,144 @@ def strip_frontmatter(content: str) -> str:
250
308
  return content
251
309
 
252
310
 
311
+ # ── Path rewriter (P1 of road-to-path-fixes.md) ───────────────────────────
312
+ # Source files use logical names that the rewriter resolves at compress
313
+ # time, so the shipped `.agent-src/` (and `.augment/` projection) carry
314
+ # deployment-correct relative paths without the agent author having to
315
+ # know how deep their file lives.
316
+ #
317
+ # Frontmatter rewrites:
318
+ # load_context: / load_context_eager:
319
+ # contexts/<area>/<file>.md (logical, preferred)
320
+ # .agent-src.uncompressed/contexts/<area>/<file>.md (legacy)
321
+ # → ../contexts/<area>/<file>.md (relative from .agent-src/rules/)
322
+ # triggers[].path_prefix:
323
+ # LEFT ALONE — `path_prefix:` is a literal match pattern, not a
324
+ # file reference. Source-of-truth rules that fire on edits under
325
+ # `.agent-src.uncompressed/` keep that prefix verbatim (see
326
+ # road-to-path-fixes.md P2.2 / Modified Option 1).
327
+ #
328
+ # Body-link rewrites:
329
+ # ../../docs/guidelines/<file>.md → ../docs/guidelines/<file>.md
330
+ # ../../docs/contracts/<file>.md → ../docs/contracts/<file>.md
331
+ #
332
+ # Idempotent: applying twice is a no-op (rewritten patterns no longer
333
+ # match the source patterns).
334
+
335
+ _LEGACY_SRC_PREFIX = ".agent-src.uncompressed/"
336
+ _PROJECTED_SRC_PREFIX = ".agent-src/"
337
+
338
+ # A YAML list item under load_context*: ` - some/path.md` (optionally quoted)
339
+ _FM_LIST_ITEM_RE = re.compile(r'^(\s*-\s*)(["\']?)([^"\'\n]+?\.md)(["\']?)\s*$')
340
+
341
+ # `path_prefix:` line — top-level or under `triggers:` (with leading dash)
342
+ _FM_PATH_PREFIX_RE = re.compile(
343
+ r'^(\s*(?:-\s+)?path_prefix:\s*)(["\']?)([^"\'\n]+?)(["\']?)\s*$'
344
+ )
345
+
346
+ # Body-link patterns (relative two-up to docs/) — capture the docs/... tail
347
+ _BODY_DOCS_RE = re.compile(r'\.\./\.\./(docs/(?:guidelines|contracts)/[^)\s]+\.md)')
348
+
349
+
350
+ def _depth_prefix(source_relative_path: str) -> str:
351
+ """Return the `../` chain to climb from `<source_relative_path>` back to
352
+ the source root. A file at `rules/X.md` (1 dir deep) needs `../`; a
353
+ file at `commands/council/default.md` (2 dirs deep) needs `../../`.
354
+ """
355
+ parts = Path(source_relative_path).parts
356
+ depth = max(len(parts) - 1, 1)
357
+ return "../" * depth
358
+
359
+
360
+ def _split_frontmatter(content: str):
361
+ """Return (frontmatter_lines, body) — frontmatter_lines is None if no FM."""
362
+ if not content.startswith("---\n"):
363
+ return None, content
364
+ end = content.find("\n---\n", 4)
365
+ if end == -1:
366
+ return None, content
367
+ fm_text = content[4:end]
368
+ body = content[end + len("\n---\n"):]
369
+ return fm_text.split("\n"), body
370
+
371
+
372
+ def _rewrite_load_context_value(value: str, prefix: str) -> str:
373
+ """Rewrite a single `load_context` list-item value to a deployment path."""
374
+ # Already relative or absolute → leave alone (idempotence).
375
+ if value.startswith(("../", "./", "/")):
376
+ return value
377
+ # Legacy fully-qualified source prefix.
378
+ if value.startswith(_LEGACY_SRC_PREFIX):
379
+ return prefix + value[len(_LEGACY_SRC_PREFIX):]
380
+ # Projected source prefix (defensive — also strip).
381
+ if value.startswith(_PROJECTED_SRC_PREFIX):
382
+ return prefix + value[len(_PROJECTED_SRC_PREFIX):]
383
+ # Logical name (e.g. `contexts/execution/foo.md`).
384
+ return prefix + value
385
+
386
+
387
+ def _rewrite_path_prefix_value(value: str) -> str:
388
+ """No-op for `triggers[].path_prefix:` values.
389
+
390
+ `path_prefix:` is a literal match pattern the host evaluates against
391
+ the file the agent is editing — not a file reference. Rewriting it
392
+ breaks the workflow it was authored for: source-of-truth rules that
393
+ fire when the agent edits files under `.agent-src.uncompressed/`
394
+ keep that prefix verbatim. The prefix ban therefore applies only to
395
+ `load_context:` entries and body links (see road-to-path-fixes.md
396
+ P2.2 + the AI-Council convergence on 2026-05-06).
397
+ """
398
+ return value
399
+
400
+
401
+ def _rewrite_frontmatter_lines(lines, prefix):
402
+ """Apply load_context / path_prefix rewrites to a frontmatter line list."""
403
+ in_load_context = False
404
+ out = []
405
+ for line in lines:
406
+ bare = line.lstrip()
407
+ if bare.startswith(("load_context:", "load_context_eager:")):
408
+ in_load_context = True
409
+ out.append(line)
410
+ continue
411
+ if in_load_context:
412
+ m = _FM_LIST_ITEM_RE.match(line)
413
+ if m:
414
+ indent, q1, value, q2 = m.groups()
415
+ rewritten = _rewrite_load_context_value(value, prefix)
416
+ out.append(f"{indent}{q1}{rewritten}{q2}")
417
+ continue
418
+ in_load_context = False
419
+ # fall through to path_prefix / passthrough
420
+ m = _FM_PATH_PREFIX_RE.match(line)
421
+ if m:
422
+ head, q1, value, q2 = m.groups()
423
+ out.append(f"{head}{q1}{_rewrite_path_prefix_value(value)}{q2}")
424
+ continue
425
+ out.append(line)
426
+ return out
427
+
428
+
429
+ def _rewrite_body_links(body: str, prefix: str) -> str:
430
+ """Rewrite `../../docs/{guidelines,contracts}/...` to use depth-prefix."""
431
+ return _BODY_DOCS_RE.sub(prefix + r"\1", body)
432
+
433
+
434
+ def _rewrite_paths(content: str, source_relative_path: str) -> str:
435
+ """Rewrite logical / legacy paths in `content` for a file shipped at
436
+ `.agent-src/{source_relative_path}`. Idempotent.
437
+
438
+ See module-level comment above for the full pattern catalog.
439
+ """
440
+ prefix = _depth_prefix(source_relative_path)
441
+ fm_lines, body = _split_frontmatter(content)
442
+ body = _rewrite_body_links(body, prefix)
443
+ if fm_lines is None:
444
+ return body
445
+ new_fm = _rewrite_frontmatter_lines(fm_lines, prefix)
446
+ return "---\n" + "\n".join(new_fm) + "\n---\n" + body
447
+
448
+
253
449
  def generate_rule_symlinks() -> int:
254
450
  """Create symlink directories for rules (.claude/rules/, .cursor/rules/, .clinerules/).
255
451
 
@@ -499,8 +695,10 @@ def generate_tools() -> None:
499
695
  # ── .augment/ projection ──────────────────────────────────────────────
500
696
  # The package uses .agent-src/ as the tool-agnostic compressed source of truth.
501
697
  # .augment/ is a generated projection so that Augment Code (which reads from
502
- # .augment/ and cannot follow symlinked rule files) works on the package repo
503
- # itself. Rules are copied (real files); everything else is symlinked.
698
+ # .augment/) works on the package repo itself. Rules default to copies
699
+ # because Augment Code historically does not load symlinked rule files;
700
+ # flip augment.rules_use_symlinks: true in .agent-settings.yml to switch
701
+ # them to symlinks (everything else is always symlinked).
504
702
 
505
703
  # Subdirectories of .agent-src/ that map into .augment/ as symlinks.
506
704
  AUGMENT_SYMLINK_DIRS = ("skills", "commands", "guidelines", "personas", "templates", "contexts", "scripts")
@@ -509,34 +707,44 @@ AUGMENT_SYMLINK_FILES = ("README.md",)
509
707
 
510
708
 
511
709
  def project_to_augment() -> None:
512
- """Mirror .agent-src/ into .augment/. Copy rules, symlink everything else."""
710
+ """Mirror .agent-src/ into .augment/. Symlink everything except rules,
711
+ which default to copies; opt into rule symlinks via
712
+ augment.rules_use_symlinks in .agent-settings.yml."""
513
713
  if not TARGET_DIR.exists():
514
714
  print(f" ⚠️ {TARGET_DIR.name}/ not found — nothing to project")
515
715
  return
516
716
 
517
717
  AUGMENT_DIR.mkdir(parents=True, exist_ok=True)
518
718
 
519
- # Rules: copy each .md file (Augment Code cannot load symlinked rules)
719
+ use_symlinks = _read_augment_rules_use_symlinks()
720
+
721
+ # Rules: copy by default (Augment Code historically does not load
722
+ # symlinked rules), or symlink when augment.rules_use_symlinks is true.
520
723
  src_rules = TARGET_DIR / "rules"
521
724
  dst_rules = AUGMENT_DIR / "rules"
522
725
  dst_rules.mkdir(parents=True, exist_ok=True)
523
726
  existing = {f.name for f in dst_rules.iterdir() if f.is_file() or f.is_symlink()}
524
727
  current = set()
525
- copied = 0
728
+ written = 0
526
729
  if src_rules.exists():
527
730
  for rule in sorted(src_rules.glob("*.md")):
528
731
  target = dst_rules / rule.name
529
- if target.is_symlink():
732
+ # Always remove first to avoid copy↔symlink mode mismatch.
733
+ if target.is_symlink() or target.exists():
530
734
  target.unlink()
531
- shutil.copy2(rule, target)
735
+ if use_symlinks:
736
+ target.symlink_to(Path("..") / ".." / ".agent-src" / "rules" / rule.name)
737
+ else:
738
+ shutil.copy2(rule, target)
532
739
  current.add(rule.name)
533
- copied += 1
740
+ written += 1
534
741
  # Remove stale rule files
535
742
  removed_rules = 0
536
743
  for name in existing - current:
537
744
  (dst_rules / name).unlink()
538
745
  removed_rules += 1
539
- print(f" ✅ Copied {copied} rules to .augment/rules/" + (f" ({removed_rules} stale removed)" if removed_rules else ""))
746
+ mode_label = "Symlinked" if use_symlinks else "Copied"
747
+ print(f" ✅ {mode_label} {written} rules to .augment/rules/" + (f" ({removed_rules} stale removed)" if removed_rules else ""))
540
748
 
541
749
  # Subdirectories: replace each with a symlink → ../.agent-src/<subdir>
542
750
  for sub in AUGMENT_SYMLINK_DIRS: