@event4u/agent-config 1.13.0 → 1.15.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 (291) hide show
  1. package/.agent-src/commands/agent-handoff.md +4 -1
  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 +7 -3
  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 +6 -1
  11. package/.agent-src/commands/chat-history-resume.md +7 -2
  12. package/.agent-src/commands/chat-history.md +7 -2
  13. package/.agent-src/commands/check-current-md.md +137 -0
  14. package/.agent-src/commands/commit-in-chunks.md +118 -0
  15. package/.agent-src/commands/commit.md +4 -0
  16. package/.agent-src/commands/compress.md +37 -2
  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 +5 -2
  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 +33 -0
  50. package/.agent-src/commands/optimize-agents.md +4 -0
  51. package/.agent-src/commands/optimize-augmentignore.md +12 -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 +12 -7
  64. package/.agent-src/commands/review-changes.md +39 -8
  65. package/.agent-src/commands/review-routing.md +4 -0
  66. package/.agent-src/commands/roadmap-create.md +18 -0
  67. package/.agent-src/commands/roadmap-execute.md +14 -1
  68. package/.agent-src/commands/rule-compliance-audit.md +4 -0
  69. package/.agent-src/commands/set-cost-profile.md +11 -0
  70. package/.agent-src/commands/sync-agent-settings.md +12 -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 +6 -3
  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 +64 -37
  89. package/.agent-src/rules/autonomous-execution.md +158 -0
  90. package/.agent-src/rules/chat-history-cadence.md +109 -0
  91. package/.agent-src/rules/chat-history-ownership.md +123 -0
  92. package/.agent-src/rules/chat-history-visibility.md +96 -0
  93. package/.agent-src/rules/cli-output-handling.md +27 -4
  94. package/.agent-src/rules/command-suggestion.md +134 -0
  95. package/.agent-src/rules/commit-policy.md +109 -0
  96. package/.agent-src/rules/direct-answers.md +114 -0
  97. package/.agent-src/rules/docs-sync.md +36 -0
  98. package/.agent-src/rules/downstream-changes.md +10 -9
  99. package/.agent-src/rules/improve-before-implement.md +9 -6
  100. package/.agent-src/rules/language-and-tone.md +85 -6
  101. package/.agent-src/rules/non-destructive-by-default.md +117 -0
  102. package/.agent-src/rules/package-ci-checks.md +4 -0
  103. package/.agent-src/rules/preservation-guard.md +20 -0
  104. package/.agent-src/rules/roadmap-progress-sync.md +159 -27
  105. package/.agent-src/rules/role-mode-adherence.md +1 -1
  106. package/.agent-src/rules/scope-control.md +42 -1
  107. package/.agent-src/rules/size-enforcement.md +2 -3
  108. package/.agent-src/rules/skill-quality.md +3 -8
  109. package/.agent-src/rules/ui-audit-before-build.md +106 -0
  110. package/.agent-src/rules/user-interaction.md +107 -51
  111. package/.agent-src/scripts/update_roadmap_progress.py +73 -9
  112. package/.agent-src/skills/blade-ui/SKILL.md +47 -3
  113. package/.agent-src/skills/command-routing/SKILL.md +32 -0
  114. package/.agent-src/skills/command-writing/SKILL.md +52 -2
  115. package/.agent-src/skills/description-assist/SKILL.md +21 -0
  116. package/.agent-src/skills/estimate-ticket/SKILL.md +0 -1
  117. package/.agent-src/skills/existing-ui-audit/SKILL.md +202 -0
  118. package/.agent-src/skills/fe-design/SKILL.md +78 -61
  119. package/.agent-src/skills/file-editor/SKILL.md +9 -0
  120. package/.agent-src/skills/finishing-a-development-branch/SKILL.md +4 -0
  121. package/.agent-src/skills/flux/SKILL.md +31 -4
  122. package/.agent-src/skills/guideline-writing/SKILL.md +24 -2
  123. package/.agent-src/skills/learning-to-rule-or-skill/SKILL.md +51 -9
  124. package/.agent-src/skills/livewire/SKILL.md +49 -4
  125. package/.agent-src/skills/md-language-check/SKILL.md +103 -0
  126. package/.agent-src/skills/php-coder/SKILL.md +24 -0
  127. package/.agent-src/skills/react-shadcn-ui/SKILL.md +121 -0
  128. package/.agent-src/skills/refine-prompt/SKILL.md +220 -0
  129. package/.agent-src/skills/refine-ticket/SKILL.md +32 -28
  130. package/.agent-src/skills/roadmap-management/SKILL.md +24 -11
  131. package/.agent-src/skills/rule-writing/SKILL.md +23 -1
  132. package/.agent-src/skills/skill-writing/SKILL.md +3 -5
  133. package/.agent-src/skills/upstream-contribute/SKILL.md +3 -3
  134. package/.agent-src/skills/using-git-worktrees/SKILL.md +3 -1
  135. package/.agent-src/templates/AGENTS.md +24 -6
  136. package/.agent-src/templates/agent-settings.md +149 -0
  137. package/.agent-src/templates/roadmaps.md +11 -4
  138. package/.agent-src/templates/scripts/implement_ticket/__init__.py +63 -26
  139. package/.agent-src/templates/scripts/implement_ticket/__main__.py +8 -2
  140. package/.agent-src/templates/scripts/memory_lookup.py +1 -1
  141. package/.agent-src/templates/scripts/telemetry/__init__.py +42 -0
  142. package/.agent-src/templates/scripts/telemetry/aggregator.py +154 -0
  143. package/.agent-src/templates/scripts/telemetry/boundary.py +171 -0
  144. package/.agent-src/templates/scripts/telemetry/engagement.py +238 -0
  145. package/.agent-src/templates/scripts/telemetry/report_renderer.py +170 -0
  146. package/.agent-src/templates/scripts/telemetry/settings.py +112 -0
  147. package/.agent-src/templates/scripts/telemetry_record.py +166 -0
  148. package/.agent-src/templates/scripts/telemetry_report.py +161 -0
  149. package/.agent-src/templates/scripts/telemetry_status.py +142 -0
  150. package/.agent-src/templates/scripts/work_engine/__init__.py +58 -0
  151. package/.agent-src/templates/scripts/work_engine/__main__.py +9 -0
  152. package/.agent-src/templates/scripts/work_engine/cli.py +195 -0
  153. package/.agent-src/templates/scripts/work_engine/cli_args.py +116 -0
  154. package/.agent-src/templates/scripts/{implement_ticket → work_engine}/delivery_state.py +10 -3
  155. package/.agent-src/templates/scripts/work_engine/directives/__init__.py +33 -0
  156. package/.agent-src/templates/scripts/work_engine/directives/backend/__init__.py +98 -0
  157. package/.agent-src/templates/scripts/{implement_ticket/steps → work_engine/directives/backend}/analyze.py +1 -1
  158. package/.agent-src/templates/scripts/{implement_ticket/steps → work_engine/directives/backend}/implement.py +3 -3
  159. package/.agent-src/templates/scripts/{implement_ticket/steps → work_engine/directives/backend}/memory.py +2 -2
  160. package/.agent-src/templates/scripts/{implement_ticket/steps → work_engine/directives/backend}/plan.py +2 -2
  161. package/.agent-src/templates/scripts/work_engine/directives/backend/refine.py +396 -0
  162. package/.agent-src/templates/scripts/{implement_ticket/steps → work_engine/directives/backend}/report.py +37 -5
  163. package/.agent-src/templates/scripts/{implement_ticket/steps → work_engine/directives/backend}/test.py +2 -2
  164. package/.agent-src/templates/scripts/{implement_ticket/steps → work_engine/directives/backend}/verify.py +2 -2
  165. package/.agent-src/templates/scripts/work_engine/directives/mixed/__init__.py +116 -0
  166. package/.agent-src/templates/scripts/work_engine/directives/mixed/contract.py +254 -0
  167. package/.agent-src/templates/scripts/work_engine/directives/mixed/stitch.py +229 -0
  168. package/.agent-src/templates/scripts/work_engine/directives/mixed/ui.py +231 -0
  169. package/.agent-src/templates/scripts/work_engine/directives/ui/__init__.py +113 -0
  170. package/.agent-src/templates/scripts/work_engine/directives/ui/_passthrough.py +44 -0
  171. package/.agent-src/templates/scripts/work_engine/directives/ui/apply.py +241 -0
  172. package/.agent-src/templates/scripts/work_engine/directives/ui/audit.py +414 -0
  173. package/.agent-src/templates/scripts/work_engine/directives/ui/design.py +335 -0
  174. package/.agent-src/templates/scripts/work_engine/directives/ui/polish.py +510 -0
  175. package/.agent-src/templates/scripts/work_engine/directives/ui/review.py +468 -0
  176. package/.agent-src/templates/scripts/work_engine/directives/ui_trivial/__init__.py +119 -0
  177. package/.agent-src/templates/scripts/work_engine/directives/ui_trivial/_skipped.py +37 -0
  178. package/.agent-src/templates/scripts/work_engine/directives/ui_trivial/apply.py +165 -0
  179. package/.agent-src/templates/scripts/work_engine/directives/ui_trivial/refine.py +66 -0
  180. package/.agent-src/templates/scripts/work_engine/directives/ui_trivial/report.py +62 -0
  181. package/.agent-src/templates/scripts/work_engine/directives/ui_trivial/test.py +115 -0
  182. package/.agent-src/templates/scripts/work_engine/dispatcher.py +331 -0
  183. package/.agent-src/templates/scripts/work_engine/emitters.py +43 -0
  184. package/.agent-src/templates/scripts/work_engine/errors.py +19 -0
  185. package/.agent-src/templates/scripts/work_engine/hook_bootstrap.py +76 -0
  186. package/.agent-src/templates/scripts/work_engine/hooks/__init__.py +54 -0
  187. package/.agent-src/templates/scripts/work_engine/hooks/builtin/__init__.py +32 -0
  188. package/.agent-src/templates/scripts/work_engine/hooks/builtin/_chat_history_base.py +103 -0
  189. package/.agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_append.py +44 -0
  190. package/.agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_halt_append.py +42 -0
  191. package/.agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_heartbeat.py +50 -0
  192. package/.agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_turn_check.py +49 -0
  193. package/.agent-src/templates/scripts/work_engine/hooks/builtin/directive_set_guard.py +53 -0
  194. package/.agent-src/templates/scripts/work_engine/hooks/builtin/halt_surface_audit.py +50 -0
  195. package/.agent-src/templates/scripts/work_engine/hooks/builtin/state_shape_validation.py +52 -0
  196. package/.agent-src/templates/scripts/work_engine/hooks/builtin/trace.py +84 -0
  197. package/.agent-src/templates/scripts/work_engine/hooks/context.py +66 -0
  198. package/.agent-src/templates/scripts/work_engine/hooks/events.py +44 -0
  199. package/.agent-src/templates/scripts/work_engine/hooks/exceptions.py +79 -0
  200. package/.agent-src/templates/scripts/work_engine/hooks/registry.py +60 -0
  201. package/.agent-src/templates/scripts/work_engine/hooks/runner.py +73 -0
  202. package/.agent-src/templates/scripts/work_engine/hooks/settings.py +141 -0
  203. package/.agent-src/templates/scripts/work_engine/input_builders.py +163 -0
  204. package/.agent-src/templates/scripts/work_engine/intent/__init__.py +47 -0
  205. package/.agent-src/templates/scripts/work_engine/intent/classify.py +280 -0
  206. package/.agent-src/templates/scripts/work_engine/migration/__init__.py +8 -0
  207. package/.agent-src/templates/scripts/work_engine/migration/v0_to_v1.py +231 -0
  208. package/.agent-src/templates/scripts/{implement_ticket → work_engine}/persona_policy.py +1 -1
  209. package/.agent-src/templates/scripts/work_engine/resolvers/__init__.py +22 -0
  210. package/.agent-src/templates/scripts/work_engine/resolvers/diff.py +106 -0
  211. package/.agent-src/templates/scripts/work_engine/resolvers/file.py +113 -0
  212. package/.agent-src/templates/scripts/work_engine/resolvers/prompt.py +90 -0
  213. package/.agent-src/templates/scripts/work_engine/scoring/__init__.py +14 -0
  214. package/.agent-src/templates/scripts/work_engine/scoring/confidence.py +300 -0
  215. package/.agent-src/templates/scripts/work_engine/stack/__init__.py +31 -0
  216. package/.agent-src/templates/scripts/work_engine/stack/detect.py +187 -0
  217. package/.agent-src/templates/scripts/work_engine/state.py +641 -0
  218. package/.agent-src/templates/scripts/work_engine/state_io.py +202 -0
  219. package/.claude-plugin/marketplace.json +105 -2
  220. package/AGENTS.md +38 -8
  221. package/CHANGELOG.md +609 -0
  222. package/README.md +136 -14
  223. package/config/agent-settings.template.yml +45 -0
  224. package/config/gitignore-block.txt +4 -0
  225. package/docs/MIGRATION.md +122 -0
  226. package/docs/architecture.md +111 -35
  227. package/docs/contracts/STABILITY.md +95 -0
  228. package/docs/contracts/adr-chat-history-split.md +132 -0
  229. package/docs/contracts/adr-command-suggestion.md +146 -0
  230. package/docs/contracts/adr-implement-ticket-runtime.md +122 -0
  231. package/docs/contracts/adr-product-ui-track.md +384 -0
  232. package/docs/contracts/adr-prompt-driven-execution.md +187 -0
  233. package/docs/contracts/agent-memory-contract.md +149 -0
  234. package/docs/contracts/artifact-engagement-flow.md +262 -0
  235. package/docs/contracts/command-clusters.md +126 -0
  236. package/docs/contracts/command-suggestion-flow.md +148 -0
  237. package/docs/contracts/implement-ticket-flow.md +628 -0
  238. package/docs/contracts/linear-ai-rules-inclusion.md +143 -0
  239. package/docs/contracts/linear-ai-three-layers.md +131 -0
  240. package/docs/contracts/rule-interactions.md +107 -0
  241. package/docs/contracts/rule-interactions.yml +142 -0
  242. package/docs/contracts/ui-stack-extension.md +236 -0
  243. package/docs/contracts/ui-track-flow.md +338 -0
  244. package/docs/development.md +1 -1
  245. package/docs/getting-started.md +3 -3
  246. package/docs/installation.md +124 -2
  247. package/docs/migrations/commands-1.15.0.md +112 -0
  248. package/docs/showcase.md +204 -0
  249. package/docs/ui-track-mental-model.md +121 -0
  250. package/package.json +1 -1
  251. package/scripts/agent-config +199 -0
  252. package/scripts/audit_cloud_compatibility.py +288 -0
  253. package/scripts/build_cloud_bundle.py +458 -0
  254. package/scripts/build_linear_digest.py +263 -0
  255. package/scripts/chat_history.py +796 -7
  256. package/scripts/check_compression.py +139 -0
  257. package/scripts/check_iron_law_prominence.py +143 -0
  258. package/scripts/check_md_language.py +159 -0
  259. package/scripts/check_portability.py +38 -0
  260. package/scripts/check_public_links.py +185 -0
  261. package/scripts/check_references.py +1 -0
  262. package/scripts/check_reply_consistency.py +140 -0
  263. package/scripts/command_suggester/__init__.py +51 -0
  264. package/scripts/command_suggester/cooldown.py +132 -0
  265. package/scripts/command_suggester/loader.py +70 -0
  266. package/scripts/command_suggester/match.py +180 -0
  267. package/scripts/command_suggester/rank.py +120 -0
  268. package/scripts/command_suggester/render.py +86 -0
  269. package/scripts/command_suggester/sanitize.py +113 -0
  270. package/scripts/command_suggester/settings.py +125 -0
  271. package/scripts/command_suggester/types.py +78 -0
  272. package/scripts/hooks/augment-chat-history.sh +56 -0
  273. package/scripts/install-hooks.sh +67 -0
  274. package/scripts/install.py +150 -33
  275. package/scripts/lint_marketplace.py +27 -0
  276. package/scripts/lint_no_new_atomic_commands.py +179 -0
  277. package/scripts/lint_rule_interactions.py +149 -0
  278. package/scripts/memory_lookup.py +1 -1
  279. package/scripts/migrate_command_suggestions.py +151 -0
  280. package/scripts/release.py +297 -64
  281. package/scripts/schemas/command.schema.json +41 -0
  282. package/scripts/skill_linter.py +81 -0
  283. package/scripts/sync_agent_settings.py +42 -12
  284. package/scripts/update_counts.py +10 -0
  285. package/templates/consumer-settings/augment-cli-hooks.json +54 -0
  286. package/templates/consumer-settings/claude-settings.json +55 -1
  287. package/.agent-src/rules/chat-history.md +0 -171
  288. package/.agent-src/templates/scripts/implement_ticket/cli.py +0 -171
  289. package/.agent-src/templates/scripts/implement_ticket/dispatcher.py +0 -134
  290. package/.agent-src/templates/scripts/implement_ticket/steps/__init__.py +0 -49
  291. package/.agent-src/templates/scripts/implement_ticket/steps/refine.py +0 -140
@@ -0,0 +1,414 @@
1
+ """``audit`` step — mandatory pre-step for the UI directive set.
2
+
3
+ Routes on ``state.ui_audit`` shape:
4
+
5
+ - **Empty / None** — first pass. Emit an ``@agent-directive:`` halt
6
+ delegating to the ``existing-ui-audit`` skill; on the rebound the
7
+ skill writes findings back into ``state.ui_audit``.
8
+ - **Greenfield without decision** — ``greenfield == True`` and
9
+ ``greenfield_decision`` unset. Emit a numbered-options halt; the
10
+ user picks ``scaffold`` / ``bare`` / ``external_reference``.
11
+ - **Populated, ambiguous, no pick** — confidence is medium, OR the
12
+ inventory has multiple matches with similar similarity scores.
13
+ Emit a numbered-options halt; the user picks the candidate to
14
+ extend (or "build new"). Records ``audit_path = "ambiguous"`` plus
15
+ the selected candidate. The downstream design step shows the brief
16
+ as a final summary, not a separate halt.
17
+ - **Populated, high-confidence** — confidence is ``high`` and the
18
+ inventory has exactly one strong reusable match (similarity
19
+ ≥ ``STRONG_SIMILARITY``). Records ``audit_path = "high_confidence"``
20
+ and returns ``SUCCESS``; the design step folds the audit findings
21
+ into the design-brief halt as default assumptions.
22
+ - **Populated and decided** — any of the above with the path / pick
23
+ already recorded round-trips through ``SUCCESS``. The audit step
24
+ is idempotent so dispatcher replay never re-emits a halt the user
25
+ already answered.
26
+
27
+ The deterministic checks live here (rather than inside the skill) for
28
+ the same reason as :mod:`work_engine.directives.backend.refine`: the
29
+ dispatcher is synchronous Python and cannot delegate mid-loop. Making
30
+ the gate deterministic keeps "no design without audit findings"
31
+ enforceable from code, not norms.
32
+ """
33
+ from __future__ import annotations
34
+
35
+ from typing import Any
36
+
37
+ from ...delivery_state import (
38
+ DeliveryState,
39
+ Outcome,
40
+ StepResult,
41
+ agent_directive,
42
+ )
43
+
44
+ STRONG_SIMILARITY: float = 0.7
45
+ """Similarity threshold for a "strong reusable match" (Step 4 contract)."""
46
+
47
+ TIE_GAP: float = 0.05
48
+ """Top-2 within this gap counts as ambiguous regardless of confidence."""
49
+
50
+ TESTED_AGAINST_SHADCN_MAJOR: int = 2
51
+ """Major version the ``react-shadcn-ui`` skill body declares as ``Tested
52
+ against`` (``shadcn@2.1`` → major ``2``). When ``state.ui_audit
53
+ .shadcn_inventory.version`` resolves to a different major, audit emits
54
+ the soft version-mismatch halt (Phase 2 Step 5)."""
55
+
56
+ AMBIGUITIES: tuple[dict[str, str], ...] = (
57
+ {
58
+ "code": "audit_missing",
59
+ "trigger": "state.ui_audit is None or empty — skill has not run yet",
60
+ "resolution": "agent directive `existing-ui-audit` → skill writes "
61
+ "findings into state.ui_audit",
62
+ },
63
+ {
64
+ "code": "greenfield_undecided",
65
+ "trigger": (
66
+ "state.ui_audit.greenfield is True but greenfield_decision "
67
+ "is unset — user has not picked a scaffolding direction"
68
+ ),
69
+ "resolution": "user picks scaffold / bare / external_reference; "
70
+ "agent records the choice in state.ui_audit.greenfield_decision",
71
+ },
72
+ {
73
+ "code": "shadcn_version_mismatch",
74
+ "trigger": (
75
+ "state.ui_audit.shadcn_inventory.version major differs from "
76
+ "TESTED_AGAINST_SHADCN_MAJOR — react-shadcn-ui skill was "
77
+ "tested against a different major"
78
+ ),
79
+ "resolution": "user accepts cautious composition or aborts; "
80
+ "agent records the choice in "
81
+ "state.ui_audit.version_mismatch_decision",
82
+ },
83
+ {
84
+ "code": "audit_ambiguous",
85
+ "trigger": (
86
+ "confidence band is medium, OR inventory has multiple matches "
87
+ "with similar similarity scores, OR no match clears "
88
+ "STRONG_SIMILARITY"
89
+ ),
90
+ "resolution": "user picks a candidate to extend (or 'build new'); "
91
+ "agent records the choice in state.ui_audit.audit_path "
92
+ "and state.ui_audit.candidate_pick",
93
+ },
94
+ )
95
+ """Declared ambiguity surfaces for this step."""
96
+
97
+
98
+ def run(state: DeliveryState) -> StepResult:
99
+ """Apply the audit gate to ``state.ui_audit``."""
100
+ audit = state.ui_audit
101
+ if not _is_populated(audit):
102
+ return _delegate_to_audit_skill(state)
103
+
104
+ if audit.get("greenfield") is True and not audit.get("greenfield_decision"):
105
+ return _halt_greenfield(state, audit)
106
+
107
+ # Greenfield with a recorded decision skips the candidate-pick halt
108
+ # (there are no existing components to weigh) and lands on SUCCESS.
109
+ if audit.get("greenfield") is True:
110
+ if not audit.get("audit_path"):
111
+ audit["audit_path"] = "greenfield"
112
+ return StepResult(outcome=Outcome.SUCCESS)
113
+
114
+ # Soft halt: react-shadcn-ui skill was tested against a specific
115
+ # major; a project on a different major needs an explicit "proceed
116
+ # with cautious composition" pick before apply runs. Idempotent via
117
+ # ``version_mismatch_decision``.
118
+ mismatch = _detect_shadcn_version_mismatch(audit)
119
+ if mismatch is not None and not audit.get("version_mismatch_decision"):
120
+ return _halt_shadcn_version_mismatch(state, mismatch)
121
+
122
+ # Idempotent re-entry: an already-decided path round-trips through
123
+ # SUCCESS without re-emitting the halt the user already answered.
124
+ if audit.get("audit_path") in {"high_confidence", "ambiguous"}:
125
+ return StepResult(outcome=Outcome.SUCCESS)
126
+
127
+ decision = _decide_path(state, audit)
128
+ if decision == "high_confidence":
129
+ audit["audit_path"] = "high_confidence"
130
+ return StepResult(outcome=Outcome.SUCCESS)
131
+
132
+ return _halt_ambiguous(state, audit)
133
+
134
+
135
+ def _is_populated(audit: Any) -> bool:
136
+ """True when ``audit`` carries actionable findings.
137
+
138
+ Non-dict and empty-dict shapes are treated as "skill has not run"
139
+ so the first-pass directive fires. Once the skill writes findings,
140
+ the dict carries at least one of the documented inventory keys
141
+ (``components_found`` or the legacy ``components`` alias).
142
+ """
143
+ if not isinstance(audit, dict):
144
+ return False
145
+ if not audit:
146
+ return False
147
+ return any(
148
+ key in audit
149
+ for key in ("components_found", "components", "greenfield")
150
+ )
151
+
152
+
153
+ def _preview_input(state: DeliveryState) -> str:
154
+ """Render a one-line preview of the input being audited."""
155
+ data = state.ticket or {}
156
+ raw = data.get("raw")
157
+ if isinstance(raw, str) and raw.strip():
158
+ text = " ".join(raw.split())
159
+ else:
160
+ title = data.get("title")
161
+ text = title if isinstance(title, str) else (data.get("id") or "(no title)")
162
+ if len(text) <= 80:
163
+ return text
164
+ return text[:79].rstrip() + "\u2026"
165
+
166
+
167
+ def _delegate_to_audit_skill(state: DeliveryState) -> StepResult:
168
+ """Halt with an agent directive so the orchestrator runs ``existing-ui-audit``."""
169
+ preview = _preview_input(state)
170
+ return StepResult(
171
+ outcome=Outcome.BLOCKED,
172
+ questions=[
173
+ agent_directive("existing-ui-audit"),
174
+ f"> Input: {preview}",
175
+ "> No UI audit findings yet — running `existing-ui-audit` "
176
+ "to inventory components, design system, tokens, and "
177
+ "candidate matches before design.",
178
+ "> 1. Continue — let the skill produce the audit",
179
+ "> 2. Abort — drop this UI request",
180
+ ],
181
+ message=(
182
+ "UI audit findings missing; delegating to existing-ui-audit skill."
183
+ ),
184
+ )
185
+
186
+
187
+ def _halt_greenfield(state: DeliveryState, audit: dict[str, Any]) -> StepResult:
188
+ """BLOCKED halt — greenfield project needs an explicit scaffolding pick."""
189
+ preview = _preview_input(state)
190
+ questions = [
191
+ f"> Input: {preview}",
192
+ "> No existing UI surface detected — this looks like greenfield.",
193
+ "> 1. Scaffold — minimal token set + base component primitive folder",
194
+ "> 2. Bare — proceed with Tailwind defaults, no scaffolding",
195
+ "> 3. External reference — point me at a design-system URL or file",
196
+ "",
197
+ "**Recommendation: 1 \u2014 Scaffold tokens + primitives** "
198
+ "\u2014 even one extra screen benefits from a shared base; the "
199
+ "scaffold cost is ~10 min and saves re-doing every primitive "
200
+ "on screen 2. Caveat: flip to 2 if this is a demo or "
201
+ "single-page prototype that will not grow.",
202
+ ]
203
+ return StepResult(
204
+ outcome=Outcome.BLOCKED,
205
+ questions=questions,
206
+ message=(
207
+ "UI audit detected greenfield; halting for scaffolding "
208
+ "direction (scaffold / bare / external_reference)."
209
+ ),
210
+ )
211
+
212
+
213
+ def _decide_path(state: DeliveryState, audit: dict[str, Any]) -> str:
214
+ """Return ``"high_confidence"`` or ``"ambiguous"`` for a populated audit.
215
+
216
+ High-confidence requires *both*:
217
+
218
+ - ``state.ticket["confidence"]["band"] == "high"`` (or the input
219
+ kind makes confidence inapplicable — see :func:`_confidence_band`).
220
+ - exactly one strong reusable match: top similarity
221
+ ``\u2265 STRONG_SIMILARITY`` and no runner-up within
222
+ :data:`TIE_GAP` of it.
223
+
224
+ Anything else is ambiguous \u2014 medium / low confidence, no strong
225
+ match, or two near-identical candidates the user must disambiguate.
226
+ """
227
+ band = _confidence_band(state)
228
+ if band != "high":
229
+ return "ambiguous"
230
+
231
+ matches = _matches(audit)
232
+ if not matches:
233
+ return "ambiguous"
234
+
235
+ scored = sorted(
236
+ (_similarity_of(m) for m in matches),
237
+ reverse=True,
238
+ )
239
+ top = scored[0]
240
+ if top < STRONG_SIMILARITY:
241
+ return "ambiguous"
242
+ if len(scored) >= 2 and (top - scored[1]) < TIE_GAP:
243
+ return "ambiguous"
244
+ return "high_confidence"
245
+
246
+
247
+ def _confidence_band(state: DeliveryState) -> str:
248
+ """Return the scored confidence band, or ``"high"`` when not applicable.
249
+
250
+ Ticket inputs that survive the backend refine step carry a band in
251
+ ``state.ticket["confidence"]["band"]``. Diff / file envelopes never
252
+ run the scorer; treat them as ``"high"`` because the user is
253
+ pointing at a concrete surface, not asking us to reconstruct one.
254
+ Missing band defaults to ``"medium"`` so the safe path (ambiguous
255
+ halt) is the fall-through.
256
+ """
257
+ data = state.ticket or {}
258
+ confidence = data.get("confidence")
259
+ if isinstance(confidence, dict):
260
+ band = confidence.get("band")
261
+ if isinstance(band, str) and band:
262
+ return band
263
+ if data.get("input_kind") in {"diff", "file"}:
264
+ return "high"
265
+ return "medium"
266
+
267
+
268
+ def _matches(audit: dict[str, Any]) -> list[dict[str, Any]]:
269
+ """Return the inventory list, preferring ``components_found``."""
270
+ for key in ("components_found", "components"):
271
+ value = audit.get(key)
272
+ if isinstance(value, list) and value:
273
+ return [m for m in value if isinstance(m, dict)]
274
+ return []
275
+
276
+
277
+ def _similarity_of(match: dict[str, Any]) -> float:
278
+ """Read a similarity score from a match entry; default to 0.0."""
279
+ raw = match.get("similarity")
280
+ try:
281
+ return float(raw) # type: ignore[arg-type]
282
+ except (TypeError, ValueError):
283
+ return 0.0
284
+
285
+
286
+ def _detect_shadcn_version_mismatch(audit: dict[str, Any]) -> dict[str, Any] | None:
287
+ """Return mismatch info when the inventory diverges by a major.
288
+
289
+ Reads ``audit["shadcn_inventory"]["version"]`` and compares its
290
+ leading integer with :data:`TESTED_AGAINST_SHADCN_MAJOR`. Returns
291
+ ``None`` (no halt) when:
292
+
293
+ - the inventory is missing, not a dict, or has no ``version``;
294
+ - the version string cannot be parsed (treat as "unknown — skill
295
+ will fall back to manual composition rather than a stale skill");
296
+ - the major matches the tested major.
297
+ """
298
+ inventory = audit.get("shadcn_inventory")
299
+ if not isinstance(inventory, dict):
300
+ return None
301
+ raw_version = inventory.get("version")
302
+ if not isinstance(raw_version, str) or not raw_version.strip():
303
+ return None
304
+ head = raw_version.lstrip("v").split(".", 1)[0].strip()
305
+ try:
306
+ installed_major = int(head)
307
+ except ValueError:
308
+ return None
309
+ if installed_major == TESTED_AGAINST_SHADCN_MAJOR:
310
+ return None
311
+ return {
312
+ "installed_version": raw_version,
313
+ "installed_major": installed_major,
314
+ "tested_major": TESTED_AGAINST_SHADCN_MAJOR,
315
+ }
316
+
317
+
318
+ def _halt_shadcn_version_mismatch(
319
+ state: DeliveryState, mismatch: dict[str, Any]
320
+ ) -> StepResult:
321
+ """BLOCKED soft-halt \u2014 user accepts cautious composition or aborts."""
322
+ preview = _preview_input(state)
323
+ installed = mismatch["installed_version"]
324
+ tested = mismatch["tested_major"]
325
+ installed_major = mismatch["installed_major"]
326
+ return StepResult(
327
+ outcome=Outcome.BLOCKED,
328
+ questions=[
329
+ f"> Input: {preview}",
330
+ f"> shadcn skill tested against v{tested}.x; project uses "
331
+ f"`{installed}` (major v{installed_major}).",
332
+ "> 1. Proceed with cautious composition \u2014 skill applies "
333
+ "general patterns, agent verifies primitive APIs against "
334
+ "the installed version",
335
+ "> 2. Abort \u2014 update the `react-shadcn-ui` skill to the "
336
+ "installed major before continuing",
337
+ "",
338
+ "**Recommendation: 1 \u2014 Proceed with caution** "
339
+ "\u2014 most shadcn primitive APIs are stable across "
340
+ "majors; the skill's structural guidance still applies. "
341
+ "Caveat: flip to 2 if the design brief leans on a "
342
+ "primitive whose API changed (Form, Sheet, Dialog have "
343
+ "had breaking renames in past majors).",
344
+ ],
345
+ message=(
346
+ f"shadcn version mismatch (skill v{tested}.x vs project "
347
+ f"{installed}); halting for cautious-composition decision."
348
+ ),
349
+ )
350
+
351
+
352
+ def _halt_ambiguous(state: DeliveryState, audit: dict[str, Any]) -> StepResult:
353
+ """BLOCKED halt \u2014 user picks an existing candidate or 'build new'."""
354
+ preview = _preview_input(state)
355
+ matches = _matches(audit)
356
+ scored = sorted(
357
+ matches,
358
+ key=_similarity_of,
359
+ reverse=True,
360
+ )[:3]
361
+
362
+ lines = [
363
+ f"> Input: {preview}",
364
+ "> Audit findings are ambiguous \u2014 pick the candidate to "
365
+ "extend, or build new:",
366
+ ]
367
+ for idx, match in enumerate(scored, start=1):
368
+ name = match.get("name") or match.get("path") or "(unnamed)"
369
+ sim = _similarity_of(match)
370
+ path = match.get("path") or ""
371
+ suffix = f" \u2014 `{path}`" if path else ""
372
+ lines.append(f"> {idx}. Extend `{name}` (similarity {sim:.2f}){suffix}")
373
+ next_idx = len(scored) + 1
374
+ lines.append(
375
+ f"> {next_idx}. Build new \u2014 none of the above is close enough",
376
+ )
377
+
378
+ if scored:
379
+ top = scored[0]
380
+ top_name = top.get("name") or top.get("path") or "candidate 1"
381
+ top_sim = _similarity_of(top)
382
+ rec = (
383
+ f"**Recommendation: 1 \u2014 Extend `{top_name}`** \u2014 "
384
+ f"similarity {top_sim:.2f} is the strongest match in the "
385
+ f"inventory; reuse beats new code unless the contract "
386
+ f"diverges. Caveat: flip to {next_idx} if the existing "
387
+ f"component cannot host the new behavior cleanly."
388
+ )
389
+ else:
390
+ rec = (
391
+ f"**Recommendation: {next_idx} \u2014 Build new** \u2014 "
392
+ f"no inventory match cleared the strong-similarity bar. "
393
+ f"Caveat: flip to an extend option only if a near-miss "
394
+ f"is a better fit than starting from scratch."
395
+ )
396
+ lines.extend(["", rec])
397
+
398
+ return StepResult(
399
+ outcome=Outcome.BLOCKED,
400
+ questions=lines,
401
+ message=(
402
+ "UI audit findings ambiguous; halting for candidate pick "
403
+ "(extend existing / build new)."
404
+ ),
405
+ )
406
+
407
+
408
+ __all__ = [
409
+ "AMBIGUITIES",
410
+ "STRONG_SIMILARITY",
411
+ "TESTED_AGAINST_SHADCN_MAJOR",
412
+ "TIE_GAP",
413
+ "run",
414
+ ]