@event4u/agent-config 1.16.0 → 1.18.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 (224) hide show
  1. package/.agent-src/commands/{agents-audit.md → agents/audit.md} +4 -3
  2. package/.agent-src/commands/{agents-cleanup.md → agents/cleanup.md} +12 -6
  3. package/.agent-src/commands/{agents-prepare.md → agents/prepare.md} +4 -3
  4. package/.agent-src/commands/agents.md +46 -0
  5. package/.agent-src/commands/{chat-history-checkpoint.md → chat-history/checkpoint.md} +4 -4
  6. package/.agent-src/commands/{chat-history-clear.md → chat-history/clear.md} +4 -4
  7. package/.agent-src/commands/{chat-history-resume.md → chat-history/resume.md} +4 -4
  8. package/.agent-src/commands/chat-history/show.md +107 -0
  9. package/.agent-src/commands/chat-history.md +33 -89
  10. package/.agent-src/commands/{commit-in-chunks.md → commit/in-chunks.md} +15 -13
  11. package/.agent-src/commands/commit.md +22 -2
  12. package/.agent-src/commands/{context-create.md → context/create.md} +4 -3
  13. package/.agent-src/commands/{context-refactor.md → context/refactor.md} +4 -3
  14. package/.agent-src/commands/context.md +44 -0
  15. package/.agent-src/commands/{copilot-agents-init.md → copilot-agents/init.md} +4 -3
  16. package/.agent-src/commands/{copilot-agents-optimize.md → copilot-agents/optimize.md} +4 -3
  17. package/.agent-src/commands/copilot-agents.md +44 -0
  18. package/.agent-src/commands/council/default.md +221 -0
  19. package/.agent-src/commands/{council-design.md → council/design.md} +6 -5
  20. package/.agent-src/commands/{council-optimize.md → council/optimize.md} +7 -6
  21. package/.agent-src/commands/{council-pr.md → council/pr.md} +6 -5
  22. package/.agent-src/commands/council.md +47 -212
  23. package/.agent-src/commands/{create-pr-description.md → create-pr/description-only.md} +4 -2
  24. package/.agent-src/commands/create-pr.md +26 -5
  25. package/.agent-src/commands/{feature-dev.md → feature/dev.md} +5 -10
  26. package/.agent-src/commands/{feature-explore.md → feature/explore.md} +4 -8
  27. package/.agent-src/commands/{feature-plan.md → feature/plan.md} +4 -8
  28. package/.agent-src/commands/{feature-refactor.md → feature/refactor.md} +4 -8
  29. package/.agent-src/commands/{feature-roadmap.md → feature/roadmap.md} +6 -10
  30. package/.agent-src/commands/feature.md +6 -12
  31. package/.agent-src/commands/{fix-ci.md → fix/ci.md} +4 -8
  32. package/.agent-src/commands/{fix-portability.md → fix/portability.md} +4 -8
  33. package/.agent-src/commands/{fix-pr-bot-comments.md → fix/pr-bots.md} +4 -8
  34. package/.agent-src/commands/{fix-pr-developer-comments.md → fix/pr-developers.md} +4 -8
  35. package/.agent-src/commands/{fix-pr-comments.md → fix/pr.md} +7 -11
  36. package/.agent-src/commands/{fix-references.md → fix/refs.md} +4 -8
  37. package/.agent-src/commands/{fix-seeder.md → fix/seeder.md} +4 -8
  38. package/.agent-src/commands/fix.md +7 -13
  39. package/.agent-src/commands/{do-and-judge.md → judge/on-diff.md} +4 -3
  40. package/.agent-src/commands/judge/solo.md +90 -0
  41. package/.agent-src/commands/{do-in-steps.md → judge/steps.md} +4 -3
  42. package/.agent-src/commands/judge.md +35 -70
  43. package/.agent-src/commands/{memory-add.md → memory/add.md} +4 -3
  44. package/.agent-src/commands/{memory-full.md → memory/load.md} +4 -3
  45. package/.agent-src/commands/{memory-promote.md → memory/promote.md} +4 -3
  46. package/.agent-src/commands/{propose-memory.md → memory/propose.md} +4 -3
  47. package/.agent-src/commands/memory.md +48 -0
  48. package/.agent-src/commands/{module-create.md → module/create.md} +4 -3
  49. package/.agent-src/commands/{module-explore.md → module/explore.md} +4 -3
  50. package/.agent-src/commands/module.md +44 -0
  51. package/.agent-src/commands/{optimize-agents.md → optimize/agents.md} +4 -8
  52. package/.agent-src/commands/{optimize-augmentignore.md → optimize/augmentignore.md} +4 -9
  53. package/.agent-src/commands/{optimize-rtk-filters.md → optimize/rtk.md} +4 -8
  54. package/.agent-src/commands/{optimize-skills.md → optimize/skills.md} +4 -8
  55. package/.agent-src/commands/optimize.md +4 -10
  56. package/.agent-src/commands/{override-create.md → override/create.md} +4 -3
  57. package/.agent-src/commands/{override-manage.md → override/manage.md} +4 -3
  58. package/.agent-src/commands/override.md +44 -0
  59. package/.agent-src/commands/{roadmap-create.md → roadmap/create.md} +4 -3
  60. package/.agent-src/commands/{roadmap-execute.md → roadmap/execute.md} +4 -3
  61. package/.agent-src/commands/roadmap.md +44 -0
  62. package/.agent-src/commands/{tests-create.md → tests/create.md} +4 -3
  63. package/.agent-src/commands/{tests-execute.md → tests/execute.md} +4 -3
  64. package/.agent-src/commands/tests.md +44 -0
  65. package/.agent-src/contexts/communication/rules-auto/artifact-engagement-recording-mechanics.md +72 -0
  66. package/.agent-src/contexts/communication/rules-auto/augment-portability-mechanics.md +79 -0
  67. package/.agent-src/contexts/communication/rules-auto/augment-source-of-truth-mechanics.md +98 -0
  68. package/.agent-src/contexts/communication/rules-auto/cli-output-handling-mechanics.md +87 -0
  69. package/.agent-src/contexts/communication/rules-auto/command-suggestion-policy-mechanics.md +62 -0
  70. package/.agent-src/contexts/communication/rules-auto/docs-sync-mechanics.md +78 -0
  71. package/.agent-src/contexts/communication/rules-auto/package-ci-checks-mechanics.md +85 -0
  72. package/.agent-src/contexts/communication/rules-auto/review-routing-awareness-mechanics.md +65 -0
  73. package/.agent-src/contexts/communication/rules-auto/roadmap-progress-sync-mechanics.md +78 -0
  74. package/.agent-src/contexts/communication/rules-auto/skill-quality-mechanics.md +62 -0
  75. package/.agent-src/contexts/communication/rules-auto/slash-command-routing-policy-mechanics.md +55 -0
  76. package/.agent-src/contexts/communication/rules-auto/ui-audit-gate-mechanics.md +53 -0
  77. package/.agent-src/contexts/communication/rules-auto/user-interaction-mechanics.md +77 -0
  78. package/.agent-src/contexts/judges/no-consolidate-rationale.md +102 -0
  79. package/.agent-src/contexts/judges/persona-voice-rubric.md +140 -0
  80. package/.agent-src/rules/artifact-engagement-recording.md +13 -69
  81. package/.agent-src/rules/ask-when-uncertain.md +27 -42
  82. package/.agent-src/rules/augment-portability.md +15 -61
  83. package/.agent-src/rules/augment-source-of-truth.md +27 -93
  84. package/.agent-src/rules/cli-output-handling.md +10 -76
  85. package/.agent-src/rules/command-suggestion-policy.md +18 -59
  86. package/.agent-src/rules/commit-conventions.md +17 -14
  87. package/.agent-src/rules/context-hygiene.md +6 -0
  88. package/.agent-src/rules/direct-answers.md +35 -59
  89. package/.agent-src/rules/docker-commands.md +5 -5
  90. package/.agent-src/rules/docs-sync.md +15 -69
  91. package/.agent-src/rules/language-and-tone.md +48 -72
  92. package/.agent-src/rules/missing-tool-handling.md +28 -22
  93. package/.agent-src/rules/no-cheap-questions.md +39 -53
  94. package/.agent-src/rules/no-roadmap-references.md +73 -0
  95. package/.agent-src/rules/onboarding-gate.md +7 -0
  96. package/.agent-src/rules/package-ci-checks.md +21 -61
  97. package/.agent-src/rules/preservation-guard.md +64 -29
  98. package/.agent-src/rules/review-routing-awareness.md +24 -43
  99. package/.agent-src/rules/roadmap-progress-sync.md +31 -65
  100. package/.agent-src/rules/rule-type-governance.md +28 -0
  101. package/.agent-src/rules/security-sensitive-stop.md +8 -8
  102. package/.agent-src/rules/skill-quality.md +16 -48
  103. package/.agent-src/rules/slash-command-routing-policy.md +7 -4
  104. package/.agent-src/rules/think-before-action.md +52 -42
  105. package/.agent-src/rules/tool-safety.md +19 -16
  106. package/.agent-src/rules/ui-audit-gate.md +24 -38
  107. package/.agent-src/rules/user-interaction.md +13 -68
  108. package/.agent-src/skills/ai-council/SKILL.md +2 -0
  109. package/.agent-src/skills/api-testing/SKILL.md +1 -1
  110. package/.agent-src/skills/check-refs/SKILL.md +59 -40
  111. package/.agent-src/skills/conventional-commits-writing/SKILL.md +86 -28
  112. package/.agent-src/skills/copilot-agents-optimization/SKILL.md +5 -5
  113. package/.agent-src/skills/developer-like-execution/SKILL.md +4 -4
  114. package/.agent-src/skills/finishing-a-development-branch/SKILL.md +101 -65
  115. package/.agent-src/skills/flux/SKILL.md +30 -10
  116. package/.agent-src/skills/github-ci/SKILL.md +2 -2
  117. package/.agent-src/skills/judge-code-quality/SKILL.md +7 -8
  118. package/.agent-src/skills/judge-security-auditor/SKILL.md +4 -5
  119. package/.agent-src/skills/judge-test-coverage/SKILL.md +3 -4
  120. package/.agent-src/skills/lint-skills/SKILL.md +57 -39
  121. package/.agent-src/skills/md-language-check/SKILL.md +61 -39
  122. package/.agent-src/skills/override-management/SKILL.md +5 -5
  123. package/.agent-src/skills/quality-tools/SKILL.md +2 -2
  124. package/.agent-src/skills/react-shadcn-ui/SKILL.md +116 -43
  125. package/.agent-src/skills/readme-reviewer/SKILL.md +30 -29
  126. package/.agent-src/skills/readme-writing/SKILL.md +78 -53
  127. package/.agent-src/skills/readme-writing-package/SKILL.md +50 -47
  128. package/.agent-src/skills/receiving-code-review/SKILL.md +52 -47
  129. package/.agent-src/skills/refine-prompt/SKILL.md +0 -1
  130. package/.agent-src/skills/requesting-code-review/SKILL.md +35 -30
  131. package/.agent-src/skills/security/SKILL.md +7 -2
  132. package/.agent-src/skills/security-audit/SKILL.md +7 -3
  133. package/.agent-src/skills/systematic-debugging/SKILL.md +68 -60
  134. package/.agent-src/skills/test-driven-development/SKILL.md +59 -57
  135. package/.agent-src/skills/test-performance/SKILL.md +0 -1
  136. package/.agent-src/skills/traefik/SKILL.md +4 -4
  137. package/.agent-src/skills/verify-completion-evidence/SKILL.md +28 -26
  138. package/.agent-src/templates/roadmaps.md +4 -0
  139. package/.claude-plugin/marketplace.json +22 -11
  140. package/AGENTS.md +2 -2
  141. package/CHANGELOG.md +125 -1
  142. package/README.md +18 -17
  143. package/docs/architecture.md +4 -6
  144. package/docs/catalog.md +67 -39
  145. package/docs/contracts/STABILITY.md +13 -7
  146. package/docs/contracts/adr-chat-history-split.md +1 -3
  147. package/docs/contracts/adr-command-suggestion.md +0 -2
  148. package/docs/contracts/adr-implement-ticket-runtime.md +1 -2
  149. package/docs/contracts/adr-product-ui-track.md +3 -6
  150. package/docs/contracts/adr-prompt-driven-execution.md +3 -4
  151. package/docs/contracts/agent-memory-contract.md +6 -11
  152. package/docs/contracts/artifact-engagement-flow.md +6 -9
  153. package/docs/contracts/command-clusters.md +56 -46
  154. package/docs/contracts/command-suggestion-flow.md +1 -3
  155. package/docs/contracts/context-paths.md +99 -0
  156. package/docs/contracts/file-ownership-matrix.json +6722 -0
  157. package/docs/contracts/file-ownership-matrix.md +134 -0
  158. package/docs/contracts/implement-ticket-flow.md +6 -9
  159. package/docs/contracts/linear-ai-rules-inclusion.md +0 -1
  160. package/docs/contracts/linear-ai-three-layers.md +0 -2
  161. package/docs/contracts/load-context-budget-model.md +258 -0
  162. package/docs/contracts/load-context-schema.md +21 -3
  163. package/docs/contracts/roadmap-complexity-standard.md +137 -0
  164. package/docs/contracts/rule-interactions.md +0 -1
  165. package/docs/contracts/rule-priority-hierarchy.md +1 -1
  166. package/docs/contracts/ui-track-flow.md +7 -17
  167. package/docs/customization.md +2 -0
  168. package/docs/getting-started.md +5 -4
  169. package/docs/guidelines/agent-infra/ask-when-uncertain-demos.md +134 -0
  170. package/docs/guidelines/agent-infra/asking-and-brevity-examples.md +100 -0
  171. package/docs/guidelines/agent-infra/direct-answers-demos.md +145 -0
  172. package/docs/guidelines/agent-infra/verify-before-complete-demos.md +128 -0
  173. package/package.json +1 -1
  174. package/scripts/_phase2_shim_helper.py +109 -0
  175. package/scripts/agent-config +30 -0
  176. package/scripts/ai_council/one_off_archive/2026-05/README.md +45 -0
  177. package/scripts/ai_council/one_off_archive/2026-05/_one_off_2a4_acceptance.py +208 -0
  178. package/scripts/ai_council/one_off_archive/2026-05/_one_off_budget_v2_audit.py +206 -0
  179. package/scripts/ai_council/one_off_archive/2026-05/_one_off_context_layer_v1_estimate.py +67 -0
  180. package/scripts/ai_council/one_off_archive/2026-05/_one_off_context_layer_v1_review.py +292 -0
  181. package/scripts/ai_council/one_off_archive/2026-05/_one_off_followups_review.py +259 -0
  182. package/scripts/ai_council/one_off_archive/2026-05/_one_off_nondestructive_inline_audit.py +209 -0
  183. package/scripts/ai_council/one_off_archive/2026-05/_one_off_phase4_dispatch_latency.py +108 -0
  184. package/scripts/ai_council/one_off_archive/2026-05/_one_off_phase6_trigger_jaccard.py +92 -0
  185. package/scripts/ai_council/one_off_archive/2026-05/_one_off_phase_2a_budget_rebalance.py +257 -0
  186. package/scripts/ai_council/one_off_archive/2026-05/_one_off_phase_2a_post_revert.py +197 -0
  187. package/scripts/ai_council/one_off_archive/2026-05/_one_off_rule_hardening_v1.py +251 -0
  188. package/scripts/ai_council/one_off_archive/2026-05/_one_off_structural_open_questions.py +232 -0
  189. package/scripts/ai_council/one_off_archive/2026-05/_one_off_structural_optimization.py +144 -0
  190. package/scripts/ai_council/one_off_archive/2026-05/_one_off_structural_v3_gaps.py +252 -0
  191. package/scripts/ai_council/one_off_archive/2026-05/_one_off_structural_v3_review.py +240 -0
  192. package/scripts/build_rule_trigger_matrix.py +360 -0
  193. package/scripts/check_always_budget.py +402 -45
  194. package/scripts/check_cluster_patterns.py +159 -0
  195. package/scripts/check_command_count_messaging.py +14 -7
  196. package/scripts/check_context_paths.py +201 -0
  197. package/scripts/check_no_roadmap_refs.py +155 -0
  198. package/scripts/check_one_off_location.py +81 -0
  199. package/scripts/check_phase_coupling.py +148 -0
  200. package/scripts/check_portability.py +2 -0
  201. package/scripts/check_references.py +35 -2
  202. package/scripts/check_safety_floor_untouched.py +125 -0
  203. package/scripts/command_suggester/loader.py +4 -1
  204. package/scripts/compress.py +64 -15
  205. package/scripts/context_hygiene_hook.py +173 -0
  206. package/scripts/generate_index.py +6 -2
  207. package/scripts/generate_ownership_matrix.py +323 -0
  208. package/scripts/hooks/augment-context-hygiene.sh +55 -0
  209. package/scripts/hooks/augment-onboarding-gate.sh +55 -0
  210. package/scripts/hooks/augment-roadmap-progress.sh +57 -0
  211. package/scripts/install.py +105 -45
  212. package/scripts/lint_examples.py +98 -0
  213. package/scripts/lint_no_new_atomic_commands.py +12 -11
  214. package/scripts/lint_roadmap_complexity.py +127 -0
  215. package/scripts/onboarding_gate_hook.py +137 -0
  216. package/scripts/requirements-evals.txt +1 -0
  217. package/scripts/roadmap_progress_hook.py +159 -0
  218. package/scripts/schemas/command.schema.json +4 -3
  219. package/scripts/schemas/rule.schema.json +5 -0
  220. package/scripts/skill_linter.py +1 -0
  221. package/scripts/sync_agent_settings.py +25 -2
  222. package/scripts/update_counts.py +7 -0
  223. /package/scripts/ai_council/{_one_off_rebalancing_audit.py → one_off_archive/2026-05/_one_off_rebalancing_audit.py} +0 -0
  224. /package/scripts/ai_council/{_one_off_roundtrip.py → one_off_archive/2026-05/_one_off_roundtrip.py} +0 -0
@@ -0,0 +1,323 @@
1
+ #!/usr/bin/env python3
2
+ """Generate the file-ownership matrix.
3
+
4
+ Produces:
5
+
6
+ * docs/contracts/file-ownership-matrix.json (machine, internal-locked)
7
+ * agents/contexts/structural/file-ownership-matrix.md (human-readable)
8
+
9
+ Walks `.agent-src.uncompressed/{rules,skills,commands,contexts,personas}/`,
10
+ parses frontmatter for `load_context:` / `load_context_eager:`, scans
11
+ markdown bodies for inline links to `.md` files inside the scanned roots,
12
+ and emits READ_ONLY edges plus depth-2 transitive closure of load_context
13
+ chains. Depth-3 chains abort the build (matches the 0.2.4 nesting cap).
14
+
15
+ Contract: docs/contracts/file-ownership-matrix.md
16
+ Roadmap: road-to-structural-optimization.md § 0.1
17
+
18
+ Modes:
19
+ --check Regenerate to memory and diff against committed JSON.
20
+ Exit 0 if identical, 1 if drifted.
21
+ (default) Regenerate JSON + MD in place; exit 0 on success.
22
+
23
+ Exit codes: 0 = ok, 1 = drift (--check), 2 = depth-3 chain, 3 = internal.
24
+ """
25
+ from __future__ import annotations
26
+
27
+ import argparse
28
+ import json
29
+ import re
30
+ import sys
31
+ from dataclasses import dataclass, field
32
+ from pathlib import Path
33
+ from typing import Iterable
34
+
35
+ import yaml
36
+
37
+ ROOT = Path(__file__).resolve().parent.parent
38
+ SRC_ROOT = ROOT / ".agent-src.uncompressed"
39
+
40
+ SCAN_DIRS = ("rules", "skills", "commands", "contexts", "personas")
41
+
42
+ JSON_OUT = ROOT / "docs" / "contracts" / "file-ownership-matrix.json"
43
+ MD_OUT = ROOT / "agents" / "contexts" / "structural" / "file-ownership-matrix.md"
44
+
45
+ LINK_RE = re.compile(r"\]\(([^)]+\.md)(?:#[^)]*)?\)")
46
+
47
+
48
+ @dataclass
49
+ class FileEntry:
50
+ path: str
51
+ kind: str
52
+ rule_type: str | None = None
53
+ load_context: list[str] = field(default_factory=list)
54
+ load_context_eager: list[str] = field(default_factory=list)
55
+
56
+
57
+ @dataclass
58
+ class Edge:
59
+ source: str
60
+ target: str
61
+ type: str
62
+ via: str
63
+ depth: int
64
+
65
+
66
+ def _rel(p: Path) -> str:
67
+ return p.relative_to(ROOT).as_posix()
68
+
69
+
70
+ def _kind_for(rel: str) -> str:
71
+ parts = rel.split("/")
72
+ if len(parts) >= 3 and parts[0] == ".agent-src.uncompressed":
73
+ return parts[1].rstrip("s") if parts[1] != "personas" else "persona"
74
+ return "unknown"
75
+
76
+
77
+ def _parse_frontmatter(p: Path) -> dict:
78
+ text = p.read_text(encoding="utf-8")
79
+ if not text.startswith("---\n"):
80
+ return {}
81
+ end = text.find("\n---\n", 4)
82
+ if end == -1:
83
+ return {}
84
+ try:
85
+ data = yaml.safe_load(text[4:end])
86
+ except yaml.YAMLError:
87
+ return {}
88
+ return data if isinstance(data, dict) else {}
89
+
90
+
91
+ def _collect_files(src_root: Path) -> list[Path]:
92
+ out: list[Path] = []
93
+ for sub in SCAN_DIRS:
94
+ d = src_root / sub
95
+ if d.exists():
96
+ out.extend(sorted(d.rglob("*.md")))
97
+ return out
98
+
99
+
100
+ def _resolve(target: str, src_root: Path) -> Path | None:
101
+ """Resolve a path string (repo-relative or short) into an absolute Path
102
+ under src_root or the repo root. Return None if not under a scanned root."""
103
+ cand = src_root.parent / target if "/" in target else src_root / target
104
+ try:
105
+ rel = cand.resolve().relative_to(src_root.parent)
106
+ except ValueError:
107
+ return None
108
+ parts = rel.parts
109
+ if len(parts) >= 3 and parts[0] == ".agent-src.uncompressed" and parts[1] in SCAN_DIRS:
110
+ return cand if cand.exists() else None
111
+ return None
112
+
113
+
114
+ def build_matrix(src_root: Path) -> tuple[dict[str, FileEntry], list[Edge], list[str]]:
115
+ """Build the file map + edge list. Returns (files, edges, depth3_chains).
116
+
117
+ depth3_chains is non-empty iff the depth invariant is violated; the
118
+ caller must abort with exit code 2.
119
+ """
120
+ files: dict[str, FileEntry] = {}
121
+ for f in _collect_files(src_root):
122
+ rel = f.relative_to(src_root.parent).as_posix()
123
+ fm = _parse_frontmatter(f)
124
+ rtype = fm.get("type")
125
+ if isinstance(rtype, str):
126
+ rtype = rtype.strip('"').strip("'")
127
+ else:
128
+ rtype = None
129
+ lazy = fm.get("load_context") or []
130
+ eager = fm.get("load_context_eager") or []
131
+ if not isinstance(lazy, list):
132
+ lazy = []
133
+ if not isinstance(eager, list):
134
+ eager = []
135
+ files[rel] = FileEntry(
136
+ path=rel,
137
+ kind=_kind_for(rel),
138
+ rule_type=rtype,
139
+ load_context=[str(x) for x in lazy if isinstance(x, str)],
140
+ load_context_eager=[str(x) for x in eager if isinstance(x, str)],
141
+ )
142
+
143
+ edges: list[Edge] = []
144
+ for rel, entry in files.items():
145
+ for tgt in entry.load_context:
146
+ edges.append(Edge(rel, tgt, "READ_ONLY", "load_context", 1))
147
+ for tgt in entry.load_context_eager:
148
+ edges.append(Edge(rel, tgt, "READ_ONLY", "load_context_eager", 1))
149
+
150
+ # Body markdown links — only count edges to files we know about
151
+ for rel, entry in files.items():
152
+ body = (src_root.parent / rel).read_text(encoding="utf-8")
153
+ body = body.split("\n---\n", 1)[-1] if body.startswith("---\n") else body
154
+ seen_targets: set[str] = set()
155
+ for m in LINK_RE.finditer(body):
156
+ href = m.group(1).strip()
157
+ if href.startswith("http"):
158
+ continue
159
+ resolved = _resolve_link(rel, href, src_root)
160
+ if resolved is None or resolved == rel or resolved in seen_targets:
161
+ continue
162
+ if resolved in files:
163
+ seen_targets.add(resolved)
164
+ edges.append(Edge(rel, resolved, "READ_ONLY", "body_link", 1))
165
+
166
+ # Transitive closure on load_context* edges, depth 2; depth 3 aborts.
167
+ lc_edges_by_src: dict[str, list[str]] = {}
168
+ for e in edges:
169
+ if e.via in ("load_context", "load_context_eager"):
170
+ lc_edges_by_src.setdefault(e.source, []).append(e.target)
171
+
172
+ transitive: list[Edge] = []
173
+ depth3: list[str] = []
174
+ for src, lvl1_targets in lc_edges_by_src.items():
175
+ for t1 in lvl1_targets:
176
+ for t2 in lc_edges_by_src.get(t1, []):
177
+ if t2 == src or t2 == t1:
178
+ continue
179
+ transitive.append(Edge(src, t2, "READ_ONLY", "load_context_transitive", 2))
180
+ # depth-3 probe
181
+ for t3 in lc_edges_by_src.get(t2, []):
182
+ if t3 in (src, t1, t2):
183
+ continue
184
+ depth3.append(f"{src} → {t1} → {t2} → {t3}")
185
+
186
+ edges.extend(transitive)
187
+ for rel in files:
188
+ edges.append(Edge(rel, rel, "WRITE", "self", 0))
189
+
190
+ edges.sort(key=lambda e: (e.source, e.target, e.via, e.depth))
191
+ return files, edges, depth3
192
+
193
+
194
+ def _resolve_link(source_rel: str, href: str, src_root: Path) -> str | None:
195
+ """Resolve a markdown link href (relative to source file) to a repo-relative
196
+ path inside a scanned root, or None."""
197
+ if href.startswith(".agent-src.uncompressed/") or href.startswith("agents/"):
198
+ cand = (src_root.parent / href).resolve()
199
+ else:
200
+ base = (src_root.parent / source_rel).parent
201
+ cand = (base / href).resolve()
202
+ try:
203
+ rel = cand.relative_to(src_root.parent).as_posix()
204
+ except ValueError:
205
+ return None
206
+ parts = rel.split("/")
207
+ if len(parts) >= 3 and parts[0] == ".agent-src.uncompressed" and parts[1] in SCAN_DIRS:
208
+ return rel if cand.exists() else None
209
+ return None
210
+
211
+
212
+ def _to_json(files: dict[str, FileEntry], edges: list[Edge]) -> dict:
213
+ return {
214
+ "version": 1,
215
+ "generated_by": "scripts/generate_ownership_matrix.py",
216
+ "source_of_truth": ".agent-src.uncompressed/",
217
+ "files": {
218
+ rel: {
219
+ "kind": e.kind,
220
+ "rule_type": e.rule_type,
221
+ "load_context": e.load_context,
222
+ "load_context_eager": e.load_context_eager,
223
+ }
224
+ for rel, e in sorted(files.items())
225
+ },
226
+ "edges": [
227
+ {
228
+ "source": e.source,
229
+ "target": e.target,
230
+ "type": e.type,
231
+ "via": e.via,
232
+ "depth": e.depth,
233
+ }
234
+ for e in edges
235
+ ],
236
+ }
237
+
238
+
239
+ def _to_markdown(payload: dict) -> str:
240
+ lines: list[str] = [
241
+ "# File-ownership matrix (regenerated)",
242
+ "",
243
+ "> **Do not edit.** Regenerated by `scripts/generate_ownership_matrix.py`.",
244
+ "> Schema: [`docs/contracts/file-ownership-matrix.md`](../../../docs/contracts/file-ownership-matrix.md).",
245
+ "",
246
+ f"- Schema version: `{payload['version']}`",
247
+ f"- Source of truth: `{payload['source_of_truth']}`",
248
+ f"- Files indexed: **{len(payload['files'])}**",
249
+ f"- Edges (incl. self-WRITE): **{len(payload['edges'])}**",
250
+ "",
251
+ "## READ_ONLY edges",
252
+ "",
253
+ "| Source | Target | Via | Depth |",
254
+ "|---|---|---|---:|",
255
+ ]
256
+ ro = [e for e in payload["edges"] if e["type"] == "READ_ONLY"]
257
+ for e in ro:
258
+ lines.append(f"| `{e['source']}` | `{e['target']}` | `{e['via']}` | {e['depth']} |")
259
+ if not ro:
260
+ lines.append("| _(none)_ | | | |")
261
+ lines += [
262
+ "",
263
+ "## Files by kind",
264
+ "",
265
+ "| Kind | Count |",
266
+ "|---|---:|",
267
+ ]
268
+ counts: dict[str, int] = {}
269
+ for f in payload["files"].values():
270
+ counts[f["kind"]] = counts.get(f["kind"], 0) + 1
271
+ for k in sorted(counts):
272
+ lines.append(f"| `{k}` | {counts[k]} |")
273
+ lines.append("")
274
+ return "\n".join(lines)
275
+
276
+
277
+ def _write_outputs(payload: dict, json_out: Path, md_out: Path) -> None:
278
+ json_out.parent.mkdir(parents=True, exist_ok=True)
279
+ md_out.parent.mkdir(parents=True, exist_ok=True)
280
+ json_out.write_text(json.dumps(payload, indent=2, sort_keys=False) + "\n", encoding="utf-8")
281
+ md_out.write_text(_to_markdown(payload) + "\n", encoding="utf-8")
282
+
283
+
284
+ def main(argv: Iterable[str] | None = None) -> int:
285
+ ap = argparse.ArgumentParser(description=__doc__)
286
+ ap.add_argument("--check", action="store_true",
287
+ help="Regenerate to memory and diff against committed JSON.")
288
+ args = ap.parse_args(list(argv) if argv is not None else None)
289
+
290
+ if not SRC_ROOT.is_dir():
291
+ print(f"❌ source dir missing: {SRC_ROOT}", file=sys.stderr)
292
+ return 3
293
+
294
+ files, edges, depth3 = build_matrix(SRC_ROOT)
295
+ if depth3:
296
+ print("❌ load_context depth-3 chain detected (limit is 2):", file=sys.stderr)
297
+ for chain in depth3:
298
+ print(f" 🔴 {chain}", file=sys.stderr)
299
+ return 2
300
+
301
+ payload = _to_json(files, edges)
302
+
303
+ if args.check:
304
+ if not JSON_OUT.exists():
305
+ print(f"❌ {JSON_OUT.relative_to(ROOT)} not committed; run `task generate-ownership-matrix`",
306
+ file=sys.stderr)
307
+ return 1
308
+ committed = json.loads(JSON_OUT.read_text(encoding="utf-8"))
309
+ if committed != payload:
310
+ print("❌ ownership matrix is stale — run `task generate-ownership-matrix` and commit",
311
+ file=sys.stderr)
312
+ return 1
313
+ print(f"✅ ownership matrix in sync ({len(files)} files, {len(edges)} edges)")
314
+ return 0
315
+
316
+ _write_outputs(payload, JSON_OUT, MD_OUT)
317
+ print(f"✅ wrote {JSON_OUT.relative_to(ROOT)} ({len(files)} files, {len(edges)} edges)")
318
+ print(f"✅ wrote {MD_OUT.relative_to(ROOT)}")
319
+ return 0
320
+
321
+
322
+ if __name__ == "__main__":
323
+ sys.exit(main())
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env bash
2
+ # Augment Code lifecycle-hook trampoline for context-hygiene.
3
+ #
4
+ # Augment requires hook scripts to use the .sh extension and live at
5
+ # either a system path (/etc/augment/...) or user scope
6
+ # (~/.augment/...). This trampoline lives at user scope and dispatches
7
+ # every PostToolUse event to whichever workspace fired it, so a single
8
+ # install covers every project that has ./agent-config available.
9
+ #
10
+ # Behaviour:
11
+ # - Read the JSON event from stdin into a buffer.
12
+ # - Extract workspace_roots[0]; bail silently when missing.
13
+ # - cd into that workspace; bail silently when it is not a directory
14
+ # or does not contain ./agent-config.
15
+ # - Re-pipe the original JSON into
16
+ # ./agent-config context-hygiene:hook --platform augment
17
+ # so context_hygiene_hook.py can update the per-turn tracker.
18
+ # - Always exit 0 — PostToolUse hooks must never block.
19
+
20
+ set -u
21
+
22
+ EVENT_DATA="$(cat)"
23
+
24
+ WORKSPACE=""
25
+ if command -v jq >/dev/null 2>&1; then
26
+ WORKSPACE="$(printf '%s' "$EVENT_DATA" \
27
+ | jq -r '.workspace_roots[0] // empty' 2>/dev/null)"
28
+ elif command -v python3 >/dev/null 2>&1; then
29
+ WORKSPACE="$(printf '%s' "$EVENT_DATA" | python3 -c '
30
+ import json, sys
31
+ try:
32
+ data = json.load(sys.stdin)
33
+ except Exception:
34
+ sys.exit(0)
35
+ roots = data.get("workspace_roots") or []
36
+ if roots:
37
+ print(roots[0])
38
+ ' 2>/dev/null)"
39
+ fi
40
+
41
+ if [ -z "$WORKSPACE" ] || [ ! -d "$WORKSPACE" ]; then
42
+ exit 0
43
+ fi
44
+
45
+ cd "$WORKSPACE" 2>/dev/null || exit 0
46
+
47
+ if [ ! -x ./agent-config ]; then
48
+ exit 0
49
+ fi
50
+
51
+ printf '%s' "$EVENT_DATA" \
52
+ | ./agent-config context-hygiene:hook --platform augment \
53
+ >/dev/null 2>&1 || true
54
+
55
+ exit 0
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env bash
2
+ # Augment Code lifecycle-hook trampoline for onboarding-gate.
3
+ #
4
+ # Augment requires hook scripts to use the .sh extension and live at
5
+ # either a system path (/etc/augment/...) or user scope
6
+ # (~/.augment/...). This trampoline lives at user scope and dispatches
7
+ # every event to whichever workspace fired it, so a single install
8
+ # covers every project that has ./agent-config available.
9
+ #
10
+ # Behaviour:
11
+ # - Read the JSON event from stdin into a buffer.
12
+ # - Extract workspace_roots[0]; bail silently when missing.
13
+ # - cd into that workspace; bail silently when it is not a directory
14
+ # or does not contain ./agent-config.
15
+ # - Re-pipe the original JSON into
16
+ # ./agent-config onboarding-gate:hook --platform augment
17
+ # so onboarding_gate_hook.py can refresh the state file.
18
+ # - Always exit 0 — onboarding-gate must never block the agent loop.
19
+
20
+ set -u
21
+
22
+ EVENT_DATA="$(cat)"
23
+
24
+ WORKSPACE=""
25
+ if command -v jq >/dev/null 2>&1; then
26
+ WORKSPACE="$(printf '%s' "$EVENT_DATA" \
27
+ | jq -r '.workspace_roots[0] // empty' 2>/dev/null)"
28
+ elif command -v python3 >/dev/null 2>&1; then
29
+ WORKSPACE="$(printf '%s' "$EVENT_DATA" | python3 -c '
30
+ import json, sys
31
+ try:
32
+ data = json.load(sys.stdin)
33
+ except Exception:
34
+ sys.exit(0)
35
+ roots = data.get("workspace_roots") or []
36
+ if roots:
37
+ print(roots[0])
38
+ ' 2>/dev/null)"
39
+ fi
40
+
41
+ if [ -z "$WORKSPACE" ] || [ ! -d "$WORKSPACE" ]; then
42
+ exit 0
43
+ fi
44
+
45
+ cd "$WORKSPACE" 2>/dev/null || exit 0
46
+
47
+ if [ ! -x ./agent-config ]; then
48
+ exit 0
49
+ fi
50
+
51
+ printf '%s' "$EVENT_DATA" \
52
+ | ./agent-config onboarding-gate:hook --platform augment \
53
+ >/dev/null 2>&1 || true
54
+
55
+ exit 0
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env bash
2
+ # Augment Code lifecycle-hook trampoline for roadmap-progress-sync.
3
+ #
4
+ # Augment requires hook scripts to use the .sh extension and live at
5
+ # either a system path (/etc/augment/...) or user scope
6
+ # (~/.augment/...). This trampoline lives at user scope and dispatches
7
+ # every PostToolUse event to whichever workspace fired it, so a single
8
+ # install covers every project that has ./agent-config available.
9
+ #
10
+ # Behaviour:
11
+ # - Read the JSON event from stdin into a buffer.
12
+ # - Extract workspace_roots[0]; bail silently when missing.
13
+ # - cd into that workspace; bail silently when it is not a directory
14
+ # or does not contain ./agent-config.
15
+ # - Re-pipe the original JSON into
16
+ # ./agent-config roadmap-progress:hook --platform augment
17
+ # so roadmap_progress_hook.py runs the path filter and decides
18
+ # whether to regenerate the dashboard.
19
+ # - Always exit 0 — PostToolUse hooks must never block.
20
+
21
+ set -u
22
+
23
+ EVENT_DATA="$(cat)"
24
+
25
+ # Extract workspace_roots[0] using whichever JSON tool is available.
26
+ WORKSPACE=""
27
+ if command -v jq >/dev/null 2>&1; then
28
+ WORKSPACE="$(printf '%s' "$EVENT_DATA" \
29
+ | jq -r '.workspace_roots[0] // empty' 2>/dev/null)"
30
+ elif command -v python3 >/dev/null 2>&1; then
31
+ WORKSPACE="$(printf '%s' "$EVENT_DATA" | python3 -c '
32
+ import json, sys
33
+ try:
34
+ data = json.load(sys.stdin)
35
+ except Exception:
36
+ sys.exit(0)
37
+ roots = data.get("workspace_roots") or []
38
+ if roots:
39
+ print(roots[0])
40
+ ' 2>/dev/null)"
41
+ fi
42
+
43
+ if [ -z "$WORKSPACE" ] || [ ! -d "$WORKSPACE" ]; then
44
+ exit 0
45
+ fi
46
+
47
+ cd "$WORKSPACE" 2>/dev/null || exit 0
48
+
49
+ if [ ! -x ./agent-config ]; then
50
+ exit 0
51
+ fi
52
+
53
+ printf '%s' "$EVENT_DATA" \
54
+ | ./agent-config roadmap-progress:hook --platform augment \
55
+ >/dev/null 2>&1 || true
56
+
57
+ exit 0
@@ -464,44 +464,76 @@ def ensure_augment_bridge(project_root: Path, force: bool) -> None:
464
464
  # .augment/settings.json is plugin enablement, not hooks.
465
465
  AUGMENT_USER_DIR = Path.home() / ".augment"
466
466
  AUGMENT_USER_HOOKS_DIR = AUGMENT_USER_DIR / "hooks"
467
- AUGMENT_TRAMPOLINE_NAME = "augment-chat-history.sh"
468
- AUGMENT_HOOK_EVENTS = ("SessionStart", "SessionEnd", "Stop", "PostToolUse")
467
+ AUGMENT_CHAT_HISTORY_TRAMPOLINE = "augment-chat-history.sh"
468
+ AUGMENT_ROADMAP_PROGRESS_TRAMPOLINE = "augment-roadmap-progress.sh"
469
+ AUGMENT_ONBOARDING_GATE_TRAMPOLINE = "augment-onboarding-gate.sh"
470
+ AUGMENT_CONTEXT_HYGIENE_TRAMPOLINE = "augment-context-hygiene.sh"
471
+ # (trampoline name, list of events it should fire on). Each trampoline
472
+ # is a self-contained workspace router; mapping them per-event keeps the
473
+ # wiring explicit and lets a future hook bind to a different surface
474
+ # without touching the chat-history one.
475
+ AUGMENT_HOOK_BINDINGS = (
476
+ (AUGMENT_CHAT_HISTORY_TRAMPOLINE,
477
+ ("SessionStart", "SessionEnd", "Stop", "PostToolUse")),
478
+ (AUGMENT_ROADMAP_PROGRESS_TRAMPOLINE,
479
+ ("PostToolUse",)),
480
+ (AUGMENT_ONBOARDING_GATE_TRAMPOLINE,
481
+ ("SessionStart",)),
482
+ (AUGMENT_CONTEXT_HYGIENE_TRAMPOLINE,
483
+ ("PostToolUse",)),
484
+ )
485
+
486
+
487
+ def _deploy_augment_trampoline(package_root: Path, name: str, force: bool) -> Path | None:
488
+ src = package_root / "scripts" / "hooks" / name
489
+ if not src.exists():
490
+ skip(f"augment trampoline missing in package: {src}")
491
+ return None
492
+ AUGMENT_USER_HOOKS_DIR.mkdir(parents=True, exist_ok=True)
493
+ dst = AUGMENT_USER_HOOKS_DIR / name
494
+ src_text = src.read_text(encoding="utf-8")
495
+ if dst.exists() and dst.read_text(encoding="utf-8") == src_text and not force:
496
+ skip(f"~/.augment/hooks/{name} already up to date")
497
+ else:
498
+ dst.write_text(src_text, encoding="utf-8")
499
+ dst.chmod(0o755)
500
+ success(f"~/.augment/hooks/{name} installed")
501
+ return dst
469
502
 
470
503
 
471
504
  def ensure_augment_user_hooks(package_root: Path, force: bool) -> None:
472
- """Deploy the Augment lifecycle-hook trampoline at user scope.
505
+ """Deploy the Augment lifecycle-hook trampolines at user scope.
473
506
 
474
507
  Augment hook scripts must use the .sh extension and be referenced by
475
508
  absolute path; user scope is the only surface that fires for both the
476
509
  CLI and the IDE plugins. This installs once per developer (not per
477
- project) — the trampoline reads workspace_roots from the event payload
478
- and dispatches into whichever project is active at hook-fire time.
510
+ project) — each trampoline reads workspace_roots from the event
511
+ payload and dispatches into whichever project is active at hook-fire
512
+ time.
513
+
514
+ Trampolines deployed (see AUGMENT_HOOK_BINDINGS for the source of
515
+ truth):
516
+ - augment-chat-history.sh → SessionStart/SessionEnd/Stop/PostToolUse
517
+ - augment-roadmap-progress.sh → PostToolUse (path-filtered to
518
+ agents/roadmaps/ — see scripts/roadmap_progress_hook.py)
519
+ - augment-onboarding-gate.sh → SessionStart (refresh
520
+ agents/state/onboarding-gate.json from .agent-settings.yml)
521
+ - augment-context-hygiene.sh → PostToolUse (per-turn counter,
522
+ loop detection, freshness milestones)
479
523
  """
480
- src = package_root / "scripts" / "hooks" / AUGMENT_TRAMPOLINE_NAME
481
- if not src.exists():
482
- skip(f"augment trampoline missing in package: {src}")
483
- return
484
-
485
- AUGMENT_USER_HOOKS_DIR.mkdir(parents=True, exist_ok=True)
486
- dst = AUGMENT_USER_HOOKS_DIR / AUGMENT_TRAMPOLINE_NAME
524
+ per_event: dict[str, list] = {}
525
+ for name, events in AUGMENT_HOOK_BINDINGS:
526
+ dst = _deploy_augment_trampoline(package_root, name, force)
527
+ if dst is None:
528
+ continue
529
+ entry = {"hooks": [{"type": "command", "command": str(dst)}]}
530
+ for event in events:
531
+ per_event.setdefault(event, []).append(entry)
487
532
 
488
- src_text = src.read_text(encoding="utf-8")
489
- if dst.exists() and dst.read_text(encoding="utf-8") == src_text and not force:
490
- skip(f"~/.augment/hooks/{AUGMENT_TRAMPOLINE_NAME} already up to date")
491
- else:
492
- dst.write_text(src_text, encoding="utf-8")
493
- dst.chmod(0o755)
494
- success(f"~/.augment/hooks/{AUGMENT_TRAMPOLINE_NAME} installed")
533
+ if not per_event:
534
+ return
495
535
 
496
- hook_entry = {
497
- "hooks": [
498
- {
499
- "type": "command",
500
- "command": str(dst),
501
- },
502
- ],
503
- }
504
- settings_patch: dict = {"hooks": {event: [hook_entry] for event in AUGMENT_HOOK_EVENTS}}
536
+ settings_patch: dict = {"hooks": per_event}
505
537
  merge_json_file(
506
538
  AUGMENT_USER_DIR / "settings.json",
507
539
  settings_patch,
@@ -510,36 +542,64 @@ def ensure_augment_user_hooks(package_root: Path, force: bool) -> None:
510
542
  )
511
543
 
512
544
 
513
- def _chat_history_hook_block(platform: str) -> dict:
514
- """Single hook entry that calls ./agent-config chat-history:hook --platform <name>."""
545
+ def _claude_hook_block(subcommand: str) -> dict:
546
+ """Single hook entry that calls ./agent-config <subcommand> --platform claude."""
515
547
  return {
516
548
  "hooks": [
517
549
  {
518
550
  "type": "command",
519
- "command": f"./agent-config chat-history:hook --platform {platform}",
551
+ "command": f"./agent-config {subcommand} --platform claude",
520
552
  },
521
553
  ],
522
554
  }
523
555
 
524
556
 
525
- def ensure_claude_bridge(project_root: Path, force: bool) -> None:
526
- """Deploy .claude/settings.json with plugin enablement and chat-history hooks.
557
+ # Claude Code Tier 1 hook bindings — keep in sync with AUGMENT_HOOK_BINDINGS.
558
+ # `chat-history:hook` is the cross-cutting transcript hook; the three
559
+ # rule-specific hooks are the Phase 4 Tier 1 set from
560
+ # `road-to-rule-hardening.md`.
561
+ CLAUDE_HOOK_SUBCOMMANDS = {
562
+ "chat-history": "chat-history:hook",
563
+ "roadmap-progress": "roadmap-progress:hook",
564
+ "onboarding-gate": "onboarding-gate:hook",
565
+ "context-hygiene": "context-hygiene:hook",
566
+ }
567
+ # (subcommand-key, list of Claude Code lifecycle events). Mirrors
568
+ # AUGMENT_HOOK_BINDINGS so each rule fires on the same logical surface
569
+ # on both platforms — the contract from
570
+ # `agents/contexts/hardening-pattern.md` § Cross-platform parity.
571
+ CLAUDE_HOOK_BINDINGS = (
572
+ ("chat-history",
573
+ ("SessionStart", "UserPromptSubmit", "PostToolUse", "Stop", "SessionEnd")),
574
+ ("roadmap-progress",
575
+ ("PostToolUse",)),
576
+ ("onboarding-gate",
577
+ ("SessionStart",)),
578
+ ("context-hygiene",
579
+ ("PostToolUse",)),
580
+ )
527
581
 
528
- Hooks dispatch to scripts/chat_history.py via the project-root ./agent-config
529
- wrapper. They are no-ops when chat_history.enabled is false in
530
- .agent-settings.yml. Idempotent: reruns merge cleanly without duplicating
531
- entries (deep_merge replaces hook arrays rather than appending).
582
+
583
+ def ensure_claude_bridge(project_root: Path, force: bool) -> None:
584
+ """Deploy .claude/settings.json with plugin enablement and Tier 1 hooks.
585
+
586
+ Hooks dispatch to the project-root ./agent-config wrapper, which routes
587
+ to the per-rule Python implementation (chat_history.py,
588
+ roadmap_progress_hook.py, onboarding_gate_hook.py,
589
+ context_hygiene_hook.py). They are no-ops when the relevant feature is
590
+ disabled in .agent-settings.yml. Idempotent: reruns merge cleanly
591
+ without duplicating entries (deep_merge replaces hook arrays rather
592
+ than appending).
532
593
  """
533
- claude_hook = _chat_history_hook_block("claude")
594
+ per_event: dict[str, list] = {}
595
+ for key, events in CLAUDE_HOOK_BINDINGS:
596
+ block = _claude_hook_block(CLAUDE_HOOK_SUBCOMMANDS[key])
597
+ for event in events:
598
+ per_event.setdefault(event, []).append(block)
599
+
534
600
  bridge = {
535
601
  "enabledPlugins": {"agent-conf@event4u": True},
536
- "hooks": {
537
- "SessionStart": [claude_hook],
538
- "UserPromptSubmit": [claude_hook],
539
- "PostToolUse": [claude_hook],
540
- "Stop": [claude_hook],
541
- "SessionEnd": [claude_hook],
542
- },
602
+ "hooks": per_event,
543
603
  }
544
604
  merge_json_file(project_root / ".claude" / "settings.json", bridge, force, ".claude/settings.json")
545
605