@event4u/agent-config 1.12.0 → 1.14.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 (260) hide show
  1. package/.agent-src/commands/agent-handoff.md +3 -0
  2. package/.agent-src/commands/agent-status.md +3 -0
  3. package/.agent-src/commands/agents-audit.md +4 -0
  4. package/.agent-src/commands/agents-cleanup.md +6 -1
  5. package/.agent-src/commands/agents-prepare.md +3 -0
  6. package/.agent-src/commands/analyze-reference-repo.md +4 -0
  7. package/.agent-src/commands/bug-fix.md +5 -1
  8. package/.agent-src/commands/bug-investigate.md +4 -0
  9. package/.agent-src/commands/chat-history-checkpoint.md +126 -0
  10. package/.agent-src/commands/chat-history-clear.md +5 -0
  11. package/.agent-src/commands/chat-history-resume.md +5 -0
  12. package/.agent-src/commands/chat-history.md +5 -0
  13. package/.agent-src/commands/check-current-md.md +126 -0
  14. package/.agent-src/commands/commit-in-chunks.md +98 -0
  15. package/.agent-src/commands/commit.md +4 -0
  16. package/.agent-src/commands/compress.md +3 -0
  17. package/.agent-src/commands/context-create.md +4 -0
  18. package/.agent-src/commands/context-refactor.md +4 -0
  19. package/.agent-src/commands/copilot-agents-init.md +3 -0
  20. package/.agent-src/commands/copilot-agents-optimize.md +3 -0
  21. package/.agent-src/commands/create-pr-description.md +4 -0
  22. package/.agent-src/commands/create-pr.md +4 -0
  23. package/.agent-src/commands/do-and-judge.md +4 -1
  24. package/.agent-src/commands/do-in-steps.md +3 -0
  25. package/.agent-src/commands/e2e-heal.md +4 -0
  26. package/.agent-src/commands/e2e-plan.md +4 -0
  27. package/.agent-src/commands/estimate-ticket.md +4 -1
  28. package/.agent-src/commands/feature-dev.md +4 -0
  29. package/.agent-src/commands/feature-explore.md +4 -0
  30. package/.agent-src/commands/feature-plan.md +4 -0
  31. package/.agent-src/commands/feature-refactor.md +4 -0
  32. package/.agent-src/commands/feature-roadmap.md +6 -0
  33. package/.agent-src/commands/fix-ci.md +4 -0
  34. package/.agent-src/commands/fix-portability.md +3 -0
  35. package/.agent-src/commands/fix-pr-bot-comments.md +4 -0
  36. package/.agent-src/commands/fix-pr-comments.md +4 -0
  37. package/.agent-src/commands/fix-pr-developer-comments.md +4 -0
  38. package/.agent-src/commands/fix-references.md +3 -0
  39. package/.agent-src/commands/fix-seeder.md +4 -0
  40. package/.agent-src/commands/implement-ticket.md +39 -13
  41. package/.agent-src/commands/jira-ticket.md +4 -0
  42. package/.agent-src/commands/judge.md +3 -0
  43. package/.agent-src/commands/memory-add.md +5 -3
  44. package/.agent-src/commands/memory-full.md +5 -2
  45. package/.agent-src/commands/memory-promote.md +7 -6
  46. package/.agent-src/commands/mode.md +3 -0
  47. package/.agent-src/commands/module-create.md +4 -0
  48. package/.agent-src/commands/module-explore.md +4 -0
  49. package/.agent-src/commands/onboard.md +24 -0
  50. package/.agent-src/commands/optimize-agents.md +4 -0
  51. package/.agent-src/commands/optimize-augmentignore.md +3 -0
  52. package/.agent-src/commands/optimize-rtk-filters.md +3 -0
  53. package/.agent-src/commands/optimize-skills.md +4 -0
  54. package/.agent-src/commands/override-create.md +4 -0
  55. package/.agent-src/commands/override-manage.md +4 -0
  56. package/.agent-src/commands/package-reset.md +3 -0
  57. package/.agent-src/commands/package-test.md +3 -0
  58. package/.agent-src/commands/prepare-for-review.md +4 -0
  59. package/.agent-src/commands/project-analyze.md +4 -0
  60. package/.agent-src/commands/project-health.md +4 -0
  61. package/.agent-src/commands/propose-memory.md +6 -8
  62. package/.agent-src/commands/quality-fix.md +4 -0
  63. package/.agent-src/commands/refine-ticket.md +4 -1
  64. package/.agent-src/commands/review-changes.md +4 -0
  65. package/.agent-src/commands/review-routing.md +4 -0
  66. package/.agent-src/commands/roadmap-create.md +7 -0
  67. package/.agent-src/commands/roadmap-execute.md +12 -1
  68. package/.agent-src/commands/rule-compliance-audit.md +4 -0
  69. package/.agent-src/commands/set-cost-profile.md +3 -0
  70. package/.agent-src/commands/sync-agent-settings.md +3 -0
  71. package/.agent-src/commands/sync-gitignore.md +3 -0
  72. package/.agent-src/commands/tests-create.md +4 -0
  73. package/.agent-src/commands/tests-execute.md +4 -0
  74. package/.agent-src/commands/threat-model.md +4 -0
  75. package/.agent-src/commands/update-form-request-messages.md +4 -0
  76. package/.agent-src/commands/upstream-contribute.md +4 -0
  77. package/.agent-src/commands/work.md +161 -0
  78. package/.agent-src/guidelines/agent-infra/engineering-memory-data-format.md +2 -6
  79. package/.agent-src/guidelines/agent-infra/layered-settings.md +0 -1
  80. package/.agent-src/guidelines/agent-infra/memory-access.md +0 -7
  81. package/.agent-src/guidelines/agent-infra/role-contracts.md +2 -4
  82. package/.agent-src/guidelines/agent-infra/self-improvement-pipeline.md +0 -1
  83. package/.agent-src/guidelines/php/patterns/strategy.md +180 -2
  84. package/.agent-src/personas/README.md +0 -1
  85. package/.agent-src/rules/artifact-drafting-protocol.md +7 -2
  86. package/.agent-src/rules/artifact-engagement-recording.md +133 -0
  87. package/.agent-src/rules/ask-when-uncertain.md +18 -13
  88. package/.agent-src/rules/augment-portability.md +8 -0
  89. package/.agent-src/rules/autonomous-execution.md +158 -0
  90. package/.agent-src/rules/chat-history.md +147 -118
  91. package/.agent-src/rules/cli-output-handling.md +26 -3
  92. package/.agent-src/rules/command-suggestion.md +133 -0
  93. package/.agent-src/rules/commit-policy.md +99 -0
  94. package/.agent-src/rules/direct-answers.md +114 -0
  95. package/.agent-src/rules/docs-sync.md +36 -0
  96. package/.agent-src/rules/downstream-changes.md +10 -9
  97. package/.agent-src/rules/improve-before-implement.md +9 -6
  98. package/.agent-src/rules/language-and-tone.md +81 -6
  99. package/.agent-src/rules/non-destructive-by-default.md +117 -0
  100. package/.agent-src/rules/package-ci-checks.md +4 -0
  101. package/.agent-src/rules/preservation-guard.md +20 -0
  102. package/.agent-src/rules/roadmap-progress-sync.md +103 -30
  103. package/.agent-src/rules/scope-control.md +42 -1
  104. package/.agent-src/rules/size-enforcement.md +1 -3
  105. package/.agent-src/rules/skill-quality.md +3 -8
  106. package/.agent-src/rules/ui-audit-before-build.md +106 -0
  107. package/.agent-src/rules/user-interaction.md +81 -3
  108. package/.agent-src/scripts/update_roadmap_progress.py +48 -6
  109. package/.agent-src/skills/blade-ui/SKILL.md +30 -5
  110. package/.agent-src/skills/command-routing/SKILL.md +32 -0
  111. package/.agent-src/skills/command-writing/SKILL.md +41 -2
  112. package/.agent-src/skills/description-assist/SKILL.md +21 -0
  113. package/.agent-src/skills/estimate-ticket/SKILL.md +0 -1
  114. package/.agent-src/skills/existing-ui-audit/SKILL.md +187 -0
  115. package/.agent-src/skills/fe-design/SKILL.md +72 -60
  116. package/.agent-src/skills/finishing-a-development-branch/SKILL.md +4 -0
  117. package/.agent-src/skills/flux/SKILL.md +31 -4
  118. package/.agent-src/skills/guideline-writing/SKILL.md +24 -2
  119. package/.agent-src/skills/learning-to-rule-or-skill/SKILL.md +51 -9
  120. package/.agent-src/skills/livewire/SKILL.md +30 -4
  121. package/.agent-src/skills/md-language-check/SKILL.md +103 -0
  122. package/.agent-src/skills/php-coder/SKILL.md +24 -0
  123. package/.agent-src/skills/react-shadcn-ui/SKILL.md +121 -0
  124. package/.agent-src/skills/refine-prompt/SKILL.md +220 -0
  125. package/.agent-src/skills/refine-ticket/SKILL.md +2 -4
  126. package/.agent-src/skills/roadmap-management/SKILL.md +10 -3
  127. package/.agent-src/skills/rule-writing/SKILL.md +23 -1
  128. package/.agent-src/skills/skill-writing/SKILL.md +1 -3
  129. package/.agent-src/skills/upstream-contribute/SKILL.md +1 -1
  130. package/.agent-src/skills/using-git-worktrees/SKILL.md +3 -1
  131. package/.agent-src/templates/AGENTS.md +24 -6
  132. package/.agent-src/templates/agent-settings.md +149 -0
  133. package/.agent-src/templates/github-workflows/roadmap-progress-check.yml +63 -0
  134. package/.agent-src/templates/hooks/pre-commit-roadmap-progress +60 -0
  135. package/.agent-src/templates/roadmaps.md +8 -2
  136. package/.agent-src/templates/scripts/implement_ticket/__init__.py +63 -26
  137. package/.agent-src/templates/scripts/implement_ticket/__main__.py +8 -2
  138. package/.agent-src/templates/scripts/memory_lookup.py +382 -21
  139. package/.agent-src/templates/scripts/memory_status.py +110 -9
  140. package/.agent-src/templates/scripts/telemetry/__init__.py +42 -0
  141. package/.agent-src/templates/scripts/telemetry/aggregator.py +154 -0
  142. package/.agent-src/templates/scripts/telemetry/boundary.py +171 -0
  143. package/.agent-src/templates/scripts/telemetry/engagement.py +238 -0
  144. package/.agent-src/templates/scripts/telemetry/report_renderer.py +170 -0
  145. package/.agent-src/templates/scripts/telemetry/settings.py +112 -0
  146. package/.agent-src/templates/scripts/telemetry_record.py +166 -0
  147. package/.agent-src/templates/scripts/telemetry_report.py +161 -0
  148. package/.agent-src/templates/scripts/telemetry_status.py +142 -0
  149. package/.agent-src/templates/scripts/work_engine/__init__.py +58 -0
  150. package/.agent-src/templates/scripts/work_engine/__main__.py +9 -0
  151. package/.agent-src/templates/scripts/work_engine/cli.py +592 -0
  152. package/.agent-src/templates/scripts/{implement_ticket → work_engine}/delivery_state.py +7 -0
  153. package/.agent-src/templates/scripts/work_engine/directives/__init__.py +33 -0
  154. package/.agent-src/templates/scripts/work_engine/directives/backend/__init__.py +98 -0
  155. package/.agent-src/templates/scripts/{implement_ticket/steps → work_engine/directives/backend}/analyze.py +1 -1
  156. package/.agent-src/templates/scripts/{implement_ticket/steps → work_engine/directives/backend}/implement.py +2 -2
  157. package/.agent-src/templates/scripts/{implement_ticket/steps → work_engine/directives/backend}/memory.py +1 -1
  158. package/.agent-src/templates/scripts/{implement_ticket/steps → work_engine/directives/backend}/plan.py +1 -1
  159. package/.agent-src/templates/scripts/work_engine/directives/backend/refine.py +396 -0
  160. package/.agent-src/templates/scripts/{implement_ticket/steps → work_engine/directives/backend}/report.py +36 -4
  161. package/.agent-src/templates/scripts/{implement_ticket/steps → work_engine/directives/backend}/test.py +2 -2
  162. package/.agent-src/templates/scripts/{implement_ticket/steps → work_engine/directives/backend}/verify.py +2 -2
  163. package/.agent-src/templates/scripts/work_engine/directives/mixed/__init__.py +116 -0
  164. package/.agent-src/templates/scripts/work_engine/directives/mixed/contract.py +254 -0
  165. package/.agent-src/templates/scripts/work_engine/directives/mixed/stitch.py +229 -0
  166. package/.agent-src/templates/scripts/work_engine/directives/mixed/ui.py +231 -0
  167. package/.agent-src/templates/scripts/work_engine/directives/ui/__init__.py +113 -0
  168. package/.agent-src/templates/scripts/work_engine/directives/ui/_passthrough.py +44 -0
  169. package/.agent-src/templates/scripts/work_engine/directives/ui/apply.py +241 -0
  170. package/.agent-src/templates/scripts/work_engine/directives/ui/audit.py +414 -0
  171. package/.agent-src/templates/scripts/work_engine/directives/ui/design.py +335 -0
  172. package/.agent-src/templates/scripts/work_engine/directives/ui/polish.py +510 -0
  173. package/.agent-src/templates/scripts/work_engine/directives/ui/review.py +468 -0
  174. package/.agent-src/templates/scripts/work_engine/directives/ui_trivial/__init__.py +119 -0
  175. package/.agent-src/templates/scripts/work_engine/directives/ui_trivial/_skipped.py +37 -0
  176. package/.agent-src/templates/scripts/work_engine/directives/ui_trivial/apply.py +165 -0
  177. package/.agent-src/templates/scripts/work_engine/directives/ui_trivial/refine.py +66 -0
  178. package/.agent-src/templates/scripts/work_engine/directives/ui_trivial/report.py +62 -0
  179. package/.agent-src/templates/scripts/work_engine/directives/ui_trivial/test.py +115 -0
  180. package/.agent-src/templates/scripts/work_engine/dispatcher.py +331 -0
  181. package/.agent-src/templates/scripts/work_engine/hooks/__init__.py +54 -0
  182. package/.agent-src/templates/scripts/work_engine/hooks/builtin/__init__.py +32 -0
  183. package/.agent-src/templates/scripts/work_engine/hooks/builtin/_chat_history_base.py +103 -0
  184. package/.agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_append.py +44 -0
  185. package/.agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_halt_append.py +42 -0
  186. package/.agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_heartbeat.py +50 -0
  187. package/.agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_turn_check.py +49 -0
  188. package/.agent-src/templates/scripts/work_engine/hooks/builtin/directive_set_guard.py +53 -0
  189. package/.agent-src/templates/scripts/work_engine/hooks/builtin/halt_surface_audit.py +50 -0
  190. package/.agent-src/templates/scripts/work_engine/hooks/builtin/state_shape_validation.py +52 -0
  191. package/.agent-src/templates/scripts/work_engine/hooks/builtin/trace.py +84 -0
  192. package/.agent-src/templates/scripts/work_engine/hooks/context.py +66 -0
  193. package/.agent-src/templates/scripts/work_engine/hooks/events.py +44 -0
  194. package/.agent-src/templates/scripts/work_engine/hooks/exceptions.py +79 -0
  195. package/.agent-src/templates/scripts/work_engine/hooks/registry.py +60 -0
  196. package/.agent-src/templates/scripts/work_engine/hooks/runner.py +73 -0
  197. package/.agent-src/templates/scripts/work_engine/hooks/settings.py +141 -0
  198. package/.agent-src/templates/scripts/work_engine/intent/__init__.py +47 -0
  199. package/.agent-src/templates/scripts/work_engine/intent/classify.py +280 -0
  200. package/.agent-src/templates/scripts/work_engine/migration/__init__.py +8 -0
  201. package/.agent-src/templates/scripts/work_engine/migration/v0_to_v1.py +199 -0
  202. package/.agent-src/templates/scripts/work_engine/resolvers/__init__.py +22 -0
  203. package/.agent-src/templates/scripts/work_engine/resolvers/diff.py +106 -0
  204. package/.agent-src/templates/scripts/work_engine/resolvers/file.py +113 -0
  205. package/.agent-src/templates/scripts/work_engine/resolvers/prompt.py +90 -0
  206. package/.agent-src/templates/scripts/work_engine/scoring/__init__.py +14 -0
  207. package/.agent-src/templates/scripts/work_engine/scoring/confidence.py +300 -0
  208. package/.agent-src/templates/scripts/work_engine/stack/__init__.py +31 -0
  209. package/.agent-src/templates/scripts/work_engine/stack/detect.py +187 -0
  210. package/.agent-src/templates/scripts/work_engine/state.py +641 -0
  211. package/.claude-plugin/marketplace.json +105 -2
  212. package/AGENTS.md +36 -8
  213. package/CHANGELOG.md +558 -0
  214. package/README.md +146 -4
  215. package/composer.json +3 -0
  216. package/config/agent-settings.template.yml +45 -0
  217. package/config/gitignore-block.txt +4 -0
  218. package/docs/architecture.md +28 -1
  219. package/docs/development.md +1 -1
  220. package/docs/getting-started.md +3 -2
  221. package/docs/installation.md +86 -0
  222. package/docs/showcase.md +204 -0
  223. package/package.json +9 -1
  224. package/scripts/agent-config +274 -0
  225. package/scripts/audit_cloud_compatibility.py +288 -0
  226. package/scripts/build_cloud_bundle.py +458 -0
  227. package/scripts/build_linear_digest.py +263 -0
  228. package/scripts/chat_history.py +796 -7
  229. package/scripts/check_compression.py +139 -0
  230. package/scripts/check_iron_law_prominence.py +143 -0
  231. package/scripts/check_md_language.py +159 -0
  232. package/scripts/check_portability.py +36 -0
  233. package/scripts/check_reply_consistency.py +140 -0
  234. package/scripts/command_suggester/__init__.py +51 -0
  235. package/scripts/command_suggester/cooldown.py +132 -0
  236. package/scripts/command_suggester/loader.py +70 -0
  237. package/scripts/command_suggester/match.py +180 -0
  238. package/scripts/command_suggester/rank.py +120 -0
  239. package/scripts/command_suggester/render.py +86 -0
  240. package/scripts/command_suggester/sanitize.py +113 -0
  241. package/scripts/command_suggester/settings.py +125 -0
  242. package/scripts/command_suggester/types.py +78 -0
  243. package/scripts/hooks/augment-chat-history.sh +56 -0
  244. package/scripts/install-hooks.sh +67 -0
  245. package/scripts/install.py +150 -33
  246. package/scripts/lint_marketplace.py +27 -0
  247. package/scripts/memory_lookup.py +143 -7
  248. package/scripts/memory_status.py +76 -14
  249. package/scripts/migrate_command_suggestions.py +151 -0
  250. package/scripts/postinstall.sh +16 -0
  251. package/scripts/schemas/command.schema.json +41 -0
  252. package/scripts/skill_linter.py +67 -0
  253. package/scripts/sync_agent_settings.py +42 -12
  254. package/templates/consumer-settings/augment-cli-hooks.json +54 -0
  255. package/templates/consumer-settings/claude-settings.json +55 -1
  256. package/.agent-src/templates/scripts/implement_ticket/cli.py +0 -171
  257. package/.agent-src/templates/scripts/implement_ticket/dispatcher.py +0 -134
  258. package/.agent-src/templates/scripts/implement_ticket/steps/__init__.py +0 -49
  259. package/.agent-src/templates/scripts/implement_ticket/steps/refine.py +0 -140
  260. /package/.agent-src/templates/scripts/{implement_ticket → work_engine}/persona_policy.py +0 -0
@@ -0,0 +1,468 @@
1
+ """``review`` step — stack-dispatched design-review pass.
2
+
3
+ Phase 3 Step 4 of ``agents/roadmaps/road-to-product-ui-track.md``: the
4
+ review step compares the rendered components from ``apply`` against
5
+ the locked design brief and produces a structured findings list. It
6
+ does **not** apply fixes — that is the polish step's job. Review's
7
+ single output is ``state.ui_review`` carrying ``findings`` (a list of
8
+ zero or more issue records) and ``review_clean`` (a bool that mirrors
9
+ ``len(findings) == 0`` once the agent finalises the pass).
10
+
11
+ Routes on ``state.ui_review`` shape:
12
+
13
+ - **Empty / None / non-dict** — first pass. Emit
14
+ ``@agent-directive: ui-design-review-<stack>`` delegating to the
15
+ stack-specific review skill; on the rebound the envelope lands in
16
+ ``state.ui_review``.
17
+ - **Populated, missing ``findings``** — partial envelope, the skill
18
+ has to finish the pass. Halt with the same directive so the agent
19
+ re-runs the review.
20
+ - **Populated, ``findings`` present, ``review_clean`` missing or not
21
+ a bool** — halt asking the agent to set the flag explicitly. Polish
22
+ reads it and would short-circuit the loop on a wrong value.
23
+ - **Populated, well-formed** — return ``SUCCESS``; the dispatcher
24
+ advances to ``verify`` (polish), which decides whether to loop.
25
+
26
+ Review does **not** enforce ``review_clean == (len(findings) == 0)``.
27
+ That looks tempting but it blocks the legitimate "ship as-is with
28
+ open findings" replay path: polish's ceiling halt asks the user to
29
+ set ``review_clean = True`` while findings are still present, the
30
+ dispatcher advances to report, and a later replay of the state file
31
+ would re-enter review with that envelope. Honesty of the flag is the
32
+ producer's contract (the review skill on first pass; the polish skill
33
+ on ship-as-is); review only checks the shape.
34
+
35
+ Idempotent: a re-entry with the same well-formed envelope round-trips
36
+ through ``SUCCESS`` without re-emitting a halt.
37
+ """
38
+ from __future__ import annotations
39
+
40
+ from typing import Any
41
+
42
+ from ...delivery_state import (
43
+ DeliveryState,
44
+ Outcome,
45
+ StepResult,
46
+ agent_directive,
47
+ )
48
+
49
+ STACK_DIRECTIVES: dict[str, str] = {
50
+ "blade-livewire-flux": "ui-design-review-blade-livewire-flux",
51
+ "react-shadcn": "ui-design-review-react-shadcn",
52
+ "vue": "ui-design-review-vue",
53
+ "plain": "ui-design-review-plain",
54
+ }
55
+ """Map ``state.stack.frontend`` → agent-directive skill name.
56
+
57
+ Mirrors :data:`work_engine.directives.ui.apply.STACK_DIRECTIVES` so
58
+ review fires the matching review skill for the stack apply targeted.
59
+ An unknown stack falls through to ``ui-design-review-plain``.
60
+ """
61
+
62
+ DEFAULT_DIRECTIVE = "ui-design-review-plain"
63
+ """Fallback directive when ``state.stack`` is missing or malformed."""
64
+
65
+ SEVERITY_ORDER: dict[str, int] = {
66
+ "minor": 0,
67
+ "moderate": 1,
68
+ "serious": 2,
69
+ "critical": 3,
70
+ }
71
+ """R4 a11y severity ranking — mirrors axe-core's impact levels."""
72
+
73
+ DEFAULT_SEVERITY_FLOOR = "moderate"
74
+ """R4 a11y default severity floor — violations strictly below this are
75
+ informational; violations at or above are actionable."""
76
+
77
+ AMBIGUITIES: tuple[dict[str, str], ...] = (
78
+ {
79
+ "code": "review_envelope_missing",
80
+ "trigger": "state.ui_review unset / empty — review skill has not run yet",
81
+ "resolution": "agent directive `ui-design-review-<stack>` → "
82
+ "skill compares rendered components against state.ui_design "
83
+ "and writes `findings` + `review_clean` back",
84
+ },
85
+ {
86
+ "code": "review_findings_missing",
87
+ "trigger": "state.ui_review populated but `findings` key absent",
88
+ "resolution": "agent re-runs the review skill with the same "
89
+ "directive; review only succeeds once findings is a list",
90
+ },
91
+ {
92
+ "code": "review_clean_missing",
93
+ "trigger": "state.ui_review.findings is set but review_clean "
94
+ "is missing or not a bool — polish needs an explicit flag",
95
+ "resolution": "agent sets state.ui_review.review_clean to "
96
+ "True or False before returning the envelope; review does "
97
+ "not infer it from findings count",
98
+ },
99
+ {
100
+ "code": "review_a11y_pending",
101
+ "trigger": "state.ui_audit declared an `a11y_baseline` but "
102
+ "state.ui_review.a11y is missing — the review skill ran but "
103
+ "did not produce an a11y envelope",
104
+ "resolution": "agent re-runs the review skill so it captures "
105
+ "axe-core (or equivalent) findings into "
106
+ "`state.ui_review.a11y.violations`; the gate then filters "
107
+ "against the baseline and the severity floor",
108
+ },
109
+ {
110
+ "code": "preview_render_failed",
111
+ "trigger": "state.ui_review.preview.render_ok is False — the "
112
+ "stack-specific review skill tried to render the changed "
113
+ "components and the headless browser reported an error",
114
+ "resolution": "user picks Retry (re-run the review skill so it "
115
+ "renders again), Skip (write `state.ui_review.preview.skipped = "
116
+ "true` so the gate stops asking this run), or Abort",
117
+ },
118
+ )
119
+ """Declared ambiguity surfaces for this step."""
120
+
121
+
122
+ def run(state: DeliveryState) -> StepResult:
123
+ """Apply the design-review gate to ``state.ui_review``."""
124
+ review = state.ui_review
125
+ if not _is_populated(review):
126
+ return _delegate_to_review_skill(state)
127
+
128
+ if "findings" not in review or not isinstance(review["findings"], list):
129
+ return _halt_findings_missing(state)
130
+
131
+ findings = review["findings"]
132
+ if not isinstance(review.get("review_clean"), bool):
133
+ return _halt_clean_missing(state, findings_count=len(findings))
134
+
135
+ a11y_halt = _apply_a11y_gate(state, review)
136
+ if a11y_halt is not None:
137
+ return a11y_halt
138
+
139
+ preview_halt = _apply_preview_gate(state, review)
140
+ if preview_halt is not None:
141
+ return preview_halt
142
+
143
+ return StepResult(outcome=Outcome.SUCCESS)
144
+
145
+
146
+ def _is_populated(review: Any) -> bool:
147
+ """True when ``review`` is a dict with at least one own key.
148
+
149
+ Non-dict and empty-dict shapes are treated as "skill has not run"
150
+ so the first-pass directive fires.
151
+ """
152
+ return isinstance(review, dict) and bool(review)
153
+
154
+
155
+ def _resolve_directive(state: DeliveryState) -> str:
156
+ """Pick the agent directive for the project's frontend stack."""
157
+ stack = getattr(state, "stack", None) or {}
158
+ if isinstance(stack, dict):
159
+ frontend = stack.get("frontend")
160
+ if isinstance(frontend, str) and frontend in STACK_DIRECTIVES:
161
+ return STACK_DIRECTIVES[frontend]
162
+ return DEFAULT_DIRECTIVE
163
+
164
+
165
+ def _stack_label(state: DeliveryState) -> str:
166
+ """Return the frontend stack label, defaulting to ``plain``."""
167
+ stack = getattr(state, "stack", None) or {}
168
+ if isinstance(stack, dict):
169
+ frontend = stack.get("frontend")
170
+ if isinstance(frontend, str) and frontend:
171
+ return frontend
172
+ return "plain"
173
+
174
+
175
+ def _delegate_to_review_skill(state: DeliveryState) -> StepResult:
176
+ """First-pass halt — emit the stack-specific review directive."""
177
+ directive = _resolve_directive(state)
178
+ stack_label = _stack_label(state)
179
+ return StepResult(
180
+ outcome=Outcome.BLOCKED,
181
+ questions=[
182
+ agent_directive(directive),
183
+ f"> Stack: `{stack_label}`. Reviewing rendered components "
184
+ "against the locked design brief.",
185
+ "> The review pass compares `state.ticket.ui_apply.rendered` "
186
+ "against `state.ui_design` (microcopy, states, a11y, layout) "
187
+ "and produces a structured `findings` list.",
188
+ "> 1. Continue \u2014 run the review and write "
189
+ "`{findings: [...], review_clean: bool}` into "
190
+ "`state.ui_review`",
191
+ "> 2. Abort \u2014 drop this UI request",
192
+ ],
193
+ message=(
194
+ f"UI review pending; delegating to `{directive}` for "
195
+ f"stack `{stack_label}`."
196
+ ),
197
+ )
198
+
199
+
200
+ def _halt_findings_missing(state: DeliveryState) -> StepResult:
201
+ """BLOCKED halt — envelope present but ``findings`` slot is unset."""
202
+ directive = _resolve_directive(state)
203
+ return StepResult(
204
+ outcome=Outcome.BLOCKED,
205
+ questions=[
206
+ agent_directive(directive),
207
+ "> Review envelope is partial: `findings` list is missing.",
208
+ "> Re-run the review skill so `state.ui_review.findings` "
209
+ "is a list (empty when nothing is wrong).",
210
+ ],
211
+ message="UI review envelope incomplete; `findings` missing.",
212
+ )
213
+
214
+
215
+ def _halt_clean_missing(
216
+ state: DeliveryState,
217
+ *,
218
+ findings_count: int,
219
+ ) -> StepResult:
220
+ """BLOCKED halt — ``review_clean`` is missing or not a bool."""
221
+ directive = _resolve_directive(state)
222
+ return StepResult(
223
+ outcome=Outcome.BLOCKED,
224
+ questions=[
225
+ agent_directive(directive),
226
+ "> Review envelope is incomplete: `review_clean` is missing "
227
+ "or not a boolean.",
228
+ f"> Findings count: {findings_count}. Set "
229
+ "`state.ui_review.review_clean` to `True` (no further "
230
+ "polish needed) or `False` (polish loop should run).",
231
+ ],
232
+ message=(
233
+ "UI review envelope incomplete; `review_clean` must be a bool."
234
+ ),
235
+ )
236
+
237
+
238
+ def _apply_a11y_gate(
239
+ state: DeliveryState,
240
+ review: dict[str, Any],
241
+ ) -> StepResult | None:
242
+ """R4 Phase 1: enforce a11y gate after the basic shape gates pass.
243
+
244
+ The gate is **opt-in via the audit baseline**: if
245
+ ``state.ui_audit.a11y_baseline`` is present (a list, possibly empty)
246
+ the audit declared this UI surface to be a11y-tracked. The review
247
+ skill must then populate ``state.ui_review.a11y.violations``;
248
+ missing → ``review_a11y_pending`` halt. Pre-R4 envelopes (no
249
+ baseline) bypass the gate so existing fixtures keep working.
250
+
251
+ When the envelope is present the gate filters violations against
252
+ the baseline (pre-existing issues are ignored), against the
253
+ accepted list (user-acknowledged issues from a previous polish
254
+ halt), and against the severity floor (default ``moderate``). Any
255
+ *actionable* leftover violations are synthesised as
256
+ ``a11y_violation`` findings on ``review.findings`` and
257
+ ``review_clean`` is forced to ``False`` so polish picks them up.
258
+
259
+ Returns ``None`` to advance the dispatcher, or a ``BLOCKED``
260
+ ``StepResult`` for the pending halt.
261
+
262
+ Side effects on ``review`` are deduplicated by ``(kind, rule)`` so
263
+ a re-entry round-trips without growing the findings list.
264
+ """
265
+ audit = getattr(state, "ui_audit", None)
266
+ has_baseline = isinstance(audit, dict) and "a11y_baseline" in audit
267
+ a11y = review.get("a11y")
268
+
269
+ if a11y is None:
270
+ if has_baseline:
271
+ return _halt_a11y_pending(state)
272
+ return None
273
+
274
+ violations = a11y.get("violations") or []
275
+ baseline = audit["a11y_baseline"] if has_baseline else []
276
+ accepted = a11y.get("accepted_violations") or []
277
+ floor = a11y.get("severity_floor") or DEFAULT_SEVERITY_FLOOR
278
+
279
+ new_violations = _filter_known(violations, baseline)
280
+ new_violations = _filter_known(new_violations, accepted)
281
+ actionable = [v for v in new_violations if _at_or_above_floor(v, floor)]
282
+
283
+ if not actionable:
284
+ return None
285
+
286
+ _synthesize_a11y_findings(review["findings"], actionable)
287
+ review["review_clean"] = False
288
+ return None
289
+
290
+
291
+ def _filter_known(
292
+ violations: list[Any],
293
+ known: list[Any],
294
+ ) -> list[Any]:
295
+ """Drop violations whose ``(rule, selector)`` matches ``known``.
296
+
297
+ Used for both the baseline filter (pre-existing violations stay
298
+ ignored) and the accepted filter (user-acknowledged violations
299
+ after a ``polish_a11y_blocking`` halt). Non-dict entries in either
300
+ list are skipped — schema only enforces list shape.
301
+ """
302
+ if not known:
303
+ return list(violations)
304
+ keys: set[tuple[Any, Any]] = set()
305
+ for entry in known:
306
+ if isinstance(entry, dict):
307
+ keys.add((entry.get("rule"), entry.get("selector")))
308
+ if not keys:
309
+ return list(violations)
310
+ return [
311
+ v for v in violations
312
+ if not (
313
+ isinstance(v, dict)
314
+ and (v.get("rule"), v.get("selector")) in keys
315
+ )
316
+ ]
317
+
318
+
319
+ def _at_or_above_floor(violation: Any, floor: str) -> bool:
320
+ """``True`` when ``violation.severity`` is at or above ``floor``.
321
+
322
+ Unknown severities default to ``moderate`` rather than dropping
323
+ the violation — a malformed envelope must not silently weaken the
324
+ gate. The floor itself is schema-validated, so a bogus floor never
325
+ reaches this helper.
326
+ """
327
+ if not isinstance(violation, dict):
328
+ return False
329
+ severity = violation.get("severity")
330
+ sev_rank = SEVERITY_ORDER.get(
331
+ severity if isinstance(severity, str) else "",
332
+ SEVERITY_ORDER[DEFAULT_SEVERITY_FLOOR],
333
+ )
334
+ floor_rank = SEVERITY_ORDER.get(floor, SEVERITY_ORDER[DEFAULT_SEVERITY_FLOOR])
335
+ return sev_rank >= floor_rank
336
+
337
+
338
+ def _synthesize_a11y_findings(
339
+ findings: list[Any],
340
+ actionable: list[Any],
341
+ ) -> None:
342
+ """Append ``a11y_violation`` findings, deduped by ``(rule, selector)``.
343
+
344
+ Polish reads these as ordinary findings; the ``kind`` discriminator
345
+ lets Phase 2's ``polish_a11y_blocking`` gate isolate the a11y
346
+ subset at the polish ceiling.
347
+ """
348
+ existing: set[tuple[Any, Any]] = {
349
+ (f.get("rule"), f.get("selector"))
350
+ for f in findings
351
+ if isinstance(f, dict) and f.get("kind") == "a11y_violation"
352
+ }
353
+ for v in actionable:
354
+ if not isinstance(v, dict):
355
+ continue
356
+ key = (v.get("rule"), v.get("selector"))
357
+ if key in existing:
358
+ continue
359
+ findings.append({
360
+ "kind": "a11y_violation",
361
+ "rule": v.get("rule"),
362
+ "selector": v.get("selector"),
363
+ "severity": v.get("severity"),
364
+ })
365
+ existing.add(key)
366
+
367
+
368
+ def _halt_a11y_pending(state: DeliveryState) -> StepResult:
369
+ """BLOCKED halt — audit declared a baseline but review has no a11y."""
370
+ directive = _resolve_directive(state)
371
+ return StepResult(
372
+ outcome=Outcome.BLOCKED,
373
+ questions=[
374
+ agent_directive(directive),
375
+ "> Review envelope is incomplete: the audit declared an "
376
+ "`a11y_baseline` but `state.ui_review.a11y` is missing.",
377
+ "> Re-run the review skill so it captures axe-core (or "
378
+ "equivalent) findings into "
379
+ "`state.ui_review.a11y.violations`. The gate filters "
380
+ "against the baseline and the severity floor "
381
+ f"(default `{DEFAULT_SEVERITY_FLOOR}`).",
382
+ ],
383
+ message=(
384
+ "UI review envelope incomplete; `a11y` envelope missing "
385
+ "(audit declared a baseline)."
386
+ ),
387
+ )
388
+
389
+
390
+ def _apply_preview_gate(
391
+ state: DeliveryState,
392
+ review: dict[str, Any],
393
+ ) -> StepResult | None:
394
+ """R4 Phase 3: validate the visual-preview envelope written by the skill.
395
+
396
+ Contract: the **engine never renders**. Stack-specific review skills
397
+ (Playwright + axe-core for ``react-shadcn``, equivalent for
398
+ ``blade-livewire-flux``) produce ``state.ui_review.preview`` with
399
+ ``render_ok`` plus optional ``screenshot_path``, ``dom_dump_path``,
400
+ and ``error``. The gate inspects shape only.
401
+
402
+ Branches:
403
+
404
+ - ``preview`` missing or not a dict → no-op (opt-in; pre-R4 envelopes
405
+ and stacks that do not produce previews flow through silently).
406
+ - ``preview.skipped`` truthy → no-op (idempotent re-entry after the
407
+ user picked the Skip option on a previous halt).
408
+ - ``preview.render_ok`` missing → no-op (envelope still in progress;
409
+ schema validates type when present, content gates wait for an
410
+ explicit signal).
411
+ - ``preview.render_ok is False`` → ``preview_render_failed`` halt
412
+ with Retry / Skip / Abort options.
413
+ - ``preview.render_ok is True`` → no-op; ``report.run`` will surface
414
+ ``screenshot_path`` / ``dom_dump_path`` as a delivery artifact.
415
+
416
+ Trivial path (``directive_set == "ui-trivial"``) never reaches this
417
+ handler — the dispatcher routes ``review`` to ``_skipped.run`` for
418
+ that set, so the preview envelope is bypassed by construction.
419
+ """
420
+ preview = review.get("preview")
421
+ if not isinstance(preview, dict):
422
+ return None
423
+ if preview.get("skipped"):
424
+ return None
425
+ if "render_ok" not in preview:
426
+ return None
427
+ if preview["render_ok"] is False:
428
+ return _halt_preview_failed(state, preview)
429
+ return None
430
+
431
+
432
+ def _halt_preview_failed(
433
+ state: DeliveryState,
434
+ preview: dict[str, Any],
435
+ ) -> StepResult:
436
+ """BLOCKED halt — render reported failure; user picks the next step."""
437
+ directive = _resolve_directive(state)
438
+ error = preview.get("error")
439
+ error_line = (
440
+ f"> Render error: `{error}`."
441
+ if isinstance(error, str) and error
442
+ else "> Render error: `(none reported)`."
443
+ )
444
+ return StepResult(
445
+ outcome=Outcome.BLOCKED,
446
+ questions=[
447
+ agent_directive(directive),
448
+ "> Visual preview failed: "
449
+ "`state.ui_review.preview.render_ok` is `False`.",
450
+ error_line,
451
+ "> 1. Retry — re-run the review skill so it renders again "
452
+ "and writes a fresh `preview` envelope",
453
+ "> 2. Skip — set `state.ui_review.preview.skipped = true` "
454
+ "so this run ships without a screenshot artifact",
455
+ "> 3. Abort — drop this UI request",
456
+ ],
457
+ message="UI preview render failed; awaiting user decision.",
458
+ )
459
+
460
+
461
+ __all__ = [
462
+ "AMBIGUITIES",
463
+ "DEFAULT_DIRECTIVE",
464
+ "DEFAULT_SEVERITY_FLOOR",
465
+ "SEVERITY_ORDER",
466
+ "STACK_DIRECTIVES",
467
+ "run",
468
+ ]
@@ -0,0 +1,119 @@
1
+ """UI-trivial directive set — single-file ≤5-line micro-edit path.
2
+
3
+ Phase 2 Step 6 of ``agents/roadmaps/road-to-product-ui-track.md``: the
4
+ short-circuit path for changes that provably cannot need the audit /
5
+ design / review / polish loop. The dispatcher routes here when Phase
6
+ 1's intent classifier landed ``ui-trivial`` (color tweak, copy change,
7
+ single-class swap, one-prop adjustment).
8
+
9
+ The eight-step shape mirrors :mod:`work_engine.directives.backend` /
10
+ :mod:`work_engine.directives.ui` — eight slots, fixed order, no
11
+ branching. The trivial path fills them as follows:
12
+
13
+ - ``refine`` → :mod:`.refine` — confirm intent gate.
14
+ - ``memory`` → :mod:`._skipped` — bypassed.
15
+ - ``analyze`` → :mod:`._skipped` — bypassed.
16
+ - ``plan`` → :mod:`._skipped` — bypassed.
17
+ - ``implement`` → :mod:`.apply` — hard preconditions; reclassify
18
+ to ``ui-improve`` (full audit gate) when violated.
19
+ - ``test`` → :mod:`.test` — smoke-test delegate.
20
+ - ``verify`` → :mod:`._skipped` — bypassed.
21
+ - ``report`` → :mod:`.report` — one-line delivery summary.
22
+
23
+ The directory uses an underscore (``ui_trivial``) because Python
24
+ packages cannot contain hyphens. The schema carries the external
25
+ hyphenated name ``"ui-trivial"``; the dispatcher's loader is the
26
+ single place that translates between them.
27
+ """
28
+ from __future__ import annotations
29
+
30
+ from collections.abc import Mapping
31
+
32
+ from ...delivery_state import Step
33
+ from . import _skipped, apply, refine, report, test
34
+
35
+ DIRECTIVE_SET_NAME = "ui-trivial"
36
+ """External name carried in ``state.directive_set`` for this set.
37
+
38
+ Note the hyphen \u2014 this is the schema/wire form, not the Python
39
+ module name. The module name (``ui_trivial``) is an implementation
40
+ detail of the loader.
41
+ """
42
+
43
+ ROADMAP = "agents/roadmaps/road-to-product-ui-track.md"
44
+ """Roadmap that defines this directive bundle (Phase 2 Step 6)."""
45
+
46
+ SUPPORTED_KINDS: tuple[str, ...] = ("ticket", "prompt", "diff", "file")
47
+ """Input kinds this directive set knows how to handle.
48
+
49
+ Phase 1's intent classifier reaches ``ui-trivial`` from any of the
50
+ four input kinds; the trivial set keeps the same tuple so input
51
+ routing stays unchanged once the intent label has landed.
52
+ """
53
+
54
+
55
+ def _build_step_map() -> dict[str, Step]:
56
+ """Wire the eight-step dispatcher slots for the trivial set.
57
+
58
+ ``refine`` validates the intent gate; ``implement``, ``test``,
59
+ and ``report`` carry the trivial-path behavior; the four bypassed
60
+ slots share :mod:`._skipped` so the dispatcher's completeness
61
+ check is satisfied without inventing per-slot stubs. The mapping
62
+ is rebuilt per call (cheap; the dispatcher invokes
63
+ :func:`get_steps` once per run).
64
+ """
65
+ skipped = _skipped.run
66
+ return {
67
+ "refine": refine.run,
68
+ "memory": skipped,
69
+ "analyze": skipped,
70
+ "plan": skipped,
71
+ "implement": apply.run,
72
+ "test": test.run,
73
+ "verify": skipped,
74
+ "report": report.run,
75
+ }
76
+
77
+
78
+ def get_steps() -> Mapping[str, Step]:
79
+ """Return the ``{step_name: handler}`` mapping the dispatcher walks.
80
+
81
+ Mirrors :func:`work_engine.directives.backend.get_steps`. ``refine``,
82
+ ``implement``, ``test``, and ``report`` carry trivial-path behavior;
83
+ the four bypassed slots delegate to :mod:`._skipped`.
84
+ """
85
+ return _build_step_map()
86
+
87
+
88
+ def all_ambiguities() -> dict[str, tuple[dict[str, str], ...]]:
89
+ """Per-step ambiguity declarations.
90
+
91
+ Mirrors :func:`work_engine.directives.backend.all_ambiguities`.
92
+ The four bypassed slots re-export :data:`_skipped.AMBIGUITIES`
93
+ (an empty tuple) so doc generators see a uniform shape across all
94
+ eight steps.
95
+ """
96
+ skipped = _skipped.AMBIGUITIES
97
+ return {
98
+ "refine": refine.AMBIGUITIES,
99
+ "memory": skipped,
100
+ "analyze": skipped,
101
+ "plan": skipped,
102
+ "implement": apply.AMBIGUITIES,
103
+ "test": test.AMBIGUITIES,
104
+ "verify": skipped,
105
+ "report": report.AMBIGUITIES,
106
+ }
107
+
108
+
109
+ __all__ = [
110
+ "DIRECTIVE_SET_NAME",
111
+ "ROADMAP",
112
+ "SUPPORTED_KINDS",
113
+ "all_ambiguities",
114
+ "apply",
115
+ "get_steps",
116
+ "refine",
117
+ "report",
118
+ "test",
119
+ ]
@@ -0,0 +1,37 @@
1
+ """Pass-through handler for slots the trivial path skips.
2
+
3
+ Phase 2 Step 6 of ``agents/roadmaps/road-to-product-ui-track.md``: the
4
+ ``ui-trivial`` directive set short-circuits the audit / design / review
5
+ / polish loop. Per the roadmap (Phase 1 Step 3, Phase 2 Step 6) the
6
+ trivial path "skips audit + design + review; runs apply + smoke-test
7
+ only; emits short delivery report".
8
+
9
+ The dispatcher's ``STEP_ORDER`` is fixed (eight slots, no branching),
10
+ so the trivial set fills the unused slots — ``memory``, ``analyze``,
11
+ ``plan``, ``verify`` — with this no-op handler. It returns ``SUCCESS``
12
+ without touching state, mutates nothing, and declares zero
13
+ ambiguities. The audit gate is **not** weakened: trivial bypass is
14
+ gated upstream by ``apply``'s hard preconditions, which reclassify
15
+ to ``ui-improve`` (and the full audit gate) when violated.
16
+ """
17
+ from __future__ import annotations
18
+
19
+ from ...delivery_state import DeliveryState, Outcome, StepResult
20
+
21
+ AMBIGUITIES: tuple[dict[str, str], ...] = ()
22
+ """No ambiguities — the slot is unconditionally skipped on the trivial path."""
23
+
24
+
25
+ def run(state: DeliveryState) -> StepResult:
26
+ """Return ``SUCCESS`` without touching ``state``.
27
+
28
+ Used as a shared handler for the slots that the trivial path
29
+ intentionally bypasses. Keeping the slot wired (rather than
30
+ raising ``NotImplementedError``) preserves the dispatcher's
31
+ completeness-check invariant: every slot in :data:`STEP_ORDER`
32
+ has a callable handler, every directive set has a uniform shape.
33
+ """
34
+ return StepResult(outcome=Outcome.SUCCESS)
35
+
36
+
37
+ __all__ = ["AMBIGUITIES", "run"]