@event4u/agent-config 8.4.0 → 8.4.1

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 (126) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +6 -0
  3. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  4. package/dist/discovery/deprecation-report.md +1 -1
  5. package/dist/discovery/discovery-manifest.json +1 -1
  6. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  7. package/dist/discovery/discovery-manifest.summary.md +1 -1
  8. package/dist/discovery/orphan-report.md +1 -1
  9. package/dist/discovery/packs.json +1 -1
  10. package/dist/discovery/trust-report.md +1 -1
  11. package/dist/discovery/workspaces.json +1 -1
  12. package/dist/mcp/registry-manifest.json +1 -1
  13. package/package.json +4 -1
  14. package/src/agent-src/README.md +64 -0
  15. package/src/agent-src/scripts/archive_completed_roadmaps.ts +497 -0
  16. package/src/agent-src/scripts/scan-seeder-violations.php +145 -0
  17. package/src/agent-src/scripts/update_roadmap_progress.ts +1087 -0
  18. package/src/agent-src/templates/AGENTS.md +34 -0
  19. package/src/agent-src/templates/scripts/README.md +195 -0
  20. package/src/agent-src/templates/scripts/check_memory.ts +662 -0
  21. package/src/agent-src/templates/scripts/check_memory_proposal.ts +370 -0
  22. package/src/agent-src/templates/scripts/historical-bug-patterns.example.yml +84 -0
  23. package/src/agent-src/templates/scripts/implement_ticket/__main__.ts +27 -0
  24. package/src/agent-src/templates/scripts/memory_hash.ts +355 -0
  25. package/src/agent-src/templates/scripts/memory_lookup.ts +1089 -0
  26. package/src/agent-src/templates/scripts/memory_report.ts +868 -0
  27. package/src/agent-src/templates/scripts/memory_signal.ts +444 -0
  28. package/src/agent-src/templates/scripts/memory_status.ts +215 -0
  29. package/src/agent-src/templates/scripts/ownership-map.example.yml +87 -0
  30. package/src/agent-src/templates/scripts/pr-risk-config.example.yml +76 -0
  31. package/src/agent-src/templates/scripts/pr_review_routing.ts +544 -0
  32. package/src/agent-src/templates/scripts/pr_risk_review.ts +0 -0
  33. package/src/agent-src/templates/scripts/telemetry/aggregator.ts +0 -0
  34. package/src/agent-src/templates/scripts/telemetry/boundary.ts +164 -0
  35. package/src/agent-src/templates/scripts/telemetry/engagement.ts +479 -0
  36. package/src/agent-src/templates/scripts/telemetry/report_renderer.ts +394 -0
  37. package/src/agent-src/templates/scripts/telemetry/settings.ts +210 -0
  38. package/src/agent-src/templates/scripts/telemetry_record.ts +276 -0
  39. package/src/agent-src/templates/scripts/telemetry_report.ts +211 -0
  40. package/src/agent-src/templates/scripts/telemetry_status.ts +333 -0
  41. package/src/agent-src/templates/scripts/tier_usage_report.ts +619 -0
  42. package/src/agent-src/templates/scripts/work_engine/__main__.ts +14 -0
  43. package/src/agent-src/templates/scripts/work_engine/_lib/agent_settings.ts +1261 -0
  44. package/src/agent-src/templates/scripts/work_engine/_lib/user_global_paths.ts +329 -0
  45. package/src/agent-src/templates/scripts/work_engine/cli.ts +206 -0
  46. package/src/agent-src/templates/scripts/work_engine/cli_args.ts +249 -0
  47. package/src/agent-src/templates/scripts/work_engine/delivery_state.ts +225 -0
  48. package/src/agent-src/templates/scripts/work_engine/directives/backend/analyze.ts +125 -0
  49. package/src/agent-src/templates/scripts/work_engine/directives/backend/implement.ts +189 -0
  50. package/src/agent-src/templates/scripts/work_engine/directives/backend/index.ts +94 -0
  51. package/src/agent-src/templates/scripts/work_engine/directives/backend/memory.ts +193 -0
  52. package/src/agent-src/templates/scripts/work_engine/directives/backend/plan.ts +267 -0
  53. package/src/agent-src/templates/scripts/work_engine/directives/backend/refine.ts +518 -0
  54. package/src/agent-src/templates/scripts/work_engine/directives/backend/report.ts +379 -0
  55. package/src/agent-src/templates/scripts/work_engine/directives/backend/test.ts +268 -0
  56. package/src/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +258 -0
  57. package/src/agent-src/templates/scripts/work_engine/directives/index.ts +32 -0
  58. package/src/agent-src/templates/scripts/work_engine/directives/mixed/contract.ts +243 -0
  59. package/src/agent-src/templates/scripts/work_engine/directives/mixed/index.ts +108 -0
  60. package/src/agent-src/templates/scripts/work_engine/directives/mixed/stitch.ts +259 -0
  61. package/src/agent-src/templates/scripts/work_engine/directives/mixed/ui.ts +216 -0
  62. package/src/agent-src/templates/scripts/work_engine/directives/ui/_passthrough.ts +40 -0
  63. package/src/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +241 -0
  64. package/src/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +216 -0
  65. package/src/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +506 -0
  66. package/src/agent-src/templates/scripts/work_engine/directives/ui/design.ts +325 -0
  67. package/src/agent-src/templates/scripts/work_engine/directives/ui/index.ts +102 -0
  68. package/src/agent-src/templates/scripts/work_engine/directives/ui/polish.ts +462 -0
  69. package/src/agent-src/templates/scripts/work_engine/directives/ui/review.ts +474 -0
  70. package/src/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +352 -0
  71. package/src/agent-src/templates/scripts/work_engine/directives/ui_trivial/_skipped.ts +33 -0
  72. package/src/agent-src/templates/scripts/work_engine/directives/ui_trivial/apply.ts +213 -0
  73. package/src/agent-src/templates/scripts/work_engine/directives/ui_trivial/index.ts +111 -0
  74. package/src/agent-src/templates/scripts/work_engine/directives/ui_trivial/refine.ts +126 -0
  75. package/src/agent-src/templates/scripts/work_engine/directives/ui_trivial/report.ts +112 -0
  76. package/src/agent-src/templates/scripts/work_engine/directives/ui_trivial/test.ts +164 -0
  77. package/src/agent-src/templates/scripts/work_engine/dispatcher.ts +515 -0
  78. package/src/agent-src/templates/scripts/work_engine/emitters.ts +119 -0
  79. package/src/agent-src/templates/scripts/work_engine/errors.ts +24 -0
  80. package/src/agent-src/templates/scripts/work_engine/hook_bootstrap.ts +104 -0
  81. package/src/agent-src/templates/scripts/work_engine/hooks/builtin/_chat_history_base.ts +189 -0
  82. package/src/agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_append.ts +41 -0
  83. package/src/agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_halt_append.ts +89 -0
  84. package/src/agent-src/templates/scripts/work_engine/hooks/builtin/decision_gate.ts +193 -0
  85. package/src/agent-src/templates/scripts/work_engine/hooks/builtin/decision_trace.ts +304 -0
  86. package/src/agent-src/templates/scripts/work_engine/hooks/builtin/directive_set_guard.ts +110 -0
  87. package/src/agent-src/templates/scripts/work_engine/hooks/builtin/halt_surface_audit.ts +118 -0
  88. package/src/agent-src/templates/scripts/work_engine/hooks/builtin/index.ts +17 -0
  89. package/src/agent-src/templates/scripts/work_engine/hooks/builtin/memory_visibility.ts +161 -0
  90. package/src/agent-src/templates/scripts/work_engine/hooks/builtin/state_shape_validation.ts +45 -0
  91. package/src/agent-src/templates/scripts/work_engine/hooks/builtin/trace.ts +134 -0
  92. package/src/agent-src/templates/scripts/work_engine/hooks/context.ts +94 -0
  93. package/src/agent-src/templates/scripts/work_engine/hooks/events.ts +58 -0
  94. package/src/agent-src/templates/scripts/work_engine/hooks/exceptions.ts +85 -0
  95. package/src/agent-src/templates/scripts/work_engine/hooks/index.ts +27 -0
  96. package/src/agent-src/templates/scripts/work_engine/hooks/registry.ts +66 -0
  97. package/src/agent-src/templates/scripts/work_engine/hooks/runner.ts +90 -0
  98. package/src/agent-src/templates/scripts/work_engine/hooks/settings.ts +260 -0
  99. package/src/agent-src/templates/scripts/work_engine/input_builders.ts +260 -0
  100. package/src/agent-src/templates/scripts/work_engine/intent/classify.ts +466 -0
  101. package/src/agent-src/templates/scripts/work_engine/migration/v0_to_v1.ts +531 -0
  102. package/src/agent-src/templates/scripts/work_engine/orchestration.ts +366 -0
  103. package/src/agent-src/templates/scripts/work_engine/persona_policy.ts +97 -0
  104. package/src/agent-src/templates/scripts/work_engine/resolvers/diff.ts +135 -0
  105. package/src/agent-src/templates/scripts/work_engine/resolvers/file.ts +175 -0
  106. package/src/agent-src/templates/scripts/work_engine/resolvers/prompt.ts +115 -0
  107. package/src/agent-src/templates/scripts/work_engine/scoring/confidence.ts +415 -0
  108. package/src/agent-src/templates/scripts/work_engine/scoring/decision_engine.ts +466 -0
  109. package/src/agent-src/templates/scripts/work_engine/scoring/decision_trace.ts +298 -0
  110. package/src/agent-src/templates/scripts/work_engine/scoring/memory_visibility.ts +444 -0
  111. package/src/agent-src/templates/scripts/work_engine/stack/detect.ts +252 -0
  112. package/src/agent-src/templates/scripts/work_engine/stack/runner.ts +745 -0
  113. package/src/agent-src/templates/scripts/work_engine/state.ts +1151 -0
  114. package/src/agent-src/templates/scripts/work_engine/state_io.ts +413 -0
  115. package/src/install/atomic.ts +111 -0
  116. package/src/install/conflict.ts +226 -0
  117. package/src/install/detect.ts +280 -0
  118. package/src/install/paths.ts +90 -0
  119. package/src/install/plan.ts +205 -0
  120. package/src/install/preflight.ts +210 -0
  121. package/src/install/selectedTools.ts +52 -0
  122. package/src/install/toolDetection.ts +120 -0
  123. package/src/install/txlog.ts +161 -0
  124. package/src/install/types.ts +175 -0
  125. package/src/install/wizard-plan.ts +216 -0
  126. package/src/scripts/prepack-check.mjs +108 -5
@@ -0,0 +1,241 @@
1
+ /**
2
+ * `app_spec` step — greenfield grounding gate (the `memory` slot).
3
+ *
4
+ * TypeScript twin of `directives/ui/app_spec.py` (ADR-200 py2ts). Public API
5
+ * names stay snake_case to mirror the Python module 1:1.
6
+ *
7
+ * greenfield-scaffold Phase 2: before any scaffolding, derive the app shape and
8
+ * confirm it fast (disambiguation, not BDUF). The step occupies the UI set's
9
+ * `memory` slot, which runs **before** `analyze` (design); it replaces the
10
+ * former no-op pass-through there.
11
+ *
12
+ * The gate is **scoped to the greenfield-scaffold path only**. It acts only
13
+ * when `state.ui_audit` records `greenfield == True` and
14
+ * `greenfield_decision == "scaffold"`. Every other UI flow sees this slot as a
15
+ * clean `SUCCESS` no-op, so those flows stay byte-identical.
16
+ */
17
+ import {
18
+ type Any,
19
+ type DeliveryState,
20
+ Outcome,
21
+ StepResult,
22
+ agent_directive,
23
+ } from '../../delivery_state.js';
24
+
25
+ /** Declared ambiguity surfaces for this step. */
26
+ export const AMBIGUITIES: ReadonlyArray<Record<string, string>> = [
27
+ {
28
+ code: 'app_spec_missing',
29
+ trigger:
30
+ 'greenfield scaffold path and state.app_spec is None / ' +
31
+ 'empty / carries no `pages` — the app-spec skill has not derived ' +
32
+ 'the page-set yet',
33
+ resolution:
34
+ 'agent directive `app-spec` → skill derives ' +
35
+ '{pages, entity_model, flow_map} and writes them into ' +
36
+ 'state.app_spec (or the user bypasses with \'just scaffold\')',
37
+ },
38
+ {
39
+ code: 'app_spec_unconfirmed',
40
+ trigger:
41
+ 'greenfield scaffold path and state.app_spec carries a ' +
42
+ 'derived page-set but `confirmed` is not True and `bypassed` is ' +
43
+ 'not set — the user has not signed off on the derived shape',
44
+ resolution:
45
+ 'user confirms the derived page-set + entity model ' +
46
+ '(agent sets state.app_spec.confirmed = True), edits and re-runs, ' +
47
+ 'or bypasses (\'just scaffold\' → state.app_spec.bypassed = True)',
48
+ },
49
+ ];
50
+
51
+ function _isDict(value: Any): value is Record<string, Any> {
52
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
53
+ }
54
+
55
+ function _pyTruthy(value: Any): boolean {
56
+ if (value === null || value === undefined) return false;
57
+ if (typeof value === 'string') return value.length > 0;
58
+ if (typeof value === 'number') return value !== 0;
59
+ if (typeof value === 'boolean') return value;
60
+ if (Array.isArray(value)) return value.length > 0;
61
+ if (typeof value === 'object') return Object.keys(value).length > 0;
62
+ return true;
63
+ }
64
+
65
+ function pyStr(value: Any): string {
66
+ if (value === null || value === undefined) return 'None';
67
+ if (value === true) return 'True';
68
+ if (value === false) return 'False';
69
+ return String(value);
70
+ }
71
+
72
+ function _pyRStrip(s: string): string {
73
+ return s.replace(/\s+$/u, '');
74
+ }
75
+
76
+ /**
77
+ * Apply the greenfield app-spec grounding gate.
78
+ *
79
+ * No-op `SUCCESS` for every non-greenfield-scaffold flow; the confirm/bypass
80
+ * gate only engages when the audit recorded a `scaffold` greenfield decision.
81
+ */
82
+ export function run(state: DeliveryState): StepResult {
83
+ if (!_is_greenfield_scaffold(state)) {
84
+ return new StepResult({ outcome: Outcome.SUCCESS });
85
+ }
86
+
87
+ const spec = state.app_spec;
88
+
89
+ // Explicit bypass — "just scaffold" / fenced step. Honoured before any
90
+ // populated-check so the user can skip derivation entirely.
91
+ if (_isDict(spec) && _pyTruthy(spec['bypassed'])) {
92
+ return new StepResult({ outcome: Outcome.SUCCESS });
93
+ }
94
+
95
+ if (!_is_populated(spec)) {
96
+ return _delegate_to_app_spec_skill(state);
97
+ }
98
+
99
+ const specDict = spec as Record<string, Any>;
100
+ // Idempotent: a confirmed spec round-trips through SUCCESS without
101
+ // re-emitting the halt the user already answered.
102
+ if (specDict['confirmed'] === true) {
103
+ return new StepResult({ outcome: Outcome.SUCCESS });
104
+ }
105
+
106
+ return _halt_unconfirmed(state, specDict);
107
+ }
108
+
109
+ /**
110
+ * True when the audit recorded a `scaffold` greenfield decision.
111
+ *
112
+ * The gate is inert for every other flow: improve-existing, the `bare` /
113
+ * `external_reference` greenfield picks, and the `diff` / `file` envelopes all
114
+ * leave `ui_audit` without the `greenfield == True` +
115
+ * `greenfield_decision == "scaffold"` pair.
116
+ */
117
+ function _is_greenfield_scaffold(state: DeliveryState): boolean {
118
+ const audit = state.ui_audit;
119
+ if (!_isDict(audit)) return false;
120
+ return (
121
+ audit['greenfield'] === true &&
122
+ audit['greenfield_decision'] === 'scaffold'
123
+ );
124
+ }
125
+
126
+ /**
127
+ * True when `spec` carries a derived page-set.
128
+ *
129
+ * Non-dict and empty-dict shapes are treated as "skill has not run". The
130
+ * skill's first deliverable is the `pages` list, so its presence is the
131
+ * populated signal.
132
+ */
133
+ function _is_populated(spec: Any): boolean {
134
+ return _isDict(spec) && Array.isArray(spec['pages']);
135
+ }
136
+
137
+ /** Render a one-line preview of the input being grounded. */
138
+ function _preview_input(state: DeliveryState): string {
139
+ const data = (_isDict(state.ticket) ? state.ticket : {}) as Record<string, Any>;
140
+ const raw = data['raw'];
141
+ let text: string;
142
+ if (typeof raw === 'string' && raw.trim() !== '') {
143
+ text = raw.split(/\s+/u).filter((x) => x.length > 0).join(' ');
144
+ } else {
145
+ const title = data['title'];
146
+ if (typeof title === 'string') {
147
+ text = title;
148
+ } else {
149
+ const id = data['id'];
150
+ text = _pyTruthy(id) ? pyStr(id) : '(no title)';
151
+ }
152
+ }
153
+ if ([...text].length <= 80) {
154
+ return text;
155
+ }
156
+ return _pyRStrip([...text].slice(0, 79).join('')) + '…';
157
+ }
158
+
159
+ /** First-pass halt — emit the `app-spec` derivation directive. */
160
+ function _delegate_to_app_spec_skill(state: DeliveryState): StepResult {
161
+ const preview = _preview_input(state);
162
+ return new StepResult({
163
+ outcome: Outcome.BLOCKED,
164
+ questions: [
165
+ agent_directive('app-spec'),
166
+ `> Input: ${preview}`,
167
+ '> Greenfield scaffold — grounding the app shape before any ' +
168
+ 'skeleton is planned. Derive the page-set, entity model, and ' +
169
+ 'flow-map from the prompt.',
170
+ '> 1. Continue — derive {pages, entity_model, flow_map} ' +
171
+ 'into `state.app_spec`',
172
+ '> 2. Just scaffold — skip the app spec (set ' +
173
+ '`state.app_spec.bypassed = true` and go straight to scaffold)',
174
+ '> 3. Abort — drop this UI request',
175
+ '',
176
+ '**Recommendation: 1 — derive the spec** — a ' +
177
+ 'confirmed page-set keeps the multi-page scaffold coherent ' +
178
+ 'and is seconds of work. Caveat: flip to 2 if the prompt ' +
179
+ 'already pins an exact, single-screen shape.',
180
+ ],
181
+ message:
182
+ 'Greenfield app-spec missing; delegating to `app-spec` ' +
183
+ 'skill to derive the page-set before scaffold.',
184
+ });
185
+ }
186
+
187
+ /** BLOCKED halt — derived spec needs the lightweight confirm or bypass. */
188
+ function _halt_unconfirmed(state: DeliveryState, spec: Record<string, Any>): StepResult {
189
+ const rawPages = Array.isArray(spec['pages']) ? (spec['pages'] as Any[]) : [];
190
+ const pages = rawPages.filter((p) => typeof p === 'string' || _isDict(p));
191
+ const rawEntities = Array.isArray(spec['entity_model']) ? (spec['entity_model'] as Any[]) : [];
192
+ const entities = rawEntities.filter((e) => typeof e === 'string' || _isDict(e));
193
+ const lines: string[] = [
194
+ `> Input: ${_preview_input(state)}`,
195
+ '> Derived app spec — confirm the shape before scaffold:',
196
+ `> Pages (${pages.length}): ${_summarize(pages)}`,
197
+ `> Entities (${entities.length}): ${_summarize(entities)}`,
198
+ '> 1. Confirm — the derived page-set + entity model look right ' +
199
+ '(set `state.app_spec.confirmed = true`)',
200
+ '> 2. Edit — adjust the derived spec, then re-run the app-spec ' +
201
+ 'skill',
202
+ '> 3. Just scaffold — skip confirmation (set ' +
203
+ '`state.app_spec.bypassed = true`)',
204
+ '',
205
+ '**Recommendation: 1 — Confirm** — the derived shape is ' +
206
+ 'the cheapest point to catch a wrong page-set, before the scaffold ' +
207
+ 'plan locks routes and layout. Caveat: flip to 2 if a page or ' +
208
+ 'entity is missing / wrong.',
209
+ ];
210
+ return new StepResult({
211
+ outcome: Outcome.BLOCKED,
212
+ questions: lines,
213
+ message:
214
+ 'Greenfield app-spec derived; halting for lightweight confirm ' +
215
+ '(confirm / edit / bypass).',
216
+ });
217
+ }
218
+
219
+ /** Render up to three item names as a compact inline preview. */
220
+ function _summarize(items: Any[]): string {
221
+ const names: string[] = [];
222
+ for (const item of items.slice(0, 3)) {
223
+ if (typeof item === 'string') {
224
+ names.push(item);
225
+ } else if (_isDict(item)) {
226
+ // Python `item.get("name") or item.get("title") or item.get("path")`:
227
+ // first truthy by Python rules ([] / {} are falsy, unlike JS `||`).
228
+ const name = _pyTruthy(item['name'])
229
+ ? item['name']
230
+ : _pyTruthy(item['title'])
231
+ ? item['title']
232
+ : item['path'];
233
+ names.push(_pyTruthy(name) ? pyStr(name) : '(unnamed)');
234
+ }
235
+ }
236
+ if (names.length === 0) {
237
+ return '(none)';
238
+ }
239
+ const suffix = items.length > 3 ? ', …' : '';
240
+ return names.join(', ') + suffix;
241
+ }
@@ -0,0 +1,216 @@
1
+ /**
2
+ * `apply` step — stack-dispatched UI implementation.
3
+ *
4
+ * TypeScript twin of `directives/ui/apply.py` (ADR-200 py2ts). Public API
5
+ * names stay snake_case to mirror the Python module 1:1.
6
+ *
7
+ * The apply step turns the locked design brief into actual files. Routes on
8
+ * `state.stack.frontend` to the appropriate implementation skill bundle, and
9
+ * on `state.ticket["ui_apply"]` shape: first-pass delegation, placeholder
10
+ * rejection, or change recording. Apply validates the output against the
11
+ * brief's microcopy lock so a mid-loop hallucination is caught at the boundary.
12
+ */
13
+ import {
14
+ type Any,
15
+ type DeliveryState,
16
+ Outcome,
17
+ StepResult,
18
+ agent_directive,
19
+ } from '../../delivery_state.js';
20
+ import { PLACEHOLDER_PATTERNS } from './design.js';
21
+
22
+ /** Map `state.stack.frontend` → agent-directive skill name. */
23
+ export const STACK_DIRECTIVES: Record<string, string> = {
24
+ 'blade-livewire-flux': 'ui-apply-blade-livewire-flux',
25
+ 'react-shadcn': 'ui-apply-react-shadcn',
26
+ vue: 'ui-apply-vue',
27
+ plain: 'ui-apply-plain',
28
+ };
29
+
30
+ /** Fallback directive when `state.stack` is missing or malformed. */
31
+ export const DEFAULT_DIRECTIVE = 'ui-apply-plain';
32
+
33
+ export const AMBIGUITIES: ReadonlyArray<Record<string, string>> = [
34
+ {
35
+ code: 'apply_envelope_missing',
36
+ trigger:
37
+ "state.ticket['ui_apply'] unset — first pass, " +
38
+ 'stack-specific skill has not run yet',
39
+ resolution:
40
+ 'agent directive `ui-apply-<stack>` → skill ' +
41
+ 'bundle implements the brief and writes the envelope back',
42
+ },
43
+ {
44
+ code: 'apply_placeholders_in_output',
45
+ trigger:
46
+ 'rendered text in apply envelope contains ' +
47
+ 'placeholder patterns (<placeholder>, Lorem, TODO:, TBD, XXX) ' +
48
+ '— design-brief lock failed mid-loop',
49
+ resolution:
50
+ 'agent re-renders the components with the locked ' +
51
+ 'microcopy verbatim from state.ui_design.microcopy',
52
+ },
53
+ ];
54
+
55
+ function _isDict(value: Any): value is Record<string, Any> {
56
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
57
+ }
58
+
59
+ function _pyTruthy(value: Any): boolean {
60
+ if (value === null || value === undefined) return false;
61
+ if (typeof value === 'string') return value.length > 0;
62
+ if (typeof value === 'number') return value !== 0;
63
+ if (typeof value === 'boolean') return value;
64
+ if (Array.isArray(value)) return value.length > 0;
65
+ if (typeof value === 'object') return Object.keys(value).length > 0;
66
+ return true;
67
+ }
68
+
69
+ /** Apply the stack-dispatched implementation gate. */
70
+ export function run(state: DeliveryState): StepResult {
71
+ const envelope = _apply_envelope(state);
72
+ if (envelope === null) {
73
+ return _delegate_to_stack_skill(state);
74
+ }
75
+
76
+ const violations = _placeholder_violations_in_output(envelope);
77
+ if (violations.length > 0) {
78
+ return _halt_placeholders(state, violations);
79
+ }
80
+
81
+ _record_changes(state, envelope);
82
+ return new StepResult({ outcome: Outcome.SUCCESS });
83
+ }
84
+
85
+ /** Return the agent-written `ui_apply` envelope, or `null`. */
86
+ function _apply_envelope(state: DeliveryState): Record<string, Any> | null {
87
+ const data = _pyTruthy(state.ticket) ? state.ticket : {};
88
+ const envelope = (data as Record<string, Any>)['ui_apply'];
89
+ if (_isDict(envelope) && _pyTruthy(envelope)) {
90
+ return envelope;
91
+ }
92
+ return null;
93
+ }
94
+
95
+ /** Pick the agent directive for the project's frontend stack. */
96
+ function _resolve_directive(state: DeliveryState): string {
97
+ const stack = _pyTruthy(state.stack) ? state.stack : {};
98
+ if (_isDict(stack)) {
99
+ const frontend = stack['frontend'];
100
+ if (typeof frontend === 'string' && frontend in STACK_DIRECTIVES) {
101
+ return STACK_DIRECTIVES[frontend] as string;
102
+ }
103
+ }
104
+ return DEFAULT_DIRECTIVE;
105
+ }
106
+
107
+ /** Return paths into `envelope['rendered']` whose text matches a pattern. */
108
+ function _placeholder_violations_in_output(envelope: Record<string, Any>): string[] {
109
+ const rendered = envelope['rendered'];
110
+ if (!_isDict(rendered)) {
111
+ return [];
112
+ }
113
+ const violations: string[] = [];
114
+ _walk_rendered(rendered, '', violations);
115
+ return violations;
116
+ }
117
+
118
+ function _walk_rendered(node: Record<string, Any>, prefix: string, violations: string[]): void {
119
+ for (const key of Object.keys(node)) {
120
+ const value = node[key];
121
+ const path = prefix ? `${prefix}.${key}` : String(key);
122
+ if (_isDict(value)) {
123
+ _walk_rendered(value, path, violations);
124
+ continue;
125
+ }
126
+ if (typeof value !== 'string') {
127
+ continue;
128
+ }
129
+ const lowered = value.toLowerCase();
130
+ for (const pattern of PLACEHOLDER_PATTERNS) {
131
+ if (lowered.includes(pattern)) {
132
+ violations.push(path);
133
+ break;
134
+ }
135
+ }
136
+ }
137
+ }
138
+
139
+ /** First-pass halt — emit the stack-specific apply directive. */
140
+ function _delegate_to_stack_skill(state: DeliveryState): StepResult {
141
+ const directive = _resolve_directive(state);
142
+ const stack_label = _stack_label(state);
143
+ return new StepResult({
144
+ outcome: Outcome.BLOCKED,
145
+ questions: [
146
+ agent_directive(directive),
147
+ `> Stack: \`${stack_label}\`. Implementing the locked design brief.`,
148
+ '> Microcopy is locked — every button label, empty-state ' +
149
+ 'message, and validation message must come verbatim from ' +
150
+ '`state.ui_design.microcopy`.',
151
+ '> 1. Continue — implement the brief and write a ' +
152
+ '`ui_apply` envelope back into state.ticket ' +
153
+ '(rendered: {path: text}, files: [...])',
154
+ '> 2. Abort — drop this UI request',
155
+ ],
156
+ message: `UI apply pending; delegating to \`${directive}\` for stack \`${stack_label}\`.`,
157
+ });
158
+ }
159
+
160
+ /** BLOCKED halt — rendered output still carries placeholder patterns. */
161
+ function _halt_placeholders(state: DeliveryState, violations: string[]): StepResult {
162
+ const directive = _resolve_directive(state);
163
+ const lines: string[] = [
164
+ agent_directive(directive),
165
+ '> Apply rejected: rendered output contains placeholder strings. ' +
166
+ 'The design-brief microcopy lock failed mid-loop.',
167
+ '> Affected paths in `ui_apply.rendered`:',
168
+ ];
169
+ for (const p of violations) {
170
+ lines.push(`> - \`${p}\``);
171
+ }
172
+ lines.push(
173
+ '> Re-render with the locked microcopy verbatim from ' +
174
+ '`state.ui_design.microcopy`; apply will not write placeholder text.',
175
+ );
176
+ return new StepResult({
177
+ outcome: Outcome.BLOCKED,
178
+ questions: lines,
179
+ message:
180
+ `UI apply rejected: ${violations.length} placeholder ` +
181
+ 'violation(s) in rendered output.',
182
+ });
183
+ }
184
+
185
+ /** Append one `state.changes` entry per file in the apply envelope. */
186
+ function _record_changes(state: DeliveryState, envelope: Record<string, Any>): void {
187
+ let files = envelope['files'];
188
+ if (!Array.isArray(files)) {
189
+ files = [];
190
+ }
191
+ const summary = _pyTruthy(envelope['summary']) ? envelope['summary'] : 'ui apply';
192
+ const stack_label = _stack_label(state);
193
+ for (const p of files as Any[]) {
194
+ if (typeof p !== 'string' || p === '') {
195
+ continue;
196
+ }
197
+ state.changes.push({
198
+ kind: 'ui',
199
+ stack: stack_label,
200
+ file: p,
201
+ summary: summary,
202
+ });
203
+ }
204
+ }
205
+
206
+ /** Return the frontend stack label, defaulting to `plain`. */
207
+ function _stack_label(state: DeliveryState): string {
208
+ const stack = _pyTruthy(state.stack) ? state.stack : {};
209
+ if (_isDict(stack)) {
210
+ const frontend = stack['frontend'];
211
+ if (typeof frontend === 'string' && frontend !== '') {
212
+ return frontend;
213
+ }
214
+ }
215
+ return 'plain';
216
+ }