@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.
- package/.agent-src/commands/agent-handoff.md +4 -1
- package/.agent-src/commands/agent-status.md +3 -0
- package/.agent-src/commands/agents-audit.md +4 -0
- package/.agent-src/commands/agents-cleanup.md +6 -1
- package/.agent-src/commands/agents-prepare.md +3 -0
- package/.agent-src/commands/analyze-reference-repo.md +4 -0
- package/.agent-src/commands/bug-fix.md +7 -3
- package/.agent-src/commands/bug-investigate.md +4 -0
- package/.agent-src/commands/chat-history-checkpoint.md +126 -0
- package/.agent-src/commands/chat-history-clear.md +6 -1
- package/.agent-src/commands/chat-history-resume.md +7 -2
- package/.agent-src/commands/chat-history.md +7 -2
- package/.agent-src/commands/check-current-md.md +137 -0
- package/.agent-src/commands/commit-in-chunks.md +118 -0
- package/.agent-src/commands/commit.md +4 -0
- package/.agent-src/commands/compress.md +37 -2
- package/.agent-src/commands/context-create.md +4 -0
- package/.agent-src/commands/context-refactor.md +4 -0
- package/.agent-src/commands/copilot-agents-init.md +3 -0
- package/.agent-src/commands/copilot-agents-optimize.md +3 -0
- package/.agent-src/commands/create-pr-description.md +4 -0
- package/.agent-src/commands/create-pr.md +4 -0
- package/.agent-src/commands/do-and-judge.md +4 -1
- package/.agent-src/commands/do-in-steps.md +3 -0
- package/.agent-src/commands/e2e-heal.md +4 -0
- package/.agent-src/commands/e2e-plan.md +4 -0
- package/.agent-src/commands/estimate-ticket.md +4 -1
- package/.agent-src/commands/feature-dev.md +4 -0
- package/.agent-src/commands/feature-explore.md +4 -0
- package/.agent-src/commands/feature-plan.md +4 -0
- package/.agent-src/commands/feature-refactor.md +4 -0
- package/.agent-src/commands/feature-roadmap.md +6 -0
- package/.agent-src/commands/fix-ci.md +4 -0
- package/.agent-src/commands/fix-portability.md +5 -2
- package/.agent-src/commands/fix-pr-bot-comments.md +4 -0
- package/.agent-src/commands/fix-pr-comments.md +4 -0
- package/.agent-src/commands/fix-pr-developer-comments.md +4 -0
- package/.agent-src/commands/fix-references.md +3 -0
- package/.agent-src/commands/fix-seeder.md +4 -0
- package/.agent-src/commands/implement-ticket.md +39 -13
- package/.agent-src/commands/jira-ticket.md +4 -0
- package/.agent-src/commands/judge.md +3 -0
- package/.agent-src/commands/memory-add.md +5 -3
- package/.agent-src/commands/memory-full.md +5 -2
- package/.agent-src/commands/memory-promote.md +7 -6
- package/.agent-src/commands/mode.md +3 -0
- package/.agent-src/commands/module-create.md +4 -0
- package/.agent-src/commands/module-explore.md +4 -0
- package/.agent-src/commands/onboard.md +33 -0
- package/.agent-src/commands/optimize-agents.md +4 -0
- package/.agent-src/commands/optimize-augmentignore.md +12 -0
- package/.agent-src/commands/optimize-rtk-filters.md +3 -0
- package/.agent-src/commands/optimize-skills.md +4 -0
- package/.agent-src/commands/override-create.md +4 -0
- package/.agent-src/commands/override-manage.md +4 -0
- package/.agent-src/commands/package-reset.md +3 -0
- package/.agent-src/commands/package-test.md +3 -0
- package/.agent-src/commands/prepare-for-review.md +4 -0
- package/.agent-src/commands/project-analyze.md +4 -0
- package/.agent-src/commands/project-health.md +4 -0
- package/.agent-src/commands/propose-memory.md +6 -8
- package/.agent-src/commands/quality-fix.md +4 -0
- package/.agent-src/commands/refine-ticket.md +12 -7
- package/.agent-src/commands/review-changes.md +39 -8
- package/.agent-src/commands/review-routing.md +4 -0
- package/.agent-src/commands/roadmap-create.md +18 -0
- package/.agent-src/commands/roadmap-execute.md +14 -1
- package/.agent-src/commands/rule-compliance-audit.md +4 -0
- package/.agent-src/commands/set-cost-profile.md +11 -0
- package/.agent-src/commands/sync-agent-settings.md +12 -0
- package/.agent-src/commands/sync-gitignore.md +3 -0
- package/.agent-src/commands/tests-create.md +4 -0
- package/.agent-src/commands/tests-execute.md +6 -3
- package/.agent-src/commands/threat-model.md +4 -0
- package/.agent-src/commands/update-form-request-messages.md +4 -0
- package/.agent-src/commands/upstream-contribute.md +4 -0
- package/.agent-src/commands/work.md +161 -0
- package/.agent-src/guidelines/agent-infra/engineering-memory-data-format.md +2 -6
- package/.agent-src/guidelines/agent-infra/layered-settings.md +0 -1
- package/.agent-src/guidelines/agent-infra/memory-access.md +0 -7
- package/.agent-src/guidelines/agent-infra/role-contracts.md +2 -4
- package/.agent-src/guidelines/agent-infra/self-improvement-pipeline.md +0 -1
- package/.agent-src/guidelines/php/patterns/strategy.md +180 -2
- package/.agent-src/personas/README.md +0 -1
- package/.agent-src/rules/artifact-drafting-protocol.md +7 -2
- package/.agent-src/rules/artifact-engagement-recording.md +133 -0
- package/.agent-src/rules/ask-when-uncertain.md +18 -13
- package/.agent-src/rules/augment-portability.md +64 -37
- package/.agent-src/rules/autonomous-execution.md +158 -0
- package/.agent-src/rules/chat-history-cadence.md +109 -0
- package/.agent-src/rules/chat-history-ownership.md +123 -0
- package/.agent-src/rules/chat-history-visibility.md +96 -0
- package/.agent-src/rules/cli-output-handling.md +27 -4
- package/.agent-src/rules/command-suggestion.md +134 -0
- package/.agent-src/rules/commit-policy.md +109 -0
- package/.agent-src/rules/direct-answers.md +114 -0
- package/.agent-src/rules/docs-sync.md +36 -0
- package/.agent-src/rules/downstream-changes.md +10 -9
- package/.agent-src/rules/improve-before-implement.md +9 -6
- package/.agent-src/rules/language-and-tone.md +85 -6
- package/.agent-src/rules/non-destructive-by-default.md +117 -0
- package/.agent-src/rules/package-ci-checks.md +4 -0
- package/.agent-src/rules/preservation-guard.md +20 -0
- package/.agent-src/rules/roadmap-progress-sync.md +159 -27
- package/.agent-src/rules/role-mode-adherence.md +1 -1
- package/.agent-src/rules/scope-control.md +42 -1
- package/.agent-src/rules/size-enforcement.md +2 -3
- package/.agent-src/rules/skill-quality.md +3 -8
- package/.agent-src/rules/ui-audit-before-build.md +106 -0
- package/.agent-src/rules/user-interaction.md +107 -51
- package/.agent-src/scripts/update_roadmap_progress.py +73 -9
- package/.agent-src/skills/blade-ui/SKILL.md +47 -3
- package/.agent-src/skills/command-routing/SKILL.md +32 -0
- package/.agent-src/skills/command-writing/SKILL.md +52 -2
- package/.agent-src/skills/description-assist/SKILL.md +21 -0
- package/.agent-src/skills/estimate-ticket/SKILL.md +0 -1
- package/.agent-src/skills/existing-ui-audit/SKILL.md +202 -0
- package/.agent-src/skills/fe-design/SKILL.md +78 -61
- package/.agent-src/skills/file-editor/SKILL.md +9 -0
- package/.agent-src/skills/finishing-a-development-branch/SKILL.md +4 -0
- package/.agent-src/skills/flux/SKILL.md +31 -4
- package/.agent-src/skills/guideline-writing/SKILL.md +24 -2
- package/.agent-src/skills/learning-to-rule-or-skill/SKILL.md +51 -9
- package/.agent-src/skills/livewire/SKILL.md +49 -4
- package/.agent-src/skills/md-language-check/SKILL.md +103 -0
- package/.agent-src/skills/php-coder/SKILL.md +24 -0
- package/.agent-src/skills/react-shadcn-ui/SKILL.md +121 -0
- package/.agent-src/skills/refine-prompt/SKILL.md +220 -0
- package/.agent-src/skills/refine-ticket/SKILL.md +32 -28
- package/.agent-src/skills/roadmap-management/SKILL.md +24 -11
- package/.agent-src/skills/rule-writing/SKILL.md +23 -1
- package/.agent-src/skills/skill-writing/SKILL.md +3 -5
- package/.agent-src/skills/upstream-contribute/SKILL.md +3 -3
- package/.agent-src/skills/using-git-worktrees/SKILL.md +3 -1
- package/.agent-src/templates/AGENTS.md +24 -6
- package/.agent-src/templates/agent-settings.md +149 -0
- package/.agent-src/templates/roadmaps.md +11 -4
- package/.agent-src/templates/scripts/implement_ticket/__init__.py +63 -26
- package/.agent-src/templates/scripts/implement_ticket/__main__.py +8 -2
- package/.agent-src/templates/scripts/memory_lookup.py +1 -1
- package/.agent-src/templates/scripts/telemetry/__init__.py +42 -0
- package/.agent-src/templates/scripts/telemetry/aggregator.py +154 -0
- package/.agent-src/templates/scripts/telemetry/boundary.py +171 -0
- package/.agent-src/templates/scripts/telemetry/engagement.py +238 -0
- package/.agent-src/templates/scripts/telemetry/report_renderer.py +170 -0
- package/.agent-src/templates/scripts/telemetry/settings.py +112 -0
- package/.agent-src/templates/scripts/telemetry_record.py +166 -0
- package/.agent-src/templates/scripts/telemetry_report.py +161 -0
- package/.agent-src/templates/scripts/telemetry_status.py +142 -0
- package/.agent-src/templates/scripts/work_engine/__init__.py +58 -0
- package/.agent-src/templates/scripts/work_engine/__main__.py +9 -0
- package/.agent-src/templates/scripts/work_engine/cli.py +195 -0
- package/.agent-src/templates/scripts/work_engine/cli_args.py +116 -0
- package/.agent-src/templates/scripts/{implement_ticket → work_engine}/delivery_state.py +10 -3
- package/.agent-src/templates/scripts/work_engine/directives/__init__.py +33 -0
- package/.agent-src/templates/scripts/work_engine/directives/backend/__init__.py +98 -0
- package/.agent-src/templates/scripts/{implement_ticket/steps → work_engine/directives/backend}/analyze.py +1 -1
- package/.agent-src/templates/scripts/{implement_ticket/steps → work_engine/directives/backend}/implement.py +3 -3
- package/.agent-src/templates/scripts/{implement_ticket/steps → work_engine/directives/backend}/memory.py +2 -2
- package/.agent-src/templates/scripts/{implement_ticket/steps → work_engine/directives/backend}/plan.py +2 -2
- package/.agent-src/templates/scripts/work_engine/directives/backend/refine.py +396 -0
- package/.agent-src/templates/scripts/{implement_ticket/steps → work_engine/directives/backend}/report.py +37 -5
- package/.agent-src/templates/scripts/{implement_ticket/steps → work_engine/directives/backend}/test.py +2 -2
- package/.agent-src/templates/scripts/{implement_ticket/steps → work_engine/directives/backend}/verify.py +2 -2
- package/.agent-src/templates/scripts/work_engine/directives/mixed/__init__.py +116 -0
- package/.agent-src/templates/scripts/work_engine/directives/mixed/contract.py +254 -0
- package/.agent-src/templates/scripts/work_engine/directives/mixed/stitch.py +229 -0
- package/.agent-src/templates/scripts/work_engine/directives/mixed/ui.py +231 -0
- package/.agent-src/templates/scripts/work_engine/directives/ui/__init__.py +113 -0
- package/.agent-src/templates/scripts/work_engine/directives/ui/_passthrough.py +44 -0
- package/.agent-src/templates/scripts/work_engine/directives/ui/apply.py +241 -0
- package/.agent-src/templates/scripts/work_engine/directives/ui/audit.py +414 -0
- package/.agent-src/templates/scripts/work_engine/directives/ui/design.py +335 -0
- package/.agent-src/templates/scripts/work_engine/directives/ui/polish.py +510 -0
- package/.agent-src/templates/scripts/work_engine/directives/ui/review.py +468 -0
- package/.agent-src/templates/scripts/work_engine/directives/ui_trivial/__init__.py +119 -0
- package/.agent-src/templates/scripts/work_engine/directives/ui_trivial/_skipped.py +37 -0
- package/.agent-src/templates/scripts/work_engine/directives/ui_trivial/apply.py +165 -0
- package/.agent-src/templates/scripts/work_engine/directives/ui_trivial/refine.py +66 -0
- package/.agent-src/templates/scripts/work_engine/directives/ui_trivial/report.py +62 -0
- package/.agent-src/templates/scripts/work_engine/directives/ui_trivial/test.py +115 -0
- package/.agent-src/templates/scripts/work_engine/dispatcher.py +331 -0
- package/.agent-src/templates/scripts/work_engine/emitters.py +43 -0
- package/.agent-src/templates/scripts/work_engine/errors.py +19 -0
- package/.agent-src/templates/scripts/work_engine/hook_bootstrap.py +76 -0
- package/.agent-src/templates/scripts/work_engine/hooks/__init__.py +54 -0
- package/.agent-src/templates/scripts/work_engine/hooks/builtin/__init__.py +32 -0
- package/.agent-src/templates/scripts/work_engine/hooks/builtin/_chat_history_base.py +103 -0
- package/.agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_append.py +44 -0
- package/.agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_halt_append.py +42 -0
- package/.agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_heartbeat.py +50 -0
- package/.agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_turn_check.py +49 -0
- package/.agent-src/templates/scripts/work_engine/hooks/builtin/directive_set_guard.py +53 -0
- package/.agent-src/templates/scripts/work_engine/hooks/builtin/halt_surface_audit.py +50 -0
- package/.agent-src/templates/scripts/work_engine/hooks/builtin/state_shape_validation.py +52 -0
- package/.agent-src/templates/scripts/work_engine/hooks/builtin/trace.py +84 -0
- package/.agent-src/templates/scripts/work_engine/hooks/context.py +66 -0
- package/.agent-src/templates/scripts/work_engine/hooks/events.py +44 -0
- package/.agent-src/templates/scripts/work_engine/hooks/exceptions.py +79 -0
- package/.agent-src/templates/scripts/work_engine/hooks/registry.py +60 -0
- package/.agent-src/templates/scripts/work_engine/hooks/runner.py +73 -0
- package/.agent-src/templates/scripts/work_engine/hooks/settings.py +141 -0
- package/.agent-src/templates/scripts/work_engine/input_builders.py +163 -0
- package/.agent-src/templates/scripts/work_engine/intent/__init__.py +47 -0
- package/.agent-src/templates/scripts/work_engine/intent/classify.py +280 -0
- package/.agent-src/templates/scripts/work_engine/migration/__init__.py +8 -0
- package/.agent-src/templates/scripts/work_engine/migration/v0_to_v1.py +231 -0
- package/.agent-src/templates/scripts/{implement_ticket → work_engine}/persona_policy.py +1 -1
- package/.agent-src/templates/scripts/work_engine/resolvers/__init__.py +22 -0
- package/.agent-src/templates/scripts/work_engine/resolvers/diff.py +106 -0
- package/.agent-src/templates/scripts/work_engine/resolvers/file.py +113 -0
- package/.agent-src/templates/scripts/work_engine/resolvers/prompt.py +90 -0
- package/.agent-src/templates/scripts/work_engine/scoring/__init__.py +14 -0
- package/.agent-src/templates/scripts/work_engine/scoring/confidence.py +300 -0
- package/.agent-src/templates/scripts/work_engine/stack/__init__.py +31 -0
- package/.agent-src/templates/scripts/work_engine/stack/detect.py +187 -0
- package/.agent-src/templates/scripts/work_engine/state.py +641 -0
- package/.agent-src/templates/scripts/work_engine/state_io.py +202 -0
- package/.claude-plugin/marketplace.json +105 -2
- package/AGENTS.md +38 -8
- package/CHANGELOG.md +609 -0
- package/README.md +136 -14
- package/config/agent-settings.template.yml +45 -0
- package/config/gitignore-block.txt +4 -0
- package/docs/MIGRATION.md +122 -0
- package/docs/architecture.md +111 -35
- package/docs/contracts/STABILITY.md +95 -0
- package/docs/contracts/adr-chat-history-split.md +132 -0
- package/docs/contracts/adr-command-suggestion.md +146 -0
- package/docs/contracts/adr-implement-ticket-runtime.md +122 -0
- package/docs/contracts/adr-product-ui-track.md +384 -0
- package/docs/contracts/adr-prompt-driven-execution.md +187 -0
- package/docs/contracts/agent-memory-contract.md +149 -0
- package/docs/contracts/artifact-engagement-flow.md +262 -0
- package/docs/contracts/command-clusters.md +126 -0
- package/docs/contracts/command-suggestion-flow.md +148 -0
- package/docs/contracts/implement-ticket-flow.md +628 -0
- package/docs/contracts/linear-ai-rules-inclusion.md +143 -0
- package/docs/contracts/linear-ai-three-layers.md +131 -0
- package/docs/contracts/rule-interactions.md +107 -0
- package/docs/contracts/rule-interactions.yml +142 -0
- package/docs/contracts/ui-stack-extension.md +236 -0
- package/docs/contracts/ui-track-flow.md +338 -0
- package/docs/development.md +1 -1
- package/docs/getting-started.md +3 -3
- package/docs/installation.md +124 -2
- package/docs/migrations/commands-1.15.0.md +112 -0
- package/docs/showcase.md +204 -0
- package/docs/ui-track-mental-model.md +121 -0
- package/package.json +1 -1
- package/scripts/agent-config +199 -0
- package/scripts/audit_cloud_compatibility.py +288 -0
- package/scripts/build_cloud_bundle.py +458 -0
- package/scripts/build_linear_digest.py +263 -0
- package/scripts/chat_history.py +796 -7
- package/scripts/check_compression.py +139 -0
- package/scripts/check_iron_law_prominence.py +143 -0
- package/scripts/check_md_language.py +159 -0
- package/scripts/check_portability.py +38 -0
- package/scripts/check_public_links.py +185 -0
- package/scripts/check_references.py +1 -0
- package/scripts/check_reply_consistency.py +140 -0
- package/scripts/command_suggester/__init__.py +51 -0
- package/scripts/command_suggester/cooldown.py +132 -0
- package/scripts/command_suggester/loader.py +70 -0
- package/scripts/command_suggester/match.py +180 -0
- package/scripts/command_suggester/rank.py +120 -0
- package/scripts/command_suggester/render.py +86 -0
- package/scripts/command_suggester/sanitize.py +113 -0
- package/scripts/command_suggester/settings.py +125 -0
- package/scripts/command_suggester/types.py +78 -0
- package/scripts/hooks/augment-chat-history.sh +56 -0
- package/scripts/install-hooks.sh +67 -0
- package/scripts/install.py +150 -33
- package/scripts/lint_marketplace.py +27 -0
- package/scripts/lint_no_new_atomic_commands.py +179 -0
- package/scripts/lint_rule_interactions.py +149 -0
- package/scripts/memory_lookup.py +1 -1
- package/scripts/migrate_command_suggestions.py +151 -0
- package/scripts/release.py +297 -64
- package/scripts/schemas/command.schema.json +41 -0
- package/scripts/skill_linter.py +81 -0
- package/scripts/sync_agent_settings.py +42 -12
- package/scripts/update_counts.py +10 -0
- package/templates/consumer-settings/augment-cli-hooks.json +54 -0
- package/templates/consumer-settings/claude-settings.json +55 -1
- package/.agent-src/rules/chat-history.md +0 -171
- package/.agent-src/templates/scripts/implement_ticket/cli.py +0 -171
- package/.agent-src/templates/scripts/implement_ticket/dispatcher.py +0 -134
- package/.agent-src/templates/scripts/implement_ticket/steps/__init__.py +0 -49
- package/.agent-src/templates/scripts/implement_ticket/steps/refine.py +0 -140
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
"""``polish`` step — bounded fix loop for review findings.
|
|
2
|
+
|
|
3
|
+
Phase 3 Step 5 of ``agents/roadmaps/road-to-product-ui-track.md``: the
|
|
4
|
+
polish step drives the fix loop after ``review`` produces findings.
|
|
5
|
+
It is the **only** step that re-enters review during a single
|
|
6
|
+
``/work`` run, and the loop is hard-capped at two rounds — anything
|
|
7
|
+
the agent cannot fix in two passes goes back to the user as a
|
|
8
|
+
ship-as-is / abort decision rather than burning rounds silently.
|
|
9
|
+
|
|
10
|
+
Routes on ``state.ui_review`` and ``state.ui_polish``:
|
|
11
|
+
|
|
12
|
+
- **review_clean is True (or findings empty)** — nothing to polish;
|
|
13
|
+
return ``SUCCESS`` so the dispatcher advances to ``report``.
|
|
14
|
+
- **review_clean is False, rounds < 2** — emit
|
|
15
|
+
``@agent-directive: ui-polish-<stack>``. The agent applies the
|
|
16
|
+
fixes from ``state.ui_review.findings``, re-runs the review skill,
|
|
17
|
+
and increments ``state.ui_polish.rounds``. On rebound, the
|
|
18
|
+
dispatcher walks back here; if the new review is clean we succeed,
|
|
19
|
+
otherwise the next round fires.
|
|
20
|
+
- **review_clean is False, rounds == 2** — ceiling reached. Halt
|
|
21
|
+
with three numbered options (ship as-is / abort / hand off) so the
|
|
22
|
+
user breaks the deadlock instead of the engine looping forever.
|
|
23
|
+
|
|
24
|
+
Idempotent: when the review is clean the step round-trips through
|
|
25
|
+
``SUCCESS`` regardless of how many rounds ran. Schema-level validation
|
|
26
|
+
in :mod:`work_engine.state` already rejects ``rounds > 2`` on disk,
|
|
27
|
+
so the ceiling check here is the runtime mirror of that contract.
|
|
28
|
+
"""
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
from typing import Any
|
|
32
|
+
|
|
33
|
+
from ...delivery_state import (
|
|
34
|
+
DeliveryState,
|
|
35
|
+
Outcome,
|
|
36
|
+
StepResult,
|
|
37
|
+
agent_directive,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
POLISH_CEILING = 2
|
|
41
|
+
"""Maximum number of polish rounds per ``/work`` run.
|
|
42
|
+
|
|
43
|
+
Mirrored by :func:`work_engine.state._validate_ui_polish` (rejects
|
|
44
|
+
``rounds > 2`` at schema load when ``extension_used`` is ``False``)
|
|
45
|
+
so the contract holds across in-memory state, on-disk state, and the
|
|
46
|
+
dispatcher. R4 Phase 2 adds a one-shot extension that lifts the
|
|
47
|
+
runtime cap to ``POLISH_CEILING + 1`` when
|
|
48
|
+
``state.ui_polish.extension_used`` is ``True``.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
A11Y_VIOLATION_KIND = "a11y_violation"
|
|
52
|
+
"""Marker on a review finding that flags an axe-core (or equivalent)
|
|
53
|
+
accessibility issue. Synthesised by
|
|
54
|
+
:func:`work_engine.directives.ui.review._synthesize_a11y_findings`
|
|
55
|
+
when actionable violations remain after baseline / accepted /
|
|
56
|
+
severity-floor filtering. Polish treats them as ordinary findings
|
|
57
|
+
during rounds 1..N but isolates them at the ceiling so the user
|
|
58
|
+
gets the dedicated ``polish_a11y_blocking`` halt instead of the
|
|
59
|
+
subjective ``polish_ceiling_reached`` halt.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
TOKEN_VIOLATION_KIND = "token_violation"
|
|
63
|
+
"""Marker on a review finding that flags a hardcoded design value.
|
|
64
|
+
|
|
65
|
+
Findings with ``kind == TOKEN_VIOLATION_KIND`` carry ``category``
|
|
66
|
+
(``"colors"`` / ``"spacing"`` / ``"typography"`` / …) and ``value``
|
|
67
|
+
(the literal hardcoded string). Polish classifies them into matched
|
|
68
|
+
(value already present in ``state.ui_audit.design_tokens``) and
|
|
69
|
+
unmatched, then reacts per :data:`TOKEN_REPEAT_THRESHOLD`.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
TOKEN_REPEAT_THRESHOLD = 2
|
|
73
|
+
"""Number of repeats above which an unmatched value triggers the
|
|
74
|
+
extraction halt — mirrors the roadmap's ">2 times" wording in
|
|
75
|
+
``agents/roadmaps/road-to-product-ui-track.md`` Phase 3 Step 5.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
STACK_DIRECTIVES: dict[str, str] = {
|
|
79
|
+
"blade-livewire-flux": "ui-polish-blade-livewire-flux",
|
|
80
|
+
"react-shadcn": "ui-polish-react-shadcn",
|
|
81
|
+
"vue": "ui-polish-vue",
|
|
82
|
+
"plain": "ui-polish-plain",
|
|
83
|
+
}
|
|
84
|
+
"""Map ``state.stack.frontend`` → agent-directive skill name.
|
|
85
|
+
|
|
86
|
+
Mirrors :data:`work_engine.directives.ui.review.STACK_DIRECTIVES`.
|
|
87
|
+
An unknown stack falls through to ``ui-polish-plain``.
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
DEFAULT_DIRECTIVE = "ui-polish-plain"
|
|
91
|
+
"""Fallback directive when ``state.stack`` is missing or malformed."""
|
|
92
|
+
|
|
93
|
+
AMBIGUITIES: tuple[dict[str, str], ...] = (
|
|
94
|
+
{
|
|
95
|
+
"code": "polish_round_pending",
|
|
96
|
+
"trigger": "state.ui_review.review_clean is False and "
|
|
97
|
+
"state.ui_polish.rounds < 2 — fixes have not yet been applied "
|
|
98
|
+
"for the current findings",
|
|
99
|
+
"resolution": "agent directive `ui-polish-<stack>` → skill "
|
|
100
|
+
"applies fixes, re-runs the review, increments "
|
|
101
|
+
"state.ui_polish.rounds",
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"code": "polish_ceiling_reached",
|
|
105
|
+
"trigger": "state.ui_polish.rounds == ceiling and remaining "
|
|
106
|
+
"findings are non-a11y (subjective polish that did not "
|
|
107
|
+
"converge) — a11y blocks take precedence via "
|
|
108
|
+
"polish_a11y_blocking",
|
|
109
|
+
"resolution": "user picks: ship as-is, abort, or hand off to "
|
|
110
|
+
"manual fix; engine refuses to start another round",
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"code": "polish_a11y_blocking",
|
|
114
|
+
"trigger": "state.ui_polish.rounds == ceiling and "
|
|
115
|
+
"state.ui_review.findings still contains a11y_violation "
|
|
116
|
+
"entries — objective gate that takes precedence over the "
|
|
117
|
+
"subjective polish_ceiling_reached halt",
|
|
118
|
+
"resolution": "user picks: extend by one round (engine sets "
|
|
119
|
+
"state.ui_polish.extension_used=True so the next round can "
|
|
120
|
+
"fire), accept-with-known-violations (engine appends the "
|
|
121
|
+
"leftover violations to state.ui_review.a11y.accepted_violations "
|
|
122
|
+
"so the review gate stops blocking on them), or abort the "
|
|
123
|
+
"UI request",
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"code": "polish_token_extraction_pending",
|
|
127
|
+
"trigger": "state.ui_review.findings has token_violation entries "
|
|
128
|
+
"whose value repeats >2 times and has no match in "
|
|
129
|
+
"state.ui_audit.design_tokens",
|
|
130
|
+
"resolution": "user picks: extract as a new token (agent adds "
|
|
131
|
+
"it to state.ui_audit.design_tokens.<category>), inline (agent "
|
|
132
|
+
"drops the token_violation findings before re-entering polish), "
|
|
133
|
+
"or abort the UI request",
|
|
134
|
+
},
|
|
135
|
+
)
|
|
136
|
+
"""Declared ambiguity surfaces for this step."""
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def run(state: DeliveryState) -> StepResult:
|
|
140
|
+
"""Apply the polish-loop gate."""
|
|
141
|
+
review = state.ui_review or {}
|
|
142
|
+
findings = review.get("findings", [])
|
|
143
|
+
if not isinstance(findings, list):
|
|
144
|
+
findings = []
|
|
145
|
+
review_clean = bool(review.get("review_clean", False))
|
|
146
|
+
|
|
147
|
+
if review_clean or not findings:
|
|
148
|
+
return StepResult(outcome=Outcome.SUCCESS)
|
|
149
|
+
|
|
150
|
+
polish = state.ui_polish or {}
|
|
151
|
+
rounds = polish.get("rounds", 0)
|
|
152
|
+
if not isinstance(rounds, int) or isinstance(rounds, bool):
|
|
153
|
+
rounds = 0
|
|
154
|
+
extension_used = bool(polish.get("extension_used", False))
|
|
155
|
+
effective_ceiling = POLISH_CEILING + (1 if extension_used else 0)
|
|
156
|
+
|
|
157
|
+
if rounds >= effective_ceiling:
|
|
158
|
+
a11y_findings = _a11y_findings(findings)
|
|
159
|
+
if a11y_findings:
|
|
160
|
+
return _halt_a11y_blocking(
|
|
161
|
+
state,
|
|
162
|
+
a11y_findings=a11y_findings,
|
|
163
|
+
rounds=rounds,
|
|
164
|
+
extension_available=not extension_used,
|
|
165
|
+
)
|
|
166
|
+
return _halt_ceiling(
|
|
167
|
+
state,
|
|
168
|
+
findings_count=len(findings),
|
|
169
|
+
rounds=rounds,
|
|
170
|
+
ceiling=effective_ceiling,
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
tokens = _design_tokens(state)
|
|
174
|
+
matched, unmatched_repeats = _classify_token_violations(findings, tokens)
|
|
175
|
+
if unmatched_repeats:
|
|
176
|
+
return _halt_token_extraction(state, repeats=unmatched_repeats)
|
|
177
|
+
|
|
178
|
+
return _delegate_to_polish_skill(
|
|
179
|
+
state,
|
|
180
|
+
findings_count=len(findings),
|
|
181
|
+
matched_token_count=len(matched),
|
|
182
|
+
rounds=rounds,
|
|
183
|
+
ceiling=effective_ceiling,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _resolve_directive(state: DeliveryState) -> str:
|
|
188
|
+
"""Pick the agent directive for the project's frontend stack."""
|
|
189
|
+
stack = getattr(state, "stack", None) or {}
|
|
190
|
+
if isinstance(stack, dict):
|
|
191
|
+
frontend = stack.get("frontend")
|
|
192
|
+
if isinstance(frontend, str) and frontend in STACK_DIRECTIVES:
|
|
193
|
+
return STACK_DIRECTIVES[frontend]
|
|
194
|
+
return DEFAULT_DIRECTIVE
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _stack_label(state: DeliveryState) -> str:
|
|
198
|
+
"""Return the frontend stack label, defaulting to ``plain``."""
|
|
199
|
+
stack = getattr(state, "stack", None) or {}
|
|
200
|
+
if isinstance(stack, dict):
|
|
201
|
+
frontend = stack.get("frontend")
|
|
202
|
+
if isinstance(frontend, str) and frontend:
|
|
203
|
+
return frontend
|
|
204
|
+
return "plain"
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _delegate_to_polish_skill(
|
|
208
|
+
state: DeliveryState,
|
|
209
|
+
*,
|
|
210
|
+
findings_count: int,
|
|
211
|
+
matched_token_count: int,
|
|
212
|
+
rounds: int,
|
|
213
|
+
ceiling: int,
|
|
214
|
+
) -> StepResult:
|
|
215
|
+
"""BLOCKED halt — emit the stack-specific polish directive.
|
|
216
|
+
|
|
217
|
+
The skill applies fixes from ``state.ui_review.findings``,
|
|
218
|
+
re-runs the review, and writes:
|
|
219
|
+
|
|
220
|
+
- new ``state.ui_review`` (refreshed findings + ``review_clean``)
|
|
221
|
+
- ``state.ui_polish.rounds = rounds + 1``
|
|
222
|
+
- ``state.ui_polish.applied`` extended with this round's fix log
|
|
223
|
+
|
|
224
|
+
``matched_token_count`` reports how many findings are
|
|
225
|
+
``token_violation`` entries whose value already lives in
|
|
226
|
+
``state.ui_audit.design_tokens`` — those auto-convert in this
|
|
227
|
+
round without further user input. ``ceiling`` is the
|
|
228
|
+
extension-aware upper bound (``POLISH_CEILING`` or
|
|
229
|
+
``POLISH_CEILING + 1`` after an a11y extension).
|
|
230
|
+
"""
|
|
231
|
+
directive = _resolve_directive(state)
|
|
232
|
+
stack_label = _stack_label(state)
|
|
233
|
+
next_round = rounds + 1
|
|
234
|
+
findings_line = (
|
|
235
|
+
f"> {findings_count} finding(s) from `state.ui_review`. "
|
|
236
|
+
"Apply each fix, re-run the review, and write the refreshed "
|
|
237
|
+
"envelope back."
|
|
238
|
+
)
|
|
239
|
+
if matched_token_count:
|
|
240
|
+
findings_line = (
|
|
241
|
+
f"> {findings_count} finding(s) from `state.ui_review` "
|
|
242
|
+
f"({matched_token_count} token-violation match(es) "
|
|
243
|
+
"auto-convert against `state.ui_audit.design_tokens`). "
|
|
244
|
+
"Apply each fix, re-run the review, and write the refreshed "
|
|
245
|
+
"envelope back."
|
|
246
|
+
)
|
|
247
|
+
return StepResult(
|
|
248
|
+
outcome=Outcome.BLOCKED,
|
|
249
|
+
questions=[
|
|
250
|
+
agent_directive(directive),
|
|
251
|
+
f"> Stack: `{stack_label}`. Polish round "
|
|
252
|
+
f"{next_round} of {ceiling}.",
|
|
253
|
+
findings_line,
|
|
254
|
+
"> 1. Continue \u2014 apply fixes, re-review, and increment "
|
|
255
|
+
"`state.ui_polish.rounds`",
|
|
256
|
+
"> 2. Abort \u2014 drop this UI request",
|
|
257
|
+
],
|
|
258
|
+
message=(
|
|
259
|
+
f"UI polish round {next_round}/{ceiling}; delegating "
|
|
260
|
+
f"to `{directive}` for stack `{stack_label}`."
|
|
261
|
+
),
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def _halt_ceiling(
|
|
266
|
+
state: DeliveryState,
|
|
267
|
+
*,
|
|
268
|
+
findings_count: int,
|
|
269
|
+
rounds: int,
|
|
270
|
+
ceiling: int,
|
|
271
|
+
) -> StepResult:
|
|
272
|
+
"""BLOCKED halt — ceiling reached on subjective (non-a11y) findings.
|
|
273
|
+
|
|
274
|
+
R4 Phase 2 narrows this halt: a11y findings are routed to
|
|
275
|
+
:func:`_halt_a11y_blocking` first, so this halt only fires when
|
|
276
|
+
every remaining finding is subjective polish that did not
|
|
277
|
+
converge.
|
|
278
|
+
"""
|
|
279
|
+
stack_label = _stack_label(state)
|
|
280
|
+
return StepResult(
|
|
281
|
+
outcome=Outcome.BLOCKED,
|
|
282
|
+
questions=[
|
|
283
|
+
f"> Stack: `{stack_label}`. Polish ceiling reached "
|
|
284
|
+
f"({rounds}/{ceiling} rounds).",
|
|
285
|
+
f"> {findings_count} finding(s) still open in "
|
|
286
|
+
"`state.ui_review`. The engine refuses a third round.",
|
|
287
|
+
"> 1. Ship as-is \u2014 mark `state.ui_review.review_clean "
|
|
288
|
+
"= True` and continue to `report` (the open findings stay "
|
|
289
|
+
"in the delivery report)",
|
|
290
|
+
"> 2. Abort \u2014 drop this UI request",
|
|
291
|
+
"> 3. Hand off \u2014 a human picks up the remaining "
|
|
292
|
+
"findings outside the engine; re-invoke `/work` only after "
|
|
293
|
+
"they are resolved",
|
|
294
|
+
"",
|
|
295
|
+
"**Recommendation: 3 \u2014 Hand off** \u2014 two automated "
|
|
296
|
+
"rounds failed to converge. Caveat: pick 1 only when the "
|
|
297
|
+
"remaining findings are explicitly acceptable (low-priority "
|
|
298
|
+
"polish, deferred to a follow-up).",
|
|
299
|
+
],
|
|
300
|
+
message=(
|
|
301
|
+
f"UI polish ceiling reached ({rounds}/{ceiling}); "
|
|
302
|
+
f"{findings_count} finding(s) still open."
|
|
303
|
+
),
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def _a11y_findings(findings: list[Any]) -> list[dict[str, Any]]:
|
|
308
|
+
"""Return the subset of ``findings`` synthesised by the a11y gate."""
|
|
309
|
+
return [
|
|
310
|
+
f for f in findings
|
|
311
|
+
if isinstance(f, dict) and f.get("kind") == A11Y_VIOLATION_KIND
|
|
312
|
+
]
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def _halt_a11y_blocking(
|
|
316
|
+
state: DeliveryState,
|
|
317
|
+
*,
|
|
318
|
+
a11y_findings: list[dict[str, Any]],
|
|
319
|
+
rounds: int,
|
|
320
|
+
extension_available: bool,
|
|
321
|
+
) -> StepResult:
|
|
322
|
+
"""BLOCKED halt — ceiling reached with actionable a11y findings.
|
|
323
|
+
|
|
324
|
+
Takes precedence over :func:`_halt_ceiling`: the user gets three
|
|
325
|
+
options tailored to a11y (extend / accept / abort) instead of
|
|
326
|
+
the subjective ship-or-handoff trio. ``extension_available`` is
|
|
327
|
+
``False`` once the one-shot extension was already used; in that
|
|
328
|
+
case option 1 disappears and only accept / abort remain.
|
|
329
|
+
"""
|
|
330
|
+
stack_label = _stack_label(state)
|
|
331
|
+
count = len(a11y_findings)
|
|
332
|
+
questions: list[str] = [
|
|
333
|
+
f"> Stack: `{stack_label}`. Polish ceiling reached "
|
|
334
|
+
f"({rounds}/{POLISH_CEILING} rounds) with {count} a11y "
|
|
335
|
+
"violation(s) still open.",
|
|
336
|
+
]
|
|
337
|
+
for finding in a11y_findings[:5]:
|
|
338
|
+
rule = finding.get("rule") or "?"
|
|
339
|
+
selector = finding.get("selector") or "?"
|
|
340
|
+
severity = finding.get("severity") or "?"
|
|
341
|
+
questions.append(
|
|
342
|
+
f"> - `{rule}` on `{selector}` (severity: {severity})"
|
|
343
|
+
)
|
|
344
|
+
if count > 5:
|
|
345
|
+
questions.append(f"> ... and {count - 5} more")
|
|
346
|
+
if extension_available:
|
|
347
|
+
questions.extend([
|
|
348
|
+
"> 1. Extend \u2014 grant one extra polish round; the "
|
|
349
|
+
"engine sets `state.ui_polish.extension_used = True` so "
|
|
350
|
+
"the next delegation can fire",
|
|
351
|
+
"> 2. Accept \u2014 append the open violations to "
|
|
352
|
+
"`state.ui_review.a11y.accepted_violations` so the review "
|
|
353
|
+
"gate stops blocking on them, then continue to `report`",
|
|
354
|
+
"> 3. Abort \u2014 drop this UI request",
|
|
355
|
+
"",
|
|
356
|
+
"**Recommendation: 1 \u2014 Extend** \u2014 a11y "
|
|
357
|
+
"violations are objective; one more round usually closes "
|
|
358
|
+
"the gap. Pick 2 only when the violations are explicitly "
|
|
359
|
+
"out of scope for this run.",
|
|
360
|
+
])
|
|
361
|
+
else:
|
|
362
|
+
questions.extend([
|
|
363
|
+
"> 1. Accept \u2014 append the open violations to "
|
|
364
|
+
"`state.ui_review.a11y.accepted_violations` so the review "
|
|
365
|
+
"gate stops blocking on them, then continue to `report`",
|
|
366
|
+
"> 2. Abort \u2014 drop this UI request",
|
|
367
|
+
"",
|
|
368
|
+
"**Recommendation: 1 \u2014 Accept** \u2014 the one-shot "
|
|
369
|
+
"extension is already spent; either accept the residual "
|
|
370
|
+
"violations or abort.",
|
|
371
|
+
])
|
|
372
|
+
return StepResult(
|
|
373
|
+
outcome=Outcome.BLOCKED,
|
|
374
|
+
questions=questions,
|
|
375
|
+
message=(
|
|
376
|
+
f"UI polish ceiling reached ({rounds}/{POLISH_CEILING}); "
|
|
377
|
+
f"{count} a11y violation(s) still open."
|
|
378
|
+
),
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def _design_tokens(state: DeliveryState) -> dict[str, Any]:
|
|
383
|
+
"""Return ``state.ui_audit.design_tokens`` as a dict, or ``{}``.
|
|
384
|
+
|
|
385
|
+
Defensive: tolerates missing audit, non-dict audit, missing
|
|
386
|
+
``design_tokens`` key, and non-dict ``design_tokens`` payload.
|
|
387
|
+
All four shapes degrade to "no tokens known" so the unmatched
|
|
388
|
+
classifier treats every value as an extraction candidate.
|
|
389
|
+
"""
|
|
390
|
+
audit = getattr(state, "ui_audit", None) or {}
|
|
391
|
+
if not isinstance(audit, dict):
|
|
392
|
+
return {}
|
|
393
|
+
tokens = audit.get("design_tokens") or {}
|
|
394
|
+
if not isinstance(tokens, dict):
|
|
395
|
+
return {}
|
|
396
|
+
return tokens
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def _classify_token_violations(
|
|
400
|
+
findings: list[Any],
|
|
401
|
+
tokens: dict[str, Any],
|
|
402
|
+
) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
|
|
403
|
+
"""Split ``token_violation`` findings into matched / unmatched-repeats.
|
|
404
|
+
|
|
405
|
+
A finding qualifies when ``kind == TOKEN_VIOLATION_KIND`` and it
|
|
406
|
+
carries string ``category`` + ``value``. Matched: ``value`` is
|
|
407
|
+
among the values of ``tokens[category]`` (the token bucket is a
|
|
408
|
+
name → literal mapping per ``state.ui_audit.design_tokens``
|
|
409
|
+
schema). Unmatched values are bucketed by ``(category, value)``;
|
|
410
|
+
only buckets with ``count > TOKEN_REPEAT_THRESHOLD`` are returned
|
|
411
|
+
so single-use hardcoded values do not trigger the halt.
|
|
412
|
+
"""
|
|
413
|
+
matched: list[dict[str, Any]] = []
|
|
414
|
+
unmatched_counts: dict[tuple[str, str], int] = {}
|
|
415
|
+
for finding in findings:
|
|
416
|
+
if not isinstance(finding, dict):
|
|
417
|
+
continue
|
|
418
|
+
if finding.get("kind") != TOKEN_VIOLATION_KIND:
|
|
419
|
+
continue
|
|
420
|
+
category = finding.get("category")
|
|
421
|
+
value = finding.get("value")
|
|
422
|
+
if not isinstance(category, str) or not isinstance(value, str):
|
|
423
|
+
continue
|
|
424
|
+
bucket = tokens.get(category)
|
|
425
|
+
if isinstance(bucket, dict) and value in bucket.values():
|
|
426
|
+
matched.append(finding)
|
|
427
|
+
continue
|
|
428
|
+
key = (category, value)
|
|
429
|
+
unmatched_counts[key] = unmatched_counts.get(key, 0) + 1
|
|
430
|
+
repeats = [
|
|
431
|
+
{"category": cat, "value": val, "count": count}
|
|
432
|
+
for (cat, val), count in unmatched_counts.items()
|
|
433
|
+
if count > TOKEN_REPEAT_THRESHOLD
|
|
434
|
+
]
|
|
435
|
+
return matched, repeats
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def _suggest_token_name(category: str, value: str) -> str:
|
|
439
|
+
"""Build a suggested CSS-custom-property name for an extraction halt.
|
|
440
|
+
|
|
441
|
+
Heuristic only — the agent picks the final name when applying
|
|
442
|
+
the extraction. Strips non-alphanumerics from ``value``, prefixes
|
|
443
|
+
with the singular of ``category`` (``colors`` → ``color``), and
|
|
444
|
+
caps the length so the halt body stays readable.
|
|
445
|
+
"""
|
|
446
|
+
safe = "".join(c if c.isalnum() else "-" for c in value).strip("-").lower()
|
|
447
|
+
if not safe:
|
|
448
|
+
safe = "value"
|
|
449
|
+
base = category.rstrip("s") or category
|
|
450
|
+
return f"{base}-{safe}"[:40]
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
def _halt_token_extraction(
|
|
454
|
+
state: DeliveryState,
|
|
455
|
+
*,
|
|
456
|
+
repeats: list[dict[str, Any]],
|
|
457
|
+
) -> StepResult:
|
|
458
|
+
"""BLOCKED halt — repeated hardcoded value(s) without a matching token.
|
|
459
|
+
|
|
460
|
+
Polish refuses to silently inline the same hardcoded value across
|
|
461
|
+
multiple call sites; the user picks whether the value graduates
|
|
462
|
+
to a design token or stays inline for this run.
|
|
463
|
+
"""
|
|
464
|
+
stack_label = _stack_label(state)
|
|
465
|
+
questions: list[str] = [
|
|
466
|
+
f"> Stack: `{stack_label}`. {len(repeats)} hardcoded value(s) "
|
|
467
|
+
f"appear >{TOKEN_REPEAT_THRESHOLD} times without a matching "
|
|
468
|
+
"entry in `state.ui_audit.design_tokens`.",
|
|
469
|
+
]
|
|
470
|
+
for repeat in repeats:
|
|
471
|
+
suggested = _suggest_token_name(repeat["category"], repeat["value"])
|
|
472
|
+
questions.append(
|
|
473
|
+
f"> - `{repeat['value']}` "
|
|
474
|
+
f"({repeat['category']}, {repeat['count']}\u00d7) "
|
|
475
|
+
f"\u2014 suggested name: `--{suggested}`"
|
|
476
|
+
)
|
|
477
|
+
questions.extend([
|
|
478
|
+
"> 1. Extract as design token(s) \u2014 add to "
|
|
479
|
+
"`state.ui_audit.design_tokens.<category>` and re-enter polish; "
|
|
480
|
+
"matching findings auto-convert next round",
|
|
481
|
+
"> 2. Inline \u2014 keep the hardcoded value(s) for this run; "
|
|
482
|
+
"drop the token_violation findings from "
|
|
483
|
+
"`state.ui_review.findings` before re-entering polish",
|
|
484
|
+
"> 3. Abort \u2014 drop this UI request",
|
|
485
|
+
"",
|
|
486
|
+
"**Recommendation: 1 \u2014 Extract** \u2014 a value used "
|
|
487
|
+
f">{TOKEN_REPEAT_THRESHOLD} times is a de-facto token; "
|
|
488
|
+
"promoting it now keeps the design system honest. Pick 2 only "
|
|
489
|
+
"when the value is intentionally one-off.",
|
|
490
|
+
])
|
|
491
|
+
return StepResult(
|
|
492
|
+
outcome=Outcome.BLOCKED,
|
|
493
|
+
questions=questions,
|
|
494
|
+
message=(
|
|
495
|
+
f"UI polish paused; {len(repeats)} hardcoded value(s) "
|
|
496
|
+
"repeat without a matching design token."
|
|
497
|
+
),
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
__all__ = [
|
|
502
|
+
"A11Y_VIOLATION_KIND",
|
|
503
|
+
"AMBIGUITIES",
|
|
504
|
+
"DEFAULT_DIRECTIVE",
|
|
505
|
+
"POLISH_CEILING",
|
|
506
|
+
"STACK_DIRECTIVES",
|
|
507
|
+
"TOKEN_REPEAT_THRESHOLD",
|
|
508
|
+
"TOKEN_VIOLATION_KIND",
|
|
509
|
+
"run",
|
|
510
|
+
]
|