@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,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())
@@ -0,0 +1,148 @@
1
+ #!/usr/bin/env python3
2
+ """Redact captured hook payloads for the verified-platforms roadmap.
3
+
4
+ Reads JSON capture files written by ``dispatch_hook.py`` (when
5
+ ``AGENT_HOOK_CAPTURE_DIR`` is set) and produces a redacted version
6
+ suitable for pasting into
7
+ ``agents/roadmaps/road-to-verified-chat-history-platforms.md``.
8
+
9
+ Redaction policy (per the roadmap's Capture-and-redact protocol):
10
+
11
+ - Replace string values at known user-content paths with
12
+ ``<REDACTED>``. Default field allowlist mirrors the fallback list
13
+ in ``scripts/chat_history.py::_extract_hook_text`` plus Augment's
14
+ nested ``conversation.*`` shape.
15
+ - Preserve envelope keys (``hook_event_name``, ``session_id``,
16
+ ``platform``, ``event``, ``cwd``, ``workspace_roots``,
17
+ ``transcript_path``, ``model``, ``cursor_version``, …) so the
18
+ schema is reviewable.
19
+ - ``--strict`` redacts any string longer than ``--max-len`` (default
20
+ 120) chars regardless of key, as a safety net for unknown fields.
21
+
22
+ Usage:
23
+
24
+ python3 scripts/redact_hook_capture.py <input> [--out <path>] [--strict]
25
+
26
+ Input may be a single JSON file or a directory; with a directory,
27
+ every ``*.json`` is redacted and written next to the original with
28
+ the suffix ``.redacted.json``.
29
+ """
30
+ from __future__ import annotations
31
+
32
+ import argparse
33
+ import json
34
+ import sys
35
+ from pathlib import Path
36
+ from typing import Any
37
+
38
+ REDACTED = "<REDACTED>"
39
+
40
+ # Field names that carry user / agent content (from
41
+ # scripts/chat_history.py::_extract_hook_text fallback list +
42
+ # nested Augment shape). Matched case-insensitively against the
43
+ # leaf key.
44
+ _USER_CONTENT_KEYS = {
45
+ "prompt", "user_prompt", "userprompt", "first_user_msg",
46
+ "firstusermsg", "usermessage", "user_message", "text",
47
+ "response", "message", "content",
48
+ # Augment Code with includeConversationData
49
+ "agenttextresponse", "agent_text_response",
50
+ "agentcoderesponse", "agent_code_response",
51
+ # Cursor / generic
52
+ "submitted_prompt", "submittedprompt",
53
+ # Free-form transcript bodies (path stays — content is in another file)
54
+ "transcript", "transcript_text",
55
+ }
56
+
57
+ # Keys whose value is a structural / schema marker — keep as-is even
58
+ # when --strict would otherwise redact long values.
59
+ _ENVELOPE_KEYS_KEEP = {
60
+ "hook_event_name", "session_id", "transcript_path", "transcriptpath",
61
+ "platform", "event", "native_event", "captured_at", "cwd",
62
+ "workspace_roots", "model", "cursor_version", "user_email",
63
+ "conversation_id", "generation_id", "agent", "type",
64
+ "schema_version", "started_at", "completed_at", "_raw_text",
65
+ "path", "changetype", "change_type",
66
+ }
67
+
68
+
69
+ def _redact_value(val: Any, *, key: str | None, strict: bool,
70
+ max_len: int) -> Any:
71
+ """Recursively redact a value."""
72
+ norm_key = (key or "").lower().replace("-", "_")
73
+ if isinstance(val, dict):
74
+ return {k: _redact_value(v, key=k, strict=strict, max_len=max_len)
75
+ for k, v in val.items()}
76
+ if isinstance(val, list):
77
+ return [_redact_value(item, key=key, strict=strict, max_len=max_len)
78
+ for item in val]
79
+ if isinstance(val, str):
80
+ if norm_key in _ENVELOPE_KEYS_KEEP:
81
+ return val
82
+ if norm_key in _USER_CONTENT_KEYS:
83
+ return REDACTED
84
+ if strict and len(val) > max_len:
85
+ return REDACTED
86
+ return val
87
+ return val
88
+
89
+
90
+ def redact(record: dict, *, strict: bool = False, max_len: int = 120) -> dict:
91
+ """Redact a single capture record. Top-level envelope is preserved."""
92
+ out: dict = {}
93
+ for k, v in record.items():
94
+ if k == "raw_payload":
95
+ out[k] = _redact_value(v, key=None, strict=strict,
96
+ max_len=max_len)
97
+ else:
98
+ out[k] = _redact_value(v, key=k, strict=strict,
99
+ max_len=max_len)
100
+ return out
101
+
102
+
103
+ def _process_file(path: Path, *, out: Path | None, strict: bool,
104
+ max_len: int) -> Path:
105
+ record = json.loads(path.read_text(encoding="utf-8"))
106
+ redacted = redact(record, strict=strict, max_len=max_len)
107
+ target = out or path.with_suffix(".redacted.json")
108
+ target.write_text(json.dumps(redacted, indent=2) + "\n",
109
+ encoding="utf-8")
110
+ return target
111
+
112
+
113
+ def main(argv: list[str] | None = None) -> int:
114
+ parser = argparse.ArgumentParser(description=__doc__)
115
+ parser.add_argument("input", help="capture file or directory")
116
+ parser.add_argument("--out", default=None,
117
+ help="output path (single-file mode only)")
118
+ parser.add_argument("--strict", action="store_true",
119
+ help="redact any string longer than --max-len")
120
+ parser.add_argument("--max-len", type=int, default=120,
121
+ help="strict-mode length threshold (default 120)")
122
+ args = parser.parse_args(argv)
123
+
124
+ src = Path(args.input).expanduser()
125
+ if not src.exists():
126
+ sys.stderr.write(f"redact: input not found: {src}\n")
127
+ return 2
128
+
129
+ if src.is_dir():
130
+ if args.out:
131
+ sys.stderr.write("redact: --out is single-file only\n")
132
+ return 2
133
+ files = sorted(p for p in src.glob("*.json")
134
+ if not p.name.endswith(".redacted.json"))
135
+ for path in files:
136
+ target = _process_file(path, out=None, strict=args.strict,
137
+ max_len=args.max_len)
138
+ print(f"redacted: {target}")
139
+ return 0
140
+
141
+ target = _process_file(src, out=Path(args.out) if args.out else None,
142
+ strict=args.strict, max_len=args.max_len)
143
+ print(f"redacted: {target}")
144
+ return 0
145
+
146
+
147
+ if __name__ == "__main__":
148
+ raise SystemExit(main())