@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.
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +6 -0
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +1 -1
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +1 -1
- package/dist/discovery/trust-report.md +1 -1
- package/dist/discovery/workspaces.json +1 -1
- package/dist/mcp/registry-manifest.json +1 -1
- package/package.json +4 -1
- package/src/agent-src/README.md +64 -0
- package/src/agent-src/scripts/archive_completed_roadmaps.ts +497 -0
- package/src/agent-src/scripts/scan-seeder-violations.php +145 -0
- package/src/agent-src/scripts/update_roadmap_progress.ts +1087 -0
- package/src/agent-src/templates/AGENTS.md +34 -0
- package/src/agent-src/templates/scripts/README.md +195 -0
- package/src/agent-src/templates/scripts/check_memory.ts +662 -0
- package/src/agent-src/templates/scripts/check_memory_proposal.ts +370 -0
- package/src/agent-src/templates/scripts/historical-bug-patterns.example.yml +84 -0
- package/src/agent-src/templates/scripts/implement_ticket/__main__.ts +27 -0
- package/src/agent-src/templates/scripts/memory_hash.ts +355 -0
- package/src/agent-src/templates/scripts/memory_lookup.ts +1089 -0
- package/src/agent-src/templates/scripts/memory_report.ts +868 -0
- package/src/agent-src/templates/scripts/memory_signal.ts +444 -0
- package/src/agent-src/templates/scripts/memory_status.ts +215 -0
- package/src/agent-src/templates/scripts/ownership-map.example.yml +87 -0
- package/src/agent-src/templates/scripts/pr-risk-config.example.yml +76 -0
- package/src/agent-src/templates/scripts/pr_review_routing.ts +544 -0
- package/src/agent-src/templates/scripts/pr_risk_review.ts +0 -0
- package/src/agent-src/templates/scripts/telemetry/aggregator.ts +0 -0
- package/src/agent-src/templates/scripts/telemetry/boundary.ts +164 -0
- package/src/agent-src/templates/scripts/telemetry/engagement.ts +479 -0
- package/src/agent-src/templates/scripts/telemetry/report_renderer.ts +394 -0
- package/src/agent-src/templates/scripts/telemetry/settings.ts +210 -0
- package/src/agent-src/templates/scripts/telemetry_record.ts +276 -0
- package/src/agent-src/templates/scripts/telemetry_report.ts +211 -0
- package/src/agent-src/templates/scripts/telemetry_status.ts +333 -0
- package/src/agent-src/templates/scripts/tier_usage_report.ts +619 -0
- package/src/agent-src/templates/scripts/work_engine/__main__.ts +14 -0
- package/src/agent-src/templates/scripts/work_engine/_lib/agent_settings.ts +1261 -0
- package/src/agent-src/templates/scripts/work_engine/_lib/user_global_paths.ts +329 -0
- package/src/agent-src/templates/scripts/work_engine/cli.ts +206 -0
- package/src/agent-src/templates/scripts/work_engine/cli_args.ts +249 -0
- package/src/agent-src/templates/scripts/work_engine/delivery_state.ts +225 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/analyze.ts +125 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/implement.ts +189 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/index.ts +94 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/memory.ts +193 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/plan.ts +267 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/refine.ts +518 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/report.ts +379 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/test.ts +268 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +258 -0
- package/src/agent-src/templates/scripts/work_engine/directives/index.ts +32 -0
- package/src/agent-src/templates/scripts/work_engine/directives/mixed/contract.ts +243 -0
- package/src/agent-src/templates/scripts/work_engine/directives/mixed/index.ts +108 -0
- package/src/agent-src/templates/scripts/work_engine/directives/mixed/stitch.ts +259 -0
- package/src/agent-src/templates/scripts/work_engine/directives/mixed/ui.ts +216 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/_passthrough.ts +40 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +241 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +216 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +506 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/design.ts +325 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/index.ts +102 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/polish.ts +462 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/review.ts +474 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +352 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui_trivial/_skipped.ts +33 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui_trivial/apply.ts +213 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui_trivial/index.ts +111 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui_trivial/refine.ts +126 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui_trivial/report.ts +112 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui_trivial/test.ts +164 -0
- package/src/agent-src/templates/scripts/work_engine/dispatcher.ts +515 -0
- package/src/agent-src/templates/scripts/work_engine/emitters.ts +119 -0
- package/src/agent-src/templates/scripts/work_engine/errors.ts +24 -0
- package/src/agent-src/templates/scripts/work_engine/hook_bootstrap.ts +104 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/_chat_history_base.ts +189 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_append.ts +41 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_halt_append.ts +89 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/decision_gate.ts +193 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/decision_trace.ts +304 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/directive_set_guard.ts +110 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/halt_surface_audit.ts +118 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/index.ts +17 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/memory_visibility.ts +161 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/state_shape_validation.ts +45 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/trace.ts +134 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/context.ts +94 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/events.ts +58 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/exceptions.ts +85 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/index.ts +27 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/registry.ts +66 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/runner.ts +90 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/settings.ts +260 -0
- package/src/agent-src/templates/scripts/work_engine/input_builders.ts +260 -0
- package/src/agent-src/templates/scripts/work_engine/intent/classify.ts +466 -0
- package/src/agent-src/templates/scripts/work_engine/migration/v0_to_v1.ts +531 -0
- package/src/agent-src/templates/scripts/work_engine/orchestration.ts +366 -0
- package/src/agent-src/templates/scripts/work_engine/persona_policy.ts +97 -0
- package/src/agent-src/templates/scripts/work_engine/resolvers/diff.ts +135 -0
- package/src/agent-src/templates/scripts/work_engine/resolvers/file.ts +175 -0
- package/src/agent-src/templates/scripts/work_engine/resolvers/prompt.ts +115 -0
- package/src/agent-src/templates/scripts/work_engine/scoring/confidence.ts +415 -0
- package/src/agent-src/templates/scripts/work_engine/scoring/decision_engine.ts +466 -0
- package/src/agent-src/templates/scripts/work_engine/scoring/decision_trace.ts +298 -0
- package/src/agent-src/templates/scripts/work_engine/scoring/memory_visibility.ts +444 -0
- package/src/agent-src/templates/scripts/work_engine/stack/detect.ts +252 -0
- package/src/agent-src/templates/scripts/work_engine/stack/runner.ts +745 -0
- package/src/agent-src/templates/scripts/work_engine/state.ts +1151 -0
- package/src/agent-src/templates/scripts/work_engine/state_io.ts +413 -0
- package/src/install/atomic.ts +111 -0
- package/src/install/conflict.ts +226 -0
- package/src/install/detect.ts +280 -0
- package/src/install/paths.ts +90 -0
- package/src/install/plan.ts +205 -0
- package/src/install/preflight.ts +210 -0
- package/src/install/selectedTools.ts +52 -0
- package/src/install/toolDetection.ts +120 -0
- package/src/install/txlog.ts +161 -0
- package/src/install/types.ts +175 -0
- package/src/install/wizard-plan.ts +216 -0
- 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
|
+
}
|