@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,231 @@
|
|
|
1
|
+
"""``ui`` step — delegates to the UI track once the contract is locked.
|
|
2
|
+
|
|
3
|
+
Phase 4 Step 2 of ``agents/roadmaps/road-to-product-ui-track.md``: in the
|
|
4
|
+
``mixed`` directive set the ``implement`` slot is the UI handoff. It
|
|
5
|
+
gates on the upstream contract sentinel
|
|
6
|
+
(``state.contract.contract_confirmed is True``) and then delegates the
|
|
7
|
+
full audit → design → apply → review → polish sub-flow to the UI track
|
|
8
|
+
via an ``@agent-directive: ui-track`` halt. The locked contract is
|
|
9
|
+
treated as immutable input — the UI track reads ``state.contract`` for
|
|
10
|
+
entity shapes and endpoint signatures but never mutates them.
|
|
11
|
+
|
|
12
|
+
Routes on the UI sub-flow's terminal sentinel
|
|
13
|
+
(``state.ui_review.review_clean``):
|
|
14
|
+
|
|
15
|
+
- **Contract sentinel missing or False** — defense-in-depth halt. The
|
|
16
|
+
``plan`` slot (``mixed.contract``) is the canonical gate; this step
|
|
17
|
+
refuses to start without the sentinel even if outcomes say
|
|
18
|
+
otherwise, mirroring the roadmap risk: "Mixed flow's contract halt
|
|
19
|
+
is bypassed".
|
|
20
|
+
- **UI sub-flow not started** (``state.ui_review`` empty / missing
|
|
21
|
+
``review_clean``) — emit ``@agent-directive: ui-track`` so the
|
|
22
|
+
orchestrator runs audit → design → apply → review → polish with the
|
|
23
|
+
contract as input. On rebound the dispatcher walks back here.
|
|
24
|
+
- **UI sub-flow finished, ``review_clean`` True** — return ``SUCCESS``;
|
|
25
|
+
the dispatcher advances to the mixed ``stitch`` step.
|
|
26
|
+
- **UI sub-flow finished, ``review_clean`` False** — halt with three
|
|
27
|
+
numbered options (re-run / hand off / abort). Polish-ceiling
|
|
28
|
+
semantics live in the UI track's polish step; if the user reaches
|
|
29
|
+
this halt the UI track has already given up.
|
|
30
|
+
|
|
31
|
+
Idempotent: a re-entry with a clean review round-trips through
|
|
32
|
+
``SUCCESS`` without re-emitting the delegation directive.
|
|
33
|
+
"""
|
|
34
|
+
from __future__ import annotations
|
|
35
|
+
|
|
36
|
+
from typing import Any
|
|
37
|
+
|
|
38
|
+
from ...delivery_state import (
|
|
39
|
+
DeliveryState,
|
|
40
|
+
Outcome,
|
|
41
|
+
StepResult,
|
|
42
|
+
agent_directive,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
UI_TRACK_DIRECTIVE = "ui-track"
|
|
46
|
+
"""Agent directive that triggers the full UI sub-flow.
|
|
47
|
+
|
|
48
|
+
The orchestrator runs the ``ui`` directive set's audit → design →
|
|
49
|
+
apply → review → polish sequence with ``state.contract`` as the
|
|
50
|
+
locked input. On rebound, ``state.ui_review.review_clean`` carries
|
|
51
|
+
the verdict the mixed step routes on.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
AMBIGUITIES: tuple[dict[str, str], ...] = (
|
|
55
|
+
{
|
|
56
|
+
"code": "upstream_contract_failed",
|
|
57
|
+
"trigger": "`plan` (contract) outcome is not `success`",
|
|
58
|
+
"resolution": "re-run the mixed flow from the start",
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"code": "contract_sentinel_missing",
|
|
62
|
+
"trigger": "state.contract.contract_confirmed is missing or False — "
|
|
63
|
+
"defense-in-depth check refuses to start the UI track",
|
|
64
|
+
"resolution": "loop back to the contract step; user must confirm "
|
|
65
|
+
"the data_model + api_surface lock before UI work begins",
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"code": "ui_track_not_started",
|
|
69
|
+
"trigger": "state.ui_review is empty or missing `review_clean` — "
|
|
70
|
+
"the UI sub-flow has not run yet",
|
|
71
|
+
"resolution": "agent directive `ui-track` → orchestrator runs "
|
|
72
|
+
"audit → design → apply → review → polish with the contract as "
|
|
73
|
+
"immutable input",
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"code": "ui_track_review_unclean",
|
|
77
|
+
"trigger": "UI sub-flow finished but `review_clean` is False — "
|
|
78
|
+
"polish ceiling reached or findings remain",
|
|
79
|
+
"resolution": "user picks re-run / hand off / abort; polish-ceiling "
|
|
80
|
+
"semantics already fired inside the UI track",
|
|
81
|
+
},
|
|
82
|
+
)
|
|
83
|
+
"""Declared ambiguity surfaces. Every BLOCKED return maps to one code."""
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def run(state: DeliveryState) -> StepResult:
|
|
87
|
+
"""Delegate the UI sub-flow once the contract is locked."""
|
|
88
|
+
if state.outcomes.get("plan") != Outcome.SUCCESS.value:
|
|
89
|
+
return _blocked_on_precondition(state)
|
|
90
|
+
|
|
91
|
+
if not _contract_confirmed(state):
|
|
92
|
+
return _blocked_on_contract_sentinel(state)
|
|
93
|
+
|
|
94
|
+
review = state.ui_review if isinstance(state.ui_review, dict) else None
|
|
95
|
+
if review is None or "review_clean" not in review:
|
|
96
|
+
return _delegate_to_ui_track(state)
|
|
97
|
+
|
|
98
|
+
if review.get("review_clean") is True:
|
|
99
|
+
return StepResult(outcome=Outcome.SUCCESS)
|
|
100
|
+
|
|
101
|
+
return _halt_review_unclean(state, review)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _contract_confirmed(state: DeliveryState) -> bool:
|
|
105
|
+
"""True when the contract sentinel is explicitly ``True``.
|
|
106
|
+
|
|
107
|
+
Mirrors the gate in :func:`work_engine.directives.mixed.contract.run`
|
|
108
|
+
so a hand-rolled state file (or a partial rebound) cannot race the
|
|
109
|
+
UI track ahead of the user's contract sign-off.
|
|
110
|
+
"""
|
|
111
|
+
contract = state.contract
|
|
112
|
+
if not isinstance(contract, dict):
|
|
113
|
+
return False
|
|
114
|
+
return contract.get("contract_confirmed") is True
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _preview_input(state: DeliveryState) -> str:
|
|
118
|
+
"""One-line preview of the original input for halt bodies."""
|
|
119
|
+
data = state.ticket or {}
|
|
120
|
+
raw = data.get("raw")
|
|
121
|
+
if isinstance(raw, str) and raw.strip():
|
|
122
|
+
text = " ".join(raw.split())
|
|
123
|
+
else:
|
|
124
|
+
title = data.get("title")
|
|
125
|
+
text = title if isinstance(title, str) else (data.get("id") or "(no title)")
|
|
126
|
+
if len(text) <= 80:
|
|
127
|
+
return text
|
|
128
|
+
return text[:79].rstrip() + "\u2026"
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _blocked_on_precondition(state: DeliveryState) -> StepResult:
|
|
132
|
+
"""BLOCKED halt — upstream contract step did not succeed."""
|
|
133
|
+
return StepResult(
|
|
134
|
+
outcome=Outcome.BLOCKED,
|
|
135
|
+
questions=[
|
|
136
|
+
"> Mixed `ui` step gated on the contract step; the contract "
|
|
137
|
+
"outcome is not success.",
|
|
138
|
+
"> 1. Re-run \u2014 restart the mixed flow from the start",
|
|
139
|
+
"> 2. Abort \u2014 drop this request",
|
|
140
|
+
],
|
|
141
|
+
message="ui step gated on plan; upstream contract outcome is not success.",
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _blocked_on_contract_sentinel(state: DeliveryState) -> StepResult:
|
|
146
|
+
"""BLOCKED halt — contract sentinel missing despite plan success.
|
|
147
|
+
|
|
148
|
+
Defense-in-depth: the dispatcher should not advance here without
|
|
149
|
+
``contract_confirmed=True``, but a partial state-file edit could
|
|
150
|
+
skip the sentinel. Refuse loudly so the bypass is visible.
|
|
151
|
+
"""
|
|
152
|
+
return StepResult(
|
|
153
|
+
outcome=Outcome.BLOCKED,
|
|
154
|
+
questions=[
|
|
155
|
+
"> Contract sentinel missing \u2014 `state.contract.contract_confirmed` "
|
|
156
|
+
"is not `True`. The UI track will not start until the data "
|
|
157
|
+
"model and API surface are locked and confirmed.",
|
|
158
|
+
"> 1. Loop back \u2014 re-enter the contract step and confirm",
|
|
159
|
+
"> 2. Abort \u2014 drop this mixed request",
|
|
160
|
+
],
|
|
161
|
+
message=(
|
|
162
|
+
"ui step refused; contract_confirmed sentinel missing despite plan success."
|
|
163
|
+
),
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _delegate_to_ui_track(state: DeliveryState) -> StepResult:
|
|
168
|
+
"""Halt with an agent directive so the orchestrator runs the UI track."""
|
|
169
|
+
preview = _preview_input(state)
|
|
170
|
+
contract = state.contract if isinstance(state.contract, dict) else {}
|
|
171
|
+
data_model = contract.get("data_model") or []
|
|
172
|
+
api_surface = contract.get("api_surface") or []
|
|
173
|
+
entity_count = len(data_model) if isinstance(data_model, list) else 0
|
|
174
|
+
endpoint_count = len(api_surface) if isinstance(api_surface, list) else 0
|
|
175
|
+
|
|
176
|
+
return StepResult(
|
|
177
|
+
outcome=Outcome.BLOCKED,
|
|
178
|
+
questions=[
|
|
179
|
+
agent_directive(UI_TRACK_DIRECTIVE),
|
|
180
|
+
f"> Input: {preview}",
|
|
181
|
+
"> Contract is locked. Handing off to the UI track:",
|
|
182
|
+
f"> - Entities: {entity_count}",
|
|
183
|
+
f"> - Endpoints / actions: {endpoint_count}",
|
|
184
|
+
"> The UI track runs audit \u2192 design \u2192 apply \u2192 "
|
|
185
|
+
"review \u2192 polish with the contract as immutable input. "
|
|
186
|
+
"No new entities or endpoints \u2014 the contract drives the UI shape.",
|
|
187
|
+
"> 1. Continue \u2014 run the UI track",
|
|
188
|
+
"> 2. Abort \u2014 drop this mixed request",
|
|
189
|
+
],
|
|
190
|
+
message="Mixed contract locked; delegating UI sub-flow to ui-track.",
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _halt_review_unclean(
|
|
195
|
+
state: DeliveryState,
|
|
196
|
+
review: dict[str, Any],
|
|
197
|
+
) -> StepResult:
|
|
198
|
+
"""BLOCKED halt — UI sub-flow finished but review is not clean.
|
|
199
|
+
|
|
200
|
+
Polish-ceiling semantics fire inside the UI track itself (Phase 3
|
|
201
|
+
Step 5). This halt is the mixed-flow's escalation when the UI
|
|
202
|
+
track gave up: the user picks re-run / hand off / abort instead
|
|
203
|
+
of the engine looping forever.
|
|
204
|
+
"""
|
|
205
|
+
findings = review.get("findings") or []
|
|
206
|
+
finding_count = len(findings) if isinstance(findings, list) else 0
|
|
207
|
+
lines = [
|
|
208
|
+
f"> UI track finished but review is not clean. Findings remaining: {finding_count}.",
|
|
209
|
+
"> Polish ceiling already fired inside the UI track \u2014 the "
|
|
210
|
+
"engine cannot resolve the remaining findings without a user "
|
|
211
|
+
"decision.",
|
|
212
|
+
"> 1. Re-run UI track \u2014 hand back to `ui-track` for another "
|
|
213
|
+
"audit \u2192 design pass (rare; usually means the contract changed)",
|
|
214
|
+
"> 2. Hand off \u2014 ship as-is and let a human resolve the "
|
|
215
|
+
"remaining findings outside the engine",
|
|
216
|
+
"> 3. Abort \u2014 drop this mixed request",
|
|
217
|
+
]
|
|
218
|
+
return StepResult(
|
|
219
|
+
outcome=Outcome.BLOCKED,
|
|
220
|
+
questions=lines,
|
|
221
|
+
message=(
|
|
222
|
+
f"Mixed ui step halted; UI track review unclean ({finding_count} findings)."
|
|
223
|
+
),
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
__all__ = [
|
|
228
|
+
"AMBIGUITIES",
|
|
229
|
+
"UI_TRACK_DIRECTIVE",
|
|
230
|
+
"run",
|
|
231
|
+
]
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""UI directive set — Phase 6 wires every slot to a working handler.
|
|
2
|
+
|
|
3
|
+
Phase 1 of ``agents/roadmaps/road-to-product-ui-track.md`` landed the
|
|
4
|
+
intent classifier; Phase 2 promoted ``refine`` to the real audit gate
|
|
5
|
+
(:mod:`.audit`); Phase 3 added the four design / apply / review / polish
|
|
6
|
+
handlers; Phase 6 retires the deferral stub by wiring the remaining
|
|
7
|
+
``memory``, ``plan``, and ``report`` slots — the first two as
|
|
8
|
+
:mod:`._passthrough` (the UI track has no memory retrieval, and the
|
|
9
|
+
design brief IS the plan), and the third as a re-export of
|
|
10
|
+
:func:`work_engine.directives.backend.report.run` (the renderer is pure
|
|
11
|
+
and state-driven, so the same Markdown contract serves both tracks).
|
|
12
|
+
|
|
13
|
+
The eight-step shape mirrors :mod:`work_engine.directives.backend`:
|
|
14
|
+
|
|
15
|
+
- ``refine`` → :mod:`.audit` — existing-UI inventory gate.
|
|
16
|
+
- ``memory`` → :mod:`._passthrough` — UI track does not consult memory.
|
|
17
|
+
- ``analyze`` → :mod:`.design` — produces the locked design brief.
|
|
18
|
+
- ``plan`` → :mod:`._passthrough` — design brief is the plan.
|
|
19
|
+
- ``implement`` → :mod:`.apply` — stack-dispatched render of the brief.
|
|
20
|
+
- ``test`` → :mod:`.review` — design-review pass produces findings.
|
|
21
|
+
- ``verify`` → :mod:`.polish` — bounded fix loop (≤ 2 rounds).
|
|
22
|
+
- ``report`` → :mod:`work_engine.directives.backend.report` — shared
|
|
23
|
+
delivery-Markdown renderer.
|
|
24
|
+
"""
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
from collections.abc import Mapping
|
|
28
|
+
|
|
29
|
+
from ...delivery_state import Step
|
|
30
|
+
from ..backend import report
|
|
31
|
+
from . import _passthrough, apply, audit, design, polish, review
|
|
32
|
+
|
|
33
|
+
DIRECTIVE_SET_NAME = "ui"
|
|
34
|
+
"""External name carried in ``state.directive_set`` for this set."""
|
|
35
|
+
|
|
36
|
+
ROADMAP = "agents/roadmaps/road-to-product-ui-track.md"
|
|
37
|
+
"""Roadmap that promoted the deferral stub to fully wired handlers."""
|
|
38
|
+
|
|
39
|
+
SUPPORTED_KINDS: tuple[str, ...] = ("ticket", "prompt", "diff", "file")
|
|
40
|
+
"""Input kinds this directive set knows how to handle.
|
|
41
|
+
|
|
42
|
+
Phase 1 wires every UI-classifiable input shape (ticket prose,
|
|
43
|
+
free-form prompt, ``diff`` / ``file`` improve-this-screen envelopes)
|
|
44
|
+
through to this set; Phase 3's design / apply / review / polish gates
|
|
45
|
+
keep the same tuple so input-routing stays unchanged.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _build_step_map() -> dict[str, Step]:
|
|
50
|
+
"""Wire the eight-step dispatcher slots for the UI set.
|
|
51
|
+
|
|
52
|
+
``refine`` runs audit; ``memory`` and ``plan`` are pass-through
|
|
53
|
+
no-ops; ``analyze`` runs design; ``implement`` runs apply; ``test``
|
|
54
|
+
runs review; ``verify`` runs polish; ``report`` re-uses the shared
|
|
55
|
+
backend renderer. The mapping is rebuilt per call (cheap; the
|
|
56
|
+
dispatcher invokes :func:`get_steps` once per run).
|
|
57
|
+
"""
|
|
58
|
+
passthrough = _passthrough.run
|
|
59
|
+
return {
|
|
60
|
+
"refine": audit.run,
|
|
61
|
+
"memory": passthrough,
|
|
62
|
+
"analyze": design.run,
|
|
63
|
+
"plan": passthrough,
|
|
64
|
+
"implement": apply.run,
|
|
65
|
+
"test": review.run,
|
|
66
|
+
"verify": polish.run,
|
|
67
|
+
"report": report.run,
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def get_steps() -> Mapping[str, Step]:
|
|
72
|
+
"""Return the ``{step_name: handler}`` mapping the dispatcher walks.
|
|
73
|
+
|
|
74
|
+
Mirrors :func:`work_engine.directives.backend.get_steps`.
|
|
75
|
+
"""
|
|
76
|
+
return _build_step_map()
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def all_ambiguities() -> dict[str, tuple[dict[str, str], ...]]:
|
|
80
|
+
"""Per-step ambiguity declarations.
|
|
81
|
+
|
|
82
|
+
Mirrors :func:`work_engine.directives.backend.all_ambiguities`.
|
|
83
|
+
Each working handler re-exports its own ``AMBIGUITIES`` tuple; the
|
|
84
|
+
pass-through slots re-export :data:`_passthrough.AMBIGUITIES` (an
|
|
85
|
+
empty tuple) so doc generators see a uniform shape across all
|
|
86
|
+
eight steps. ``report`` borrows the backend renderer's surface.
|
|
87
|
+
"""
|
|
88
|
+
passthrough = _passthrough.AMBIGUITIES
|
|
89
|
+
return {
|
|
90
|
+
"refine": audit.AMBIGUITIES,
|
|
91
|
+
"memory": passthrough,
|
|
92
|
+
"analyze": design.AMBIGUITIES,
|
|
93
|
+
"plan": passthrough,
|
|
94
|
+
"implement": apply.AMBIGUITIES,
|
|
95
|
+
"test": review.AMBIGUITIES,
|
|
96
|
+
"verify": polish.AMBIGUITIES,
|
|
97
|
+
"report": report.AMBIGUITIES,
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
__all__ = [
|
|
102
|
+
"DIRECTIVE_SET_NAME",
|
|
103
|
+
"ROADMAP",
|
|
104
|
+
"SUPPORTED_KINDS",
|
|
105
|
+
"all_ambiguities",
|
|
106
|
+
"apply",
|
|
107
|
+
"audit",
|
|
108
|
+
"design",
|
|
109
|
+
"get_steps",
|
|
110
|
+
"polish",
|
|
111
|
+
"report",
|
|
112
|
+
"review",
|
|
113
|
+
]
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Pass-through handler for UI directive slots that have no work.
|
|
2
|
+
|
|
3
|
+
Phase 6 of ``agents/roadmaps/road-to-product-ui-track.md`` retired the
|
|
4
|
+
Phase 3 deferral stub once design / apply / review / polish landed. Two
|
|
5
|
+
slots remain semantically empty for the UI track:
|
|
6
|
+
|
|
7
|
+
- ``memory`` — the UI track does not consult the four memory types the
|
|
8
|
+
backend retrieves over (``domain-invariants``, ``architecture-decisions``,
|
|
9
|
+
``incident-learnings``, ``historical-patterns``). UI work pivots on the
|
|
10
|
+
audit findings in ``state.ui_audit`` instead, which the audit gate has
|
|
11
|
+
already populated by the time this handler runs.
|
|
12
|
+
- ``plan`` — :mod:`.design` produces the locked design brief that
|
|
13
|
+
:mod:`.apply` follows verbatim. The brief IS the plan; a separate plan
|
|
14
|
+
step would duplicate state with no user-visible payoff.
|
|
15
|
+
|
|
16
|
+
Both slots therefore return :data:`Outcome.SUCCESS` without writing to
|
|
17
|
+
state. The dispatcher advances to the next slot and the locked transcripts
|
|
18
|
+
record the no-op cycle as a clean rebound.
|
|
19
|
+
|
|
20
|
+
This module is ``directives/ui``-internal — the underscore prefix marks
|
|
21
|
+
it as a private slot filler, not a published handler. The backend track
|
|
22
|
+
keeps its own real ``memory`` and ``plan`` modules.
|
|
23
|
+
"""
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
from ...delivery_state import DeliveryState, Outcome, StepResult
|
|
27
|
+
|
|
28
|
+
AMBIGUITIES: tuple[dict[str, str], ...] = ()
|
|
29
|
+
"""Pass-through never blocks — empty surface, declared intent."""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def run(state: DeliveryState) -> StepResult:
|
|
33
|
+
"""Return ``SUCCESS`` without mutating state.
|
|
34
|
+
|
|
35
|
+
The handler is intentionally pure and side-effect-free: it neither
|
|
36
|
+
reads nor writes ``state``. The dispatcher records the step as
|
|
37
|
+
successful in ``state.outcomes`` (its own bookkeeping) and advances
|
|
38
|
+
to the next slot.
|
|
39
|
+
"""
|
|
40
|
+
del state # explicitly unused — the slot is a no-op by design
|
|
41
|
+
return StepResult(outcome=Outcome.SUCCESS)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
__all__ = ["AMBIGUITIES", "run"]
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
"""``apply`` step — stack-dispatched UI implementation.
|
|
2
|
+
|
|
3
|
+
Phase 3 Step 2 of ``agents/roadmaps/road-to-product-ui-track.md``: the
|
|
4
|
+
apply step turns the locked design brief into actual files. Routes on
|
|
5
|
+
``state.stack.frontend`` to the appropriate implementation skill bundle:
|
|
6
|
+
|
|
7
|
+
- ``blade-livewire-flux`` → ``ui-apply-blade-livewire-flux`` (composes
|
|
8
|
+
``flux`` + ``livewire`` + ``blade-ui``)
|
|
9
|
+
- ``react-shadcn`` → ``ui-apply-react-shadcn`` (new skill, Phase 3 Step 3)
|
|
10
|
+
- ``vue`` → ``ui-apply-vue``
|
|
11
|
+
- ``plain`` → ``ui-apply-plain`` (``blade-ui`` + Tailwind base)
|
|
12
|
+
|
|
13
|
+
Routes on ``state.ticket["ui_apply"]`` shape:
|
|
14
|
+
|
|
15
|
+
- **Empty / None** — first pass. Emit the stack-specific
|
|
16
|
+
``@agent-directive:`` halt; on the rebound the agent writes the
|
|
17
|
+
apply envelope back.
|
|
18
|
+
- **Populated, rendered text contains placeholder patterns** — emit a
|
|
19
|
+
rejection halt. The design-brief lock failed; placeholder strings
|
|
20
|
+
(``<placeholder>``, ``Lorem``, ``TODO:``) must never reach apply
|
|
21
|
+
output. Halt forces the agent to re-render with the locked microcopy.
|
|
22
|
+
- **Populated, well-formed** — record changes from the envelope,
|
|
23
|
+
return ``SUCCESS``.
|
|
24
|
+
|
|
25
|
+
Apply does **not** re-validate the design brief itself — that is the
|
|
26
|
+
design step's job. It validates the *output* against the brief's
|
|
27
|
+
microcopy lock so a mid-loop hallucination is caught at the boundary.
|
|
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
|
+
from .design import PLACEHOLDER_PATTERNS
|
|
40
|
+
|
|
41
|
+
STACK_DIRECTIVES: dict[str, str] = {
|
|
42
|
+
"blade-livewire-flux": "ui-apply-blade-livewire-flux",
|
|
43
|
+
"react-shadcn": "ui-apply-react-shadcn",
|
|
44
|
+
"vue": "ui-apply-vue",
|
|
45
|
+
"plain": "ui-apply-plain",
|
|
46
|
+
}
|
|
47
|
+
"""Map ``state.stack.frontend`` → agent-directive skill name.
|
|
48
|
+
|
|
49
|
+
Mirrors the heuristic table in :mod:`work_engine.stack.detect`. An
|
|
50
|
+
unknown stack falls through to ``ui-apply-plain`` (Tailwind defaults)
|
|
51
|
+
rather than raising — a wrong skill pick is recoverable, a crash
|
|
52
|
+
mid-dispatch is not.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
DEFAULT_DIRECTIVE = "ui-apply-plain"
|
|
56
|
+
"""Fallback directive when ``state.stack`` is missing or malformed."""
|
|
57
|
+
|
|
58
|
+
AMBIGUITIES: tuple[dict[str, str], ...] = (
|
|
59
|
+
{
|
|
60
|
+
"code": "apply_envelope_missing",
|
|
61
|
+
"trigger": "state.ticket['ui_apply'] unset — first pass, "
|
|
62
|
+
"stack-specific skill has not run yet",
|
|
63
|
+
"resolution": "agent directive `ui-apply-<stack>` → skill "
|
|
64
|
+
"bundle implements the brief and writes the envelope back",
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"code": "apply_placeholders_in_output",
|
|
68
|
+
"trigger": "rendered text in apply envelope contains "
|
|
69
|
+
"placeholder patterns (<placeholder>, Lorem, TODO:, TBD, XXX) "
|
|
70
|
+
"— design-brief lock failed mid-loop",
|
|
71
|
+
"resolution": "agent re-renders the components with the locked "
|
|
72
|
+
"microcopy verbatim from state.ui_design.microcopy",
|
|
73
|
+
},
|
|
74
|
+
)
|
|
75
|
+
"""Declared ambiguity surfaces for this step."""
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def run(state: DeliveryState) -> StepResult:
|
|
79
|
+
"""Apply the stack-dispatched implementation gate."""
|
|
80
|
+
envelope = _apply_envelope(state)
|
|
81
|
+
if envelope is None:
|
|
82
|
+
return _delegate_to_stack_skill(state)
|
|
83
|
+
|
|
84
|
+
violations = _placeholder_violations_in_output(envelope)
|
|
85
|
+
if violations:
|
|
86
|
+
return _halt_placeholders(state, violations)
|
|
87
|
+
|
|
88
|
+
_record_changes(state, envelope)
|
|
89
|
+
return StepResult(outcome=Outcome.SUCCESS)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _apply_envelope(state: DeliveryState) -> dict[str, Any] | None:
|
|
93
|
+
"""Return the agent-written ``ui_apply`` envelope, or ``None``."""
|
|
94
|
+
data = state.ticket or {}
|
|
95
|
+
envelope = data.get("ui_apply")
|
|
96
|
+
if isinstance(envelope, dict) and envelope:
|
|
97
|
+
return envelope
|
|
98
|
+
return None
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _resolve_directive(state: DeliveryState) -> str:
|
|
102
|
+
"""Pick the agent directive for the project's frontend stack."""
|
|
103
|
+
stack = getattr(state, "stack", None) or {}
|
|
104
|
+
if isinstance(stack, dict):
|
|
105
|
+
frontend = stack.get("frontend")
|
|
106
|
+
if isinstance(frontend, str) and frontend in STACK_DIRECTIVES:
|
|
107
|
+
return STACK_DIRECTIVES[frontend]
|
|
108
|
+
return DEFAULT_DIRECTIVE
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _placeholder_violations_in_output(
|
|
112
|
+
envelope: dict[str, Any],
|
|
113
|
+
) -> list[str]:
|
|
114
|
+
"""Return paths into ``envelope['rendered']`` whose text matches
|
|
115
|
+
a placeholder pattern.
|
|
116
|
+
|
|
117
|
+
The agent writes ``rendered`` as a flat ``{path: text}`` map (or
|
|
118
|
+
nested dict of strings). Walked recursively, lower-cased, and
|
|
119
|
+
matched against :data:`PLACEHOLDER_PATTERNS` from the design step
|
|
120
|
+
so the gate stays consistent with the brief's lock.
|
|
121
|
+
"""
|
|
122
|
+
rendered = envelope.get("rendered")
|
|
123
|
+
if not isinstance(rendered, dict):
|
|
124
|
+
return []
|
|
125
|
+
violations: list[str] = []
|
|
126
|
+
_walk_rendered(rendered, prefix="", violations=violations)
|
|
127
|
+
return violations
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _walk_rendered(
|
|
131
|
+
node: dict[str, Any],
|
|
132
|
+
*,
|
|
133
|
+
prefix: str,
|
|
134
|
+
violations: list[str],
|
|
135
|
+
) -> None:
|
|
136
|
+
"""Recursive helper for :func:`_placeholder_violations_in_output`."""
|
|
137
|
+
for key, value in node.items():
|
|
138
|
+
path = f"{prefix}.{key}" if prefix else str(key)
|
|
139
|
+
if isinstance(value, dict):
|
|
140
|
+
_walk_rendered(value, prefix=path, violations=violations)
|
|
141
|
+
continue
|
|
142
|
+
if not isinstance(value, str):
|
|
143
|
+
continue
|
|
144
|
+
lowered = value.lower()
|
|
145
|
+
for pattern in PLACEHOLDER_PATTERNS:
|
|
146
|
+
if pattern in lowered:
|
|
147
|
+
violations.append(path)
|
|
148
|
+
break
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _delegate_to_stack_skill(state: DeliveryState) -> StepResult:
|
|
152
|
+
"""First-pass halt — emit the stack-specific apply directive."""
|
|
153
|
+
directive = _resolve_directive(state)
|
|
154
|
+
stack_label = _stack_label(state)
|
|
155
|
+
return StepResult(
|
|
156
|
+
outcome=Outcome.BLOCKED,
|
|
157
|
+
questions=[
|
|
158
|
+
agent_directive(directive),
|
|
159
|
+
f"> Stack: `{stack_label}`. Implementing the locked design brief.",
|
|
160
|
+
"> Microcopy is locked \u2014 every button label, empty-state "
|
|
161
|
+
"message, and validation message must come verbatim from "
|
|
162
|
+
"`state.ui_design.microcopy`.",
|
|
163
|
+
"> 1. Continue \u2014 implement the brief and write a "
|
|
164
|
+
"`ui_apply` envelope back into state.ticket "
|
|
165
|
+
"(rendered: {path: text}, files: [...])",
|
|
166
|
+
"> 2. Abort \u2014 drop this UI request",
|
|
167
|
+
],
|
|
168
|
+
message=(
|
|
169
|
+
f"UI apply pending; delegating to `{directive}` for "
|
|
170
|
+
f"stack `{stack_label}`."
|
|
171
|
+
),
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _halt_placeholders(
|
|
176
|
+
state: DeliveryState,
|
|
177
|
+
violations: list[str],
|
|
178
|
+
) -> StepResult:
|
|
179
|
+
"""BLOCKED halt — rendered output still carries placeholder patterns."""
|
|
180
|
+
directive = _resolve_directive(state)
|
|
181
|
+
lines = [
|
|
182
|
+
agent_directive(directive),
|
|
183
|
+
"> Apply rejected: rendered output contains placeholder strings. "
|
|
184
|
+
"The design-brief microcopy lock failed mid-loop.",
|
|
185
|
+
"> Affected paths in `ui_apply.rendered`:",
|
|
186
|
+
]
|
|
187
|
+
for path in violations:
|
|
188
|
+
lines.append(f"> - `{path}`")
|
|
189
|
+
lines.append(
|
|
190
|
+
"> Re-render with the locked microcopy verbatim from "
|
|
191
|
+
"`state.ui_design.microcopy`; apply will not write placeholder text.",
|
|
192
|
+
)
|
|
193
|
+
return StepResult(
|
|
194
|
+
outcome=Outcome.BLOCKED,
|
|
195
|
+
questions=lines,
|
|
196
|
+
message=(
|
|
197
|
+
f"UI apply rejected: {len(violations)} placeholder "
|
|
198
|
+
f"violation(s) in rendered output."
|
|
199
|
+
),
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def _record_changes(
|
|
204
|
+
state: DeliveryState,
|
|
205
|
+
envelope: dict[str, Any],
|
|
206
|
+
) -> None:
|
|
207
|
+
"""Append one ``state.changes`` entry per file in the apply envelope."""
|
|
208
|
+
files = envelope.get("files")
|
|
209
|
+
if not isinstance(files, list):
|
|
210
|
+
files = []
|
|
211
|
+
summary = envelope.get("summary") or "ui apply"
|
|
212
|
+
stack_label = _stack_label(state)
|
|
213
|
+
for path in files:
|
|
214
|
+
if not isinstance(path, str) or not path:
|
|
215
|
+
continue
|
|
216
|
+
state.changes.append(
|
|
217
|
+
{
|
|
218
|
+
"kind": "ui",
|
|
219
|
+
"stack": stack_label,
|
|
220
|
+
"file": path,
|
|
221
|
+
"summary": summary,
|
|
222
|
+
},
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _stack_label(state: DeliveryState) -> str:
|
|
227
|
+
"""Return the frontend stack label, defaulting to ``plain``."""
|
|
228
|
+
stack = getattr(state, "stack", None) or {}
|
|
229
|
+
if isinstance(stack, dict):
|
|
230
|
+
frontend = stack.get("frontend")
|
|
231
|
+
if isinstance(frontend, str) and frontend:
|
|
232
|
+
return frontend
|
|
233
|
+
return "plain"
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
__all__ = [
|
|
237
|
+
"AMBIGUITIES",
|
|
238
|
+
"DEFAULT_DIRECTIVE",
|
|
239
|
+
"STACK_DIRECTIVES",
|
|
240
|
+
"run",
|
|
241
|
+
]
|