@event4u/agent-config 10.4.0 → 12.0.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 (303) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +73 -182
  3. package/dist/agent-src/commands/agent-handoff.md +0 -1
  4. package/dist/agent-src/commands/agent-status.md +0 -1
  5. package/dist/agent-src/commands/agents/audit.md +0 -1
  6. package/dist/agent-src/commands/agents/init.md +0 -1
  7. package/dist/agent-src/commands/agents/optimize.md +0 -1
  8. package/dist/agent-src/commands/agents/user/accept.md +0 -1
  9. package/dist/agent-src/commands/agents/user/delete.md +0 -1
  10. package/dist/agent-src/commands/agents/user/init.md +0 -1
  11. package/dist/agent-src/commands/agents/user/review.md +0 -1
  12. package/dist/agent-src/commands/agents/user/show.md +0 -1
  13. package/dist/agent-src/commands/agents/user/update.md +0 -1
  14. package/dist/agent-src/commands/agents/user.md +0 -1
  15. package/dist/agent-src/commands/agents.md +0 -1
  16. package/dist/agent-src/commands/analytics/prune.md +0 -1
  17. package/dist/agent-src/commands/analytics/show.md +0 -1
  18. package/dist/agent-src/commands/analytics.md +0 -1
  19. package/dist/agent-src/commands/analyze/conformance.md +0 -1
  20. package/dist/agent-src/commands/analyze/decision.md +0 -1
  21. package/dist/agent-src/commands/analyze/inbox.md +0 -1
  22. package/dist/agent-src/commands/analyze/incident.md +0 -1
  23. package/dist/agent-src/commands/analyze/near-miss.md +0 -1
  24. package/dist/agent-src/commands/analyze/postmortem.md +0 -1
  25. package/dist/agent-src/commands/analyze/premortem.md +0 -1
  26. package/dist/agent-src/commands/analyze/reference-repo.md +0 -1
  27. package/dist/agent-src/commands/analyze.md +0 -1
  28. package/dist/agent-src/commands/brand/identity.md +0 -1
  29. package/dist/agent-src/commands/brand/review.md +0 -1
  30. package/dist/agent-src/commands/brand/strategy.md +0 -1
  31. package/dist/agent-src/commands/brand/tokens.md +0 -1
  32. package/dist/agent-src/commands/brand/voice.md +0 -1
  33. package/dist/agent-src/commands/brand.md +0 -1
  34. package/dist/agent-src/commands/bug/fix.md +0 -1
  35. package/dist/agent-src/commands/bug/investigate.md +0 -1
  36. package/dist/agent-src/commands/bug.md +0 -1
  37. package/dist/agent-src/commands/challenge-me/vision.md +0 -1
  38. package/dist/agent-src/commands/challenge-me/with-docs.md +0 -1
  39. package/dist/agent-src/commands/challenge-me.md +0 -1
  40. package/dist/agent-src/commands/chat-history/import.md +0 -1
  41. package/dist/agent-src/commands/chat-history.md +0 -1
  42. package/dist/agent-src/commands/check-current-md.md +0 -1
  43. package/dist/agent-src/commands/commit/in-chunks.md +0 -1
  44. package/dist/agent-src/commands/commit.md +0 -1
  45. package/dist/agent-src/commands/condense.md +0 -1
  46. package/dist/agent-src/commands/context/create.md +0 -1
  47. package/dist/agent-src/commands/context/refactor.md +0 -1
  48. package/dist/agent-src/commands/context.md +0 -1
  49. package/dist/agent-src/commands/contribution-precheck.md +0 -1
  50. package/dist/agent-src/commands/cost/profile.md +0 -1
  51. package/dist/agent-src/commands/cost/report.md +0 -1
  52. package/dist/agent-src/commands/cost.md +0 -1
  53. package/dist/agent-src/commands/council/analysis.md +0 -1
  54. package/dist/agent-src/commands/council/debate.md +13 -5
  55. package/dist/agent-src/commands/council/default.md +26 -12
  56. package/dist/agent-src/commands/council/design.md +0 -1
  57. package/dist/agent-src/commands/council/optimize.md +0 -1
  58. package/dist/agent-src/commands/council/pr.md +0 -1
  59. package/dist/agent-src/commands/council.md +1 -2
  60. package/dist/agent-src/commands/design-system/capture.md +0 -1
  61. package/dist/agent-src/commands/design-system/generate.md +23 -2
  62. package/dist/agent-src/commands/design-system/import.md +0 -1
  63. package/dist/agent-src/commands/design-system.md +0 -1
  64. package/dist/agent-src/commands/estimate-ticket.md +0 -1
  65. package/dist/agent-src/commands/explain-run.md +0 -1
  66. package/dist/agent-src/commands/feature/dev.md +0 -1
  67. package/dist/agent-src/commands/feature/explore.md +0 -1
  68. package/dist/agent-src/commands/feature/plan.md +0 -1
  69. package/dist/agent-src/commands/feature/refactor.md +0 -1
  70. package/dist/agent-src/commands/feature/roadmap.md +0 -1
  71. package/dist/agent-src/commands/feature.md +0 -1
  72. package/dist/agent-src/commands/fix/ci.md +0 -1
  73. package/dist/agent-src/commands/fix/comments.md +0 -1
  74. package/dist/agent-src/commands/fix/portability.md +0 -1
  75. package/dist/agent-src/commands/fix/pr-comments-loop.md +0 -1
  76. package/dist/agent-src/commands/fix/pr-comments.md +0 -1
  77. package/dist/agent-src/commands/fix/quality.md +0 -1
  78. package/dist/agent-src/commands/fix/refs.md +0 -1
  79. package/dist/agent-src/commands/fix/route.md +0 -1
  80. package/dist/agent-src/commands/fix/seeder.md +0 -1
  81. package/dist/agent-src/commands/fix.md +0 -1
  82. package/dist/agent-src/commands/ghostwriter/delete.md +0 -1
  83. package/dist/agent-src/commands/ghostwriter/fetch.md +0 -1
  84. package/dist/agent-src/commands/ghostwriter/list.md +0 -1
  85. package/dist/agent-src/commands/ghostwriter/show.md +0 -1
  86. package/dist/agent-src/commands/ghostwriter/write.md +0 -1
  87. package/dist/agent-src/commands/ghostwriter.md +0 -1
  88. package/dist/agent-src/commands/grill-me.md +0 -1
  89. package/dist/agent-src/commands/humanize.md +0 -1
  90. package/dist/agent-src/commands/image/analyse.md +0 -1
  91. package/dist/agent-src/commands/image/create.md +0 -1
  92. package/dist/agent-src/commands/image/verify.md +0 -1
  93. package/dist/agent-src/commands/image.md +0 -1
  94. package/dist/agent-src/commands/implement-ticket.md +0 -1
  95. package/dist/agent-src/commands/jira-ticket.md +0 -1
  96. package/dist/agent-src/commands/judge/on-diff.md +0 -1
  97. package/dist/agent-src/commands/judge/solo.md +0 -1
  98. package/dist/agent-src/commands/judge/steps.md +0 -1
  99. package/dist/agent-src/commands/judge.md +0 -1
  100. package/dist/agent-src/commands/knowledge/cross-repo.md +0 -1
  101. package/dist/agent-src/commands/knowledge/forget.md +0 -1
  102. package/dist/agent-src/commands/knowledge/ingest.md +0 -1
  103. package/dist/agent-src/commands/knowledge/list.md +0 -1
  104. package/dist/agent-src/commands/knowledge.md +0 -1
  105. package/dist/agent-src/commands/memory/add.md +0 -1
  106. package/dist/agent-src/commands/memory/learn-low-impact.md +0 -1
  107. package/dist/agent-src/commands/memory/load.md +0 -1
  108. package/dist/agent-src/commands/memory/mine-session.md +0 -1
  109. package/dist/agent-src/commands/memory/promote.md +0 -1
  110. package/dist/agent-src/commands/memory/propose.md +0 -1
  111. package/dist/agent-src/commands/memory.md +0 -1
  112. package/dist/agent-src/commands/mission/upgrade.md +0 -1
  113. package/dist/agent-src/commands/mode.md +0 -1
  114. package/dist/agent-src/commands/module/create.md +0 -1
  115. package/dist/agent-src/commands/module/explore.md +0 -1
  116. package/dist/agent-src/commands/module.md +0 -1
  117. package/dist/agent-src/commands/optimize/agents-dir.md +0 -1
  118. package/dist/agent-src/commands/optimize/augmentignore.md +0 -1
  119. package/dist/agent-src/commands/optimize/deep.md +0 -1
  120. package/dist/agent-src/commands/optimize/project.md +0 -1
  121. package/dist/agent-src/commands/optimize/prompt.md +0 -1
  122. package/dist/agent-src/commands/optimize/rtk.md +0 -1
  123. package/dist/agent-src/commands/optimize/skills.md +0 -1
  124. package/dist/agent-src/commands/optimize.md +0 -1
  125. package/dist/agent-src/commands/orchestrate.md +0 -1
  126. package/dist/agent-src/commands/override/create.md +0 -1
  127. package/dist/agent-src/commands/override/manage.md +0 -1
  128. package/dist/agent-src/commands/override.md +0 -1
  129. package/dist/agent-src/commands/package/reset.md +0 -1
  130. package/dist/agent-src/commands/package/test.md +0 -1
  131. package/dist/agent-src/commands/package.md +0 -1
  132. package/dist/agent-src/commands/post-as/ghostwriter.md +0 -1
  133. package/dist/agent-src/commands/post-as/me.md +0 -1
  134. package/dist/agent-src/commands/post-as.md +0 -1
  135. package/dist/agent-src/commands/pr/create/description-only.md +0 -1
  136. package/dist/agent-src/commands/pr/create.md +67 -28
  137. package/dist/agent-src/commands/prediction-pool.md +0 -1
  138. package/dist/agent-src/commands/prepare-for-review.md +0 -1
  139. package/dist/agent-src/commands/profile/activate.md +0 -1
  140. package/dist/agent-src/commands/profile/deactivate.md +0 -1
  141. package/dist/agent-src/commands/profile/show.md +0 -1
  142. package/dist/agent-src/commands/profile.md +0 -1
  143. package/dist/agent-src/commands/project/analyze.md +0 -1
  144. package/dist/agent-src/commands/project/health.md +0 -1
  145. package/dist/agent-src/commands/project.md +0 -1
  146. package/dist/agent-src/commands/refine-ticket.md +0 -1
  147. package/dist/agent-src/commands/research/deep.md +0 -1
  148. package/dist/agent-src/commands/research/report.md +0 -1
  149. package/dist/agent-src/commands/research.md +0 -1
  150. package/dist/agent-src/commands/review/changes.md +0 -1
  151. package/dist/agent-src/commands/review/routing.md +0 -1
  152. package/dist/agent-src/commands/review.md +0 -1
  153. package/dist/agent-src/commands/roadmap/ai-council.md +0 -1
  154. package/dist/agent-src/commands/roadmap/create.md +0 -1
  155. package/dist/agent-src/commands/roadmap/materialize.md +0 -1
  156. package/dist/agent-src/commands/roadmap/next.md +0 -1
  157. package/dist/agent-src/commands/roadmap/process-full.md +0 -1
  158. package/dist/agent-src/commands/roadmap/process-phase.md +0 -1
  159. package/dist/agent-src/commands/roadmap/process-step.md +0 -1
  160. package/dist/agent-src/commands/roadmap.md +0 -1
  161. package/dist/agent-src/commands/rule-compliance-audit.md +0 -1
  162. package/dist/agent-src/commands/security-audit-config.md +0 -1
  163. package/dist/agent-src/commands/skill/preview.md +0 -1
  164. package/dist/agent-src/commands/skill.md +0 -1
  165. package/dist/agent-src/commands/skills/discover.md +0 -1
  166. package/dist/agent-src/commands/skills.md +0 -1
  167. package/dist/agent-src/commands/sync/agent-settings.md +0 -1
  168. package/dist/agent-src/commands/sync/gitignore/fix.md +0 -1
  169. package/dist/agent-src/commands/sync/gitignore.md +0 -1
  170. package/dist/agent-src/commands/sync.md +0 -1
  171. package/dist/agent-src/commands/tdd/green.md +0 -1
  172. package/dist/agent-src/commands/tdd/red.md +0 -1
  173. package/dist/agent-src/commands/tdd/refactor.md +0 -1
  174. package/dist/agent-src/commands/tdd.md +0 -1
  175. package/dist/agent-src/commands/team/adversarial.md +0 -1
  176. package/dist/agent-src/commands/team/delegate.md +0 -1
  177. package/dist/agent-src/commands/team/review.md +0 -1
  178. package/dist/agent-src/commands/team/status.md +0 -1
  179. package/dist/agent-src/commands/team-knowledge/bootstrap.md +0 -1
  180. package/dist/agent-src/commands/team-knowledge/consolidate.md +0 -1
  181. package/dist/agent-src/commands/team-knowledge.md +0 -1
  182. package/dist/agent-src/commands/team.md +0 -1
  183. package/dist/agent-src/commands/tests/create.md +0 -1
  184. package/dist/agent-src/commands/tests/e2e-heal.md +0 -1
  185. package/dist/agent-src/commands/tests/e2e-plan.md +0 -1
  186. package/dist/agent-src/commands/tests/execute.md +0 -1
  187. package/dist/agent-src/commands/tests.md +0 -1
  188. package/dist/agent-src/commands/threat-model.md +0 -1
  189. package/dist/agent-src/commands/update-form-request-messages.md +0 -1
  190. package/dist/agent-src/commands/upstream-contribute.md +0 -1
  191. package/dist/agent-src/commands/video/from-script.md +0 -1
  192. package/dist/agent-src/commands/video/from-song.md +0 -1
  193. package/dist/agent-src/commands/video/scene.md +0 -1
  194. package/dist/agent-src/commands/video/stitch.md +0 -1
  195. package/dist/agent-src/commands/video/storyboard.md +0 -1
  196. package/dist/agent-src/commands/video.md +0 -1
  197. package/dist/agent-src/commands/work.md +0 -1
  198. package/dist/agent-src/commands/worktree/cleanup.md +0 -1
  199. package/dist/agent-src/commands/worktree/create.md +5 -4
  200. package/dist/agent-src/commands/worktree/status.md +0 -1
  201. package/dist/agent-src/commands/worktree/verify.md +0 -1
  202. package/dist/agent-src/commands/worktree.md +0 -1
  203. package/dist/agent-src/rules/settings-ask-protocol.md +14 -9
  204. package/dist/agent-src/skills/ai-council/SKILL.md +24 -6
  205. package/dist/agent-src/skills/corpus-grounding/SKILL.md +12 -1
  206. package/dist/agent-src/skills/corpus-grounding/scripts/decision_engine.ts +215 -7
  207. package/dist/agent-src/skills/corpus-grounding/scripts/ground.ts +39 -1
  208. package/dist/agent-src/skills/design-intelligence/ATTRIBUTION.md +19 -2
  209. package/dist/agent-src/skills/design-intelligence/data/manifest.json +36 -1
  210. package/dist/agent-src/skills/design-intelligence/data/motion.csv +17 -0
  211. package/dist/agent-src/skills/docx-authoring/SKILL.md +1 -0
  212. package/dist/agent-src/skills/git-workflow/SKILL.md +17 -3
  213. package/dist/agent-src/skills/pdf-tools/SKILL.md +1 -0
  214. package/dist/agent-src/skills/skill-writing/SKILL.md +35 -0
  215. package/dist/agent-src/skills/subagent-orchestration/SKILL.md +9 -6
  216. package/dist/agent-src/skills/using-git-worktrees/SKILL.md +25 -21
  217. package/dist/agent-src/templates/agent-settings.md +0 -1
  218. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  219. package/dist/cli/commands/commands.candidates.test.js +1 -1
  220. package/dist/cli/commands/commands.candidates.test.js.map +1 -1
  221. package/dist/cli/commands/commands.js +9 -15
  222. package/dist/cli/commands/commands.js.map +1 -1
  223. package/dist/cli/commands/profiles.test.js +7 -7
  224. package/dist/cli/commands/profiles.test.js.map +1 -1
  225. package/dist/cli/discovery/loadManifest.js.map +1 -1
  226. package/dist/cli/main.js +2 -2
  227. package/dist/cli/main.js.map +1 -1
  228. package/dist/cli-delegate/{chunk-BV3WR67D.js → chunk-55WZN5VF.js} +3 -6
  229. package/dist/cli-delegate/{chunk-X3VOARZD.js → chunk-L565YFXH.js} +3 -3
  230. package/dist/cli-delegate/{chunk-JMQZ7MM5.js → chunk-MNWRDMR5.js} +1 -1
  231. package/dist/cli-delegate/{chunk-IR3IVRVC.js → chunk-NLP54U3J.js} +2 -1
  232. package/dist/cli-delegate/{chunk-FV34EGEX.js → chunk-NN4KW4HZ.js} +1 -1
  233. package/dist/cli-delegate/{chunk-6ENSXSBW.js → chunk-PPQO4NGR.js} +1 -1
  234. package/dist/cli-delegate/{chunk-6LOPHJLD.js → chunk-RYNADUFG.js} +1 -1
  235. package/dist/cli-delegate/cmd_analyze_session.js +1 -1
  236. package/dist/cli-delegate/cmd_brand_status.js +1 -1
  237. package/dist/cli-delegate/cmd_conformance.js +16 -4
  238. package/dist/cli-delegate/cmd_doctor.js +2 -2
  239. package/dist/cli-delegate/cmd_explain.js +2 -2
  240. package/dist/cli-delegate/cmd_export.js +4 -4
  241. package/dist/cli-delegate/cmd_handoff.js +2 -2
  242. package/dist/cli-delegate/cmd_mcp_available.js +1 -1
  243. package/dist/cli-delegate/cmd_migrate.js +2 -2
  244. package/dist/cli-delegate/cmd_packs_active.js +2 -2
  245. package/dist/cli-delegate/cmd_prune.js +4 -4
  246. package/dist/cli-delegate/cmd_route_audit.js +2 -2
  247. package/dist/cli-delegate/cmd_route_explain.js +1 -1
  248. package/dist/cli-delegate/cmd_session_recycle.js +1 -1
  249. package/dist/cli-delegate/cmd_settings_get.js +1 -1
  250. package/dist/cli-delegate/cmd_settings_migrate.js +4 -4
  251. package/dist/cli-delegate/cmd_settings_set.js +1 -1
  252. package/dist/cli-delegate/cmd_sync.js +4 -4
  253. package/dist/cli-delegate/cmd_uninstall.js +4 -4
  254. package/dist/cli-delegate/cmd_update.js +1 -1
  255. package/dist/cli-delegate/cmd_upgrade.js +1 -1
  256. package/dist/cli-delegate/cmd_validate.js +4 -4
  257. package/dist/cli-delegate/cmd_versions.js +1 -1
  258. package/dist/discovery/deprecation-report.md +1 -1
  259. package/dist/discovery/discovery-manifest.json +216 -415
  260. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  261. package/dist/discovery/discovery-manifest.summary.md +2 -2
  262. package/dist/discovery/dormancy-report.md +1 -1
  263. package/dist/discovery/orphan-report.md +1 -1
  264. package/dist/discovery/packs.json +3 -3
  265. package/dist/discovery/trust-report.md +1 -1
  266. package/dist/discovery/workspaces.json +3 -3
  267. package/dist/hooks/dispatch.js +68 -14
  268. package/dist/install/install.mjs +5 -7
  269. package/dist/mcp/registry-manifest.json +3 -3
  270. package/dist/mcp/server.json +2 -2
  271. package/dist/mcp/server.mjs +48 -6
  272. package/dist/scripts/_lib/agent_settings.js +8 -0
  273. package/dist/scripts/_lib/agent_settings.js.map +1 -1
  274. package/dist/server/schemas/settings.js +3 -4
  275. package/dist/server/schemas/settings.js.map +1 -1
  276. package/dist/ui/assets/{index-5fXOHoVh.js → index-B0aMJnuz.js} +2 -2
  277. package/dist/ui/assets/{index-5fXOHoVh.js.map → index-B0aMJnuz.js.map} +1 -1
  278. package/dist/ui/index.html +1 -1
  279. package/docs/MIGRATION.md +1 -0
  280. package/docs/contracts/settings-classes.md +53 -9
  281. package/package.json +1 -1
  282. package/src/config/agent-settings.template.yml +49 -18
  283. package/src/config/gate-coverage.yml +13 -3
  284. package/src/config/gate-violation-baselines.json +4 -4
  285. package/src/config/pack-size-budget.json +3 -2
  286. package/src/scripts/_dispatch.bash +42 -5
  287. package/src/scripts/_lib/agent_settings.ts +8 -0
  288. package/src/scripts/audit_command_surface.ts +21 -15
  289. package/src/scripts/build_catalog_index.ts +1 -1
  290. package/src/scripts/build_discovery_manifest.ts +30 -34
  291. package/src/scripts/check_branch_freshness.ts +393 -12
  292. package/src/scripts/external_sources_denylist.json +7 -2
  293. package/src/scripts/gen_discovery_baseline.ts +3 -3
  294. package/src/scripts/hook_manifest.yaml +20 -0
  295. package/src/scripts/hooks/subagent_ledger_hook.ts +17 -4
  296. package/src/scripts/lint_command_tiers.ts +29 -75
  297. package/src/scripts/lint_originality.ts +101 -14
  298. package/src/scripts/schemas/command.schema.json +4 -9
  299. package/src/scripts/schemas/skill.schema.json +10 -1
  300. package/src/scripts/session_register_hook.ts +120 -10
  301. package/src/scripts/skill_linter.ts +82 -2
  302. package/src/scripts/worktree_cleanup_check.ts +111 -7
  303. package/src/server/schemas/settings.ts +3 -6
@@ -328,9 +328,25 @@ not a default.
328
328
  2. **Bundle + redact.** Call `scripts/ai_council/bundler.ts` to produce
329
329
  a redacted artefact bundle. If `BundleTooLarge` fires, surface the
330
330
  size and ask the user to narrow scope — do NOT truncate silently.
331
- 3. **Confirm spend.** Before any network call, surface members + cost
332
- ceiling and require an explicit user `1` to proceed. Autonomy
333
- settings do not override this gate.
331
+ 3. **Clear the spend bound.** The council is standing-authorized — there is
332
+ no per-invocation approval. What still binds is a *bound*, resolved in
333
+ this order:
334
+ - **No billable member** (every member is `mode: cli` under
335
+ subscription auth — `billable=False`) → nothing is spent. Render the
336
+ estimate table as information and fan out. No gate.
337
+ - **Billable members with a configured ceiling** (`cost_budget.max_total_usd`
338
+ non-zero, or `daily_limit_usd` non-zero) → the ceiling **is** the
339
+ authorization the user already gave. Render the estimate and fan out.
340
+ `on_overrun` still fires per member on breach (below) — that is where
341
+ the user regains the decision.
342
+ - **Billable members with no ceiling at all** (`max_total_usd: 0` AND
343
+ `daily_limit_usd: 0`) → unbounded paid spend, so surface the estimate
344
+ and require an explicit `1`. Nothing bounds the call, so the user must.
345
+
346
+ Autonomy settings do not *create* a bound and do not lift the last case.
347
+ Consumers who want the old per-run gate back set a small
348
+ `cost_budget.max_total_usd`: every call then breaches and `on_overrun`
349
+ asks, per member.
334
350
  4. **Fan out.** Dispatch the bundle to each enabled council member via
335
351
  `scripts/ai_council/orchestrator.ts`. Each member receives the
336
352
  neutrality preamble from `prompts.ts` plus the artefact — nothing
@@ -594,8 +610,9 @@ overrides the table regardless of how the lens resolved.
594
610
  identity — that primes the reviewer and collapses diversity.
595
611
  - Do NOT silently truncate a too-large bundle — surface the size and
596
612
  ask for narrower scope.
597
- - Do NOT auto-spend tokens under `personal.autonomy: on` — the cost
598
- gate fires every time, no exceptions.
613
+ - Do NOT spend against an unbounded budget without asking — a billable
614
+ member with neither `max_total_usd` nor `daily_limit_usd` set has no
615
+ ceiling, and autonomy is not a ceiling.
599
616
  - Do NOT reuse SDK clients across invocations — re-load keys via
600
617
  `load_*_key()` each call.
601
618
 
@@ -620,7 +637,8 @@ Real failure modes seen in the wild:
620
637
  | Paste the host-agent identity ("I am Augment / Claude Code…") | Identity primes the reviewer's model. | Neutrality preamble in `prompts.ts` already handles this. |
621
638
  | Silently truncate a too-large bundle. | Misleads the reviewer into thinking they saw the whole thing. | Bundler raises `BundleTooLarge`; surface and ask for narrower scope. |
622
639
  | Reuse the same SDK client across calls without re-loading the key. | Leaks the key in long-lived process state. | Each invocation builds fresh clients from `load_*_key()`. |
623
- | Auto-spend tokens under `personal.autonomy: on`. | Autonomy ≠ permission to spend money. | Always ask before consultation, even under autonomy. |
640
+ | Spend against a billable member with no configured ceiling because "the council is standing-authorized". | Standing authorization is a *bound* the user set once, not a blank cheque. No ceiling = nothing was authorized. | Surface the estimate and ask, exactly in that one case (Procedure § 3). |
641
+ | Re-ask per invocation once a ceiling exists. | The ceiling already carried the decision; re-asking is the approval burden this default removed. | Fan out; let `on_overrun` ask on breach. |
624
642
  | Forward council convergence to the user as numbered options without a host verdict. | Convergence ≠ correctness; the council never saw the codebase. | Apply the *Critical evaluation* lens; tag every finding `accept` / `accept-with-modification` / `reject` / `needs-input` with one-line reason. |
625
643
  | Reject a finding on preference, not evidence. | "I don't like this" is not a verdict. | Cite the file, line, ADR, or contract that justifies the rejection — or surface as `needs-input`. |
626
644
  | Paraphrase council output into the host's own analysis to defend a verdict. | Strips attribution, breaches `direct-answers` no-invented-facts. | Verdict cites host evidence (file:line); council output stays attributed in the per-member sections. |
@@ -71,7 +71,8 @@ Scripts resolve all paths **skill-relative** (per
71
71
 
72
72
  ./scripts-run <skills-root>/corpus-grounding/scripts/ground ground \
73
73
  --manifest <skills-root>/<domain-skill>/data/manifest.json \
74
- "luxury e-commerce" [--context '{"data_heavy": true}'] [--persist DIR]
74
+ "luxury e-commerce" [--context '{"data_heavy": true}'] [--persist DIR] \
75
+ [--variance 1-10] [--motion 1-10] [--density 1-10]
75
76
 
76
77
  ./scripts-run <skills-root>/corpus-grounding/scripts/ground validate --manifest …
77
78
  ```
@@ -79,6 +80,16 @@ Scripts resolve all paths **skill-relative** (per
79
80
  `<skills-root>` is wherever skills are deployed (`~/.claude/skills/` for
80
81
  Claude Code installs; `src/skills/` inside this repo).
81
82
 
83
+ **The three `ground` dials are optional 1-10 preferences, and unset means
84
+ unset** — a dial you do not pass changes nothing, so the no-flag result is
85
+ byte-identical to what it was before they existed. `--variance` biases which
86
+ style is **selected** from the rows the corpus returned (never which rows come
87
+ back), and when it changes the pick away from the grounding rule's own choice
88
+ the run says so in its evidence gaps. `--motion` needs a `gsap` domain in the
89
+ manifest and reports when a tier matches nothing. `--density` overrides the
90
+ spacing scale. All three render in the text output and in a persisted
91
+ `MASTER.md`, not only under `--json`.
92
+
82
93
  ## Output format
83
94
 
84
95
  **Interface contract v1 — stability promise.** Per ADR-061 §2 ("Opus
@@ -531,16 +531,76 @@ function _select_best_match(
531
531
 
532
532
  // ── grounding ────────────────────────────────────────────────────────────
533
533
 
534
+ /**
535
+ * The three optional 1-10 design dials.
536
+ *
537
+ * Adapted from the upstream generator's tier table (MIT; see
538
+ * `design-intelligence/ATTRIBUTION.md` and `provenance/borrows.jsonl`). The
539
+ * TIER BOUNDARIES and the values they carry are the upstream's data; the
540
+ * plumbing below is this engine's own, because upstream threads them through a
541
+ * `generate()` this tree does not have and ours has `ground()` instead.
542
+ *
543
+ * Unset means unset: a dial the caller did not pass changes nothing, which is
544
+ * the upstream's own contract and the only thing that keeps the no-flag path
545
+ * byte-identical to the behaviour before this existed.
546
+ */
547
+ export const DIAL_TIERS: Record<string, Array<[number, number, ResultDict]>> = {
548
+ variance: [
549
+ [1, 3, { label: 'Centered / Minimal', style_keywords: ['Minimalism', 'Exaggerated Minimalism', 'centered', 'symmetric', 'grid-based'] }],
550
+ [4, 7, { label: 'Balanced / Modern', style_keywords: ['modern', 'structured', 'balanced'] }],
551
+ [8, 10, { label: 'Bold / Asymmetric', style_keywords: ['Brutalism', 'Bento Grids', 'asymmetric', 'experimental'] }],
552
+ ],
553
+ motion: [
554
+ [1, 3, { label: 'Subtle', tier: 'Subtle' }],
555
+ [4, 7, { label: 'Standard', tier: 'Standard' }],
556
+ [8, 10, { label: 'Complex', tier: 'Complex' }],
557
+ ],
558
+ density: [
559
+ [1, 3, { label: 'Spacious', spacing: { xs: '4px', sm: '8px', md: '24px', lg: '32px', xl: '48px', '2xl': '64px', '3xl': '96px' } }],
560
+ [4, 7, { label: 'Standard', spacing: { xs: '4px', sm: '8px', md: '16px', lg: '24px', xl: '32px', '2xl': '48px', '3xl': '64px' } }],
561
+ [8, 10, { label: 'Dense / Dashboard', spacing: { xs: '2px', sm: '4px', md: '8px', lg: '12px', xl: '16px', '2xl': '24px', '3xl': '32px' } }],
562
+ ],
563
+ };
564
+
565
+ /** The dial values a caller may pass; `null`/absent means "do not engage". */
566
+ export interface Dials {
567
+ variance?: number | null;
568
+ motion?: number | null;
569
+ density?: number | null;
570
+ }
571
+
572
+ /**
573
+ * Bucket a 1-10 dial value into its tier config, or `null` when unset.
574
+ *
575
+ * Out-of-range input is clamped rather than rejected: the dial is a coarse
576
+ * preference, and refusing an 11 would turn a typo into a failed run for no
577
+ * gain in meaning.
578
+ */
579
+ export function resolve_dial(dial_name: string, value: number | null | undefined): ResultDict | null {
580
+ if (value === null || value === undefined || Number.isNaN(value)) return null;
581
+ const tiers = DIAL_TIERS[dial_name];
582
+ if (tiers === undefined) return null;
583
+ const v = Math.max(1, Math.min(10, Math.trunc(value)));
584
+ for (const [lo, hi, info] of tiers) {
585
+ if (v >= lo && v <= hi) return { ...info, value: v };
586
+ }
587
+ return null;
588
+ }
589
+
534
590
  /**
535
591
  * Conditional grounding: category → rules → planned multi-domain search.
536
592
  *
537
593
  * Returns the interface-v1 grounded dict (see Python docstring). Async because
538
594
  * `_load_rules_callable` is async (dynamic import).
595
+ *
596
+ * `dials` is optional and additive: with none passed, the returned dict is
597
+ * exactly what it was before dials existed.
539
598
  */
540
599
  export async function ground(
541
600
  manifest: Manifest,
542
601
  query: string,
543
602
  context: Record<string, unknown> | null = null,
603
+ dials: Dials | null = null,
544
604
  ): Promise<ResultDict> {
545
605
  const reasoning = manifest.reasoning as Record<string, unknown> | null | undefined;
546
606
  if (!reasoning) {
@@ -607,6 +667,29 @@ export async function ground(
607
667
  .filter((s) => s.length > 0);
608
668
  }
609
669
 
670
+ // 4b — dials.
671
+ //
672
+ // Variance biases SELECTION, never RETRIEVAL, and never silently.
673
+ //
674
+ // An earlier version prepended the dial's keywords to `priority` outright.
675
+ // That looked like "biasing" and was not: `priority.slice(0, 2)` augments
676
+ // the query, so the rule's own corpus-grounded keywords were pushed out of
677
+ // the retrieval entirely, and five dial keywords then outscored two rule
678
+ // keywords in the match. The dial did not bias the evidence — it replaced
679
+ // it, and nothing said so.
680
+ //
681
+ // Split instead. `priority` stays the rule's, so what comes BACK from the
682
+ // corpus is unchanged by the dial. `selection_priority` carries the dial in
683
+ // front for choosing among those rows — which is what a preference dial is
684
+ // legitimately for. And when that choice differs from the one the rule's own
685
+ // keywords would have made, it is reported rather than left invisible.
686
+ const variance_info = resolve_dial('variance', dials?.variance);
687
+ const motion_info = resolve_dial('motion', dials?.motion);
688
+ const density_info = resolve_dial('density', dials?.density);
689
+ const selection_priority = variance_info
690
+ ? [...(variance_info.style_keywords as string[]), ...priority]
691
+ : priority;
692
+
610
693
  // 5 — planned multi-domain search.
611
694
  const selections: ResultDict = {};
612
695
  const domain_confidences: number[] = [];
@@ -621,12 +704,28 @@ export async function ground(
621
704
  }
622
705
  const result = search_domain(manifest, q, domain_name, _int(max_results));
623
706
  const results = (result.results as Row[] | null) || [];
624
- const best =
625
- domain_name === priority_domain
626
- ? _select_best_match(results, priority, name_cols[domain_name])
627
- : results.length > 0
628
- ? (results[0] as Row)
629
- : {};
707
+ let best: Row;
708
+ if (domain_name === priority_domain) {
709
+ best = _select_best_match(results, selection_priority, name_cols[domain_name]);
710
+ if (variance_info) {
711
+ // What the rule alone would have picked from the SAME rows.
712
+ // Reporting the divergence is the whole difference between a
713
+ // dial that biases and a dial that quietly overrules.
714
+ const ungated = _select_best_match(results, priority, name_cols[domain_name]);
715
+ if (ungated !== best) {
716
+ const col = name_cols[domain_name];
717
+ const nameOf = (r: Row): string =>
718
+ col && r[col] !== undefined ? pyStr(r[col]) : '(unnamed)';
719
+ gaps.push(
720
+ `--variance ${String(variance_info.value)} changed the ${domain_name} pick to ` +
721
+ `'${nameOf(best)}'; the rule's own keywords would have selected ` +
722
+ `'${nameOf(ungated)}' — the dial biased the choice, it did not come from the corpus`,
723
+ );
724
+ }
725
+ }
726
+ } else {
727
+ best = results.length > 0 ? (results[0] as Row) : {};
728
+ }
630
729
  selections[domain_name] = {
631
730
  best,
632
731
  // Python: [r for r in results if r is not best] — identity compare.
@@ -641,6 +740,53 @@ export async function ground(
641
740
  domain_confidences.push(score instanceof PyFloat ? score.value : (score as number | undefined) ?? 0.0);
642
741
  }
643
742
 
743
+ // 5b — the motion dial reads a corpus, which is why it needs `motion.csv`
744
+ // and the other two do not. Rows are filtered by the `Intensity Tier`
745
+ // column; a tier with no rows yields an evidence gap rather than a silent
746
+ // empty block, because "the dial did nothing" and "the corpus has nothing
747
+ // at this tier" are different facts to the reader.
748
+ let motion_selection: ResultDict | null = null;
749
+ if (motion_info) {
750
+ const tier = motion_info.tier as string;
751
+ const motion_domain = (manifest.domains as Record<string, ResultDict>)['gsap'];
752
+ if (motion_domain === undefined) {
753
+ gaps.push(
754
+ "the --motion dial needs a 'gsap' search domain (motion.csv); this manifest has none, so the dial had no effect",
755
+ );
756
+ } else {
757
+ // FILTER FIRST, then rank. Retrieving the global top-N and filtering
758
+ // afterwards drops rows at the requested tier that lost the global
759
+ // cut to rows of another tier — the dial then silently returns less
760
+ // than the corpus holds for it. `search_domain` already merges a
761
+ // filters dict (it is what the `--filter` flag uses), and the row
762
+ // count comes from the manifest like every other domain rather than
763
+ // from a literal here.
764
+ const motion_max = motion_domain['max_results'];
765
+ const motion_result = search_domain(
766
+ manifest,
767
+ query,
768
+ 'gsap',
769
+ typeof motion_max === 'number' ? motion_max : null,
770
+ { 'Intensity Tier': tier },
771
+ );
772
+ const rows = (motion_result.results as Row[] | null) || [];
773
+ if (rows.length === 0) {
774
+ // No block at all, not an empty one: `motion: {best: {}}` reads
775
+ // as "the dial ran and found nothing worth showing", which is
776
+ // indistinguishable from a corpus that has nothing at this tier.
777
+ // The gap says which, and the absent key says the rest.
778
+ gaps.push(`no motion row at tier '${tier}' matched the query — the dial narrowed to nothing`);
779
+ } else {
780
+ motion_selection = {
781
+ tier,
782
+ best: rows[0] as Row,
783
+ alternatives: rows.slice(1),
784
+ confidence: motion_result.confidence,
785
+ };
786
+ }
787
+ }
788
+ }
789
+
644
790
  // 6 — aggregate confidence (weakest link wins).
645
791
  const numeric = domain_confidences.length > 0 ? pyRound(Math.min(...domain_confidences), 3) : 0.0;
646
792
  const label = numeric >= 0.4 ? 'high' : numeric >= 0.15 ? 'medium' : 'low';
@@ -653,7 +799,7 @@ export async function ground(
653
799
  }
654
800
  }
655
801
 
656
- return {
802
+ const out: ResultDict = {
657
803
  domain: manifest.domain,
658
804
  query,
659
805
  category,
@@ -664,6 +810,24 @@ export async function ground(
664
810
  evidence_gap:
665
811
  gaps.length > 0 ? gaps : ['none — every planned domain returned a scored match'],
666
812
  };
813
+
814
+ // Dial output is ADDITIVE and omitted entirely when no dial was passed, so
815
+ // the no-flag result stays byte-identical to what it was before dials
816
+ // existed. `spacing_scale` is a top-level override because it replaces a
817
+ // scale rather than informing one.
818
+ if (variance_info || motion_info || density_info) {
819
+ out.dials = {
820
+ variance: variance_info ? variance_info.value : null,
821
+ variance_label: variance_info ? variance_info.label : null,
822
+ motion: motion_info ? motion_info.value : null,
823
+ motion_label: motion_info ? motion_info.label : null,
824
+ density: density_info ? density_info.value : null,
825
+ density_label: density_info ? density_info.label : null,
826
+ };
827
+ if (motion_selection) out.motion = motion_selection;
828
+ if (density_info) out.spacing_scale = density_info.spacing;
829
+ }
830
+ return out;
667
831
  }
668
832
 
669
833
  /** Python int(x) for a plan value (int or numeric string). */
@@ -745,6 +909,50 @@ export function _render_markdown(grounded: ResultDict, master = false): string {
745
909
  `- **Confidence:** ${pyStr(conf.label ?? '?')} ` + `(${_confScore(conf.score)})`,
746
910
  '',
747
911
  );
912
+
913
+ // Dials render HERE, not only under --json. This is the whole consumption
914
+ // path for two of the three output paths: the default text render and the
915
+ // persisted MASTER.md both come through this function, and `--density`'s
916
+ // only payload is `spacing_scale` — omitted here, that dial is inert
917
+ // everywhere a human actually reads the result.
918
+ const dials = (grounded.dials as ResultDict | null) || null;
919
+ if (dials) {
920
+ const dialLines: string[] = [];
921
+ for (const name of ['variance', 'motion', 'density']) {
922
+ const value = dials[name];
923
+ if (value === null || value === undefined) {
924
+ continue;
925
+ }
926
+ const label = dials[`${name}_label`];
927
+ dialLines.push(
928
+ `- **${name[0]?.toUpperCase()}${name.slice(1)}:** ${pyStr(value)}/10` +
929
+ (label ? ` — ${pyStr(label)}` : ''),
930
+ );
931
+ }
932
+ if (dialLines.length > 0) {
933
+ lines.push('### Dials', '', ...dialLines, '');
934
+ }
935
+ }
936
+
937
+ const spacing = (grounded.spacing_scale as ResultDict | null) || null;
938
+ if (spacing && Object.keys(spacing).length > 0) {
939
+ lines.push('### Spacing scale (density override)', '');
940
+ for (const step of Object.keys(spacing)) {
941
+ lines.push(`- **${step}:** ${pyStr(spacing[step])}`);
942
+ }
943
+ lines.push('');
944
+ }
945
+
946
+ const motion = (grounded.motion as ResultDict | null) || null;
947
+ const motionBest = motion ? ((motion.best as Row | null) || {}) : {};
948
+ if (motion && Object.keys(motionBest).length > 0) {
949
+ lines.push(`### Motion (tier: ${pyStr(motion.tier ?? '')})`, '');
950
+ for (const key of Object.keys(motionBest)) {
951
+ lines.push(`- **${key}:** ${pyStr(motionBest[key])}`);
952
+ }
953
+ lines.push('');
954
+ }
955
+
748
956
  const selections = (grounded.selections as ResultDict | null) || {};
749
957
  for (const domain of Object.keys(selections)) {
750
958
  const sel = (selections[domain] as ResultDict | null) || {};
@@ -125,6 +125,28 @@ interface Args {
125
125
  persist?: string | null;
126
126
  project_name?: string | null;
127
127
  page?: string | null;
128
+ /** Optional 1-10 design dials; `null` means "not passed", never a default. */
129
+ variance?: number | null;
130
+ motion?: number | null;
131
+ density?: number | null;
132
+ }
133
+
134
+ /**
135
+ * Parse a 1-10 dial value.
136
+ *
137
+ * A non-numeric value is rejected rather than clamped: `--motion high` is a
138
+ * mistake the caller can fix, and silently reading it as "no dial" would run
139
+ * the whole grounding pass and return a result that quietly ignored the flag.
140
+ * Out-of-RANGE numbers are a different case and are clamped downstream — 11 is
141
+ * a typo with an obvious intent, `high` is not a number at all.
142
+ */
143
+ function _dial(flag: string, raw: string): number {
144
+ const n = Number(raw);
145
+ if (!Number.isFinite(n)) {
146
+ process.stderr.write(`argument ${flag}: expected an integer 1-10, got '${raw}'\n`);
147
+ process.exit(2);
148
+ }
149
+ return Math.trunc(n);
128
150
  }
129
151
 
130
152
  /**
@@ -212,6 +234,18 @@ function _parseArgs(argv: string[]): Args {
212
234
  args.page = eat();
213
235
  } else if (a.startsWith('--page=')) {
214
236
  args.page = a.slice('--page='.length);
237
+ } else if (a === '--variance') {
238
+ args.variance = _dial('--variance', eat());
239
+ } else if (a.startsWith('--variance=')) {
240
+ args.variance = _dial('--variance', a.slice('--variance='.length));
241
+ } else if (a === '--motion') {
242
+ args.motion = _dial('--motion', eat());
243
+ } else if (a.startsWith('--motion=')) {
244
+ args.motion = _dial('--motion', a.slice('--motion='.length));
245
+ } else if (a === '--density') {
246
+ args.density = _dial('--density', eat());
247
+ } else if (a.startsWith('--density=')) {
248
+ args.density = _dial('--density', a.slice('--density='.length));
215
249
  } else {
216
250
  positionals.push(a);
217
251
  }
@@ -293,7 +327,11 @@ export async function main(argv: string[] | null = null): Promise<number> {
293
327
 
294
328
  // ground
295
329
  const context = args.context ? (JSON.parse(args.context) as Record<string, unknown>) : {};
296
- const grounded = await run_ground(manifest, args.query as string, context);
330
+ const grounded = await run_ground(manifest, args.query as string, context, {
331
+ variance: args.variance ?? null,
332
+ motion: args.motion ?? null,
333
+ density: args.density ?? null,
334
+ });
297
335
  if (args.persist) {
298
336
  const info = persist_grounding(
299
337
  grounded,
@@ -8,9 +8,26 @@ upstream repository below. Keep this file shipping with the skill; update the
8
8
 
9
9
  - Repository: `https://github.com/nextlevelbuilder/ui-ux-pro-max-skill`
10
10
  - Commit SHA: `b7e3af80f6e331f6fb456667b82b12cade7c9d35`
11
- - Last checked: 2026-06-07
11
+ - Last checked: 2026-08-13
12
12
  - Upstream version: `skill.json` v2.5.x (npm `uipro-cli`)
13
13
 
14
+ ### Partial adoptions from a later upstream commit
15
+
16
+ The base pin above still describes the corpus as a whole. Two assets were
17
+ adopted **selectively** from `97eb2a20f...` on 2026-08-13, and the base pin is
18
+ deliberately NOT bumped: at that commit nine files are absent from our `data/`
19
+ and eleven more have drifted, so claiming the newer SHA wholesale would assert
20
+ an adoption that did not happen (six of the absent files are desktop-UI stacks
21
+ outside this pack's scope, and `google-fonts.csv` is declined by ADR-061 § 8).
22
+
23
+ | Asset | From | Why |
24
+ |---|---|---|
25
+ | `data/motion.csv` (16 rows) | `97eb2a20` | Consumed by the `gsap` search domain and by the `--motion` design dial, which filters its `Intensity Tier` column. Without it the dial is inert. |
26
+ | The three design dials (`variance` / `motion` / `density`) in `corpus-grounding/scripts/decision_engine.ts` + `ground.ts` | `97eb2a20`, `scripts/design_system.py` | Re-derived, not copied — see `provenance/borrows.jsonl`. |
27
+
28
+ Licence re-verified at `97eb2a20` before adopting: MIT, same copyright holder,
29
+ unchanged from the base pin.
30
+
14
31
  ## Licenses
15
32
 
16
33
  ### MIT — corpus + core engine (root `LICENSE`)
@@ -18,7 +35,7 @@ upstream repository below. Keep this file shipping with the skill; update the
18
35
  Copyright (c) 2024 **Next Level Builder**. The tabular corpus
19
36
  (`ui-reasoning`, `products`, `colors`, `styles`, `typography`, `charts`,
20
37
  `landing`, `icons`, `ux-guidelines`, `react-performance`, `app-interface`,
21
- `stacks/*`) and the Python engine sources (`core.py`, `search.py`,
38
+ `motion`, `stacks/*`) and the Python engine sources (`core.py`, `search.py`,
22
39
  `design_system.py`) are MIT-licensed. Obligation: **retain this notice**.
23
40
 
24
41
  ### Apache-2.0 — `ui-styling`-derived material ("claudekit")
@@ -2,7 +2,7 @@
2
2
  "upstream": {
3
3
  "sha": "b7e3af80f6e331f6fb456667b82b12cade7c9d35",
4
4
  "repo": "https://github.com/nextlevelbuilder/ui-ux-pro-max-skill",
5
- "last_checked": "2026-06-07",
5
+ "last_checked": "2026-08-13",
6
6
  "last_eval": {
7
7
  "date": "2026-06-16",
8
8
  "model": "claude-sonnet-4-5",
@@ -257,6 +257,29 @@
257
257
  "Severity"
258
258
  ],
259
259
  "max_results": 3
260
+ },
261
+ "gsap": {
262
+ "file": "motion.csv",
263
+ "search_cols": [
264
+ "Category",
265
+ "Intensity Tier",
266
+ "Keywords",
267
+ "Trigger"
268
+ ],
269
+ "output_cols": [
270
+ "Category",
271
+ "Intensity Tier",
272
+ "Keywords",
273
+ "Trigger",
274
+ "Duration",
275
+ "Easing",
276
+ "GSAP Snippet",
277
+ "Framework Notes",
278
+ "Do",
279
+ "Don't",
280
+ "Performance Notes"
281
+ ],
282
+ "max_results": 3
260
283
  }
261
284
  },
262
285
  "detect": {
@@ -454,6 +477,18 @@
454
477
  "form",
455
478
  "input type",
456
479
  "preconnect"
480
+ ],
481
+ "gsap": [
482
+ "motion",
483
+ "animation",
484
+ "animate",
485
+ "transition",
486
+ "easing",
487
+ "gsap",
488
+ "hover effect",
489
+ "scroll reveal",
490
+ "parallax",
491
+ "stagger"
457
492
  ]
458
493
  },
459
494
  "reasoning": {
@@ -0,0 +1,17 @@
1
+ No,Category,Intensity Tier,Keywords,Trigger,Duration,Easing,GSAP Snippet,Framework Notes,Do,Don't,Performance Notes
2
+ 1,Hover Micro-interaction,Subtle,"hover, button, opacity, lift, press feedback",hover,150-200ms,power1.out,"gsap.to(el, { y: -1, opacity: 0.9, duration: 0.15, ease: 'power1.out' });",Bind on mouseenter/mouseleave; in React wrap in a ref + useEffect (or onMouseEnter/onMouseLeave props directly calling gsap.to),Keep displacement under 2px so it reads as feedback not motion,Don't animate layout-affecting props (width/height/margin) on hover,Runs on transform/opacity only so it stays on the compositor thread
3
+ 2,Hover Micro-interaction,Standard,"hover, card, scale, tilt, cursor feedback",hover,200-300ms,power2.out,"gsap.to(el, { y: -4, scale: 1.02, boxShadow: '0 12px 24px rgba(0,0,0,0.12)', duration: 0.25, ease: 'power2.out' });","Use gsap.quickTo(el, 'y') for cards with many hover targets to avoid re-creating tweens every event",Pair with a matching mouseleave tween that reverses the same properties,Don't leave the hover state stuck if the pointer leaves fast; always attach the reverse tween,quickTo() avoids GC churn on lists with 20+ hoverable cards
4
+ 3,Hover Micro-interaction,Complex,"hover, magnetic, cursor follow, 3d tilt",hover + mousemove,300-500ms,"elastic.out(1,0.4)","const xTo = gsap.quickTo(el, 'x', { duration: 0.4, ease: 'elastic.out(1,0.4)' }); const yTo = gsap.quickTo(el, 'y', { duration: 0.4, ease: 'elastic.out(1,0.4)' }); el.addEventListener('mousemove', (e) => { const r = el.getBoundingClientRect(); xTo((e.clientX - r.left - r.width/2) * 0.3); yTo((e.clientY - r.top - r.height/2) * 0.3); });",Debounce is not needed since quickTo interpolates; remove listeners on component unmount in React/Vue to avoid leaks,Clamp the pull strength (e.g. * 0.3) so the element never fully leaves its hit box,Don't apply magnetic effect to more than 1-2 focal elements per screen; it becomes noisy,Use will-change: transform on the target element for smoother compositing
5
+ 4,Scroll Reveal,Subtle,"scroll, fade in, reveal, on view",scroll (viewport enter),300-400ms,power1.out,"gsap.from(el, { opacity: 0, y: 12, duration: 0.35, ease: 'power1.out', scrollTrigger: { trigger: el, start: 'top 90%', toggleActions: 'play none none reverse' } });",Requires the ScrollTrigger plugin registered once via gsap.registerPlugin(ScrollTrigger),"Keep the y offset small (8-16px) so it reads as a fade, not a slide",Don't reveal below-the-fold content needed for SEO/crawlers as invisible-by-default without a no-JS fallback,toggleActions 'play none none reverse' avoids re-triggering on every scroll direction change
6
+ 5,Scroll Reveal,Standard,"scroll, slide up, staggered section, reveal",scroll (viewport enter),400-600ms,power2.out,"gsap.from(el.children, { opacity: 0, y: 24, duration: 0.5, stagger: 0.08, ease: 'power2.out', scrollTrigger: { trigger: el, start: 'top 85%' } });","In React use useGSAP(() => {...}, { scope: containerRef }) from @gsap/react to auto-cleanup on unmount",Scope the ScrollTrigger to the section container so it doesn't re-scan the whole page,Don't stagger more than ~8 children; beyond that the last items feel laggy,Set scroller/markers: false in production; markers is dev-only
7
+ 6,Scroll Reveal,Complex,"scroll, pin, scrub, storytelling, scrollytelling",scroll (continuous scrub),tied to scroll position,none (scrub-driven),"gsap.timeline({ scrollTrigger: { trigger: section, start: 'top top', end: '+=150%', scrub: 1, pin: true } }).from('.headline', { opacity: 0, y: 40 }).to('.bg-layer', { yPercent: -20 }, '<');",Pinning needs the section to have deterministic height; recalc ScrollTrigger.refresh() after images/fonts load,Use scrub: true or a small number (0.5-1.5) instead of instant jumps so it feels tied to the scrollbar,Don't pin more than 1-2 sections per page; excessive pinning fights native scroll feel and hurts mobile UX,"Pinning forces layout reflow; test on mid-tier mobile devices, not just desktop"
8
+ 7,Stagger List,Subtle,"list, stagger, cards, grid entrance",load or scroll,250-350ms,power1.out,"gsap.from('.list-item', { opacity: 0, y: 8, duration: 0.3, stagger: 0.03 });",Select items with a stable class/data-attribute (not array index) so re-renders in React don't break targeting,Keep per-item stagger delay small (0.02-0.04s) for lists longer than 10 items,Don't stagger by more than 0.1s per item on long lists; total reveal time becomes sluggish,"For virtualized lists, only animate items currently mounted in the DOM"
9
+ 8,Stagger List,Standard,"grid, bento, cards, staggered scale",load or scroll,300-450ms,back.out(1.4),"gsap.from('.grid-item', { opacity: 0, scale: 0.92, y: 16, duration: 0.4, stagger: { each: 0.06, from: 'start', grid: 'auto' }, ease: 'back.out(1.4)' });",grid: 'auto' lets GSAP infer rows/columns from a CSS grid layout for a natural wave stagger,Combine with from: 'center' for a bento-grid layout to draw the eye inward first,Don't use back.out on dense data tables; the overshoot reads as sloppy on informational UI,Group DOM writes; avoid interleaving layout reads (getBoundingClientRect) between staggered tweens
10
+ 9,Stagger List,Complex,"stagger, wave, text reveal, split text",load or scroll,400-700ms,expo.out,"const split = new SplitText(headline, { type: 'chars' }); gsap.from(split.chars, { opacity: 0, y: 20, rotateX: -40, duration: 0.6, stagger: 0.015, ease: 'expo.out' });",SplitText is a GSAP Club/paid plugin; confirm license before shipping and provide a plain fade fallback if unavailable,Revert SplitText on unmount/cleanup (split.revert()) to restore original text nodes for accessibility tools,Don't split-animate long paragraphs; reserve for short headlines (under ~8 words),Splitting text creates one element per character; keep it to headline-length copy only for DOM size
11
+ 10,Page Transition,Subtle,"route change, fade, page transition",route change,200-300ms,power1.inOut,"gsap.to(main, { opacity: 0, duration: 0.2, onComplete: () => { navigate(); gsap.fromTo(main, { opacity: 0 }, { opacity: 1, duration: 0.2 }); } });","Pair with the router's transition hooks (Next.js App Router transitions, React Router's useNavigate, Vue Router's beforeEach/afterEach)",Preload the destination route's critical assets before the exit tween finishes,Don't block navigation on animation; cap exit duration at ~250ms so the app never feels unresponsive,Exit animation should always resolve faster than entrance (asymmetric timing) so back/forward feels snappy
12
+ 11,Page Transition,Standard,"route change, slide, overlay wipe",route change,400-600ms,power2.inOut,"const tl = gsap.timeline(); tl.to('.transition-overlay', { yPercent: 0, duration: 0.4, ease: 'power2.inOut' }).call(navigate).to('.transition-overlay', { yPercent: -100, duration: 0.4, ease: 'power2.inOut', delay: 0.1 });",Keep the overlay element mounted at the layout root (outside the page component) so it survives the route swap,Show a lightweight loading indicator if the destination route's data fetch outlasts the overlay,Don't tie the overlay's reveal directly to data-fetch completion without a max-wait timeout; a slow API stalls the whole transition,Prefer CSS transform (yPercent) over top/left to keep the overlay animation on the compositor thread
13
+ 12,Page Transition,Complex,"shared element, morph, hero transition",route change,500-800ms,expo.inOut,"const state = Flip.getState('.hero-image'); navigate(); Flip.from(state, { duration: 0.6, ease: 'expo.inOut', absolute: true, zIndex: 100 });",Requires the GSAP Flip plugin; the 'from' and 'to' route must render the same element with a shared data-flip-id,Verify the shared element exists in both DOM states before calling Flip.from to avoid a silent no-op,Don't use shared-element transitions across more than one element pair per navigation; compounding Flips are hard to time correctly,Flip recalculates layout (FLIP technique) so test on low-end devices for jank
14
+ 13,Parallax Scroll,Subtle,"parallax, background, depth, scroll speed",scroll (continuous),tied to scroll position,linear (scrub),"gsap.to('.bg-layer', { yPercent: 10, ease: 'none', scrollTrigger: { trigger: section, scrub: true } });","Apply parallax to background/decorative layers only, never to text or interactive controls",Keep the yPercent delta small (5-15) so foreground and background never desync distractingly,Don't parallax body copy; it hurts reading comfort and can trigger motion sickness,will-change: transform on the parallax layer only; remove it after scroll settles to free GPU memory
15
+ 14,Parallax Scroll,Standard,"multi-layer parallax, depth, hero background",scroll (continuous),tied to scroll position,linear (scrub),"gsap.utils.toArray('.parallax-layer').forEach((layer, i) => { gsap.to(layer, { yPercent: (i + 1) * -8, ease: 'none', scrollTrigger: { trigger: layer.parentElement, scrub: 0.5 } }); });",Layer count beyond 3-4 has diminishing visual return and multiplies scroll-listener cost,"Vary speed per layer (background slowest, foreground fastest) to sell the depth illusion",Don't let parallax layers overflow their container; clip with overflow: hidden on the wrapper,Batch all layers under one ScrollTrigger container where possible instead of one per layer
16
+ 15,Loading / Skeleton,Subtle,"loading, skeleton, shimmer, pulse",on mount / async wait,1200-1600ms loop,sine.inOut,"gsap.to('.skeleton', { backgroundPosition: '200% 0', duration: 1.4, ease: 'sine.inOut', repeat: -1 });",Kill the loop tween (tween.kill()) as soon as real content mounts to avoid orphaned repeating animations,Use a CSS gradient background-position sweep rather than opacity pulsing; reads as 'loading' more clearly,Don't run more than one shimmer loop per skeleton group; sync them under one timeline so the wave reads as a single unit,repeat: -1 tweens are cheap but must be explicitly killed on unmount or they leak in SPA route changes
17
+ 16,Loading / Skeleton,Standard,"progress, spinner, morphing loader",on mount / async wait,800-1200ms loop,power1.inOut,"gsap.timeline({ repeat: -1 }).to('.loader-dot', { y: -8, duration: 0.4, stagger: { each: 0.15, yoyo: true, repeat: 1 } });",Wrap the whole loop timeline in useGSAP with { revertOnUpdate: false } in React so it isn't rebuilt every render,Cap total loop duration under ~1.5s so long waits don't feel like the UI froze on a single beat,Don't use elaborate loaders for sub-300ms waits; they flash and feel worse than no indicator,Pause the timeline (tl.pause()) when the loading tab/view is not visible to save CPU on background tabs
@@ -6,6 +6,7 @@ status: active
6
6
  tier: senior
7
7
  domain: process
8
8
  compatibility: "Requires a consumer-installed OOXML library (python-docx for create/fill; a zip+xml toolchain for the unpack→edit→pack path). No LibreOffice/soffice dependency — that surface is gated (see pptx, road-to-ecosystem-harvest-document-skills Phase 2). Ships zero runtime in this package."
9
+ harness_compat: consumer-installed-deps
9
10
  workspaces:
10
11
  - agent-config-maintainer
11
12
  packs:
@@ -52,9 +52,23 @@ gh pr view <number> --json number,state,mergeStateStatus,mergedAt,baseRefName
52
52
  - **Before merging** → re-fetch and re-read `state` + `mergeStateStatus` in the
53
53
  **same turn**; never merge on a status seen earlier in the conversation.
54
54
  - **"Based on main" / "current"** → prove it with
55
- `git rev-list --count HEAD..origin/main` (after `git fetch`); non-zero ⇒ the
56
- branch is behind and is **not** current — merge `main` in before opening a PR
57
- (see [`/create-pr`](../../commands/pr/create.md) § 1b).
55
+ `npx tsx node_modules/@event4u/agent-config/src/scripts/check_branch_freshness.ts`;
56
+ exit `1` ⇒ the branch is behind and is **not** current — merge the base in,
57
+ regenerate the derived files, then open the PR (see
58
+ [`/create-pr`](../../commands/pr/create.md) § 1b). Exit `0` means only that
59
+ the gate did **not refuse**: read the line. `branch is current` is the pass;
60
+ `NOT VERIFIED` means the base could not be reached and freshness is
61
+ **unknown** — the gate exits `0` there on purpose so an offline push is not
62
+ blocked, which makes reporting it as "current" the one misread it cannot
63
+ catch. Exit `0` also covers the paths with nothing to check — a no-op in CI, a
64
+ detached HEAD, standing on the base itself — and under `--quiet` a genuine
65
+ pass prints nothing, so run it without the flag when you need a verdict to
66
+ read.
67
+ Prefer it over `git rev-list --count HEAD..origin/main`, which is wrong twice
68
+ over: it pins `main` as the base for a branch whose PR may target something
69
+ else, and it reads the local tracking ref — a fetch from earlier in the
70
+ session, which is memory rather than a check. The gate asks the remote and
71
+ resolves the base from the open PR.
58
72
 
59
73
  ## Conventions
60
74
 
@@ -6,6 +6,7 @@ status: active
6
6
  tier: senior
7
7
  domain: process
8
8
  compatibility: "Requires consumer-installed PDF libraries: pypdf (merge/split/rotate/encrypt/extract), reportlab (create), a form library for AcroForm fill; an OCR engine (e.g. tesseract) only for the OCR task. No LibreOffice dependency. Ships zero runtime in this package."
9
+ harness_compat: consumer-installed-deps
9
10
  workspaces:
10
11
  - agent-config-maintainer
11
12
  packs:
@@ -672,6 +672,41 @@ This duplicates the rules on purpose, and the duplication is the point: the
672
672
  rules are the enforcement, the section is what survives the artifact leaving
673
673
  their reach.
674
674
 
675
+ ## Action-reference split (required pattern, `safety_mode: strict` skills)
676
+
677
+ `execution.safety_mode: strict` says the skill's execution path may mutate
678
+ something. That is a claim made in the frontmatter, and until the body backs it
679
+ the claim costs nothing: a reader who follows the procedure top to bottom finds
680
+ the mutating step sitting inline with every other step, indistinguishable from a
681
+ read.
682
+
683
+ A strict skill therefore takes one of exactly two shapes, and says which:
684
+
685
+ - **Gate it inline.** The mutating step carries its precondition in the same
686
+ breath — propose the exact command first, `--dry-run` before the live run, the
687
+ user confirms, verify the effect landed afterwards. The precondition is part of
688
+ the step, never a general reassurance three sections away.
689
+ - **Defer it.** The SKILL.md states plainly that it **does not define the
690
+ mutating workflow**, and points the write-path steps at a file under the
691
+ skill's own `references/`. The pointer is the precondition's home: the
692
+ reference opens with what must be true before its first step runs.
693
+
694
+ Deferring is the better shape once the write path has more than a couple of
695
+ steps, and for the reason the split exists at all — the main body stays readable
696
+ as the thing an agent loads to decide *whether* to act, while the steps that
697
+ change something live behind one deliberate extra read. What it must never
698
+ become is a second copy: the reference holds the write path, the SKILL.md holds
699
+ the pointer, and neither restates the other.
700
+
701
+ `skill_linter` nudges (`strict_mode_missing_write_gate`, warning) when a strict
702
+ skill's body carries neither shape. It is a warning and it matches on prose, so
703
+ treat it as a prompt to check the body rather than a verdict on it — a skill that
704
+ gates its write path some third way is doing the right thing and should say so
705
+ plainly enough that the next reader sees the gate without running the linter.
706
+
707
+ Read with § Security-constraints above: that section states what the script may
708
+ touch, this one states what must be true before it touches it.
709
+
675
710
  ## Mechanism-teaching section (optional pattern, skills whose subject has one)
676
711
 
677
712
  A procedure tells the agent what to do in the cases you thought of. A