@event4u/agent-config 1.20.0 → 1.22.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 (268) hide show
  1. package/.agent-src/commands/agents.md +1 -1
  2. package/.agent-src/commands/bug-fix.md +2 -1
  3. package/.agent-src/commands/bug-investigate.md +3 -2
  4. package/.agent-src/commands/challenge-me/vision.md +348 -0
  5. package/.agent-src/commands/challenge-me/with-docs.md +333 -0
  6. package/.agent-src/commands/challenge-me.md +61 -0
  7. package/.agent-src/commands/chat-history/import.md +60 -64
  8. package/.agent-src/commands/compress.md +12 -0
  9. package/.agent-src/commands/context/create.md +2 -2
  10. package/.agent-src/commands/context.md +1 -1
  11. package/.agent-src/commands/copilot-agents.md +1 -1
  12. package/.agent-src/commands/council/default.md +69 -10
  13. package/.agent-src/commands/council.md +1 -1
  14. package/.agent-src/commands/create-pr.md +7 -3
  15. package/.agent-src/commands/e2e-heal.md +1 -1
  16. package/.agent-src/commands/e2e-plan.md +1 -1
  17. package/.agent-src/commands/feature/dev.md +3 -3
  18. package/.agent-src/commands/feature.md +1 -1
  19. package/.agent-src/commands/fix/seeder.md +2 -2
  20. package/.agent-src/commands/fix.md +1 -1
  21. package/.agent-src/commands/grill-me.md +38 -0
  22. package/.agent-src/commands/jira-ticket.md +1 -1
  23. package/.agent-src/commands/judge/steps.md +1 -1
  24. package/.agent-src/commands/judge.md +2 -2
  25. package/.agent-src/commands/memory.md +1 -1
  26. package/.agent-src/commands/mode.md +5 -5
  27. package/.agent-src/commands/module.md +1 -1
  28. package/.agent-src/commands/onboard.md +4 -4
  29. package/.agent-src/commands/optimize/augmentignore.md +1 -1
  30. package/.agent-src/commands/optimize-prompt.md +61 -0
  31. package/.agent-src/commands/optimize.md +1 -1
  32. package/.agent-src/commands/override.md +1 -1
  33. package/.agent-src/commands/review-changes.md +1 -1
  34. package/.agent-src/commands/review-routing.md +1 -1
  35. package/.agent-src/commands/roadmap/ai-council.md +183 -0
  36. package/.agent-src/commands/roadmap/create.md +6 -1
  37. package/.agent-src/commands/roadmap/process-full.md +58 -0
  38. package/.agent-src/commands/roadmap/process-phase.md +69 -0
  39. package/.agent-src/commands/roadmap/process-step.md +57 -0
  40. package/.agent-src/commands/roadmap.md +45 -17
  41. package/.agent-src/commands/set-cost-profile.md +3 -3
  42. package/.agent-src/commands/sync-agent-settings.md +2 -2
  43. package/.agent-src/commands/tests/create.md +2 -2
  44. package/.agent-src/commands/tests.md +1 -1
  45. package/.agent-src/commands/threat-model.md +5 -4
  46. package/.agent-src/contexts/augment-infrastructure.md +1 -1
  47. package/.agent-src/contexts/authority/commit-mechanics.md +14 -1
  48. package/.agent-src/contexts/authority/destructive-mechanics.md +14 -1
  49. package/.agent-src/contexts/authority/scope-mechanics.md +5 -0
  50. package/.agent-src/contexts/communication/rules-auto/guidelines-mechanics.md +76 -0
  51. package/.agent-src/contexts/communication/rules-auto/slash-command-routing-policy-mechanics.md +54 -19
  52. package/.agent-src/contexts/communication/rules-auto/think-before-action-mechanics.md +98 -0
  53. package/.agent-src/contexts/communication/rules-auto/token-efficiency-mechanics.md +93 -0
  54. package/.agent-src/contexts/communication/rules-auto/user-interaction-mechanics.md +128 -5
  55. package/.agent-src/contexts/execution/autonomy-mechanics.md +44 -0
  56. package/.agent-src/contexts/execution/roadmap-process-loop.md +125 -0
  57. package/.agent-src/contexts/model-recommendations.md +2 -2
  58. package/.agent-src/contexts/override-system.md +1 -1
  59. package/.agent-src/contexts/skills-and-commands.md +1 -1
  60. package/.agent-src/personas/product-owner.md +2 -2
  61. package/.agent-src/personas/qa.md +1 -1
  62. package/.agent-src/rules/agent-authority.md +5 -6
  63. package/.agent-src/rules/agent-docs.md +11 -53
  64. package/.agent-src/rules/analysis-skill-routing.md +10 -40
  65. package/.agent-src/rules/architecture.md +6 -1
  66. package/.agent-src/rules/artifact-drafting-protocol.md +5 -0
  67. package/.agent-src/rules/artifact-engagement-recording.md +23 -59
  68. package/.agent-src/rules/ask-when-uncertain.md +24 -47
  69. package/.agent-src/rules/augment-portability.md +14 -62
  70. package/.agent-src/rules/augment-source-of-truth.md +10 -1
  71. package/.agent-src/rules/autonomous-execution.md +17 -98
  72. package/.agent-src/rules/capture-learnings.md +9 -80
  73. package/.agent-src/rules/cli-output-handling.md +12 -42
  74. package/.agent-src/rules/command-suggestion-policy.md +25 -73
  75. package/.agent-src/rules/commit-conventions.md +9 -58
  76. package/.agent-src/rules/commit-policy.md +16 -47
  77. package/.agent-src/rules/context-hygiene.md +5 -0
  78. package/.agent-src/rules/direct-answers.md +21 -50
  79. package/.agent-src/rules/docker-commands.md +11 -45
  80. package/.agent-src/rules/docs-sync.md +10 -56
  81. package/.agent-src/rules/downstream-changes.md +5 -0
  82. package/.agent-src/rules/e2e-testing.md +9 -44
  83. package/.agent-src/rules/guidelines.md +13 -75
  84. package/.agent-src/rules/improve-before-implement.md +11 -2
  85. package/.agent-src/rules/invite-challenge.md +71 -0
  86. package/.agent-src/rules/language-and-tone.md +41 -106
  87. package/.agent-src/rules/laravel-translations.md +11 -40
  88. package/.agent-src/rules/markdown-safe-codeblocks.md +4 -0
  89. package/.agent-src/rules/minimal-safe-diff.md +4 -0
  90. package/.agent-src/rules/missing-tool-handling.md +4 -0
  91. package/.agent-src/rules/model-recommendation.md +9 -61
  92. package/.agent-src/rules/no-attribution-footers.md +5 -0
  93. package/.agent-src/rules/no-cheap-questions.md +11 -27
  94. package/.agent-src/rules/no-council-references.md +76 -0
  95. package/.agent-src/rules/no-roadmap-references.md +7 -0
  96. package/.agent-src/rules/non-destructive-by-default.md +13 -43
  97. package/.agent-src/rules/onboarding-gate.md +9 -117
  98. package/.agent-src/rules/package-ci-checks.md +10 -37
  99. package/.agent-src/rules/php-coding.md +10 -55
  100. package/.agent-src/rules/preservation-guard.md +9 -0
  101. package/.agent-src/rules/review-routing-awareness.md +9 -97
  102. package/.agent-src/rules/reviewer-awareness.md +8 -83
  103. package/.agent-src/rules/roadmap-progress-sync.md +7 -170
  104. package/.agent-src/rules/role-mode-adherence.md +6 -2
  105. package/.agent-src/rules/rule-type-governance.md +8 -66
  106. package/.agent-src/rules/runtime-safety.md +5 -0
  107. package/.agent-src/rules/scope-control.md +17 -62
  108. package/.agent-src/rules/security-sensitive-stop.md +7 -1
  109. package/.agent-src/rules/size-enforcement.md +6 -1
  110. package/.agent-src/rules/skill-improvement-trigger.md +9 -49
  111. package/.agent-src/rules/skill-quality.md +7 -113
  112. package/.agent-src/rules/slash-command-routing-policy.md +11 -63
  113. package/.agent-src/rules/think-before-action.md +22 -87
  114. package/.agent-src/rules/token-efficiency.md +10 -74
  115. package/.agent-src/rules/token-optimizer-maintenance.md +68 -0
  116. package/.agent-src/rules/tool-safety.md +4 -0
  117. package/.agent-src/rules/ui-audit-gate.md +25 -61
  118. package/.agent-src/rules/upstream-proposal.md +9 -67
  119. package/.agent-src/rules/user-interaction.md +22 -108
  120. package/.agent-src/rules/verify-before-complete.md +1 -1
  121. package/.agent-src/skills/adversarial-review/SKILL.md +1 -0
  122. package/.agent-src/skills/agent-docs-writing/SKILL.md +1 -1
  123. package/.agent-src/skills/ai-council/SKILL.md +197 -8
  124. package/.agent-src/skills/analysis-autonomous-mode/SKILL.md +1 -1
  125. package/.agent-src/skills/analysis-skill-router/SKILL.md +3 -3
  126. package/.agent-src/skills/artisan-commands/SKILL.md +2 -2
  127. package/.agent-src/skills/authz-review/SKILL.md +1 -1
  128. package/.agent-src/skills/aws-infrastructure/SKILL.md +5 -5
  129. package/.agent-src/skills/blast-radius-analyzer/SKILL.md +8 -8
  130. package/.agent-src/skills/bug-analyzer/SKILL.md +6 -5
  131. package/.agent-src/skills/code-refactoring/SKILL.md +4 -4
  132. package/.agent-src/skills/code-review/SKILL.md +2 -2
  133. package/.agent-src/skills/command-writing/SKILL.md +11 -0
  134. package/.agent-src/skills/composer-packages/SKILL.md +2 -2
  135. package/.agent-src/skills/context-authoring/SKILL.md +11 -0
  136. package/.agent-src/skills/context-document/SKILL.md +1 -1
  137. package/.agent-src/skills/copilot-agents-optimization/SKILL.md +23 -0
  138. package/.agent-src/skills/copilot-config/SKILL.md +1 -1
  139. package/.agent-src/skills/dependency-upgrade/SKILL.md +2 -2
  140. package/.agent-src/skills/devcontainer/SKILL.md +2 -2
  141. package/.agent-src/skills/developer-like-execution/SKILL.md +1 -1
  142. package/.agent-src/skills/docker/SKILL.md +1 -1
  143. package/.agent-src/skills/dto-creator/SKILL.md +1 -1
  144. package/.agent-src/skills/estimate-ticket/SKILL.md +2 -2
  145. package/.agent-src/skills/fe-design/SKILL.md +4 -4
  146. package/.agent-src/skills/feature-planning/SKILL.md +5 -5
  147. package/.agent-src/skills/funnel-analysis/SKILL.md +1 -1
  148. package/.agent-src/skills/laravel/SKILL.md +1 -1
  149. package/.agent-src/skills/laravel-notifications/SKILL.md +5 -5
  150. package/.agent-src/skills/laravel-pennant/SKILL.md +1 -1
  151. package/.agent-src/skills/laravel-pulse/SKILL.md +4 -4
  152. package/.agent-src/skills/laravel-reverb/SKILL.md +2 -2
  153. package/.agent-src/skills/laravel-scheduling/SKILL.md +1 -1
  154. package/.agent-src/skills/migration-creator/SKILL.md +7 -7
  155. package/.agent-src/skills/multi-tenancy/SKILL.md +8 -8
  156. package/.agent-src/skills/performance-analysis/SKILL.md +3 -3
  157. package/.agent-src/skills/pest-testing/SKILL.md +6 -6
  158. package/.agent-src/skills/php-service/SKILL.md +2 -2
  159. package/.agent-src/skills/project-analysis-hypothesis-driven/SKILL.md +3 -3
  160. package/.agent-src/skills/project-analysis-react/SKILL.md +1 -1
  161. package/.agent-src/skills/project-analysis-symfony/SKILL.md +1 -1
  162. package/.agent-src/skills/project-analysis-zend-laminas/SKILL.md +2 -2
  163. package/.agent-src/skills/project-analyzer/SKILL.md +4 -4
  164. package/.agent-src/skills/prompt-optimizer/SKILL.md +108 -0
  165. package/.agent-src/skills/readme-reviewer/SKILL.md +1 -1
  166. package/.agent-src/skills/roadmap-management/SKILL.md +7 -7
  167. package/.agent-src/skills/rule-writing/SKILL.md +33 -0
  168. package/.agent-src/skills/sentry-integration/SKILL.md +1 -1
  169. package/.agent-src/skills/skill-writing/SKILL.md +14 -0
  170. package/.agent-src/skills/systematic-debugging/SKILL.md +22 -2
  171. package/.agent-src/skills/technical-specification/SKILL.md +58 -1
  172. package/.agent-src/skills/terraform/SKILL.md +2 -2
  173. package/.agent-src/skills/terragrunt/SKILL.md +8 -8
  174. package/.agent-src/skills/test-performance/SKILL.md +5 -5
  175. package/.agent-src/skills/threat-modeling/SKILL.md +3 -2
  176. package/.agent-src/skills/token-optimizer/SKILL.md +110 -0
  177. package/.agent-src/skills/universal-project-analysis/SKILL.md +1 -1
  178. package/.agent-src/templates/AGENTS.md +1 -1
  179. package/.agent-src/templates/agent-settings.md +35 -19
  180. package/.agent-src/templates/command.md +17 -1
  181. package/.agent-src/templates/contexts/tenant-boundaries.md +2 -2
  182. package/.agent-src/templates/contexts.md +1 -1
  183. package/.agent-src/templates/copilot-instructions.md +21 -0
  184. package/.agent-src/templates/copilot-review-instructions.md +76 -0
  185. package/.agent-src/templates/features.md +1 -1
  186. package/.agent-src/templates/roadmaps.md +10 -2
  187. package/.agent-src/templates/rule.md +129 -0
  188. package/.agent-src/templates/skill.md +17 -0
  189. package/.claude-plugin/marketplace.json +12 -2
  190. package/AGENTS.md +32 -5
  191. package/CHANGELOG.md +107 -3
  192. package/README.md +22 -21
  193. package/config/agent-settings.template.yml +66 -10
  194. package/config/gitignore-block.txt +7 -0
  195. package/docs/architecture.md +86 -5
  196. package/docs/catalog.md +16 -6
  197. package/docs/contracts/agent-memory-contract.md +1 -1
  198. package/docs/contracts/command-clusters.md +45 -1
  199. package/docs/contracts/context-paths.md +2 -1
  200. package/docs/contracts/file-ownership-matrix.json +354 -500
  201. package/docs/contracts/iron-law-overrides.txt +25 -0
  202. package/docs/contracts/kernel-membership.md +273 -0
  203. package/docs/contracts/load-context-schema.md +26 -11
  204. package/docs/contracts/pilot/agent-authority.md +24 -0
  205. package/docs/contracts/pilot/direct-answers.md +70 -0
  206. package/docs/contracts/pilot/language-and-tone.md +63 -0
  207. package/docs/contracts/rule-classification.md +170 -0
  208. package/docs/contracts/rule-router.md +153 -0
  209. package/docs/customization.md +17 -6
  210. package/docs/decisions/ADR-001-kernel-swap-deferred.md +109 -0
  211. package/docs/decisions/ADR-002-kernel-bucket-overrides.md +124 -0
  212. package/docs/decisions/ADR-003-flat-cluster-subs-and-colon-syntax.md +126 -0
  213. package/docs/decisions/ADR-rule-kernel-and-router.md +122 -0
  214. package/docs/getting-started.md +2 -2
  215. package/docs/guidelines/agent-infra/naming.md +1 -1
  216. package/docs/guidelines/agent-infra/roadmap-progress-mechanics.md +176 -0
  217. package/docs/guidelines/agent-infra/rule-type-governance.md +73 -0
  218. package/docs/guidelines/agent-infra/size-and-scope.md +13 -2
  219. package/docs/guidelines/agent-infra/skill-quality-checklist.md +119 -0
  220. package/docs/guidelines/augment-portability-patterns.md +68 -0
  221. package/docs/guidelines/php/php-coding-patterns.md +62 -0
  222. package/package.json +1 -1
  223. package/scripts/_p43_bodies.py +235 -0
  224. package/scripts/_p43_compress.py +118 -0
  225. package/scripts/_p4_migrate.py +199 -0
  226. package/scripts/_phase2_shim_helper.py +1 -1
  227. package/scripts/_pilot_council_question.py +57 -0
  228. package/scripts/_pilot_measure.py +53 -0
  229. package/scripts/ai_council/session.py +107 -5
  230. package/scripts/build_linear_digest.py +3 -5
  231. package/scripts/check_always_budget.py +39 -6
  232. package/scripts/check_compressed_paths.py +213 -0
  233. package/scripts/check_compression.py +15 -0
  234. package/scripts/check_context_paths.py +1 -0
  235. package/scripts/check_council_layout.py +105 -0
  236. package/scripts/check_council_references.py +145 -0
  237. package/scripts/check_portability.py +2 -0
  238. package/scripts/check_references.py +2 -0
  239. package/scripts/check_token_optimizer_freshness.py +131 -0
  240. package/scripts/compile_router.py +148 -0
  241. package/scripts/compress.py +219 -11
  242. package/scripts/council_cli.py +132 -11
  243. package/scripts/council_prune.py +81 -0
  244. package/scripts/count_token_optimizer_usage.sh +54 -0
  245. package/scripts/install.sh +44 -2
  246. package/scripts/iron_law_sha.py +98 -0
  247. package/scripts/lint_load_context.py +35 -5
  248. package/scripts/measure_rule_budget.py +314 -0
  249. package/scripts/migrate_command_suggestions.py +2 -2
  250. package/scripts/prototype_lint_contradictions.py +150 -0
  251. package/scripts/schemas/command.schema.json +5 -0
  252. package/scripts/schemas/rule.schema.json +60 -6
  253. package/scripts/schemas/skill.schema.json +5 -0
  254. package/scripts/skill_linter.py +197 -7
  255. package/scripts/smoke_path_resolution.py +93 -0
  256. package/scripts/validate_frontmatter.py +41 -1
  257. package/.agent-src/commands/roadmap/execute.md +0 -109
  258. package/.agent-src/contexts/communication/rules-auto/artifact-engagement-recording-mechanics.md +0 -72
  259. package/.agent-src/contexts/communication/rules-auto/augment-portability-mechanics.md +0 -79
  260. package/.agent-src/contexts/communication/rules-auto/cli-output-handling-mechanics.md +0 -87
  261. package/.agent-src/contexts/communication/rules-auto/command-suggestion-policy-mechanics.md +0 -62
  262. package/.agent-src/contexts/communication/rules-auto/docs-sync-mechanics.md +0 -78
  263. package/.agent-src/contexts/communication/rules-auto/package-ci-checks-mechanics.md +0 -85
  264. package/.agent-src/contexts/communication/rules-auto/review-routing-awareness-mechanics.md +0 -65
  265. package/.agent-src/contexts/communication/rules-auto/roadmap-progress-sync-mechanics.md +0 -78
  266. package/.agent-src/contexts/communication/rules-auto/ui-audit-gate-mechanics.md +0 -53
  267. /package/{docs → .agent-src/contexts}/contracts/artifact-engagement-flow.md +0 -0
  268. /package/{docs → .agent-src/contexts}/contracts/command-suggestion-flow.md +0 -0
@@ -0,0 +1,213 @@
1
+ #!/usr/bin/env python3
2
+ """Validate compressed-output paths in `.agent-src/rules/*.md`.
3
+
4
+ Runs after `scripts/compress.py` projects sources to `.agent-src/`. The
5
+ rewriter in `compress.py` is the load-bearing primitive (road-to-path-fixes
6
+ P1.2); this script is the post-condition gate (P5.1) — every `load_context:`
7
+ entry in `.agent-src/rules/*.md` must resolve relative to the rule file's
8
+ directory to an existing file, and forbidden substrings must not survive
9
+ the rewrite (unless declared in `validator_ignore`).
10
+
11
+ Forbidden substrings (load_context + body):
12
+ - `.agent-src.uncompressed/` unless declared in validator_ignore
13
+ - `../../docs/` body-link two-up form (rewriter
14
+ collapses to single-up)
15
+ - `../../agents/` same shape, different root
16
+
17
+ Body-link checks (Council Decision 2, 2026-05-06):
18
+ - `load_context:` entries MUST resolve to an existing file under
19
+ `.agent-src/`.
20
+ - Body markdown links to `../contexts/...md` MUST resolve.
21
+ - Body markdown links to `../docs/guidelines/...md` are NOT checked
22
+ (P3.1 was cancelled; resolution is intentionally out of scope, the
23
+ Copilot suppression floor in P6 is the silencer).
24
+
25
+ `validator_ignore:` frontmatter primitive:
26
+ - Per-rule allowlist for rules that *describe* a forbidden substring as
27
+ their subject matter (e.g. `augment-source-of-truth` documents the
28
+ `.agent-src.uncompressed/` boundary). Each entry: `{type, pattern,
29
+ reason}`. The validator emits an audit line per matched ignore so
30
+ drift cannot hide.
31
+
32
+ Exit codes: 0 = clean, 1 = violations found, 3 = internal error.
33
+ """
34
+ from __future__ import annotations
35
+
36
+ import re
37
+ import sys
38
+ from dataclasses import dataclass
39
+ from pathlib import Path
40
+
41
+ import yaml
42
+
43
+ ROOT = Path(__file__).resolve().parent.parent
44
+ RULES_DIR = ROOT / ".agent-src" / "rules"
45
+
46
+ FORBIDDEN_SUBSTRINGS = (
47
+ ".agent-src.uncompressed/",
48
+ "../../docs/",
49
+ "../../agents/",
50
+ )
51
+
52
+ # Markdown links: `[text](path)` — capture path. Skip URLs and anchors.
53
+ _LINK_RE = re.compile(r'\[[^\]]*\]\(([^)#\s]+)(?:#[^)]*)?\)')
54
+
55
+
56
+ # Body-link prefixes whose resolution is intentionally out of scope.
57
+ # Council Decision 2 (2026-05-06): P3.1 was cancelled, so guideline links
58
+ # under `.agent-src/rules/` cannot resolve in the projected tree. Copilot
59
+ # suppression (P6) is the silencer for the noise.
60
+ UNCHECKED_LINK_PREFIXES = (
61
+ "../docs/guidelines/",
62
+ "../../docs/guidelines/",
63
+ )
64
+
65
+
66
+ @dataclass
67
+ class Violation:
68
+ file: str
69
+ line: int
70
+ kind: str
71
+ detail: str
72
+
73
+
74
+ @dataclass
75
+ class IgnoreEntry:
76
+ """Frontmatter `validator_ignore:` entry."""
77
+ kind: str # "substring" | "link"
78
+ pattern: str # exact substring or link prefix to ignore
79
+ reason: str # human-readable rationale (audited)
80
+
81
+
82
+ def _split_frontmatter(text: str):
83
+ if not text.startswith("---\n"):
84
+ return None, text
85
+ end = text.find("\n---\n", 4)
86
+ if end == -1:
87
+ return None, text
88
+ fm_text = text[4:end]
89
+ body = text[end + len("\n---\n"):]
90
+ try:
91
+ fm = yaml.safe_load(fm_text)
92
+ except yaml.YAMLError:
93
+ return None, text
94
+ return fm if isinstance(fm, dict) else {}, body
95
+
96
+
97
+ def _parse_ignores(fm: dict) -> list[IgnoreEntry]:
98
+ entries = fm.get("validator_ignore") or []
99
+ if not isinstance(entries, list):
100
+ return []
101
+ out: list[IgnoreEntry] = []
102
+ for raw in entries:
103
+ if not isinstance(raw, dict):
104
+ continue
105
+ kind = str(raw.get("type") or "").strip()
106
+ pattern = str(raw.get("pattern") or "").strip()
107
+ reason = str(raw.get("reason") or "").strip()
108
+ if kind in ("substring", "link") and pattern and reason:
109
+ out.append(IgnoreEntry(kind=kind, pattern=pattern, reason=reason))
110
+ return out
111
+
112
+
113
+ def _ignored(needle: str, ignores: list[IgnoreEntry], kind: str) -> IgnoreEntry | None:
114
+ for ig in ignores:
115
+ if ig.kind == kind and ig.pattern == needle:
116
+ return ig
117
+ return None
118
+
119
+
120
+ def _check_load_context(rule_file: Path, fm: dict, viols: list[Violation],
121
+ ignores: list[IgnoreEntry], audited: list[tuple[str, IgnoreEntry]]) -> None:
122
+ rule_dir = rule_file.parent
123
+ for key in ("load_context", "load_context_eager"):
124
+ entries = fm.get(key) or []
125
+ if not isinstance(entries, list):
126
+ continue
127
+ for entry in entries:
128
+ if not isinstance(entry, str):
129
+ continue
130
+ blocked = False
131
+ for needle in FORBIDDEN_SUBSTRINGS:
132
+ if needle in entry:
133
+ ig = _ignored(needle, ignores, "substring")
134
+ if ig:
135
+ audited.append((str(rule_file.relative_to(ROOT)), ig))
136
+ continue
137
+ viols.append(Violation(
138
+ str(rule_file.relative_to(ROOT)), 0, f"{key}-forbidden",
139
+ f"forbidden substring {needle!r} in entry {entry!r}",
140
+ ))
141
+ blocked = True
142
+ break
143
+ if blocked:
144
+ continue
145
+ target = (rule_dir / entry).resolve()
146
+ if not target.is_file():
147
+ viols.append(Violation(
148
+ str(rule_file.relative_to(ROOT)), 0, f"{key}-missing",
149
+ f"{entry!r} does not resolve to an existing file",
150
+ ))
151
+
152
+
153
+ def _check_body(rule_file: Path, body: str, viols: list[Violation],
154
+ ignores: list[IgnoreEntry], audited: list[tuple[str, IgnoreEntry]]) -> None:
155
+ rule_dir = rule_file.parent
156
+ for line_num, line in enumerate(body.splitlines(), start=1):
157
+ for needle in FORBIDDEN_SUBSTRINGS:
158
+ if needle in line:
159
+ ig = _ignored(needle, ignores, "substring")
160
+ if ig:
161
+ audited.append((f"{rule_file.relative_to(ROOT)}:{line_num}", ig))
162
+ continue
163
+ viols.append(Violation(
164
+ str(rule_file.relative_to(ROOT)), line_num, "body-forbidden",
165
+ f"forbidden substring {needle!r}",
166
+ ))
167
+ for m in _LINK_RE.finditer(line):
168
+ link = m.group(1)
169
+ if link.startswith(("http://", "https://", "mailto:", "#")):
170
+ continue
171
+ if not link.endswith(".md"):
172
+ continue
173
+ if any(link.startswith(p) for p in UNCHECKED_LINK_PREFIXES):
174
+ continue
175
+ target = (rule_dir / link).resolve()
176
+ if not target.is_file():
177
+ viols.append(Violation(
178
+ str(rule_file.relative_to(ROOT)), line_num, "body-link-missing",
179
+ f"link target {link!r} does not resolve",
180
+ ))
181
+
182
+
183
+ def main() -> int:
184
+ if not RULES_DIR.is_dir():
185
+ print(f"❌ {RULES_DIR} not found — run compression first", file=sys.stderr)
186
+ return 3
187
+ viols: list[Violation] = []
188
+ audited: list[tuple[str, IgnoreEntry]] = []
189
+ for rule_file in sorted(RULES_DIR.glob("*.md")):
190
+ text = rule_file.read_text(encoding="utf-8")
191
+ fm, body = _split_frontmatter(text)
192
+ ignores: list[IgnoreEntry] = _parse_ignores(fm) if fm is not None else []
193
+ if fm is not None:
194
+ _check_load_context(rule_file, fm, viols, ignores, audited)
195
+ _check_body(rule_file, body, viols, ignores, audited)
196
+ if audited:
197
+ print("ℹ️ validator_ignore audit:")
198
+ for loc, ig in audited:
199
+ print(f" {loc} — [{ig.kind}] {ig.pattern!r} → {ig.reason}")
200
+ print()
201
+ if viols:
202
+ for v in viols:
203
+ loc = f"{v.file}:{v.line}" if v.line else v.file
204
+ print(f"❌ [{v.kind}] {loc} — {v.detail}")
205
+ print(f"\n{len(viols)} violation(s) in .agent-src/rules/")
206
+ return 1
207
+ rule_count = len(list(RULES_DIR.glob('*.md')))
208
+ print(f"✅ compressed-path check clean ({rule_count} rules, {len(audited)} ignore(s) audited)")
209
+ return 0
210
+
211
+
212
+ if __name__ == "__main__":
213
+ sys.exit(main())
@@ -24,6 +24,15 @@ from dataclasses import dataclass, asdict
24
24
  from pathlib import Path
25
25
  from typing import List, Literal
26
26
 
27
+ # Import the rewriter so frontmatter comparison can normalise the source
28
+ # side through the same path transformations the compressor applies. Without
29
+ # this, every `load_context:` logical name (e.g. `contexts/foo.md`) and every
30
+ # `../../docs/...` body link looks like a frontmatter / body mismatch even
31
+ # though the rewriter is doing exactly what road-to-path-fixes.md P2/P3
32
+ # specified.
33
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
34
+ from compress import _rewrite_paths # noqa: E402
35
+
27
36
  Severity = Literal["error", "warning", "info"]
28
37
 
29
38
  SOURCE_DIR = Path(".agent-src.uncompressed")
@@ -275,6 +284,12 @@ def scan_all(root: Path) -> List[Issue]:
275
284
 
276
285
  source_text = source_file.read_text(encoding="utf-8")
277
286
  target_text = target_file.read_text(encoding="utf-8")
287
+ # Normalise source through the path rewriter (idempotent) so logical
288
+ # `load_context:` names and `../../docs/...` body links match the
289
+ # depth-aware form the compressor produced. Compression word-count
290
+ # checks downstream are unaffected because rewriting only edits
291
+ # frontmatter list values and link targets, not prose tokens.
292
+ source_text = _rewrite_paths(source_text, rel_str)
278
293
  issues.extend(check_pair(rel_str, source_text, target_text))
279
294
 
280
295
  return issues
@@ -35,6 +35,7 @@ LOCKED_SUBTREES = (
35
35
  "chat-history",
36
36
  "execution",
37
37
  "authority",
38
+ "contracts",
38
39
  )
39
40
 
40
41
  # Files allowed to remain at the contexts root. Anything else at the root
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env python3
2
+ """CI guard for the `ai-council` skill's output-path convention.
3
+
4
+ Council artefacts (questions, responses, sessions) belong in three
5
+ canonical directories under `agents/`:
6
+
7
+ - agents/council-questions/<topic-slug>.md (paired with roadmap/ADR)
8
+ - agents/council-responses/<topic-slug>.json (paired with question)
9
+ - agents/council-sessions/<UTC-timestamp>.json (ad-hoc sessions)
10
+
11
+ The three canonical dirs are gitignored — the linter therefore only
12
+ catches **misplacement**, not naming-conventions inside the dirs:
13
+
14
+ - Files at agents/ root with a council-* or .council-* prefix
15
+ (e.g. agents/council-question-foo.md, agents/.council-foo.md).
16
+ - council-* files under any other subdirectory of agents/.
17
+
18
+ Failure modes are enforced by `.agent-src.uncompressed/skills/ai-council/SKILL.md`
19
+ § "Output path convention".
20
+
21
+ Exit codes:
22
+ 0 — layout is clean.
23
+ 1 — at least one violation found; details printed to stdout.
24
+
25
+ Invocation (from project root):
26
+ python3 scripts/check_council_layout.py
27
+ """
28
+
29
+ from __future__ import annotations
30
+
31
+ import re
32
+ import sys
33
+ from pathlib import Path
34
+
35
+ AGENTS_ROOT = Path("agents")
36
+ CANONICAL_DIRS = {
37
+ "council-questions": ".md",
38
+ "council-responses": ".json",
39
+ "council-sessions": ".json",
40
+ }
41
+ # A council artefact is a file whose name starts with `council-` or
42
+ # `.council-`. This intentionally excludes roadmaps like
43
+ # `road-to-ai-council.md` whose stem only contains the word "council".
44
+ COUNCIL_PREFIX_RE = re.compile(r"^\.?council-")
45
+
46
+
47
+ def is_council_artefact(path: Path) -> bool:
48
+ return bool(COUNCIL_PREFIX_RE.match(path.name))
49
+
50
+
51
+ def find_violations(root: Path) -> list[str]:
52
+ findings: list[str] = []
53
+ if not root.is_dir():
54
+ return findings
55
+
56
+ # 1. Stray council artefacts at agents/ root
57
+ for path in sorted(root.iterdir()):
58
+ if not path.is_file():
59
+ continue
60
+ if is_council_artefact(path):
61
+ findings.append(
62
+ f"{path}: council artefact at agents/ root — move to "
63
+ f"agents/council-questions/, agents/council-responses/, "
64
+ f"or agents/council-sessions/ per ai-council § Output path "
65
+ f"convention."
66
+ )
67
+
68
+ # 2. Council artefacts in non-canonical subdirectories
69
+ for path in sorted(root.rglob("*")):
70
+ if not path.is_file() or not is_council_artefact(path):
71
+ continue
72
+ try:
73
+ rel = path.relative_to(root)
74
+ except ValueError:
75
+ continue
76
+ if len(rel.parts) == 1:
77
+ continue # already handled above
78
+ if rel.parts[0] in CANONICAL_DIRS:
79
+ continue
80
+ findings.append(
81
+ f"{path}: council artefact in non-canonical directory "
82
+ f"agents/{rel.parts[0]}/ — only council-questions/, "
83
+ f"council-responses/, council-sessions/ are allowed."
84
+ )
85
+
86
+ return findings
87
+
88
+
89
+ def main() -> int:
90
+ findings = find_violations(AGENTS_ROOT)
91
+ if findings:
92
+ print("❌ Council layout violations:\n")
93
+ for f in findings:
94
+ print(f" - {f}")
95
+ print(
96
+ "\nRule: .agent-src.uncompressed/skills/ai-council/SKILL.md "
97
+ '§ "Output path convention"'
98
+ )
99
+ return 1
100
+ print("✅ Council layout clean.")
101
+ return 0
102
+
103
+
104
+ if __name__ == "__main__":
105
+ sys.exit(main())
@@ -0,0 +1,145 @@
1
+ #!/usr/bin/env python3
2
+ """CI guard for the `no-council-references` rule.
3
+
4
+ Council artefacts under `agents/council-{questions,responses,sessions}/`
5
+ are gitignored, local-only, and auto-pruned. A link to a specific
6
+ council file rots three ways: gitignored (not in cloned repo),
7
+ pruned after the retention window (gone even locally), and the
8
+ installed `.augment/` projection cannot follow a path that does not
9
+ exist in the consumer.
10
+
11
+ This linter scans durable artefacts for forbidden links to specific
12
+ council files. Directory mentions and placeholder paths
13
+ (`<timestamp>`, `<topic-slug>`) are allowed — they document the
14
+ output-path convention, not a live reference.
15
+
16
+ Forbidden hits in this codebase exist today (kernel-membership ADRs
17
+ cite real session JSONs as decision traces). Suppress them with an
18
+ inline pragma at the end of the line:
19
+
20
+ `agents/council-sessions/...json` <!-- council-ref-allowed: <reason> -->
21
+
22
+ Exit codes:
23
+ 0 — no forbidden references.
24
+ 1 — at least one forbidden reference found.
25
+
26
+ Invocation (from project root):
27
+ python3 scripts/check_council_references.py
28
+ """
29
+
30
+ from __future__ import annotations
31
+
32
+ import re
33
+ import sys
34
+ from pathlib import Path
35
+ from typing import Iterable
36
+
37
+ ROOT = Path(".")
38
+
39
+ # A specific file inside a council dir: must end with .md or .json,
40
+ # must NOT contain `<` or `>` (placeholders), must NOT contain backticks
41
+ # or quotes (those are line delimiters, not path content).
42
+ PATTERN = re.compile(
43
+ r"agents/council-(?:questions|responses|sessions)/"
44
+ r"([^\s\"'<>)\]`]+\.(?:md|json))"
45
+ )
46
+
47
+ # Only these durable surfaces are scanned. Archive, analysis, and the
48
+ # council dirs themselves are excluded by design.
49
+ SCAN_ROOTS = (
50
+ ".agent-src.uncompressed",
51
+ "agents/roadmaps",
52
+ "agents/contexts",
53
+ "agents/docs",
54
+ "docs/contracts",
55
+ "docs/decisions",
56
+ "docs/guidelines",
57
+ )
58
+ SCAN_EXTS = (".md", ".yml", ".yaml", ".json", ".py")
59
+
60
+ # Files (or directory prefixes) that legitimately document the output
61
+ # convention or are scratch / archived. Paths are POSIX-style, repo-relative.
62
+ ALLOWLIST_PREFIXES: tuple[str, ...] = (
63
+ # Archived roadmaps — historical evidence trail.
64
+ "agents/roadmaps/archive/",
65
+ # Working comparison docs — forward-refs to planned artefacts (mirrors
66
+ # the SKIP_DIRS contract in scripts/check_references.py).
67
+ "agents/analysis/",
68
+ # The rule itself documents forbidden vs. allowed forms.
69
+ ".agent-src.uncompressed/rules/no-council-references.md",
70
+ # ai-council skill documents the output-path schema.
71
+ ".agent-src.uncompressed/skills/ai-council/",
72
+ # Council commands document the output-path schema.
73
+ ".agent-src.uncompressed/commands/council/",
74
+ ".agent-src.uncompressed/commands/council.md",
75
+ )
76
+ # Top-level files that are also exempt (e.g. CHANGELOG with historical entries).
77
+ ALLOWLIST_FILES: frozenset[str] = frozenset({
78
+ "CHANGELOG.md",
79
+ })
80
+
81
+ INLINE_PRAGMA = re.compile(r"<!--\s*council-ref-allowed:[^>]*-->")
82
+
83
+
84
+ def _is_allowlisted(rel: str) -> bool:
85
+ if rel in ALLOWLIST_FILES:
86
+ return True
87
+ return any(rel.startswith(prefix) for prefix in ALLOWLIST_PREFIXES)
88
+
89
+
90
+ def _scan_file(path: Path) -> list[tuple[int, str]]:
91
+ findings: list[tuple[int, str]] = []
92
+ try:
93
+ text = path.read_text(encoding="utf-8")
94
+ except (OSError, UnicodeDecodeError):
95
+ return findings
96
+ for ln, line in enumerate(text.splitlines(), 1):
97
+ if INLINE_PRAGMA.search(line):
98
+ continue
99
+ for m in PATTERN.finditer(line):
100
+ findings.append((ln, m.group(0)))
101
+ return findings
102
+
103
+
104
+ def _iter_files(roots: Iterable[str]) -> Iterable[Path]:
105
+ for root in roots:
106
+ base = ROOT / root
107
+ if not base.exists():
108
+ continue
109
+ if base.is_file():
110
+ yield base
111
+ continue
112
+ for path in sorted(base.rglob("*")):
113
+ if path.is_file() and path.suffix in SCAN_EXTS:
114
+ yield path
115
+
116
+
117
+ def main() -> int:
118
+ violations: list[tuple[Path, int, str]] = []
119
+ for path in _iter_files(SCAN_ROOTS):
120
+ rel = path.as_posix()
121
+ if _is_allowlisted(rel):
122
+ continue
123
+ for ln, ref in _scan_file(path):
124
+ violations.append((path, ln, ref))
125
+
126
+ if not violations:
127
+ print("✅ No forbidden council references in durable artefacts.")
128
+ return 0
129
+
130
+ print(f"❌ {len(violations)} forbidden council reference(s):\n")
131
+ for path, ln, ref in violations:
132
+ print(f" - {path.as_posix()}:{ln}: {ref}")
133
+ print(
134
+ "\nRule: .agent-src/rules/no-council-references.md\n"
135
+ "Fix: inline the convergence summary (members + date) instead of\n"
136
+ "linking the file. Append "
137
+ "<!-- council-ref-allowed: <reason> --> on the same line to\n"
138
+ "suppress when the reference is genuinely required (ADR / contract\n"
139
+ "decision trace)."
140
+ )
141
+ return 1
142
+
143
+
144
+ if __name__ == "__main__":
145
+ sys.exit(main())
@@ -316,6 +316,8 @@ _TASK_FENCE_RE = re.compile(r"^\s*task\s+([a-z][a-z0-9:_-]*)\b")
316
316
  _TASK_DETECTOR_SKIP = (
317
317
  "rules/augment-portability.md",
318
318
  "contexts/communication/rules-auto/augment-portability-mechanics.md",
319
+ "rules/package-ci-checks.md",
320
+ "contexts/communication/rules-auto/package-ci-checks-mechanics.md",
319
321
  )
320
322
 
321
323
 
@@ -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())