@event4u/agent-config 1.15.0 → 1.16.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 (244) hide show
  1. package/.agent-src/commands/bug-fix.md +1 -1
  2. package/.agent-src/commands/bug-investigate.md +2 -2
  3. package/.agent-src/commands/chat-history-checkpoint.md +1 -1
  4. package/.agent-src/commands/chat-history-clear.md +1 -1
  5. package/.agent-src/commands/chat-history.md +1 -1
  6. package/.agent-src/commands/check-current-md.md +1 -1
  7. package/.agent-src/commands/council-design.md +96 -0
  8. package/.agent-src/commands/council-optimize.md +115 -0
  9. package/.agent-src/commands/council-pr.md +123 -0
  10. package/.agent-src/commands/council.md +219 -0
  11. package/.agent-src/commands/create-pr.md +23 -0
  12. package/.agent-src/commands/do-and-judge.md +3 -3
  13. package/.agent-src/commands/do-in-steps.md +4 -4
  14. package/.agent-src/commands/e2e-heal.md +1 -1
  15. package/.agent-src/commands/e2e-plan.md +1 -1
  16. package/.agent-src/commands/feature-dev.md +8 -0
  17. package/.agent-src/commands/feature-explore.md +6 -1
  18. package/.agent-src/commands/feature-plan.md +33 -2
  19. package/.agent-src/commands/feature-refactor.md +5 -0
  20. package/.agent-src/commands/feature-roadmap.md +6 -1
  21. package/.agent-src/commands/feature.md +58 -0
  22. package/.agent-src/commands/fix-ci.md +5 -0
  23. package/.agent-src/commands/fix-portability.md +5 -0
  24. package/.agent-src/commands/fix-pr-bot-comments.md +5 -0
  25. package/.agent-src/commands/fix-pr-comments.md +5 -0
  26. package/.agent-src/commands/fix-pr-developer-comments.md +5 -0
  27. package/.agent-src/commands/fix-references.md +5 -0
  28. package/.agent-src/commands/fix-seeder.md +5 -0
  29. package/.agent-src/commands/fix.md +60 -0
  30. package/.agent-src/commands/jira-ticket.md +1 -1
  31. package/.agent-src/commands/judge.md +1 -1
  32. package/.agent-src/commands/memory-add.md +3 -3
  33. package/.agent-src/commands/memory-full.md +2 -2
  34. package/.agent-src/commands/memory-promote.md +2 -2
  35. package/.agent-src/commands/mode.md +5 -5
  36. package/.agent-src/commands/onboard.md +3 -3
  37. package/.agent-src/commands/optimize-agents.md +6 -1
  38. package/.agent-src/commands/optimize-augmentignore.md +5 -0
  39. package/.agent-src/commands/optimize-rtk-filters.md +5 -0
  40. package/.agent-src/commands/optimize-skills.md +6 -1
  41. package/.agent-src/commands/optimize.md +54 -0
  42. package/.agent-src/commands/propose-memory.md +2 -2
  43. package/.agent-src/commands/review-changes.md +26 -1
  44. package/.agent-src/commands/review-routing.md +1 -1
  45. package/.agent-src/commands/roadmap-create.md +29 -2
  46. package/.agent-src/commands/set-cost-profile.md +3 -3
  47. package/.agent-src/commands/sync-agent-settings.md +2 -2
  48. package/.agent-src/commands/tests-create.md +1 -1
  49. package/.agent-src/commands/upstream-contribute.md +1 -1
  50. package/.agent-src/contexts/authority/commit-mechanics.md +57 -0
  51. package/.agent-src/contexts/authority/destructive-mechanics.md +66 -0
  52. package/.agent-src/contexts/authority/scope-mechanics.md +87 -0
  53. package/.agent-src/contexts/execution/autonomy-detection.md +54 -0
  54. package/.agent-src/contexts/execution/autonomy-examples.md +90 -0
  55. package/.agent-src/contexts/execution/autonomy-mechanics.md +29 -0
  56. package/.agent-src/contexts/execution/verification-mechanics.md +80 -0
  57. package/.agent-src/personas/README.md +1 -1
  58. package/.agent-src/rules/agent-authority.md +24 -0
  59. package/.agent-src/rules/architecture.md +1 -1
  60. package/.agent-src/rules/artifact-drafting-protocol.md +1 -1
  61. package/.agent-src/rules/artifact-engagement-recording.md +1 -1
  62. package/.agent-src/rules/ask-when-uncertain.md +1 -1
  63. package/.agent-src/rules/autonomous-execution.md +78 -114
  64. package/.agent-src/rules/capture-learnings.md +1 -1
  65. package/.agent-src/rules/chat-history-cadence.md +3 -3
  66. package/.agent-src/rules/chat-history-ownership.md +3 -3
  67. package/.agent-src/rules/chat-history-visibility.md +3 -3
  68. package/.agent-src/rules/{command-suggestion.md → command-suggestion-policy.md} +7 -7
  69. package/.agent-src/rules/commit-conventions.md +1 -1
  70. package/.agent-src/rules/commit-policy.md +14 -42
  71. package/.agent-src/rules/context-hygiene.md +3 -3
  72. package/.agent-src/rules/direct-answers.md +1 -1
  73. package/.agent-src/rules/docs-sync.md +1 -1
  74. package/.agent-src/rules/e2e-testing.md +1 -1
  75. package/.agent-src/rules/guidelines.md +4 -4
  76. package/.agent-src/rules/improve-before-implement.md +2 -2
  77. package/.agent-src/rules/language-and-tone.md +37 -96
  78. package/.agent-src/rules/minimal-safe-diff.md +3 -3
  79. package/.agent-src/rules/model-recommendation.md +4 -4
  80. package/.agent-src/rules/no-cheap-questions.md +89 -0
  81. package/.agent-src/rules/non-destructive-by-default.md +15 -49
  82. package/.agent-src/rules/onboarding-gate.md +5 -5
  83. package/.agent-src/rules/review-routing-awareness.md +9 -9
  84. package/.agent-src/rules/roadmap-progress-sync.md +26 -33
  85. package/.agent-src/rules/role-mode-adherence.md +2 -2
  86. package/.agent-src/rules/scope-control.md +65 -46
  87. package/.agent-src/rules/security-sensitive-stop.md +2 -2
  88. package/.agent-src/rules/size-enforcement.md +1 -1
  89. package/.agent-src/rules/think-before-action.md +5 -5
  90. package/.agent-src/rules/token-efficiency.md +4 -4
  91. package/.agent-src/rules/{ui-audit-before-build.md → ui-audit-gate.md} +3 -3
  92. package/.agent-src/rules/user-interaction.md +3 -3
  93. package/.agent-src/rules/verify-before-complete.md +12 -67
  94. package/.agent-src/scripts/update_roadmap_progress.py +9 -4
  95. package/.agent-src/skills/ai-council/SKILL.md +333 -0
  96. package/.agent-src/skills/api-endpoint/SKILL.md +2 -2
  97. package/.agent-src/skills/blade-ui/SKILL.md +1 -1
  98. package/.agent-src/skills/blast-radius-analyzer/SKILL.md +1 -1
  99. package/.agent-src/skills/bug-analyzer/SKILL.md +1 -1
  100. package/.agent-src/skills/command-routing/SKILL.md +1 -1
  101. package/.agent-src/skills/command-writing/SKILL.md +1 -1
  102. package/.agent-src/skills/conventional-commits-writing/SKILL.md +1 -1
  103. package/.agent-src/skills/copilot-agents-optimization/SKILL.md +2 -2
  104. package/.agent-src/skills/developer-like-execution/SKILL.md +2 -2
  105. package/.agent-src/skills/flux/SKILL.md +1 -1
  106. package/.agent-src/skills/git-workflow/SKILL.md +1 -1
  107. package/.agent-src/skills/guideline-writing/SKILL.md +11 -11
  108. package/.agent-src/skills/learning-to-rule-or-skill/SKILL.md +4 -4
  109. package/.agent-src/skills/livewire/SKILL.md +1 -1
  110. package/.agent-src/skills/override-management/SKILL.md +2 -2
  111. package/.agent-src/skills/php-coder/SKILL.md +1 -1
  112. package/.agent-src/skills/playwright-testing/SKILL.md +2 -2
  113. package/.agent-src/skills/readme-reviewer/SKILL.md +1 -1
  114. package/.agent-src/skills/readme-writing/SKILL.md +1 -1
  115. package/.agent-src/skills/readme-writing-package/SKILL.md +1 -1
  116. package/.agent-src/skills/receiving-code-review/SKILL.md +1 -1
  117. package/.agent-src/skills/review-routing/SKILL.md +2 -2
  118. package/.agent-src/skills/rule-writing/SKILL.md +1 -1
  119. package/.agent-src/skills/skill-reviewer/SKILL.md +1 -1
  120. package/.agent-src/skills/skill-writing/SKILL.md +3 -3
  121. package/.agent-src/skills/subagent-orchestration/SKILL.md +1 -0
  122. package/.agent-src/skills/systematic-debugging/SKILL.md +1 -1
  123. package/.agent-src/skills/upstream-contribute/SKILL.md +1 -1
  124. package/.agent-src/skills/validate-feature-fit/SKILL.md +2 -2
  125. package/.agent-src/skills/{verify-before-complete → verify-completion-evidence}/SKILL.md +2 -2
  126. package/.agent-src/templates/agent-settings.md +8 -8
  127. package/.agent-src/templates/contexts/auth-model.md +1 -1
  128. package/.agent-src/templates/scripts/README.md +2 -2
  129. package/.agent-src/templates/scripts/telemetry/aggregator.py +16 -1
  130. package/.agent-src/templates/scripts/telemetry/engagement.py +59 -0
  131. package/.agent-src/templates/scripts/telemetry/report_renderer.py +28 -1
  132. package/.agent-src/templates/scripts/telemetry_record.py +14 -1
  133. package/.claude-plugin/marketplace.json +10 -2
  134. package/AGENTS.md +11 -9
  135. package/CHANGELOG.md +123 -1
  136. package/README.md +28 -30
  137. package/config/agent-settings.template.yml +58 -1
  138. package/config/gitignore-block.txt +3 -0
  139. package/docs/architecture.md +4 -4
  140. package/docs/catalog.md +331 -0
  141. package/docs/contracts/STABILITY.md +39 -0
  142. package/docs/contracts/adr-command-suggestion.md +3 -3
  143. package/docs/contracts/adr-product-ui-track.md +2 -2
  144. package/docs/contracts/agent-memory-contract.md +2 -2
  145. package/docs/contracts/artifact-engagement-flow.md +1 -1
  146. package/docs/contracts/command-clusters.md +2 -2
  147. package/docs/contracts/command-suggestion-flow.md +3 -3
  148. package/docs/contracts/implement-ticket-flow.md +2 -2
  149. package/docs/contracts/linear-ai-rules-inclusion.md +1 -1
  150. package/docs/contracts/load-context-schema.md +186 -0
  151. package/docs/contracts/rule-interactions.yml +96 -0
  152. package/docs/contracts/rule-priority-hierarchy.md +87 -0
  153. package/docs/contracts/ui-track-flow.md +1 -1
  154. package/docs/customization.md +14 -0
  155. package/docs/end-to-end-walkthroughs.md +165 -0
  156. package/docs/getting-started.md +26 -8
  157. package/docs/github-topics.md +12 -3
  158. package/docs/guidelines/agent-infra/language-and-tone-examples.md +79 -0
  159. package/{.agent-src → docs}/guidelines/docs/readme-size-and-splitting.md +26 -25
  160. package/docs/guidelines/php/git.md +164 -0
  161. package/docs/migrations/commands-1.15.0.md +1 -1
  162. package/docs/showcase.md +9 -4
  163. package/docs/skills-catalog.md +14 -8
  164. package/docs/ui-track-mental-model.md +2 -2
  165. package/llms.txt +13 -7
  166. package/package.json +1 -1
  167. package/scripts/agent-config +23 -0
  168. package/scripts/ai_council/__init__.py +39 -0
  169. package/scripts/ai_council/_default_prices.py +41 -0
  170. package/scripts/ai_council/_one_off_rebalancing_audit.py +149 -0
  171. package/scripts/ai_council/_one_off_roundtrip.py +106 -0
  172. package/scripts/ai_council/budget_guard.py +172 -0
  173. package/scripts/ai_council/bundler.py +261 -0
  174. package/scripts/ai_council/clients.py +381 -0
  175. package/scripts/ai_council/modes.py +127 -0
  176. package/scripts/ai_council/orchestrator.py +350 -0
  177. package/scripts/ai_council/pricing.py +213 -0
  178. package/scripts/ai_council/project_context.py +159 -0
  179. package/scripts/ai_council/prompts.py +232 -0
  180. package/scripts/ai_council/session.py +144 -0
  181. package/scripts/check_always_budget.py +126 -0
  182. package/scripts/check_augmentignore.py +69 -0
  183. package/scripts/check_command_count_messaging.py +120 -0
  184. package/scripts/check_portability.py +55 -0
  185. package/scripts/check_public_catalog_links.py +122 -0
  186. package/scripts/check_references.py +4 -1
  187. package/scripts/check_roadmap_trackable.py +111 -0
  188. package/scripts/command_suggester/cooldown.py +1 -1
  189. package/scripts/generate_index.py +266 -0
  190. package/scripts/install_anthropic_key.sh +5 -0
  191. package/scripts/install_openai_key.sh +106 -0
  192. package/scripts/lint_load_context.py +163 -0
  193. package/scripts/schemas/command.schema.json +20 -0
  194. package/scripts/schemas/rule.schema.json +10 -0
  195. package/scripts/skill_linter.py +12 -4
  196. package/scripts/sync_agent_settings.py +1 -1
  197. package/scripts/update_counts.py +9 -4
  198. package/scripts/update_prices.py +124 -0
  199. package/.agent-src/guidelines/php/git.md +0 -96
  200. /package/.agent-src/rules/{slash-commands.md → slash-command-routing-policy.md} +0 -0
  201. /package/{.agent-src → docs}/guidelines/agent-infra/agent-interaction-and-decision-quality.md +0 -0
  202. /package/{.agent-src → docs}/guidelines/agent-infra/break-glass-usage.md +0 -0
  203. /package/{.agent-src → docs}/guidelines/agent-infra/developer-judgment.md +0 -0
  204. /package/{.agent-src → docs}/guidelines/agent-infra/engineering-memory-data-format.md +0 -0
  205. /package/{.agent-src → docs}/guidelines/agent-infra/layered-settings.md +0 -0
  206. /package/{.agent-src → docs}/guidelines/agent-infra/memory-access.md +0 -0
  207. /package/{.agent-src → docs}/guidelines/agent-infra/naming.md +0 -0
  208. /package/{.agent-src → docs}/guidelines/agent-infra/output-patterns.md +0 -0
  209. /package/{.agent-src → docs}/guidelines/agent-infra/review-routing-data-format.md +0 -0
  210. /package/{.agent-src → docs}/guidelines/agent-infra/role-contracts.md +0 -0
  211. /package/{.agent-src → docs}/guidelines/agent-infra/role-mode-router.md +0 -0
  212. /package/{.agent-src → docs}/guidelines/agent-infra/runtime-layer.md +0 -0
  213. /package/{.agent-src → docs}/guidelines/agent-infra/self-improvement-pipeline.md +0 -0
  214. /package/{.agent-src → docs}/guidelines/agent-infra/size-and-scope.md +0 -0
  215. /package/{.agent-src → docs}/guidelines/agent-infra/tool-integration.md +0 -0
  216. /package/{.agent-src → docs}/guidelines/e2e/playwright.md +0 -0
  217. /package/{.agent-src → docs}/guidelines/php/api-design.md +0 -0
  218. /package/{.agent-src → docs}/guidelines/php/artisan-commands.md +0 -0
  219. /package/{.agent-src → docs}/guidelines/php/blade-ui.md +0 -0
  220. /package/{.agent-src → docs}/guidelines/php/controllers.md +0 -0
  221. /package/{.agent-src → docs}/guidelines/php/database.md +0 -0
  222. /package/{.agent-src → docs}/guidelines/php/eloquent.md +0 -0
  223. /package/{.agent-src → docs}/guidelines/php/flux.md +0 -0
  224. /package/{.agent-src → docs}/guidelines/php/general.md +0 -0
  225. /package/{.agent-src → docs}/guidelines/php/jobs.md +0 -0
  226. /package/{.agent-src → docs}/guidelines/php/livewire.md +0 -0
  227. /package/{.agent-src → docs}/guidelines/php/logging.md +0 -0
  228. /package/{.agent-src → docs}/guidelines/php/naming.md +0 -0
  229. /package/{.agent-src → docs}/guidelines/php/patterns/dependency-injection.md +0 -0
  230. /package/{.agent-src → docs}/guidelines/php/patterns/dtos.md +0 -0
  231. /package/{.agent-src → docs}/guidelines/php/patterns/events.md +0 -0
  232. /package/{.agent-src → docs}/guidelines/php/patterns/factory.md +0 -0
  233. /package/{.agent-src → docs}/guidelines/php/patterns/pipelines.md +0 -0
  234. /package/{.agent-src → docs}/guidelines/php/patterns/policies.md +0 -0
  235. /package/{.agent-src → docs}/guidelines/php/patterns/repositories.md +0 -0
  236. /package/{.agent-src → docs}/guidelines/php/patterns/service-layer.md +0 -0
  237. /package/{.agent-src → docs}/guidelines/php/patterns/strategy.md +0 -0
  238. /package/{.agent-src → docs}/guidelines/php/patterns.md +0 -0
  239. /package/{.agent-src → docs}/guidelines/php/performance.md +0 -0
  240. /package/{.agent-src → docs}/guidelines/php/resources.md +0 -0
  241. /package/{.agent-src → docs}/guidelines/php/security.md +0 -0
  242. /package/{.agent-src → docs}/guidelines/php/sql.md +0 -0
  243. /package/{.agent-src → docs}/guidelines/php/validations.md +0 -0
  244. /package/{.agent-src → docs}/guidelines/php/websocket.md +0 -0
@@ -0,0 +1,266 @@
1
+ #!/usr/bin/env python3
2
+ """Generate `agents/index.md` (internal) and `docs/catalog.md` (public).
3
+
4
+ Scans `.agent-src.uncompressed/{skills,rules,commands}/` plus `docs/guidelines/`
5
+ and renders two artefact tables — one for maintainers, one for consumers.
6
+
7
+ Both files are sync-checked in CI via `--check`; drift = build break.
8
+
9
+ Usage:
10
+ python3 scripts/generate_index.py # write both files
11
+ python3 scripts/generate_index.py --check # exit 1 if drift
12
+ """
13
+ from __future__ import annotations
14
+
15
+ import argparse
16
+ import re
17
+ import sys
18
+ from dataclasses import dataclass
19
+ from pathlib import Path
20
+
21
+ ROOT = Path(__file__).resolve().parent.parent
22
+ SRC = ROOT / ".agent-src.uncompressed"
23
+ GUIDELINES = ROOT / "docs" / "guidelines"
24
+ INDEX_PATH = ROOT / "agents" / "index.md"
25
+ CATALOG_PATH = ROOT / "docs" / "catalog.md"
26
+
27
+ # Internal-only rules — excluded from the public catalog.
28
+ INTERNAL_RULES = {
29
+ "augment-source-of-truth",
30
+ "augment-portability",
31
+ "docs-sync",
32
+ }
33
+
34
+ FRONTMATTER_RE = re.compile(r"^---\s*\n(.*?)\n---\s*\n", re.DOTALL)
35
+
36
+
37
+ @dataclass(frozen=True)
38
+ class Entry:
39
+ kind: str # skill | rule | command | shim | guideline
40
+ name: str
41
+ description: str
42
+ extra: str # rule type · cluster · sub-folder, etc.
43
+ path: str # repo-relative link target
44
+
45
+
46
+ def _parse_frontmatter(text: str) -> dict[str, str]:
47
+ m = FRONTMATTER_RE.match(text)
48
+ if not m:
49
+ return {}
50
+ out: dict[str, str] = {}
51
+ for line in m.group(1).splitlines():
52
+ if ":" not in line or line.startswith(" "):
53
+ continue
54
+ k, _, v = line.partition(":")
55
+ out[k.strip()] = v.strip().strip('"').strip("'")
56
+ return out
57
+
58
+
59
+ def _truncate(text: str, limit: int = 200) -> str:
60
+ text = text.replace("|", "\\|").replace("\n", " ").strip()
61
+ return text if len(text) <= limit else text[: limit - 1].rstrip() + "…"
62
+
63
+
64
+ def _collect_skills() -> list[Entry]:
65
+ out = []
66
+ for skill_dir in sorted((SRC / "skills").iterdir()):
67
+ skill_md = skill_dir / "SKILL.md"
68
+ if not skill_md.exists():
69
+ continue
70
+ fm = _parse_frontmatter(skill_md.read_text(encoding="utf-8"))
71
+ name = fm.get("name") or skill_dir.name
72
+ out.append(Entry(
73
+ kind="skill",
74
+ name=name,
75
+ description=_truncate(fm.get("description", "")),
76
+ extra="",
77
+ path=f".agent-src.uncompressed/skills/{skill_dir.name}/SKILL.md",
78
+ ))
79
+ return out
80
+
81
+
82
+ def _collect_rules() -> list[Entry]:
83
+ out = []
84
+ for rule_md in sorted((SRC / "rules").glob("*.md")):
85
+ fm = _parse_frontmatter(rule_md.read_text(encoding="utf-8"))
86
+ out.append(Entry(
87
+ kind="rule",
88
+ name=rule_md.stem,
89
+ description=_truncate(fm.get("description", "")),
90
+ extra=fm.get("type", "?"),
91
+ path=f".agent-src.uncompressed/rules/{rule_md.name}",
92
+ ))
93
+ return out
94
+
95
+
96
+ def _collect_commands() -> list[Entry]:
97
+ out = []
98
+ for cmd_md in sorted((SRC / "commands").glob("*.md")):
99
+ fm = _parse_frontmatter(cmd_md.read_text(encoding="utf-8"))
100
+ is_shim = bool(fm.get("superseded_by"))
101
+ extra = ""
102
+ if is_shim:
103
+ extra = f"shim → /{fm['superseded_by']}"
104
+ elif fm.get("cluster"):
105
+ extra = f"cluster: {fm['cluster']}"
106
+ out.append(Entry(
107
+ kind="shim" if is_shim else "command",
108
+ name=fm.get("name") or cmd_md.stem,
109
+ description=_truncate(fm.get("description", "")),
110
+ extra=extra,
111
+ path=f".agent-src.uncompressed/commands/{cmd_md.name}",
112
+ ))
113
+ return out
114
+
115
+
116
+ def _collect_guidelines() -> list[Entry]:
117
+ out = []
118
+ if not GUIDELINES.exists():
119
+ return out
120
+ for g_md in sorted(GUIDELINES.rglob("*.md")):
121
+ rel = g_md.relative_to(ROOT)
122
+ category = g_md.parent.name if g_md.parent != GUIDELINES else "(root)"
123
+ out.append(Entry(
124
+ kind="guideline",
125
+ name=g_md.stem,
126
+ description="",
127
+ extra=category,
128
+ path=str(rel),
129
+ ))
130
+ return out
131
+
132
+
133
+ # Path rewriter for the public catalog: link to the shipped surface
134
+ # (`.agent-src/`) instead of the source-of-truth (`.agent-src.uncompressed/`),
135
+ # which is excluded from `package.json#files` and `composer.json` archives.
136
+ def _to_shipped_path(path: str) -> str:
137
+ return path.replace(".agent-src.uncompressed/", ".agent-src/", 1)
138
+
139
+
140
+ def _render_table(
141
+ entries: list[Entry],
142
+ cols: list[str],
143
+ link_prefix: str,
144
+ path_rewrite=None,
145
+ ) -> str:
146
+ rows = ["| " + " | ".join(cols) + " |", "|" + "|".join(["---"] * len(cols)) + "|"]
147
+ for e in entries:
148
+ path = path_rewrite(e.path) if path_rewrite else e.path
149
+ link = f"[`{e.name}`]({link_prefix}{path})"
150
+ row = [e.kind, link, e.extra, e.description]
151
+ rows.append("| " + " | ".join(row) + " |")
152
+ return "\n".join(rows)
153
+
154
+
155
+
156
+ def _render_index(skills, rules, commands, guidelines) -> str:
157
+ total = len(skills) + len(rules) + len(commands) + len(guidelines)
158
+ parts = [
159
+ "# Agent-Config Internal Index",
160
+ "",
161
+ f"Maintainer-facing index of all **{total} artefacts** in this package.",
162
+ "Auto-generated from `.agent-src.uncompressed/` and `docs/guidelines/`.",
163
+ "",
164
+ "> **Regenerate:** `python3 scripts/generate_index.py`",
165
+ "> **Drift check:** `python3 scripts/generate_index.py --check` (runs in `task ci`)",
166
+ "> Do not edit manually.",
167
+ "",
168
+ f"## Skills ({len(skills)})",
169
+ "",
170
+ _render_table(skills, ["kind", "name", "extra", "description"], "../"),
171
+ "",
172
+ f"## Rules ({len(rules)})",
173
+ "",
174
+ _render_table(rules, ["kind", "name", "type", "description"], "../"),
175
+ "",
176
+ f"## Commands ({len(commands)})",
177
+ "",
178
+ _render_table(commands, ["kind", "name", "cluster/shim", "description"], "../"),
179
+ "",
180
+ f"## Guidelines ({len(guidelines)})",
181
+ "",
182
+ _render_table(guidelines, ["kind", "name", "category", "description"], "../"),
183
+ "",
184
+ ]
185
+ return "\n".join(parts)
186
+
187
+
188
+ def _render_catalog(skills, rules, commands, guidelines) -> str:
189
+ public_rules = [r for r in rules if r.name not in INTERNAL_RULES]
190
+ public_commands = [c for c in commands if c.kind == "command"]
191
+ total = len(skills) + len(public_rules) + len(public_commands) + len(guidelines)
192
+ parts = [
193
+ "# agent-config — Public Catalog",
194
+ "",
195
+ f"Consumer-facing catalog of all **{total} public artefacts** shipped by",
196
+ "this package. Internal package-maintenance rules and deprecation shims",
197
+ "are excluded.",
198
+ "",
199
+ "> **Regenerate:** `python3 scripts/generate_index.py`",
200
+ "> Auto-generated — do not edit manually.",
201
+ "",
202
+ f"## Skills ({len(skills)})",
203
+ "",
204
+ _render_table(skills, ["kind", "name", "extra", "description"], "../", _to_shipped_path),
205
+ "",
206
+ f"## Rules ({len(public_rules)})",
207
+ "",
208
+ _render_table(public_rules, ["kind", "name", "type", "description"], "../", _to_shipped_path),
209
+ "",
210
+ f"## Commands ({len(public_commands)})",
211
+ "",
212
+ _render_table(public_commands, ["kind", "name", "cluster", "description"], "../", _to_shipped_path),
213
+ "",
214
+ f"## Guidelines ({len(guidelines)})",
215
+ "",
216
+ _render_table(guidelines, ["kind", "name", "category", "description"], "../", _to_shipped_path),
217
+ "",
218
+ "---",
219
+ "",
220
+ "← [Back to README](../README.md)",
221
+ "",
222
+ ]
223
+ return "\n".join(parts)
224
+
225
+
226
+ def main() -> int:
227
+ parser = argparse.ArgumentParser(description=__doc__)
228
+ parser.add_argument("--check", action="store_true",
229
+ help="Exit 1 if generated content differs from on-disk files.")
230
+ args = parser.parse_args()
231
+
232
+ skills = _collect_skills()
233
+ rules = _collect_rules()
234
+ commands = _collect_commands()
235
+ guidelines = _collect_guidelines()
236
+
237
+ index_text = _render_index(skills, rules, commands, guidelines)
238
+ catalog_text = _render_catalog(skills, rules, commands, guidelines)
239
+
240
+ if args.check:
241
+ drift = []
242
+ if not INDEX_PATH.exists() or INDEX_PATH.read_text(encoding="utf-8") != index_text:
243
+ drift.append(str(INDEX_PATH.relative_to(ROOT)))
244
+ if not CATALOG_PATH.exists() or CATALOG_PATH.read_text(encoding="utf-8") != catalog_text:
245
+ drift.append(str(CATALOG_PATH.relative_to(ROOT)))
246
+ if drift:
247
+ print("❌ Index drift detected — regenerate with:")
248
+ print(" python3 scripts/generate_index.py")
249
+ for d in drift:
250
+ print(f" - {d}")
251
+ return 1
252
+ print("✅ Index files in sync.")
253
+ return 0
254
+
255
+ INDEX_PATH.parent.mkdir(parents=True, exist_ok=True)
256
+ CATALOG_PATH.parent.mkdir(parents=True, exist_ok=True)
257
+ INDEX_PATH.write_text(index_text, encoding="utf-8")
258
+ CATALOG_PATH.write_text(catalog_text, encoding="utf-8")
259
+ print(f"✅ Wrote {INDEX_PATH.relative_to(ROOT)} ({len(skills)} skills, "
260
+ f"{len(rules)} rules, {len(commands)} commands, {len(guidelines)} guidelines)")
261
+ print(f"✅ Wrote {CATALOG_PATH.relative_to(ROOT)} (public subset)")
262
+ return 0
263
+
264
+
265
+ if __name__ == "__main__":
266
+ sys.exit(main())
@@ -99,3 +99,8 @@ echo "✅ Key installed: ${TARGET_FILE} (mode 0600)."
99
99
  echo " Verify: ls -la ${TARGET_FILE}"
100
100
  echo " Rotate: rerun this script (you'll be prompted to overwrite)."
101
101
  echo " Remove: rm ${TARGET_FILE}"
102
+ echo
103
+ echo "ℹ️ Key install ≠ enable. To use this provider in /council:"
104
+ echo " set ai_council.enabled: true and ai_council.members.anthropic.enabled: true"
105
+ echo " in .agent-settings.yml. Council is a 'full' cost_profile feature; under"
106
+ echo " 'minimal' / 'balanced' the runtime hooks stay inactive."
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env bash
2
+ # Interactive OpenAI-API-key installer for scripts/ai_council/clients.py.
3
+ #
4
+ # Reads the key with `read -s` so it never echoes to the terminal and
5
+ # never lands in shell history or scrollback. Writes atomically to
6
+ # ~/.config/agent-config/openai.key with mode 0600.
7
+ #
8
+ # Contract — companion to scripts/ai_council/clients.py:
9
+ # - File path: $HOME/.config/agent-config/openai.key
10
+ # - File mode: 0600 (owner read/write only)
11
+ # - Key format: must start with `sk-`
12
+ # - No --force, no --yes, no env-var bypass. Piped stdin is rejected.
13
+ #
14
+ # The runner re-checks all of the above at every live invocation and
15
+ # refuses to run if the file drifts from this contract.
16
+
17
+ set -euo pipefail
18
+
19
+ TARGET_DIR="${HOME}/.config/agent-config"
20
+ TARGET_FILE="${TARGET_DIR}/openai.key"
21
+
22
+ # ── controlling-terminal requirement ─────────────────────────────────────
23
+ # We read from /dev/tty directly (fd 3), not from stdin. This is the
24
+ # stricter and more portable contract:
25
+ # - works under `task`, `script`, `sudo`, anything that reattaches stdin
26
+ # - forces every character to come from the user's real keyboard, so a
27
+ # pipe or redirected file cannot smuggle the key into the process
28
+ # - exits cleanly if there is no controlling terminal at all (e.g. CI,
29
+ # cron, agent automation)
30
+ if ! exec 3</dev/tty 2>/dev/null; then
31
+ echo "❌ install_openai_key.sh requires a controlling terminal." >&2
32
+ echo " /dev/tty not available — refusing to run under automation." >&2
33
+ exit 2
34
+ fi
35
+
36
+ # ── overwrite confirmation ───────────────────────────────────────────────
37
+ if [[ -e "${TARGET_FILE}" ]]; then
38
+ echo "⚠️ ${TARGET_FILE} already exists."
39
+ printf "Overwrite? [type 'yes' to replace, anything else aborts]: "
40
+ read -r -u 3 answer
41
+ if [[ "${answer}" != "yes" ]]; then
42
+ echo "Aborted. Existing key untouched."
43
+ exit 0
44
+ fi
45
+ fi
46
+
47
+ # ── read key (no echo, no history) ───────────────────────────────────────
48
+ echo "Paste your OpenAI API key (input is hidden, no echo)."
49
+ echo "The key should start with 'sk-'."
50
+ printf "Key: "
51
+ # -s = silent (no echo), read from fd 3 = /dev/tty, not stdin.
52
+ read -r -s -u 3 API_KEY
53
+ echo
54
+
55
+ if [[ -z "${API_KEY}" ]]; then
56
+ echo "❌ Empty input — no file written." >&2
57
+ exit 2
58
+ fi
59
+
60
+ if [[ "${API_KEY}" != sk-* ]]; then
61
+ echo "❌ Input does not look like an OpenAI key (missing 'sk-' prefix)." >&2
62
+ echo " No file written." >&2
63
+ exit 2
64
+ fi
65
+
66
+ # ── create config dir with 0700, atomic write with 0600 ──────────────────
67
+ mkdir -p "${TARGET_DIR}"
68
+ chmod 0700 "${TARGET_DIR}"
69
+
70
+ TMP_FILE="$(mktemp "${TARGET_DIR}/.openai.key.XXXXXX")"
71
+ cleanup() { rm -f "${TMP_FILE}"; }
72
+ trap cleanup EXIT
73
+
74
+ # chmod the tmpfile BEFORE writing the key, so there is no window where
75
+ # the key sits on disk with group/other-readable permissions.
76
+ chmod 0600 "${TMP_FILE}"
77
+ printf '%s\n' "${API_KEY}" > "${TMP_FILE}"
78
+ mv "${TMP_FILE}" "${TARGET_FILE}"
79
+ trap - EXIT
80
+
81
+ # Clear the variable and the `mv` positional argument — defence in depth
82
+ # against a crash handler that dumps the process environment.
83
+ API_KEY=""
84
+
85
+ # ── verify mode post-write (portable stat: BSD on macOS, GNU on Linux) ───
86
+ if ACTUAL_MODE=$(stat -f '%Lp' "${TARGET_FILE}" 2>/dev/null); then
87
+ : # macOS / BSD
88
+ else
89
+ ACTUAL_MODE=$(stat -c '%a' "${TARGET_FILE}")
90
+ fi
91
+
92
+ if [[ "${ACTUAL_MODE}" != "600" ]]; then
93
+ echo "❌ Permissions verification failed: ${TARGET_FILE} has mode ${ACTUAL_MODE}, expected 600." >&2
94
+ echo " Delete and reinstall: rm ${TARGET_FILE} && $0" >&2
95
+ exit 3
96
+ fi
97
+
98
+ echo "✅ Key installed: ${TARGET_FILE} (mode 0600)."
99
+ echo " Verify: ls -la ${TARGET_FILE}"
100
+ echo " Rotate: rerun this script (you'll be prompted to overwrite)."
101
+ echo " Remove: rm ${TARGET_FILE}"
102
+ echo
103
+ echo "ℹ️ Key install ≠ enable. To use this provider in /council:"
104
+ echo " set ai_council.enabled: true and ai_council.members.openai.enabled: true"
105
+ echo " in .agent-settings.yml. Council is a 'full' cost_profile feature; under"
106
+ echo " 'minimal' / 'balanced' the runtime hooks stay inactive."
@@ -0,0 +1,163 @@
1
+ #!/usr/bin/env python3
2
+ """Lint the `load_context:` / `load_context_eager:` frontmatter schema.
3
+
4
+ Validates per docs/contracts/load-context-schema.md:
5
+ - Paths exist and are .md
6
+ - Allowed roots only (.agent-src*/contexts/, agents/contexts/)
7
+ - No public→project-local leak (warn)
8
+ - No circular refs across lazy + eager edges
9
+ - Combined char-budget for eager edges (rule + eager targets ≤ cap)
10
+
11
+ Exits non-zero on error; warnings are reported but do not fail.
12
+ Used in CI via `task lint-load-context`.
13
+ """
14
+ from __future__ import annotations
15
+
16
+ import sys
17
+ from pathlib import Path
18
+ from typing import Iterable
19
+
20
+ import yaml
21
+
22
+ ROOT = Path(__file__).resolve().parent.parent
23
+
24
+ SCAN_DIRS = [
25
+ ROOT / ".agent-src.uncompressed" / "rules",
26
+ ROOT / ".agent-src.uncompressed" / "contexts",
27
+ ROOT / "agents" / "contexts",
28
+ ]
29
+
30
+ ALLOWED_PREFIXES = (
31
+ ".agent-src.uncompressed/contexts/",
32
+ ".agent-src/contexts/",
33
+ "agents/contexts/",
34
+ )
35
+
36
+ PUBLIC_RULE_PREFIX = ".agent-src.uncompressed/rules/"
37
+ PROJECT_LOCAL_PREFIX = "agents/contexts/"
38
+
39
+ HARD_FLOOR_RULES = {"non-destructive-by-default", "security-sensitive-stop"}
40
+
41
+ CAP_ALWAYS = 2_500
42
+ CAP_AUTO = 4_000
43
+ CAP_SAFETY = 5_000
44
+
45
+
46
+ def parse_frontmatter(path: Path) -> dict:
47
+ text = path.read_text(encoding="utf-8")
48
+ if not text.startswith("---\n"):
49
+ return {}
50
+ end = text.find("\n---\n", 4)
51
+ if end == -1:
52
+ return {}
53
+ try:
54
+ data = yaml.safe_load(text[4:end])
55
+ except yaml.YAMLError:
56
+ return {}
57
+ return data if isinstance(data, dict) else {}
58
+
59
+
60
+ def collect_files() -> Iterable[Path]:
61
+ for d in SCAN_DIRS:
62
+ if d.exists():
63
+ yield from d.rglob("*.md")
64
+
65
+
66
+ def rel(p: Path) -> str:
67
+ return p.relative_to(ROOT).as_posix()
68
+
69
+
70
+ def cap_for(rule_path: Path, fm: dict) -> int:
71
+ if rule_path.stem in HARD_FLOOR_RULES:
72
+ return CAP_SAFETY
73
+ rtype = (fm.get("type") or "").strip('"').strip("'")
74
+ if rtype == "always":
75
+ return CAP_ALWAYS
76
+ if rtype == "auto":
77
+ return CAP_AUTO
78
+ return CAP_AUTO # default for non-rule contexts cited in eager (won't trigger)
79
+
80
+
81
+ def find_cycles(graph: dict[str, list[str]]) -> list[list[str]]:
82
+ cycles: list[list[str]] = []
83
+ visiting: set[str] = set()
84
+ visited: set[str] = set()
85
+ stack: list[str] = []
86
+
87
+ def dfs(node: str) -> None:
88
+ if node in visiting:
89
+ i = stack.index(node)
90
+ cycles.append(stack[i:] + [node])
91
+ return
92
+ if node in visited:
93
+ return
94
+ visiting.add(node)
95
+ stack.append(node)
96
+ for nxt in graph.get(node, []):
97
+ dfs(nxt)
98
+ stack.pop()
99
+ visiting.discard(node)
100
+ visited.add(node)
101
+
102
+ for n in graph:
103
+ dfs(n)
104
+ return cycles
105
+
106
+
107
+ def main() -> int:
108
+ errors: list[str] = []
109
+ warnings: list[str] = []
110
+ graph: dict[str, list[str]] = {}
111
+
112
+ for f in collect_files():
113
+ fm = parse_frontmatter(f)
114
+ lazy = fm.get("load_context") or []
115
+ eager = fm.get("load_context_eager") or []
116
+ if not (lazy or eager):
117
+ continue
118
+ if not isinstance(lazy, list) or not isinstance(eager, list):
119
+ errors.append(f"{rel(f)}: load_context* must be a list")
120
+ continue
121
+
122
+ edges = list(lazy) + list(eager)
123
+ graph[rel(f)] = edges
124
+
125
+ for entry in edges:
126
+ if not isinstance(entry, str) or not entry.endswith(".md"):
127
+ errors.append(f"{rel(f)}: entry not str ending in .md → {entry!r}")
128
+ continue
129
+ if not entry.startswith(ALLOWED_PREFIXES):
130
+ errors.append(f"{rel(f)}: disallowed root → {entry}")
131
+ continue
132
+ target = ROOT / entry
133
+ if not target.exists():
134
+ errors.append(f"{rel(f)}: target missing → {entry}")
135
+ continue
136
+ if rel(f).startswith(PUBLIC_RULE_PREFIX) and entry.startswith(PROJECT_LOCAL_PREFIX):
137
+ warnings.append(f"{rel(f)}: public rule references project-local context → {entry}")
138
+
139
+ if eager:
140
+ cap = cap_for(f, fm)
141
+ total = len(f.read_text(encoding="utf-8"))
142
+ for entry in eager:
143
+ tgt = ROOT / entry
144
+ if tgt.exists():
145
+ total += len(tgt.read_text(encoding="utf-8"))
146
+ if total > cap:
147
+ errors.append(f"{rel(f)}: eager-load combined chars {total} > cap {cap}")
148
+
149
+ for cycle in find_cycles(graph):
150
+ errors.append("circular load_context: " + " → ".join(cycle))
151
+
152
+ for w in warnings:
153
+ print(f"⚠️ {w}")
154
+ for e in errors:
155
+ print(f"❌ {e}")
156
+ if errors:
157
+ return 1
158
+ print(f"✅ load_context schema clean ({len(graph)} declarer(s))")
159
+ return 0
160
+
161
+
162
+ if __name__ == "__main__":
163
+ sys.exit(main())
@@ -28,6 +28,26 @@
28
28
  "pattern": "^[a-z][a-z0-9-]*$"
29
29
  }
30
30
  },
31
+ "cluster": {
32
+ "type": "string",
33
+ "pattern": "^[a-z][a-z0-9-]*$",
34
+ "description": "Locked verb cluster this command belongs to. See docs/contracts/command-clusters.md."
35
+ },
36
+ "sub": {
37
+ "type": "string",
38
+ "pattern": "^[a-z][a-z0-9-]*$",
39
+ "description": "Sub-command identifier within the cluster (e.g. `ci` for `/fix ci`)."
40
+ },
41
+ "superseded_by": {
42
+ "type": "string",
43
+ "pattern": "^[a-z][a-z0-9-]*( [a-z][a-z0-9-]*)?$",
44
+ "description": "Set on deprecation shims. Format: '<cluster> <sub>' (e.g. 'fix ci'). See docs/contracts/command-clusters.md § Deprecation shim contract."
45
+ },
46
+ "deprecated_in": {
47
+ "type": "string",
48
+ "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$",
49
+ "description": "Semver release in which this command became a shim (e.g. '1.15.0')."
50
+ },
31
51
  "suggestion": {
32
52
  "type": "object",
33
53
  "additionalProperties": false,
@@ -23,6 +23,16 @@
23
23
  "alwaysApply": {
24
24
  "type": "boolean",
25
25
  "description": "Optional sidecar for Cursor/Cline; by convention true when type=always, false when type=auto."
26
+ },
27
+ "load_context": {
28
+ "type": "array",
29
+ "items": {"type": "string", "pattern": "\\.md$"},
30
+ "description": "Lazy on-demand context references. Path rules and budget caps enforced by scripts/lint_load_context.py. Contract: docs/contracts/load-context-schema.md."
31
+ },
32
+ "load_context_eager": {
33
+ "type": "array",
34
+ "items": {"type": "string", "pattern": "\\.md$"},
35
+ "description": "Eager auto-loaded context references. Counts against the per-rule char budget; enforced by scripts/lint_load_context.py."
26
36
  }
27
37
  }
28
38
  }
@@ -136,9 +136,7 @@ def read_text(path: Path) -> str:
136
136
  # --- Role-contract anchor cache (see road-to-role-modes Phase 1) ---
137
137
  # Populated lazily so the linter stays fast when the guideline is absent.
138
138
  _ROLE_CONTRACT_CANDIDATES = (
139
- Path(".agent-src.uncompressed/guidelines/agent-infra/role-contracts.md"),
140
- Path(".agent-src/guidelines/agent-infra/role-contracts.md"),
141
- Path(".augment/guidelines/agent-infra/role-contracts.md"),
139
+ Path("docs/guidelines/agent-infra/role-contracts.md"),
142
140
  )
143
141
  _ROLE_CONTRACT_SLUGS_CACHE: Optional[set[str]] = None
144
142
 
@@ -373,7 +371,8 @@ def lint_skill(path: Path, text: str) -> LintResult:
373
371
 
374
372
  if description:
375
373
  if len(description) > 200:
376
- issues.append(Issue("warning", "description_too_long", "Description is longer than 200 characters"))
374
+ issues.append(Issue("error", "description_too_long",
375
+ f"Description is {len(description)} chars (hard cap: 200) — see road-to-governance-cleanup F6"))
377
376
  for pattern in TRIGGER_WARNING_PATTERNS:
378
377
  if re.search(pattern, description, re.IGNORECASE):
379
378
  issues.append(Issue("warning", "weak_trigger", f"Description looks too generic: {description}"))
@@ -716,6 +715,12 @@ def lint_rule(path: Path, text: str) -> LintResult:
716
715
  if not description:
717
716
  issues.append(Issue("error", "auto_missing_description", "Auto rules require a 'description' field for matching"))
718
717
 
718
+ # description length cap (F6 — 200-char hard cap, see road-to-governance-cleanup)
719
+ rule_description = extract_description(text)
720
+ if rule_description and len(rule_description) > 200:
721
+ issues.append(Issue("error", "description_too_long",
722
+ f"Description is {len(rule_description)} chars (hard cap: 200) — see road-to-governance-cleanup F6"))
723
+
719
724
  # always-rules that look like auto candidates (rule-type-governance check)
720
725
  if rule_type == "always":
721
726
  description = extract_description(text) or ""
@@ -863,6 +868,9 @@ def lint_command(path: Path, text: str) -> LintResult:
863
868
  description = extract_description(text)
864
869
  if not description:
865
870
  issues.append(Issue("warning", "missing_description", "Frontmatter description is missing"))
871
+ elif len(description) > 200:
872
+ issues.append(Issue("error", "description_too_long",
873
+ f"Description is {len(description)} chars (hard cap: 200) — see road-to-governance-cleanup F6"))
866
874
 
867
875
  # suggestion block (road-to-context-aware-command-suggestion Phase 2)
868
876
  issues.extend(_lint_command_suggestion_block(text))
@@ -2,7 +2,7 @@
2
2
  """Sync `.agent-settings.yml` against the template + profile.
3
3
 
4
4
  Applies the section-aware merge rules documented in
5
- `.agent-src.uncompressed/guidelines/agent-infra/layered-settings.md`:
5
+ `docs/guidelines/agent-infra/layered-settings.md`:
6
6
 
7
7
  - Template section order always wins — reorder keys to match.
8
8
  - Existing user scalar values are preserved verbatim (as parsed).
@@ -28,8 +28,9 @@ def count(kind: str) -> int:
28
28
  if kind == "skills":
29
29
  return sum(1 for _ in (SRC / "skills").rglob("SKILL.md"))
30
30
  if kind == "guidelines":
31
- # guidelines are grouped by topic subdirectory
32
- return sum(1 for _ in (SRC / "guidelines").rglob("*.md"))
31
+ # Guidelines live under docs/guidelines/{topic}/ they are reference
32
+ # material, not packaged artefacts. Recursive walk to count every .md.
33
+ return sum(1 for _ in (REPO_ROOT / "docs" / "guidelines").rglob("*.md"))
33
34
  if kind == "personas":
34
35
  # personas live as flat .md files, README excluded
35
36
  pdir = SRC / "personas"
@@ -47,12 +48,16 @@ TARGETS: list[tuple[str, list[tuple[str, str]]]] = [
47
48
  [
48
49
  (r"(Browse all )(\d+)( commands\])", "commands"),
49
50
  (r"(package \(rules \+ )(\d+)( skills)", "skills"),
50
- (r"(skills \+ )(\d+)( native commands)", "commands"),
51
51
  # Hero line: **NNN Skills** · **NNN Rules** · **NNN Commands** · **NNN Guidelines**
52
52
  (r"(<strong>)(\d+)( Skills</strong>)", "skills"),
53
53
  (r"(<strong>)(\d+)( Rules</strong>)", "rules"),
54
- (r"(<strong>)(\d+)( Commands</strong>)", "commands"),
55
54
  (r"(<strong>)(\d+)( Guidelines</strong>)", "guidelines"),
55
+ # NOTE: hero `<strong>N Commands</strong>` and tools-blurb
56
+ # `skills + N native commands` are owned by
57
+ # `check_command_count_messaging.py` (Phase-1.2 of
58
+ # road-to-pr-34-followups). Those surfaces advertise the
59
+ # **active** command count (total − deprecation shims), not
60
+ # the raw file count this script computes.
56
61
  ],
57
62
  ),
58
63
  (