@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,352 @@
1
+ /**
2
+ * `scaffold` step — greenfield Zero-to-One skeleton gate (the `plan` slot).
3
+ *
4
+ * TypeScript twin of `directives/ui/scaffold.py` (ADR-200 py2ts). Public API
5
+ * names stay snake_case to mirror the Python module 1:1.
6
+ *
7
+ * greenfield-scaffold Phase 3: raise a real multi-page skeleton from a
8
+ * confirmed app-spec, under gates, without the engine ever writing app files.
9
+ * The step occupies the UI set's `plan` slot, which runs **after** `analyze`
10
+ * (design) and before `implement` (apply); it replaces the former no-op
11
+ * pass-through there.
12
+ *
13
+ * Order: audit → app-spec → design → scaffold → apply. `design` fixes the
14
+ * abstract visual language; `scaffold` maps that language onto concrete
15
+ * structure. The recoverable state is "designed but not scaffolded" — the plan
16
+ * is stack-agnostic and the engine writes zero files, so a failed scaffold
17
+ * re-runs from this step alone.
18
+ *
19
+ * The gate is **scoped to the greenfield-scaffold path only** (the same guard
20
+ * as `app_spec`). Every other UI flow sees this slot as a clean `SUCCESS`
21
+ * no-op, so those flows stay byte-identical.
22
+ *
23
+ * Two stages, both honouring the engine-never-renders contract:
24
+ *
25
+ * 1. **Plan** — `state.ui_scaffold` carries no structural plan yet. Emit
26
+ * `@agent-directive: ui-scaffold-plan`: the agent/skill derives a
27
+ * stack-agnostic `{pages, routes, layout_strategy, component_manifest,
28
+ * token_seed}` and writes it into `state.ui_scaffold`.
29
+ * 2. **Build** — the plan exists but `scaffolded` is not `True`. Emit the
30
+ * stack-specific `@agent-directive: ui-scaffold-<stack>`: the stack skill
31
+ * consumes the plan, creates the skeleton files, and writes
32
+ * `state.ui_scaffold.scaffolded = true` + `artifacts`.
33
+ *
34
+ * Idempotent: a fully-scaffolded plan round-trips through `SUCCESS`.
35
+ */
36
+ import * as fs from 'node:fs';
37
+ import * as path from 'node:path';
38
+ import {
39
+ type Any,
40
+ type DeliveryState,
41
+ Outcome,
42
+ StepResult,
43
+ agent_directive,
44
+ } from '../../delivery_state.js';
45
+
46
+ /**
47
+ * Conventional locations for a project's DTCG `tokens.json`.
48
+ *
49
+ * When one is present (authored by the `design-tokens` skill, or shipped by
50
+ * `pack-brand`), the scaffold plan's `token_seed` is seeded from it — the
51
+ * anti-generic moat that makes a generated multi-page app coherent rather than
52
+ * default-shadcn. Absent → sane defaults. The dependency is acyclic and
53
+ * degrades gracefully: scaffold never *requires* `pack-brand`.
54
+ */
55
+ export const BRAND_TOKEN_PATHS: ReadonlyArray<string> = [
56
+ 'tokens.json',
57
+ 'assets/tokens.json',
58
+ 'resources/tokens.json',
59
+ 'agents/settings/brand/tokens.json',
60
+ ];
61
+
62
+ /**
63
+ * Stack-agnostic directive that derives the scaffold plan.
64
+ *
65
+ * The plan IS stack-agnostic, so plan derivation is a single directive
66
+ * regardless of frontend; only the build stage dispatches per stack.
67
+ */
68
+ export const PLAN_DIRECTIVE = 'ui-scaffold-plan';
69
+
70
+ /**
71
+ * Map `state.stack.frontend` → build-stage agent-directive skill name.
72
+ *
73
+ * Mirrors `directives/ui/apply.STACK_DIRECTIVES`. An unknown / missing stack
74
+ * falls through to `ui-scaffold-plain` rather than raising — a wrong skill
75
+ * pick is recoverable, a crash is not.
76
+ */
77
+ export const STACK_DIRECTIVES: Record<string, string> = {
78
+ 'blade-livewire-flux': 'ui-scaffold-blade-livewire-flux',
79
+ 'react-shadcn': 'ui-scaffold-react-shadcn',
80
+ vue: 'ui-scaffold-vue',
81
+ plain: 'ui-scaffold-plain',
82
+ };
83
+
84
+ /** Fallback build directive when `state.stack` is missing or malformed. */
85
+ export const DEFAULT_DIRECTIVE = 'ui-scaffold-plain';
86
+
87
+ /** Declared ambiguity surfaces for this step. */
88
+ export const AMBIGUITIES: ReadonlyArray<Record<string, string>> = [
89
+ {
90
+ code: 'scaffold_plan_missing',
91
+ trigger:
92
+ 'greenfield scaffold path and state.ui_scaffold carries no ' +
93
+ 'structural plan (no routes / layout_strategy / component_manifest) — ' +
94
+ 'the scaffold plan has not been derived yet',
95
+ resolution:
96
+ 'agent directive `ui-scaffold-plan` → derive a ' +
97
+ 'stack-agnostic {pages, routes, layout_strategy, component_manifest, ' +
98
+ 'token_seed} from the confirmed app_spec + locked design brief into ' +
99
+ 'state.ui_scaffold',
100
+ },
101
+ {
102
+ code: 'scaffold_build_pending',
103
+ trigger:
104
+ 'greenfield scaffold path and state.ui_scaffold carries a ' +
105
+ 'plan but `scaffolded` is not True — the stack skill has not created ' +
106
+ 'the skeleton files yet',
107
+ resolution:
108
+ 'agent directive `ui-scaffold-<stack>` → stack skill ' +
109
+ 'consumes the plan, creates the skeleton, and writes ' +
110
+ 'state.ui_scaffold.scaffolded = true + artifacts',
111
+ },
112
+ ];
113
+
114
+ function _isDict(value: Any): value is Record<string, Any> {
115
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
116
+ }
117
+
118
+ /**
119
+ * Apply the greenfield scaffold gate.
120
+ *
121
+ * No-op `SUCCESS` for every non-greenfield-scaffold flow; the plan → build loop
122
+ * only engages when the audit recorded a `scaffold` greenfield decision.
123
+ */
124
+ export function run(state: DeliveryState): StepResult {
125
+ if (!_is_greenfield_scaffold(state)) {
126
+ return new StepResult({ outcome: Outcome.SUCCESS });
127
+ }
128
+
129
+ const scaffold = state.ui_scaffold;
130
+
131
+ if (!_is_planned(scaffold)) {
132
+ return _delegate_plan(state);
133
+ }
134
+
135
+ const scaffoldDict = scaffold as Record<string, Any>;
136
+ if (scaffoldDict['scaffolded'] !== true) {
137
+ return _delegate_build(state, scaffoldDict);
138
+ }
139
+
140
+ return new StepResult({ outcome: Outcome.SUCCESS });
141
+ }
142
+
143
+ /**
144
+ * True when the audit recorded a `scaffold` greenfield decision.
145
+ *
146
+ * Identical guard to `app_spec._is_greenfield_scaffold`: the gate is inert for
147
+ * improve-existing, the `bare` / `external_reference` greenfield picks, and the
148
+ * `diff` / `file` envelopes.
149
+ */
150
+ function _is_greenfield_scaffold(state: DeliveryState): boolean {
151
+ const audit = state.ui_audit;
152
+ if (!_isDict(audit)) return false;
153
+ return (
154
+ audit['greenfield'] === true &&
155
+ audit['greenfield_decision'] === 'scaffold'
156
+ );
157
+ }
158
+
159
+ /**
160
+ * True when `scaffold` carries a structural plan.
161
+ *
162
+ * The defining content of the scaffold plan is the structure the app-spec did
163
+ * not produce — routes, a layout strategy, or a component manifest. Their
164
+ * presence (any one) is the "plan derived" signal; an empty dict or a bare
165
+ * `None` is treated as "skill has not run". `pages` alone is *not* sufficient,
166
+ * since the app-spec slice also carries a page-set.
167
+ */
168
+ function _is_planned(scaffold: Any): boolean {
169
+ if (!_isDict(scaffold)) {
170
+ return false;
171
+ }
172
+ return ['routes', 'layout_strategy', 'component_manifest'].some(
173
+ (key) => key in scaffold,
174
+ );
175
+ }
176
+
177
+ /** Render a one-line preview of the input being scaffolded. */
178
+ function _preview_input(state: DeliveryState): string {
179
+ const data = (_isDict(state.ticket) ? state.ticket : {}) as Record<string, Any>;
180
+ const raw = data['raw'];
181
+ let text: string;
182
+ if (typeof raw === 'string' && raw.trim() !== '') {
183
+ text = raw.split(/\s+/u).filter((x) => x.length > 0).join(' ');
184
+ } else {
185
+ const title = data['title'];
186
+ if (typeof title === 'string') {
187
+ text = title;
188
+ } else {
189
+ const id = data['id'];
190
+ text = _pyTruthy(id) ? pyStr(id) : '(no title)';
191
+ }
192
+ }
193
+ if ([...text].length <= 80) {
194
+ return text;
195
+ }
196
+ return _pyRStrip([...text].slice(0, 79).join('')) + '…';
197
+ }
198
+
199
+ function _pyTruthy(value: Any): boolean {
200
+ if (value === null || value === undefined) return false;
201
+ if (typeof value === 'string') return value.length > 0;
202
+ if (typeof value === 'number') return value !== 0;
203
+ if (typeof value === 'boolean') return value;
204
+ if (Array.isArray(value)) return value.length > 0;
205
+ if (typeof value === 'object') return Object.keys(value).length > 0;
206
+ return true;
207
+ }
208
+
209
+ function pyStr(value: Any): string {
210
+ if (value === null || value === undefined) return 'None';
211
+ if (value === true) return 'True';
212
+ if (value === false) return 'False';
213
+ return String(value);
214
+ }
215
+
216
+ function _pyRStrip(s: string): string {
217
+ return s.replace(/\s+$/u, '');
218
+ }
219
+
220
+ /** Return the frontend stack label, defaulting to `plain`. */
221
+ function _stack_label(state: DeliveryState): string {
222
+ const stack = _pyTruthy(state.stack) ? state.stack : {};
223
+ if (_isDict(stack)) {
224
+ const frontend = stack['frontend'];
225
+ if (typeof frontend === 'string' && frontend !== '') {
226
+ return frontend;
227
+ }
228
+ }
229
+ return 'plain';
230
+ }
231
+
232
+ /** Pick the build-stage directive for the project's frontend stack. */
233
+ function _resolve_build_directive(state: DeliveryState): string {
234
+ const stack = _pyTruthy(state.stack) ? state.stack : {};
235
+ if (_isDict(stack)) {
236
+ const frontend = stack['frontend'];
237
+ if (typeof frontend === 'string' && frontend in STACK_DIRECTIVES) {
238
+ return STACK_DIRECTIVES[frontend] as string;
239
+ }
240
+ }
241
+ return DEFAULT_DIRECTIVE;
242
+ }
243
+
244
+ /**
245
+ * Return the relative path to a present `tokens.json`, or `null`.
246
+ *
247
+ * Checks {@link BRAND_TOKEN_PATHS} under `root` (default: the current working
248
+ * directory, which is the consumer project root when the engine runs). The
249
+ * first existing file wins. Absent → `null` so the caller degrades to default
250
+ * tokens — the acyclic, graceful-degradation contract.
251
+ */
252
+ function _brand_token_source(root: string | null = null): string | null {
253
+ const base = root !== null ? root : process.cwd();
254
+ for (const rel of BRAND_TOKEN_PATHS) {
255
+ try {
256
+ if (fs.statSync(path.join(base, rel)).isFile()) {
257
+ return rel;
258
+ }
259
+ } catch {
260
+ continue;
261
+ }
262
+ }
263
+ return null;
264
+ }
265
+
266
+ /**
267
+ * Describe the token-seed source for the plan directive.
268
+ *
269
+ * Brand-seeds from a present DTCG `tokens.json` when one exists; otherwise
270
+ * instructs the skill to derive from the locked design brief + sane defaults.
271
+ * Graceful degradation: no token source → defaults, never a hard failure.
272
+ */
273
+ function _token_seed_line(_state: DeliveryState): string {
274
+ const source = _brand_token_source();
275
+ if (source !== null) {
276
+ return (
277
+ `> \`token_seed\`: seed from the project's brand/design tokens ` +
278
+ `at \`${source}\` (DTCG \`tokens.json\`) — the anti-generic moat. ` +
279
+ `Do NOT fall back to default shadcn tokens when this source is ` +
280
+ `present; carry its primitives + semantic layers into the plan.`
281
+ );
282
+ }
283
+ return (
284
+ '> `token_seed`: derive from the locked design brief\'s tokens; ' +
285
+ 'fall back to sane defaults (neutral scale, system font) when the ' +
286
+ 'brief leaves a slot open (no `tokens.json` brand source detected).'
287
+ );
288
+ }
289
+
290
+ /** Stage 1 — emit the stack-agnostic plan-derivation directive. */
291
+ function _delegate_plan(state: DeliveryState): StepResult {
292
+ return new StepResult({
293
+ outcome: Outcome.BLOCKED,
294
+ questions: [
295
+ agent_directive(PLAN_DIRECTIVE),
296
+ `> Input: ${_preview_input(state)}`,
297
+ '> Greenfield scaffold — derive a stack-agnostic skeleton plan ' +
298
+ 'from the confirmed `state.app_spec` (page-set + entity model) ' +
299
+ 'and the locked `state.ui_design` brief. The engine writes no ' +
300
+ 'files; this stage only produces the plan.',
301
+ '> Write `state.ui_scaffold` = ' +
302
+ '{pages, routes, layout_strategy, component_manifest, token_seed}.',
303
+ _token_seed_line(state),
304
+ '> 1. Continue — derive the scaffold plan into ' +
305
+ '`state.ui_scaffold`',
306
+ '> 2. Abort — drop this UI request',
307
+ '',
308
+ '**Recommendation: 1 — derive the plan** — the ' +
309
+ 'stack-agnostic plan is the recoverable checkpoint: if the build ' +
310
+ 'stage fails, scaffold re-runs from here without touching design ' +
311
+ 'or apply.',
312
+ ],
313
+ message:
314
+ 'Greenfield scaffold plan missing; delegating to ' +
315
+ '`ui-scaffold-plan` to derive the stack-agnostic skeleton plan.',
316
+ });
317
+ }
318
+
319
+ /** Stage 2 — emit the stack-specific skeleton-build directive. */
320
+ function _delegate_build(state: DeliveryState, scaffold: Record<string, Any>): StepResult {
321
+ const directive = _resolve_build_directive(state);
322
+ const stack_label = _stack_label(state);
323
+ const pages = scaffold['pages'];
324
+ const page_count = Array.isArray(pages) ? pages.length : 0;
325
+ const routes = scaffold['routes'];
326
+ const route_count = Array.isArray(routes) ? routes.length : 0;
327
+ return new StepResult({
328
+ outcome: Outcome.BLOCKED,
329
+ questions: [
330
+ agent_directive(directive),
331
+ `> Stack: \`${stack_label}\`. Scaffold plan is ready ` +
332
+ `(${page_count} page(s), ${route_count} route(s)). Create the ` +
333
+ 'skeleton from `state.ui_scaffold` — routes, layout shell, and ' +
334
+ 'the component-manifest stubs.',
335
+ '> The engine writes no files: the stack skill creates the ' +
336
+ 'skeleton and writes `state.ui_scaffold.scaffolded = true` plus ' +
337
+ '`state.ui_scaffold.artifacts` (the created paths).',
338
+ '> Recoverable: a failed build re-runs from this scaffold step ' +
339
+ 'alone — design and app-spec stay locked.',
340
+ '> 1. Continue — create the skeleton and mark ' +
341
+ '`scaffolded = true`',
342
+ '> 2. Abort — drop this UI request',
343
+ '',
344
+ `**Recommendation: 1 — build the skeleton** — the ` +
345
+ `\`${stack_label}\` scaffold skill materialises the confirmed plan; ` +
346
+ `the rendered output is verified downstream by the review gate.`,
347
+ ],
348
+ message:
349
+ `Greenfield scaffold plan ready; delegating to \`${directive}\` to ` +
350
+ `create the skeleton for stack \`${stack_label}\`.`,
351
+ });
352
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Pass-through handler for slots the trivial path skips.
3
+ *
4
+ * TypeScript twin of `directives/ui_trivial/_skipped.py` (ADR-200 py2ts).
5
+ * Public API names stay snake_case to mirror the Python module 1:1.
6
+ *
7
+ * The `ui-trivial` directive set short-circuits the audit / design / review /
8
+ * polish loop. The dispatcher's `STEP_ORDER` is fixed (eight slots, no
9
+ * branching), so the trivial set fills the unused slots — `memory`,
10
+ * `analyze`, `plan`, `verify` — with this no-op handler. It returns `SUCCESS`
11
+ * without touching state, mutates nothing, and declares zero ambiguities.
12
+ */
13
+ import {
14
+ type DeliveryState,
15
+ Outcome,
16
+ StepResult,
17
+ } from '../../delivery_state.js';
18
+
19
+ /** No ambiguities — the slot is unconditionally skipped on the trivial path. */
20
+ export const AMBIGUITIES: ReadonlyArray<Record<string, string>> = [];
21
+
22
+ /**
23
+ * Return `SUCCESS` without touching `state`.
24
+ *
25
+ * Shared handler for the slots the trivial path intentionally bypasses.
26
+ * Keeping the slot wired (rather than raising) preserves the dispatcher's
27
+ * completeness-check invariant: every slot in `STEP_ORDER` has a callable
28
+ * handler, every directive set has a uniform shape.
29
+ */
30
+ export function run(state: DeliveryState): StepResult {
31
+ void state;
32
+ return new StepResult({ outcome: Outcome.SUCCESS });
33
+ }
@@ -0,0 +1,213 @@
1
+ /**
2
+ * `apply` step — single-file edit path for the `ui-trivial` set.
3
+ *
4
+ * TypeScript twin of `directives/ui_trivial/apply.py` (ADR-200 py2ts).
5
+ * Public API names stay snake_case to mirror the Python module 1:1.
6
+ *
7
+ * The short-circuit path for micro UI edits that provably cannot warrant the
8
+ * full audit / design / review / polish loop. Hard preconditions enforced on
9
+ * every rebound: ≤ 1 file, ≤ 5 changed lines, no new component, no new state,
10
+ * no new dependency. Any precondition violation BLOCKS with a
11
+ * `reclassify-to-ui-improve` halt; the orchestrator promotes
12
+ * `state.directive_set` to `"ui-improve"` and re-invokes the engine.
13
+ */
14
+ import {
15
+ type Any,
16
+ type DeliveryState,
17
+ Outcome,
18
+ StepResult,
19
+ agent_directive,
20
+ } from '../../delivery_state.js';
21
+
22
+ /** Edit-surface ceiling — single-file edits only. */
23
+ export const MAX_FILES = 1;
24
+
25
+ /** Changed-line ceiling — anything larger is structural, not trivial. */
26
+ export const MAX_LINES_CHANGED = 5;
27
+
28
+ export const AMBIGUITIES: ReadonlyArray<Record<string, string>> = [
29
+ {
30
+ code: 'trivial_envelope_missing',
31
+ trigger: "state.ticket['trivial_edit'] unset — first pass",
32
+ resolution:
33
+ 'agent directive `trivial-apply` → agent performs ' +
34
+ 'the single-file edit and writes the envelope back',
35
+ },
36
+ {
37
+ code: 'trivial_preconditions_violated',
38
+ trigger: 'edit touches >1 file, >5 lines, adds component/state/dependency',
39
+ resolution:
40
+ 'agent directive `reclassify-to-ui-improve` → ' +
41
+ "orchestrator promotes directive_set='ui-improve' and re-runs " +
42
+ 'through the full audit gate',
43
+ },
44
+ ];
45
+
46
+ function _isDict(value: Any): value is Record<string, Any> {
47
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
48
+ }
49
+
50
+ /** Python truthiness for the values reached here. */
51
+ function _pyTruthy(value: Any): boolean {
52
+ if (value === null || value === undefined) return false;
53
+ if (typeof value === 'string') return value.length > 0;
54
+ if (typeof value === 'number') return value !== 0;
55
+ if (typeof value === 'boolean') return value;
56
+ if (Array.isArray(value)) return value.length > 0;
57
+ if (typeof value === 'object') return Object.keys(value).length > 0;
58
+ return true;
59
+ }
60
+
61
+ /**
62
+ * Python `int(x)` for the values that reach the precondition check. Mirrors
63
+ * CPython semantics for the shapes JSON produces:
64
+ *
65
+ * - `bool` → 0 / 1.
66
+ * - integer-valued / fractional `float` → truncate toward zero.
67
+ * - `str` → parse a base-10 integer literal (optional sign, surrounding
68
+ * whitespace allowed); a non-integer string raises `ValueError`.
69
+ * - `None` / arrays / objects → `TypeError`.
70
+ *
71
+ * Returns the int on success, or the sentinel `null` to mirror the
72
+ * `except (TypeError, ValueError)` branch in the source.
73
+ */
74
+ function _pyInt(value: Any): number | null {
75
+ if (typeof value === 'boolean') {
76
+ return value ? 1 : 0;
77
+ }
78
+ if (typeof value === 'number') {
79
+ if (!Number.isFinite(value)) {
80
+ return null; // int(inf)/int(nan) → ValueError/OverflowError
81
+ }
82
+ return Math.trunc(value);
83
+ }
84
+ if (typeof value === 'string') {
85
+ const trimmed = value.trim();
86
+ // Python int() accepts an optional sign + digits + optional `_`
87
+ // separators between digits. The inputs here are plain integers; match
88
+ // the common case and reject anything else.
89
+ if (/^[+-]?[0-9]+$/.test(trimmed)) {
90
+ return Number(trimmed);
91
+ }
92
+ return null;
93
+ }
94
+ return null;
95
+ }
96
+
97
+ /** Apply the trivial-edit gate. */
98
+ export function run(state: DeliveryState): StepResult {
99
+ const envelope = _trivial_envelope(state);
100
+ if (envelope === null) {
101
+ return _delegate_to_agent(state);
102
+ }
103
+
104
+ const violations = _check_preconditions(envelope);
105
+ if (violations.length > 0) {
106
+ return _halt_reclassify(state, violations);
107
+ }
108
+
109
+ _record_change(state, envelope);
110
+ return new StepResult({ outcome: Outcome.SUCCESS });
111
+ }
112
+
113
+ /** Return the agent-written `trivial_edit` envelope, or `null`. */
114
+ function _trivial_envelope(state: DeliveryState): Record<string, Any> | null {
115
+ const data = _pyTruthy(state.ticket) ? state.ticket : {};
116
+ const envelope = (data as Record<string, Any>)['trivial_edit'];
117
+ if (_isDict(envelope) && _pyTruthy(envelope)) {
118
+ return envelope;
119
+ }
120
+ return null;
121
+ }
122
+
123
+ /** Return a list of violation codes; empty when all preconditions pass. */
124
+ function _check_preconditions(envelope: Record<string, Any>): string[] {
125
+ const violations: string[] = [];
126
+
127
+ const files = envelope['files'];
128
+ if (!Array.isArray(files) || files.length === 0) {
129
+ violations.push('files_missing');
130
+ } else if (files.length > MAX_FILES) {
131
+ violations.push(`files_exceeded:${files.length}>${MAX_FILES}`);
132
+ }
133
+
134
+ const lines = envelope['lines_changed'];
135
+ const lines_int = _pyInt(lines);
136
+ if (lines_int === null) {
137
+ violations.push('lines_changed_missing');
138
+ } else {
139
+ if (lines_int > MAX_LINES_CHANGED) {
140
+ violations.push(`lines_exceeded:${lines_int}>${MAX_LINES_CHANGED}`);
141
+ }
142
+ if (lines_int < 0) {
143
+ violations.push('lines_changed_negative');
144
+ }
145
+ }
146
+
147
+ if (_pyTruthy(envelope['new_component'])) {
148
+ violations.push('new_component');
149
+ }
150
+ if (_pyTruthy(envelope['new_state'])) {
151
+ violations.push('new_state');
152
+ }
153
+ if (_pyTruthy(envelope['new_dependency'])) {
154
+ violations.push('new_dependency');
155
+ }
156
+
157
+ return violations;
158
+ }
159
+
160
+ /** First-pass halt — delegate to the agent for the single-file edit. */
161
+ function _delegate_to_agent(state: DeliveryState): StepResult {
162
+ void state;
163
+ return new StepResult({
164
+ outcome: Outcome.BLOCKED,
165
+ questions: [
166
+ agent_directive('trivial-apply'),
167
+ '> Trivial UI edit path — audit / design / review skipped.',
168
+ '> 1. Continue — perform the single-file edit, then ' +
169
+ 'write a `trivial_edit` envelope back into state.ticket ' +
170
+ '(files, lines_changed, new_component, new_state, new_dependency)',
171
+ '> 2. Abort — drop this trivial edit',
172
+ ],
173
+ message: 'Trivial UI edit pending; delegating to agent for single-file edit.',
174
+ });
175
+ }
176
+
177
+ /** BLOCKED halt — orchestrator must reclassify to `ui-improve`. */
178
+ function _halt_reclassify(state: DeliveryState, violations: string[]): StepResult {
179
+ state.ticket['__reclassify_to__'] = 'ui-improve';
180
+ delete state.ticket['trivial_edit'];
181
+ return new StepResult({
182
+ outcome: Outcome.BLOCKED,
183
+ questions: [
184
+ agent_directive('reclassify-to-ui-improve'),
185
+ '> Trivial-edit preconditions violated; this work needs the ' +
186
+ 'full audit gate.',
187
+ `> Violations: ${violations.join(', ')}.`,
188
+ '> 1. Reclassify — orchestrator sets ' +
189
+ '`state.directive_set = "ui-improve"` and re-runs the engine',
190
+ '> 2. Abort — drop this UI request',
191
+ ],
192
+ message:
193
+ 'Trivial-edit preconditions failed ' +
194
+ `(${violations.join(', ')}); reclassification required.`,
195
+ });
196
+ }
197
+
198
+ /** Write a single `state.changes` entry summarising the trivial edit. */
199
+ function _record_change(state: DeliveryState, envelope: Record<string, Any>): void {
200
+ const filesRaw = envelope['files'];
201
+ const files = _pyTruthy(filesRaw) ? filesRaw : [];
202
+ const lines = 'lines_changed' in envelope ? envelope['lines_changed'] : 0;
203
+ const summaryRaw = envelope['summary'];
204
+ const summary = _pyTruthy(summaryRaw) ? summaryRaw : 'trivial UI edit';
205
+ // `_record_change` only runs after preconditions pass, so `files` is a
206
+ // non-empty list here; mirror Python `list(files)` with a shallow copy.
207
+ state.changes.push({
208
+ kind: 'ui-trivial',
209
+ files: [...(files as Any[])],
210
+ lines_changed: lines,
211
+ summary: summary,
212
+ });
213
+ }