@event4u/agent-config 1.19.0 → 1.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (297) hide show
  1. package/.agent-src/commands/agent-handoff.md +14 -10
  2. package/.agent-src/commands/agents.md +1 -1
  3. package/.agent-src/commands/bug-fix.md +1 -1
  4. package/.agent-src/commands/bug-investigate.md +2 -2
  5. package/.agent-src/commands/chat-history/import.md +166 -0
  6. package/.agent-src/commands/chat-history/learn.md +178 -0
  7. package/.agent-src/commands/chat-history/show.md +17 -18
  8. package/.agent-src/commands/chat-history.md +26 -25
  9. package/.agent-src/commands/compress.md +12 -0
  10. package/.agent-src/commands/context/create.md +2 -2
  11. package/.agent-src/commands/context.md +1 -1
  12. package/.agent-src/commands/copilot-agents.md +1 -1
  13. package/.agent-src/commands/council/default.md +21 -12
  14. package/.agent-src/commands/council.md +1 -1
  15. package/.agent-src/commands/create-pr.md +28 -8
  16. package/.agent-src/commands/e2e-heal.md +1 -1
  17. package/.agent-src/commands/e2e-plan.md +1 -1
  18. package/.agent-src/commands/feature/dev.md +3 -3
  19. package/.agent-src/commands/feature.md +1 -1
  20. package/.agent-src/commands/fix/seeder.md +2 -2
  21. package/.agent-src/commands/fix.md +1 -1
  22. package/.agent-src/commands/jira-ticket.md +1 -1
  23. package/.agent-src/commands/judge.md +2 -2
  24. package/.agent-src/commands/memory.md +1 -1
  25. package/.agent-src/commands/mode.md +5 -5
  26. package/.agent-src/commands/module.md +1 -1
  27. package/.agent-src/commands/onboard.md +4 -4
  28. package/.agent-src/commands/optimize/augmentignore.md +1 -1
  29. package/.agent-src/commands/optimize-prompt.md +61 -0
  30. package/.agent-src/commands/optimize.md +1 -1
  31. package/.agent-src/commands/override.md +1 -1
  32. package/.agent-src/commands/review-changes.md +1 -1
  33. package/.agent-src/commands/review-routing.md +1 -1
  34. package/.agent-src/commands/roadmap.md +1 -1
  35. package/.agent-src/commands/set-cost-profile.md +3 -3
  36. package/.agent-src/commands/sync-agent-settings.md +2 -2
  37. package/.agent-src/commands/sync-gitignore.md +1 -1
  38. package/.agent-src/commands/tests/create.md +2 -2
  39. package/.agent-src/commands/tests.md +1 -1
  40. package/.agent-src/commands/threat-model.md +4 -4
  41. package/.agent-src/contexts/authority/commit-mechanics.md +14 -1
  42. package/.agent-src/contexts/authority/destructive-mechanics.md +14 -1
  43. package/.agent-src/contexts/authority/scope-mechanics.md +5 -0
  44. package/.agent-src/contexts/communication/rules-auto/guidelines-mechanics.md +76 -0
  45. package/.agent-src/contexts/communication/rules-auto/skill-quality-mechanics.md +76 -0
  46. package/.agent-src/contexts/communication/rules-auto/slash-command-routing-policy-mechanics.md +4 -4
  47. package/.agent-src/contexts/communication/rules-auto/think-before-action-mechanics.md +98 -0
  48. package/.agent-src/contexts/communication/rules-auto/token-efficiency-mechanics.md +93 -0
  49. package/.agent-src/contexts/communication/rules-auto/user-interaction-mechanics.md +125 -9
  50. package/.agent-src/contexts/execution/autonomy-mechanics.md +44 -0
  51. package/.agent-src/contexts/model-recommendations.md +2 -2
  52. package/.agent-src/contexts/override-system.md +1 -1
  53. package/.agent-src/personas/product-owner.md +2 -2
  54. package/.agent-src/personas/qa.md +1 -1
  55. package/.agent-src/rules/agent-authority.md +5 -6
  56. package/.agent-src/rules/agent-docs.md +11 -53
  57. package/.agent-src/rules/analysis-skill-routing.md +10 -40
  58. package/.agent-src/rules/architecture.md +6 -1
  59. package/.agent-src/rules/artifact-drafting-protocol.md +5 -0
  60. package/.agent-src/rules/artifact-engagement-recording.md +23 -59
  61. package/.agent-src/rules/ask-when-uncertain.md +24 -47
  62. package/.agent-src/rules/augment-portability.md +14 -62
  63. package/.agent-src/rules/augment-source-of-truth.md +10 -1
  64. package/.agent-src/rules/autonomous-execution.md +17 -98
  65. package/.agent-src/rules/capture-learnings.md +9 -80
  66. package/.agent-src/rules/cli-output-handling.md +12 -42
  67. package/.agent-src/rules/command-suggestion-policy.md +25 -73
  68. package/.agent-src/rules/commit-conventions.md +9 -58
  69. package/.agent-src/rules/commit-policy.md +16 -47
  70. package/.agent-src/rules/context-hygiene.md +5 -0
  71. package/.agent-src/rules/direct-answers.md +21 -42
  72. package/.agent-src/rules/docker-commands.md +11 -45
  73. package/.agent-src/rules/docs-sync.md +10 -56
  74. package/.agent-src/rules/downstream-changes.md +5 -0
  75. package/.agent-src/rules/e2e-testing.md +9 -44
  76. package/.agent-src/rules/guidelines.md +13 -75
  77. package/.agent-src/rules/improve-before-implement.md +10 -2
  78. package/.agent-src/rules/language-and-tone.md +35 -69
  79. package/.agent-src/rules/laravel-translations.md +11 -40
  80. package/.agent-src/rules/markdown-safe-codeblocks.md +4 -0
  81. package/.agent-src/rules/minimal-safe-diff.md +4 -0
  82. package/.agent-src/rules/missing-tool-handling.md +4 -0
  83. package/.agent-src/rules/model-recommendation.md +9 -61
  84. package/.agent-src/rules/no-attribution-footers.md +53 -0
  85. package/.agent-src/rules/no-cheap-questions.md +11 -27
  86. package/.agent-src/rules/no-council-references.md +76 -0
  87. package/.agent-src/rules/no-roadmap-references.md +8 -1
  88. package/.agent-src/rules/non-destructive-by-default.md +13 -43
  89. package/.agent-src/rules/onboarding-gate.md +9 -117
  90. package/.agent-src/rules/package-ci-checks.md +10 -37
  91. package/.agent-src/rules/php-coding.md +10 -55
  92. package/.agent-src/rules/preservation-guard.md +9 -0
  93. package/.agent-src/rules/review-routing-awareness.md +9 -97
  94. package/.agent-src/rules/reviewer-awareness.md +8 -83
  95. package/.agent-src/rules/roadmap-progress-sync.md +7 -170
  96. package/.agent-src/rules/role-mode-adherence.md +6 -2
  97. package/.agent-src/rules/rule-type-governance.md +8 -66
  98. package/.agent-src/rules/runtime-safety.md +5 -0
  99. package/.agent-src/rules/scope-control.md +17 -62
  100. package/.agent-src/rules/security-sensitive-stop.md +7 -1
  101. package/.agent-src/rules/size-enforcement.md +6 -1
  102. package/.agent-src/rules/skill-improvement-trigger.md +9 -49
  103. package/.agent-src/rules/skill-quality.md +7 -64
  104. package/.agent-src/rules/slash-command-routing-policy.md +11 -63
  105. package/.agent-src/rules/think-before-action.md +22 -87
  106. package/.agent-src/rules/token-efficiency.md +10 -74
  107. package/.agent-src/rules/token-optimizer-maintenance.md +68 -0
  108. package/.agent-src/rules/tool-safety.md +4 -0
  109. package/.agent-src/rules/ui-audit-gate.md +25 -61
  110. package/.agent-src/rules/upstream-proposal.md +9 -67
  111. package/.agent-src/rules/user-interaction.md +25 -95
  112. package/.agent-src/rules/verify-before-complete.md +1 -1
  113. package/.agent-src/skills/agent-docs-writing/SKILL.md +1 -1
  114. package/.agent-src/skills/ai-council/SKILL.md +69 -5
  115. package/.agent-src/skills/analysis-autonomous-mode/SKILL.md +1 -1
  116. package/.agent-src/skills/analysis-skill-router/SKILL.md +3 -3
  117. package/.agent-src/skills/artisan-commands/SKILL.md +2 -2
  118. package/.agent-src/skills/authz-review/SKILL.md +1 -1
  119. package/.agent-src/skills/aws-infrastructure/SKILL.md +5 -5
  120. package/.agent-src/skills/blast-radius-analyzer/SKILL.md +8 -8
  121. package/.agent-src/skills/bug-analyzer/SKILL.md +5 -5
  122. package/.agent-src/skills/code-refactoring/SKILL.md +4 -4
  123. package/.agent-src/skills/code-review/SKILL.md +2 -2
  124. package/.agent-src/skills/command-writing/SKILL.md +11 -0
  125. package/.agent-src/skills/composer-packages/SKILL.md +2 -2
  126. package/.agent-src/skills/context-authoring/SKILL.md +11 -0
  127. package/.agent-src/skills/context-document/SKILL.md +1 -1
  128. package/.agent-src/skills/copilot-agents-optimization/SKILL.md +23 -0
  129. package/.agent-src/skills/copilot-config/SKILL.md +1 -1
  130. package/.agent-src/skills/dcf-modeling/SKILL.md +89 -0
  131. package/.agent-src/skills/dependency-upgrade/SKILL.md +2 -2
  132. package/.agent-src/skills/devcontainer/SKILL.md +2 -2
  133. package/.agent-src/skills/developer-like-execution/SKILL.md +1 -1
  134. package/.agent-src/skills/docker/SKILL.md +1 -1
  135. package/.agent-src/skills/dto-creator/SKILL.md +1 -1
  136. package/.agent-src/skills/estimate-ticket/SKILL.md +2 -2
  137. package/.agent-src/skills/fe-design/SKILL.md +4 -4
  138. package/.agent-src/skills/feature-planning/SKILL.md +5 -5
  139. package/.agent-src/skills/funnel-analysis/SKILL.md +100 -0
  140. package/.agent-src/skills/laravel/SKILL.md +1 -1
  141. package/.agent-src/skills/laravel-notifications/SKILL.md +5 -5
  142. package/.agent-src/skills/laravel-pennant/SKILL.md +1 -1
  143. package/.agent-src/skills/laravel-pulse/SKILL.md +4 -4
  144. package/.agent-src/skills/laravel-reverb/SKILL.md +2 -2
  145. package/.agent-src/skills/laravel-scheduling/SKILL.md +1 -1
  146. package/.agent-src/skills/md-language-check/SKILL.md +1 -1
  147. package/.agent-src/skills/migration-creator/SKILL.md +7 -7
  148. package/.agent-src/skills/multi-tenancy/SKILL.md +8 -8
  149. package/.agent-src/skills/okr-tree-modeling/SKILL.md +93 -0
  150. package/.agent-src/skills/performance-analysis/SKILL.md +3 -3
  151. package/.agent-src/skills/pest-testing/SKILL.md +6 -6
  152. package/.agent-src/skills/php-service/SKILL.md +2 -2
  153. package/.agent-src/skills/project-analysis-hypothesis-driven/SKILL.md +3 -3
  154. package/.agent-src/skills/project-analysis-react/SKILL.md +1 -1
  155. package/.agent-src/skills/project-analysis-symfony/SKILL.md +1 -1
  156. package/.agent-src/skills/project-analysis-zend-laminas/SKILL.md +2 -2
  157. package/.agent-src/skills/project-analyzer/SKILL.md +4 -4
  158. package/.agent-src/skills/prompt-optimizer/SKILL.md +108 -0
  159. package/.agent-src/skills/readme-reviewer/SKILL.md +1 -1
  160. package/.agent-src/skills/rice-prioritization/SKILL.md +100 -0
  161. package/.agent-src/skills/rule-writing/SKILL.md +33 -0
  162. package/.agent-src/skills/sentry-integration/SKILL.md +1 -1
  163. package/.agent-src/skills/skill-writing/SKILL.md +14 -0
  164. package/.agent-src/skills/subagent-orchestration/SKILL.md +34 -2
  165. package/.agent-src/skills/terraform/SKILL.md +2 -2
  166. package/.agent-src/skills/terragrunt/SKILL.md +8 -8
  167. package/.agent-src/skills/test-performance/SKILL.md +5 -5
  168. package/.agent-src/skills/threat-modeling/SKILL.md +2 -2
  169. package/.agent-src/skills/token-optimizer/SKILL.md +110 -0
  170. package/.agent-src/skills/unit-economics-modeling/SKILL.md +104 -0
  171. package/.agent-src/skills/universal-project-analysis/SKILL.md +1 -1
  172. package/.agent-src/skills/using-git-worktrees/SKILL.md +1 -0
  173. package/.agent-src/templates/AGENTS.md +1 -1
  174. package/.agent-src/templates/agent-settings.md +25 -41
  175. package/.agent-src/templates/contexts/tenant-boundaries.md +2 -2
  176. package/.agent-src/templates/contexts.md +1 -1
  177. package/.agent-src/templates/copilot-instructions.md +21 -0
  178. package/.agent-src/templates/copilot-review-instructions.md +76 -0
  179. package/.agent-src/templates/features.md +1 -1
  180. package/.agent-src/templates/rule.md +127 -0
  181. package/.agent-src/templates/scripts/work_engine/hook_bootstrap.py +7 -5
  182. package/.agent-src/templates/scripts/work_engine/hooks/__init__.py +0 -4
  183. package/.agent-src/templates/scripts/work_engine/hooks/builtin/__init__.py +0 -4
  184. package/.agent-src/templates/scripts/work_engine/hooks/builtin/_chat_history_base.py +7 -51
  185. package/.agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_append.py +1 -2
  186. package/.agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_halt_append.py +1 -2
  187. package/.agent-src/templates/scripts/work_engine/hooks/builtin/memory_visibility.py +2 -3
  188. package/.agent-src/templates/skill.md +30 -1
  189. package/.claude-plugin/marketplace.json +11 -4
  190. package/AGENTS.md +71 -3
  191. package/CHANGELOG.md +180 -3
  192. package/README.md +24 -23
  193. package/config/agent-settings.template.yml +63 -23
  194. package/config/gitignore-block.txt +11 -4
  195. package/docs/architecture.md +84 -3
  196. package/docs/catalog.md +23 -11
  197. package/docs/contracts/adr-chat-history-split.md +10 -1
  198. package/docs/contracts/agent-memory-contract.md +1 -1
  199. package/docs/contracts/command-clusters.md +1 -1
  200. package/docs/contracts/context-paths.md +2 -1
  201. package/docs/contracts/cross-wing-handoff.md +133 -0
  202. package/docs/contracts/file-ownership-matrix.json +678 -609
  203. package/docs/contracts/hook-architecture-v1.md +8 -1
  204. package/docs/contracts/iron-law-overrides.txt +25 -0
  205. package/docs/contracts/kernel-membership.md +273 -0
  206. package/docs/contracts/load-context-schema.md +26 -11
  207. package/docs/contracts/memory-visibility-v1.md +8 -24
  208. package/docs/contracts/pilot/agent-authority.md +24 -0
  209. package/docs/contracts/pilot/direct-answers.md +70 -0
  210. package/docs/contracts/pilot/language-and-tone.md +63 -0
  211. package/docs/contracts/rule-classification.md +170 -0
  212. package/docs/contracts/rule-router.md +153 -0
  213. package/docs/customization.md +18 -7
  214. package/docs/decisions/ADR-001-kernel-swap-deferred.md +109 -0
  215. package/docs/decisions/ADR-002-kernel-bucket-overrides.md +124 -0
  216. package/docs/decisions/ADR-rule-kernel-and-router.md +122 -0
  217. package/docs/getting-started.md +19 -27
  218. package/docs/guidelines/agent-infra/ask-when-uncertain-demos.md +1 -1
  219. package/docs/guidelines/agent-infra/roadmap-progress-mechanics.md +176 -0
  220. package/docs/guidelines/agent-infra/rule-type-governance.md +73 -0
  221. package/docs/guidelines/agent-infra/size-and-scope.md +13 -2
  222. package/docs/guidelines/agent-infra/skill-quality-checklist.md +119 -0
  223. package/docs/guidelines/augment-portability-patterns.md +68 -0
  224. package/docs/guidelines/php/php-coding-patterns.md +62 -0
  225. package/docs/hook-payload-capture.md +221 -0
  226. package/docs/migrations/commands-1.15.0.md +17 -12
  227. package/docs/skills-catalog.md +5 -4
  228. package/llms.txt +4 -3
  229. package/package.json +1 -1
  230. package/scripts/_p43_bodies.py +235 -0
  231. package/scripts/_p43_compress.py +118 -0
  232. package/scripts/_p4_migrate.py +199 -0
  233. package/scripts/_pilot_council_question.py +57 -0
  234. package/scripts/_pilot_measure.py +53 -0
  235. package/scripts/agent-config +1 -1
  236. package/scripts/ai_council/_default_prices.py +4 -4
  237. package/scripts/ai_council/clients.py +1 -1
  238. package/scripts/ai_council/modes.py +3 -4
  239. package/scripts/ai_council/pricing.py +10 -9
  240. package/scripts/ai_council/session.py +107 -5
  241. package/scripts/build_linear_digest.py +3 -5
  242. package/scripts/build_rule_trigger_matrix.py +1 -9
  243. package/scripts/chat_history.py +952 -596
  244. package/scripts/check_always_budget.py +39 -6
  245. package/scripts/check_compressed_paths.py +213 -0
  246. package/scripts/check_compression.py +15 -0
  247. package/scripts/check_context_paths.py +1 -0
  248. package/scripts/check_council_layout.py +105 -0
  249. package/scripts/check_council_references.py +145 -0
  250. package/scripts/check_portability.py +2 -0
  251. package/scripts/check_references.py +14 -2
  252. package/scripts/check_token_optimizer_freshness.py +131 -0
  253. package/scripts/compile_router.py +148 -0
  254. package/scripts/compress.py +219 -11
  255. package/scripts/council_cli.py +63 -9
  256. package/scripts/council_prune.py +81 -0
  257. package/scripts/count_token_optimizer_usage.sh +54 -0
  258. package/scripts/hook_manifest.yaml +33 -0
  259. package/scripts/hooks/augment-chat-history.sh +10 -0
  260. package/scripts/hooks/cowork-dispatcher.sh +98 -0
  261. package/scripts/hooks/dispatch_hook.py +35 -0
  262. package/scripts/hooks_status.py +12 -1
  263. package/scripts/install-hooks.sh +2 -2
  264. package/scripts/install.sh +81 -2
  265. package/scripts/iron_law_sha.py +98 -0
  266. package/scripts/lint_handoffs.py +214 -0
  267. package/scripts/lint_hook_manifest.py +2 -1
  268. package/scripts/lint_load_context.py +35 -5
  269. package/scripts/measure_rule_budget.py +314 -0
  270. package/scripts/prototype_lint_contradictions.py +150 -0
  271. package/scripts/redact_hook_capture.py +148 -0
  272. package/scripts/schemas/rule.schema.json +55 -6
  273. package/scripts/schemas/skill.schema.json +5 -0
  274. package/scripts/skill_linter.py +359 -7
  275. package/scripts/smoke_path_resolution.py +93 -0
  276. package/scripts/update_prices.py +3 -3
  277. package/scripts/validate_frontmatter.py +41 -1
  278. package/.agent-src/commands/chat-history/checkpoint.md +0 -126
  279. package/.agent-src/commands/chat-history/clear.md +0 -103
  280. package/.agent-src/commands/chat-history/resume.md +0 -183
  281. package/.agent-src/contexts/communication/rules-auto/artifact-engagement-recording-mechanics.md +0 -72
  282. package/.agent-src/contexts/communication/rules-auto/augment-portability-mechanics.md +0 -79
  283. package/.agent-src/contexts/communication/rules-auto/cli-output-handling-mechanics.md +0 -87
  284. package/.agent-src/contexts/communication/rules-auto/command-suggestion-policy-mechanics.md +0 -62
  285. package/.agent-src/contexts/communication/rules-auto/docs-sync-mechanics.md +0 -78
  286. package/.agent-src/contexts/communication/rules-auto/package-ci-checks-mechanics.md +0 -85
  287. package/.agent-src/contexts/communication/rules-auto/review-routing-awareness-mechanics.md +0 -65
  288. package/.agent-src/contexts/communication/rules-auto/roadmap-progress-sync-mechanics.md +0 -78
  289. package/.agent-src/contexts/communication/rules-auto/ui-audit-gate-mechanics.md +0 -53
  290. package/.agent-src/rules/chat-history-cadence.md +0 -143
  291. package/.agent-src/rules/chat-history-ownership.md +0 -124
  292. package/.agent-src/rules/chat-history-visibility.md +0 -97
  293. package/.agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_heartbeat.py +0 -50
  294. package/.agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_turn_check.py +0 -49
  295. package/scripts/check_phase_coupling.py +0 -148
  296. /package/{docs → .agent-src/contexts}/contracts/artifact-engagement-flow.md +0 -0
  297. /package/{docs → .agent-src/contexts}/contracts/command-suggestion-flow.md +0 -0
@@ -2,7 +2,9 @@
2
2
  # install.sh — Agent-config payload sync (one of two installer stages).
3
3
  #
4
4
  # Reads from vendor's .agent-src/ (fallback: .augment/ for pre-2.0 packages) and
5
- # writes the target project's .augment/ tree: copies rules, symlinks everything else.
5
+ # writes the target project's .augment/ tree: copies rules, symlinks everything
6
+ # else. When augment.rules_use_symlinks: true is set in the target's
7
+ # .agent-settings.yml, rules are symlinked instead of copied.
6
8
  # Creates tool-specific directories for Claude Code, Cursor, Cline, Windsurf, Gemini.
7
9
  #
8
10
  # Does NOT render .agent-settings.yml or bridge JSONs — that is the job of
@@ -35,6 +37,9 @@ DRY_RUN=false
35
37
  VERBOSE=false
36
38
  QUIET=false
37
39
  SKIP_GITIGNORE=false
40
+ # Resolved from <TARGET>/.agent-settings.yml in resolve_settings(); when
41
+ # true, .augment/rules/ files are symlinked instead of copied.
42
+ USE_RULES_SYMLINKS=false
38
43
 
39
44
  # --- Logging ---
40
45
  log_info() { $QUIET || echo " ✅ $*"; }
@@ -114,6 +119,30 @@ EOF
114
119
 
115
120
  # --- Utility functions ---
116
121
 
122
+ # Read augment.rules_use_symlinks from <TARGET>/.agent-settings.yml.
123
+ # Sets USE_RULES_SYMLINKS=true|false. Missing file or absent key → false.
124
+ # Minimal scoped parser; avoids a hard yq/python dependency.
125
+ resolve_settings() {
126
+ USE_RULES_SYMLINKS=false
127
+ local settings_file="$TARGET_DIR/.agent-settings.yml"
128
+ [[ -f "$settings_file" ]] || return 0
129
+ local val
130
+ val=$(awk '
131
+ /^[^[:space:]#]/ { in_block = ($0 ~ /^augment:[[:space:]]*$/) }
132
+ in_block && /^[[:space:]]+rules_use_symlinks[[:space:]]*:/ {
133
+ line = $0
134
+ sub(/^[[:space:]]*rules_use_symlinks[[:space:]]*:[[:space:]]*/, "", line)
135
+ sub(/[[:space:]]*#.*$/, "", line)
136
+ gsub(/[[:space:]]/, "", line)
137
+ print tolower(line)
138
+ exit
139
+ }
140
+ ' "$settings_file" 2>/dev/null || true)
141
+ case "$val" in
142
+ true|yes|on|1) USE_RULES_SYMLINKS=true ;;
143
+ esac
144
+ }
145
+
117
146
  # Check if a relative path should be copied (true=copy) or symlinked (false=symlink)
118
147
  should_copy() {
119
148
  local rel_path="$1"
@@ -127,6 +156,10 @@ should_copy() {
127
156
  # Check against COPY_DIRS
128
157
  for dir in $COPY_DIRS; do
129
158
  if [[ "$first_segment" == "$dir" ]]; then
159
+ # Honor augment.rules_use_symlinks toggle for the rules dir.
160
+ if [[ "$dir" == "rules" ]] && $USE_RULES_SYMLINKS; then
161
+ return 1
162
+ fi
130
163
  return 0
131
164
  fi
132
165
  done
@@ -563,6 +596,40 @@ copy_if_missing() {
563
596
  cp "$source" "$target"
564
597
  }
565
598
 
599
+ # Migrate legacy infra files from project root to agents/.
600
+ # Pre-2.x layout: .agent-chat-history (+ .bak), .agent-prices.md lived at
601
+ # the project root. They now live under agents/. Move them in place before
602
+ # any other content sync so the updated gitignore block (which lists
603
+ # /agents/.agent-chat-history*) and the chat-history hooks operate on the
604
+ # already-migrated layout. Idempotent: skips silently if the target already
605
+ # exists; never overwrites.
606
+ migrate_legacy_root_infra() {
607
+ local project_root="$1"
608
+ local agents_dir="$project_root/agents"
609
+ local items=(".agent-chat-history" ".agent-chat-history.bak" ".agent-prices.md")
610
+
611
+ for name in "${items[@]}"; do
612
+ local old="$project_root/$name"
613
+ local new="$agents_dir/$name"
614
+
615
+ [[ -e "$old" ]] || continue
616
+
617
+ if [[ -e "$new" ]]; then
618
+ log_warn "Legacy $name found at project root, but agents/$name already exists — leaving root copy in place"
619
+ continue
620
+ fi
621
+
622
+ if $DRY_RUN; then
623
+ log_verbose "would migrate $name → agents/$name"
624
+ continue
625
+ fi
626
+
627
+ mkdir -p "$agents_dir"
628
+ mv "$old" "$new"
629
+ log_info "Migrated $name → agents/$name"
630
+ done
631
+ }
632
+
566
633
  # Ensure .gitignore contains the managed agent-config block.
567
634
  # Delegates to scripts/sync_gitignore.py so the installer and the
568
635
  # standalone /sync-gitignore command share one source of truth
@@ -632,9 +699,20 @@ main() {
632
699
  $DRY_RUN && ! $QUIET && echo " Mode: DRY RUN"
633
700
  echo ""
634
701
 
702
+ # 0. Migrate legacy infra files (root → agents/) before any content sync.
703
+ migrate_legacy_root_infra "$TARGET_DIR"
704
+
705
+ # 0b. Resolve settings (e.g. augment.rules_use_symlinks). On first
706
+ # install the file does not exist yet → defaults preserved.
707
+ resolve_settings
708
+
635
709
  # 1. Hybrid sync payload → target/.augment/
636
710
  sync_hybrid "$SOURCE_PAYLOAD" "$TARGET_DIR/.augment"
637
- log_info "Synced .augment/ (rules copied, rest symlinked)"
711
+ if $USE_RULES_SYMLINKS; then
712
+ log_info "Synced .augment/ (rules symlinked, rest symlinked)"
713
+ else
714
+ log_info "Synced .augment/ (rules copied, rest symlinked)"
715
+ fi
638
716
 
639
717
  # 2. Copy standalone files from templates if missing on the target.
640
718
  # We copy from templates/ (generic placeholders), NOT from the package's
@@ -643,6 +721,7 @@ main() {
643
721
  # into consumer projects.
644
722
  copy_if_missing "$SOURCE_PAYLOAD/templates/AGENTS.md" "$TARGET_DIR/AGENTS.md"
645
723
  copy_if_missing "$SOURCE_PAYLOAD/templates/copilot-instructions.md" "$TARGET_DIR/.github/copilot-instructions.md"
724
+ copy_if_missing "$SOURCE_PAYLOAD/templates/copilot-review-instructions.md" "$TARGET_DIR/.github/copilot-review-instructions.md"
646
725
 
647
726
  # 3. Create tool-specific symlinks
648
727
  create_tool_symlinks "$TARGET_DIR"
@@ -0,0 +1,98 @@
1
+ #!/usr/bin/env python3
2
+ """SHA-256 of every triple-fence block in a rule file (Iron Law preservation).
3
+
4
+ Usage:
5
+ python3 scripts/iron_law_sha.py <rule-id> [<rule-id> ...]
6
+ python3 scripts/iron_law_sha.py --all-kernel
7
+ python3 scripts/iron_law_sha.py --diff <rule-id> --against <baseline-sha>
8
+
9
+ The Iron-Law block is delimited by triple-backtick fences. Every line
10
+ inside any fence in the file is concatenated, whitespace-normalised
11
+ (runs of spaces collapsed; leading / trailing whitespace stripped per
12
+ line), case-folded, then SHA-256-hashed. Empty fences hash to
13
+ SHA-256(''), which is `e3b0c442…` (the well-known empty-string hash).
14
+
15
+ Acceptance per `road-to-kernel-and-router.md` P2.2: re-runnable,
16
+ deterministic, stdlib-only, no network. Compression of a kernel rule
17
+ must preserve this SHA (or surface a deliberate ADR-tracked diff).
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import argparse
23
+ import hashlib
24
+ import re
25
+ import sys
26
+ from pathlib import Path
27
+
28
+ REPO_ROOT = Path(__file__).resolve().parent.parent
29
+ RULES_DIR = REPO_ROOT / ".agent-src.uncompressed" / "rules"
30
+
31
+ # Locked kernel set — kept in sync with measure_rule_budget.KERNEL_RULES.
32
+ KERNEL_RULES = (
33
+ "agent-authority",
34
+ "ask-when-uncertain",
35
+ "commit-policy",
36
+ "direct-answers",
37
+ "language-and-tone",
38
+ "no-cheap-questions",
39
+ "non-destructive-by-default",
40
+ "scope-control",
41
+ "verify-before-complete",
42
+ )
43
+
44
+ _FENCE_RE = re.compile(r"```(?:[^\n]*\n)([\s\S]*?)```")
45
+ _WS_RE = re.compile(r"\s+")
46
+
47
+
48
+ def iron_law_sha(text: str) -> str:
49
+ """SHA-256 of all triple-fence content, whitespace-collapsed, upper-cased.
50
+
51
+ Algorithm matches `scripts/_pilot_measure.py` exactly so the SHAs
52
+ recorded in `docs/contracts/kernel-membership.md` § 2 stay
53
+ reproducible across pre / post compression.
54
+ """
55
+ blocks = _FENCE_RE.findall(text)
56
+ norm = "".join(_WS_RE.sub(" ", b).strip().upper() for b in blocks)
57
+ return hashlib.sha256(norm.encode("utf-8")).hexdigest()
58
+
59
+
60
+ def rule_sha(rule_id: str) -> str:
61
+ path = RULES_DIR / f"{rule_id}.md"
62
+ if not path.exists():
63
+ raise FileNotFoundError(path)
64
+ return iron_law_sha(path.read_text(encoding="utf-8"))
65
+
66
+
67
+ def main(argv: list[str] | None = None) -> int:
68
+ parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
69
+ parser.add_argument("rules", nargs="*", help="rule ids (omit if --all-kernel)")
70
+ parser.add_argument("--all-kernel", action="store_true", help="hash all 9 kernel rules")
71
+ parser.add_argument(
72
+ "--diff", metavar="RULE", help="hash one rule and compare to --against"
73
+ )
74
+ parser.add_argument("--against", metavar="SHA", help="expected SHA (for --diff)")
75
+ args = parser.parse_args(argv)
76
+
77
+ if args.diff:
78
+ if not args.against:
79
+ parser.error("--diff requires --against")
80
+ actual = rule_sha(args.diff)
81
+ match = actual == args.against
82
+ symbol = "✅" if match else "❌"
83
+ print(f"{symbol} {args.diff}: {actual} (expected {args.against})")
84
+ return 0 if match else 1
85
+
86
+ targets = list(KERNEL_RULES) if args.all_kernel else args.rules
87
+ if not targets:
88
+ parser.error("provide rule ids, or use --all-kernel")
89
+
90
+ width = max(len(t) for t in targets)
91
+ for rid in targets:
92
+ sha = rule_sha(rid)
93
+ print(f"{rid:<{width}} {sha}")
94
+ return 0
95
+
96
+
97
+ if __name__ == "__main__":
98
+ sys.exit(main())
@@ -0,0 +1,214 @@
1
+ #!/usr/bin/env python3
2
+ """Lint cross-wing handoffs declared in senior-tier skills' ``## Related Skills`` blocks.
3
+
4
+ Builds a directed graph from every ``tier: senior`` skill's Related Skills
5
+ block (markdown links pointing at peer ``SKILL.md`` files), then enforces
6
+ the rules from ``docs/contracts/cross-wing-handoff.md`` § 4:
7
+
8
+ handoff_cycle — graph must be a DAG.
9
+ handoff_dangling — every linked target must exist.
10
+ handoff_tier_mismatch — senior may delegate only to senior.
11
+
12
+ Hooked into ``task lint-handoffs`` and ``task ci`` (between ``lint-skills``
13
+ and ``test``). Output mirrors ``scripts/skill_linter.py``: ``file:line:reason``.
14
+
15
+ Exit codes:
16
+ 0 no violations
17
+ 1 one or more violations
18
+ """
19
+ from __future__ import annotations
20
+
21
+ import re
22
+ import sys
23
+ from dataclasses import dataclass
24
+ from pathlib import Path
25
+ from typing import Iterable
26
+
27
+ REPO = Path(__file__).resolve().parents[1]
28
+ SKILLS_DIR = REPO / ".agent-src.uncompressed" / "skills"
29
+
30
+ LINK_RE = re.compile(r"\[`?([a-z0-9][a-z0-9-]*)`?\]\(([^)]+SKILL\.md)\)")
31
+ RELATED_HEADING_RE = re.compile(r"^##\s+Related\s+Skills\s*$", re.IGNORECASE)
32
+ NEXT_HEADING_RE = re.compile(r"^##\s+\S")
33
+ WHEN_USE_RE = re.compile(r"^\*\*WHEN\s+to\s+use\s+this\*\*\s*$", re.IGNORECASE)
34
+ WHEN_NOT_RE = re.compile(r"^\*\*WHEN\s+NOT\s+to\s+use\s+this\*\*\s*$", re.IGNORECASE)
35
+
36
+
37
+ @dataclass(frozen=True)
38
+ class Violation:
39
+ file: Path
40
+ line: int
41
+ code: str
42
+ message: str
43
+
44
+ def render(self, repo: Path) -> str:
45
+ rel = self.file.relative_to(repo) if self.file.is_absolute() else self.file
46
+ return f"{rel}:{self.line}:{self.code}: {self.message}"
47
+
48
+
49
+ def parse_frontmatter_tier(text: str) -> str | None:
50
+ if not text.startswith("---\n"):
51
+ return None
52
+ end = text.find("\n---\n", 4)
53
+ if end == -1:
54
+ return None
55
+ for raw in text[4:end].splitlines():
56
+ if ":" not in raw:
57
+ continue
58
+ key, _, val = raw.partition(":")
59
+ if key.strip() == "tier":
60
+ return val.strip().strip('"').strip("'")
61
+ return None
62
+
63
+
64
+ def extract_related_block(text: str) -> tuple[int, list[tuple[int, str]]] | None:
65
+ """Return (block_start_line, [(line, raw_line), ...]) for ``## Related Skills``."""
66
+ lines = text.splitlines()
67
+ start: int | None = None
68
+ for idx, line in enumerate(lines):
69
+ if RELATED_HEADING_RE.match(line):
70
+ start = idx
71
+ break
72
+ if start is None:
73
+ return None
74
+ body: list[tuple[int, str]] = []
75
+ for idx in range(start + 1, len(lines)):
76
+ if NEXT_HEADING_RE.match(lines[idx]):
77
+ break
78
+ body.append((idx + 1, lines[idx]))
79
+ return start + 1, body
80
+
81
+
82
+ def split_when_subblocks(body: list[tuple[int, str]]) -> tuple[
83
+ list[tuple[int, str]], list[tuple[int, str]]
84
+ ]:
85
+ """Split a ``## Related Skills`` body into (when_to_use, when_not_to_use).
86
+
87
+ WHEN-to-use links are composition (delegation) edges — graph for cycles.
88
+ WHEN-NOT-to-use links are alternative pointers (peer cognition the user
89
+ picks instead) — never composition edges. Lines outside both sub-blocks
90
+ are treated as WHEN-to-use for backward compatibility.
91
+ """
92
+ when_use: list[tuple[int, str]] = []
93
+ when_not: list[tuple[int, str]] = []
94
+ current = when_use
95
+ for lineno, raw in body:
96
+ if WHEN_USE_RE.match(raw):
97
+ current = when_use
98
+ continue
99
+ if WHEN_NOT_RE.match(raw):
100
+ current = when_not
101
+ continue
102
+ current.append((lineno, raw))
103
+ return when_use, when_not
104
+
105
+
106
+ def extract_links(body: list[tuple[int, str]]) -> list[tuple[int, str, str]]:
107
+ """Yield ``(line, slug, target_path)`` for every markdown link in the block."""
108
+ out: list[tuple[int, str, str]] = []
109
+ for lineno, raw in body:
110
+ for match in LINK_RE.finditer(raw):
111
+ out.append((lineno, match.group(1), match.group(2)))
112
+ return out
113
+
114
+
115
+ def resolve_target(skill_file: Path, link: str) -> Path:
116
+ return (skill_file.parent / link).resolve()
117
+
118
+
119
+ def detect_cycles(graph: dict[Path, set[Path]]) -> list[list[Path]]:
120
+ cycles: list[list[Path]] = []
121
+ visited: set[Path] = set()
122
+ stack: list[Path] = []
123
+ on_stack: set[Path] = set()
124
+
125
+ def dfs(node: Path) -> None:
126
+ if node in on_stack:
127
+ i = stack.index(node)
128
+ cycles.append(stack[i:] + [node])
129
+ return
130
+ if node in visited:
131
+ return
132
+ visited.add(node)
133
+ on_stack.add(node)
134
+ stack.append(node)
135
+ for nxt in graph.get(node, ()):
136
+ dfs(nxt)
137
+ stack.pop()
138
+ on_stack.discard(node)
139
+
140
+ for node in list(graph):
141
+ dfs(node)
142
+ return cycles
143
+
144
+
145
+ def lint(skills_dir: Path) -> list[Violation]:
146
+ senior_skills: dict[Path, str] = {}
147
+ all_skills: dict[Path, str] = {}
148
+ for skill_md in sorted(skills_dir.rglob("SKILL.md")):
149
+ text = skill_md.read_text(encoding="utf-8")
150
+ tier = parse_frontmatter_tier(text)
151
+ all_skills[skill_md.resolve()] = tier or ""
152
+ if tier == "senior":
153
+ senior_skills[skill_md.resolve()] = text
154
+
155
+ violations: list[Violation] = []
156
+ graph: dict[Path, set[Path]] = {}
157
+
158
+ for skill_path, text in senior_skills.items():
159
+ block = extract_related_block(text)
160
+ if block is None:
161
+ continue
162
+ _, body = block
163
+ when_use, when_not = split_when_subblocks(body)
164
+
165
+ # WHEN-to-use links: composition edges (graph) + dangling/tier checks.
166
+ for lineno, slug, link in extract_links(when_use):
167
+ target = resolve_target(skill_path, link)
168
+ graph.setdefault(skill_path, set()).add(target)
169
+ if target not in all_skills:
170
+ violations.append(Violation(skill_path, lineno, "handoff_dangling",
171
+ f"link to `{slug}` resolves to missing file {link}"))
172
+ continue
173
+ if all_skills[target] != "senior":
174
+ violations.append(Violation(skill_path, lineno, "handoff_tier_mismatch",
175
+ f"senior skill links to non-senior `{slug}` "
176
+ f"(tier={all_skills[target] or 'unset'!r})"))
177
+
178
+ # WHEN-NOT-to-use links: alternative pointers, NOT composition edges.
179
+ # Dangling + tier-mismatch still apply (a broken alternative is wrong);
180
+ # cycles do not (mutual "use X instead" pointers are intentional).
181
+ for lineno, slug, link in extract_links(when_not):
182
+ target = resolve_target(skill_path, link)
183
+ if target not in all_skills:
184
+ violations.append(Violation(skill_path, lineno, "handoff_dangling",
185
+ f"link to `{slug}` resolves to missing file {link}"))
186
+ continue
187
+ if all_skills[target] != "senior":
188
+ violations.append(Violation(skill_path, lineno, "handoff_tier_mismatch",
189
+ f"senior skill links to non-senior `{slug}` "
190
+ f"(tier={all_skills[target] or 'unset'!r})"))
191
+
192
+ for cycle in detect_cycles(graph):
193
+ names = " → ".join(p.parent.name for p in cycle)
194
+ violations.append(Violation(cycle[0], 1, "handoff_cycle",
195
+ f"composition cycle: {names}"))
196
+ return violations
197
+
198
+
199
+ def main(argv: list[str] | None = None) -> int:
200
+ skills_dir = SKILLS_DIR
201
+ if argv:
202
+ skills_dir = Path(argv[0]).resolve()
203
+ violations = lint(skills_dir)
204
+ if not violations:
205
+ print(f"✅ lint_handoffs: no violations under {skills_dir.relative_to(REPO)}")
206
+ return 0
207
+ for v in violations:
208
+ print(v.render(REPO))
209
+ print(f"\n❌ lint_handoffs: {len(violations)} violation(s)", file=sys.stderr)
210
+ return 1
211
+
212
+
213
+ if __name__ == "__main__":
214
+ sys.exit(main(sys.argv[1:]))
@@ -57,7 +57,8 @@ EVENT_VOCABULARY: set[str] = {
57
57
  # Known platform identifiers. New platforms MUST be added here as they
58
58
  # land — the linter is the gate that proves no orphan slot escapes.
59
59
  KNOWN_PLATFORMS: set[str] = {
60
- "augment", "claude", "cursor", "cline", "windsurf", "gemini", "copilot",
60
+ "augment", "claude", "cowork",
61
+ "cursor", "cline", "windsurf", "gemini", "copilot",
61
62
  }
62
63
 
63
64
 
@@ -28,14 +28,37 @@ SCAN_DIRS = [
28
28
  ]
29
29
 
30
30
  ALLOWED_PREFIXES = (
31
- ".agent-src.uncompressed/contexts/",
32
- ".agent-src/contexts/",
33
- "agents/contexts/",
31
+ "contexts/", # logical name (canonical — P1.1 / P5.3)
32
+ ".agent-src/contexts/", # projected (defensive — only seen in compressed inputs)
33
+ "agents/contexts/", # project-local
34
34
  )
35
35
 
36
+ # `.agent-src.uncompressed/contexts/` was the legacy fully-qualified form
37
+ # (pre road-to-path-fixes.md P5.3). It is now a hard error: P2.1 migrated
38
+ # all in-tree rules to logical names, the rewriter resolves them at
39
+ # compress time, and the schema regex in `scripts/schemas/rule.schema.json`
40
+ # rejects the prefix at validate-schema time. Keeping a separate runtime
41
+ # diagnostic so the failure points authors at the canonical
42
+ # `contexts/<area>/<file>.md` form rather than a generic schema mismatch.
43
+ LEGACY_PREFIX = ".agent-src.uncompressed/contexts/"
44
+
45
+ # Logical names resolve against the source root.
46
+ SOURCE_ROOT = ROOT / ".agent-src.uncompressed"
47
+
36
48
  PUBLIC_RULE_PREFIX = ".agent-src.uncompressed/rules/"
37
49
  PROJECT_LOCAL_PREFIX = "agents/contexts/"
38
50
 
51
+
52
+ def resolve_entry(entry: str) -> Path:
53
+ """Resolve a `load_context:` entry to an absolute path on disk.
54
+
55
+ Logical names (`contexts/...`) live under `.agent-src.uncompressed/`;
56
+ fully-qualified entries are repo-root-relative.
57
+ """
58
+ if entry.startswith("contexts/"):
59
+ return SOURCE_ROOT / entry
60
+ return ROOT / entry
61
+
39
62
  HARD_FLOOR_RULES = {"non-destructive-by-default", "security-sensitive-stop"}
40
63
 
41
64
  CAP_ALWAYS = 2_500
@@ -126,10 +149,17 @@ def main() -> int:
126
149
  if not isinstance(entry, str) or not entry.endswith(".md"):
127
150
  errors.append(f"{rel(f)}: entry not str ending in .md → {entry!r}")
128
151
  continue
152
+ if entry.startswith(LEGACY_PREFIX):
153
+ logical = entry[len(".agent-src.uncompressed/"):]
154
+ errors.append(
155
+ f"{rel(f)}: legacy `.agent-src.uncompressed/` prefix in load_context → {entry} "
156
+ f"— use logical name `{logical}` instead (road-to-path-fixes.md P5.3)"
157
+ )
158
+ continue
129
159
  if not entry.startswith(ALLOWED_PREFIXES):
130
160
  errors.append(f"{rel(f)}: disallowed root → {entry}")
131
161
  continue
132
- target = ROOT / entry
162
+ target = resolve_entry(entry)
133
163
  if not target.exists():
134
164
  errors.append(f"{rel(f)}: target missing → {entry}")
135
165
  continue
@@ -140,7 +170,7 @@ def main() -> int:
140
170
  cap = cap_for(f, fm)
141
171
  total = len(f.read_text(encoding="utf-8"))
142
172
  for entry in eager:
143
- tgt = ROOT / entry
173
+ tgt = resolve_entry(entry)
144
174
  if tgt.exists():
145
175
  total += len(tgt.read_text(encoding="utf-8"))
146
176
  if total > cap: