@event4u/agent-config 9.11.0 → 9.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +52 -0
- package/README.md +8 -1
- package/dist/agent-src/commands/fix/ci.md +29 -0
- package/dist/agent-src/commands/pr/create.md +60 -0
- package/dist/agent-src/contexts/authority/kernel-rule-edits.md +18 -2
- package/dist/agent-src/rules/design-fidelity.md +1 -1
- package/dist/agent-src/skills/ai-code-blindspots/SKILL.md +1 -0
- package/dist/agent-src/skills/design-intelligence/SKILL.md +27 -1
- package/dist/agent-src/skills/design-intelligence/data/font-pairings-reference.csv +74 -74
- package/dist/agent-src/skills/design-intelligence/data/manifest.json +2 -0
- package/dist/agent-src/skills/design-tokens/scripts/tokens.ts +0 -7
- package/dist/agent-src/skills/typography-system/SKILL.md +55 -11
- package/dist/agent-src/skills/ui-apply-generic/SKILL.md +180 -0
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +9 -5
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/polish.ts +9 -6
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/review.ts +9 -5
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/stack_bundles.ts +182 -29
- package/dist/agent-src/templates/scripts/work_engine/stack/detect.ts +311 -24
- package/dist/cli-delegate/chunk-523HCFYO.js +90 -0
- package/dist/cli-delegate/{chunk-IR7P7JWA.js → chunk-A3HJ6NTI.js} +271 -59
- package/dist/cli-delegate/chunk-EJYIGZ3T.js +125 -0
- package/dist/cli-delegate/{chunk-RDGRHAAB.js → chunk-HYWSRNUX.js} +6 -86
- package/dist/cli-delegate/{chunk-YMFO6FDN.js → chunk-QFKQLQ7X.js} +313 -81
- package/dist/cli-delegate/{chunk-4SV3XGFA.js → chunk-TEGJU2JX.js} +7 -9
- package/dist/cli-delegate/chunk-TJIAM5FW.js +133 -0
- package/dist/cli-delegate/cmd_conformance.js +4 -7
- package/dist/cli-delegate/cmd_doctor.js +3 -5
- package/dist/cli-delegate/cmd_explain.js +153 -85
- package/dist/cli-delegate/cmd_export.js +5 -3
- package/dist/cli-delegate/cmd_fleet.js +2 -1
- package/dist/cli-delegate/cmd_preflight.js +6 -123
- package/dist/cli-delegate/cmd_prune.js +5 -3
- package/dist/cli-delegate/cmd_settings_migrate.js +5 -3
- package/dist/cli-delegate/cmd_sync.js +5 -3
- package/dist/cli-delegate/cmd_uninstall.js +6 -5
- package/dist/cli-delegate/cmd_validate.js +5 -3
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +36 -14
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +3 -3
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +6 -5
- package/dist/discovery/trust-report.md +2 -2
- package/dist/discovery/workspaces.json +5 -4
- package/dist/install/install.mjs +605 -412
- package/dist/install/ruleInScope.js +123 -0
- package/dist/install/ruleInScope.js.map +1 -0
- package/dist/install/rule_scope.js +2 -2
- package/dist/install/rule_scope.js.map +1 -1
- package/dist/install/wizard-plan.js +10 -1
- package/dist/install/wizard-plan.js.map +1 -1
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +405 -2429
- package/dist/scripts/condense.js +6 -22
- package/dist/scripts/condense.js.map +1 -1
- package/docs/guidelines/design-fidelity-mechanics.md +14 -0
- package/package.json +1 -1
- package/src/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +9 -5
- package/src/agent-src/templates/scripts/work_engine/directives/ui/polish.ts +9 -6
- package/src/agent-src/templates/scripts/work_engine/directives/ui/review.ts +9 -5
- package/src/agent-src/templates/scripts/work_engine/directives/ui/stack_bundles.ts +182 -29
- package/src/agent-src/templates/scripts/work_engine/stack/detect.ts +311 -24
- package/src/install/ruleInScope.ts +133 -0
- package/src/install/rule_scope.ts +2 -2
- package/src/install/wizard-plan.ts +11 -1
- package/src/scripts/_lib/global_deploy_inventory.ts +38 -2
- package/src/scripts/check_installer_import_purity.ts +295 -0
- package/src/scripts/condense.ts +7 -28
- package/src/scripts/install-hooks.sh +36 -4
- package/src/scripts/install.ts +210 -5
- package/src/scripts/lint_ui_stack_bundles.ts +31 -0
- package/dist/cli-delegate/chunk-7DSAMZFU.js +0 -356
- package/dist/cli-delegate/chunk-BJGXZNPM.js +0 -266
- package/dist/cli-delegate/chunk-TFG57JJM.js +0 -1969
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
---
|
|
2
|
+
model_tier: medium
|
|
3
|
+
name: ui-apply-generic
|
|
4
|
+
description: "Use when implementing a UI brief on a stack with no framework executor — Svelte, Astro, Angular, plain HTML. Carries the stack-independent contract; idiom comes from the stack corpus."
|
|
5
|
+
personas:
|
|
6
|
+
- frontend-engineer
|
|
7
|
+
domain: engineering
|
|
8
|
+
workspaces:
|
|
9
|
+
- engineering
|
|
10
|
+
packs:
|
|
11
|
+
- engineering-base
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# ui-apply-generic
|
|
15
|
+
|
|
16
|
+
> The `plain` lane used to be a name with nothing behind it. This skill is the
|
|
17
|
+
> lane: it carries the implementation discipline that previously lived **only**
|
|
18
|
+
> inside the framework executors, and it gets framework idiom from the 16-stack
|
|
19
|
+
> corpus instead of from prose written here.
|
|
20
|
+
>
|
|
21
|
+
> That split is the whole point. One skill plus a corpus serves Svelte, Astro,
|
|
22
|
+
> Angular, Alpine, htmx and everything future at once. Sixteen framework skills
|
|
23
|
+
> would serve the same set and cost a single maintainer sixteen surfaces to keep
|
|
24
|
+
> current — the enumeration this package refuses.
|
|
25
|
+
|
|
26
|
+
## When to use
|
|
27
|
+
|
|
28
|
+
- `state.stack.frontend` resolves to `plain`, or to a label with no
|
|
29
|
+
framework-specific overlay.
|
|
30
|
+
- A stack whose `axes.reactivity` or `axes.meta` is recognised but has no
|
|
31
|
+
executor skill (`svelte`, `angular`, `astro`, `solid`, `qwik`, `htmx`).
|
|
32
|
+
- As the base of a composition: an overlay skill (`livewire`, `flux`,
|
|
33
|
+
`blade-ui`, `react-shadcn-ui`) adds framework specifics **on top** of this
|
|
34
|
+
contract, it does not replace it.
|
|
35
|
+
|
|
36
|
+
Do NOT use when a framework executor already covers the whole job — then this
|
|
37
|
+
skill is the base and the executor is the overlay, dispatched together.
|
|
38
|
+
|
|
39
|
+
## Procedure
|
|
40
|
+
|
|
41
|
+
### Step 1 — Query the stack corpus (mandatory)
|
|
42
|
+
|
|
43
|
+
Read `state.stack.axes` and query per resolved axis value, most specific first:
|
|
44
|
+
`meta` → `reactivity` → `component_lib`.
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
<skills-root>/corpus-grounding/scripts/ground search \
|
|
48
|
+
--manifest <skills-root>/design-intelligence/data/manifest.json \
|
|
49
|
+
--stack <axis-value> "<the concrete question>"
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`axes.meta: nuxt` with `axes.reactivity: vue` means **two** queries —
|
|
53
|
+
`--stack nuxtjs` and `--stack vue`. A meta-framework does not replace the layer
|
|
54
|
+
it wraps; that conflation is the defect the axes exist to fix.
|
|
55
|
+
|
|
56
|
+
**Cite what you used.** The result names the corpus rows the implementation
|
|
57
|
+
followed. An uncited pick is indistinguishable from the model's memory, and
|
|
58
|
+
memory is exactly what the corpus exists to replace.
|
|
59
|
+
|
|
60
|
+
**No corpus domain for the detected stack** → say so, in the result:
|
|
61
|
+
|
|
62
|
+
> No stack corpus for `<stack>` — proceeding on the generic contract. Framework
|
|
63
|
+
> idiom below is not grounded and should be reviewed by someone who knows this
|
|
64
|
+
> stack.
|
|
65
|
+
|
|
66
|
+
Silence is the failure mode. An unstated gap reads as grounded output.
|
|
67
|
+
|
|
68
|
+
### Step 2 — Apply the stack-independent contract
|
|
69
|
+
|
|
70
|
+
Every item is a hard requirement, and none of them is framework-specific:
|
|
71
|
+
|
|
72
|
+
1. **Verbatim floor.** Microcopy comes from `state.ui_design.microcopy` exactly
|
|
73
|
+
as written — every label, empty-state line, and validation message. No
|
|
74
|
+
paraphrase, no improvement. Where a source artifact was provided, it beats
|
|
75
|
+
the brief (see `design-fidelity`).
|
|
76
|
+
2. **Token discipline.** Colour, spacing, radius and shadow values come from the
|
|
77
|
+
project's tokens. A raw hex or arbitrary px that no token backs is a finding,
|
|
78
|
+
not a choice. Validate with `design-tokens`' `tokens validate`.
|
|
79
|
+
3. **Component reuse.** Check `state.ui_audit.components_found` before creating
|
|
80
|
+
anything. In a run that has already added components, re-read the component
|
|
81
|
+
directories — the audit inventory is computed once per state-file and does
|
|
82
|
+
not refresh (see `existing-ui-audit` § Gotchas).
|
|
83
|
+
4. **a11y floor.** Semantic elements over `div` + role; every interactive
|
|
84
|
+
element reachable and labelled; focus visible; contrast meets the project's
|
|
85
|
+
stated level, AA when unstated.
|
|
86
|
+
5. **All five states.** `empty`, `loading`, `error`, `success`, `disabled` per
|
|
87
|
+
the brief. An explicit `n/a` is legitimate for a surface that genuinely has
|
|
88
|
+
no such state — declaring that is the opposite of inventing filler.
|
|
89
|
+
6. **Asset discipline.** Copy referenced assets into the project's asset path.
|
|
90
|
+
Never hotlink; never fabricate a logo or a screenshot.
|
|
91
|
+
7. **No placeholders.** `<placeholder>`, `lorem`, `todo:`, `tbd`, `xxx` are
|
|
92
|
+
rejected at the boundary — including inside arrays.
|
|
93
|
+
|
|
94
|
+
### Step 3 — Verify, with honest degrade
|
|
95
|
+
|
|
96
|
+
Exercise the result with whatever the host actually has, and name which:
|
|
97
|
+
|
|
98
|
+
| Available | Do |
|
|
99
|
+
|---|---|
|
|
100
|
+
| Browser / Playwright | Render, check the states, exercise one interaction |
|
|
101
|
+
| Screenshot only | Capture and inspect layout at 375 / 768 / 1280 |
|
|
102
|
+
| Neither | Static inspection only, and **say** the render is unverified |
|
|
103
|
+
|
|
104
|
+
Never claim a state or interaction works without having exercised it. A caveat
|
|
105
|
+
is a finding; a silent claim is a defect.
|
|
106
|
+
|
|
107
|
+
## Output format
|
|
108
|
+
|
|
109
|
+
Four requirements, in order — each is checkable by a reader of the result:
|
|
110
|
+
|
|
111
|
+
1. **Name the corpora queried**, or state that none exists for this stack.
|
|
112
|
+
2. **Cite the rows that changed a decision** — not every row read.
|
|
113
|
+
3. **State the verification method and its caveats**, never an unqualified
|
|
114
|
+
"works".
|
|
115
|
+
4. **List components reused before components added**, so a duplicate is
|
|
116
|
+
visible rather than buried.
|
|
117
|
+
|
|
118
|
+
Write `state.ticket["ui_apply"]`:
|
|
119
|
+
|
|
120
|
+
```yaml
|
|
121
|
+
ui_apply:
|
|
122
|
+
files: ["resources/js/UserCard.svelte"]
|
|
123
|
+
rendered:
|
|
124
|
+
"resources/js/UserCard.svelte": |
|
|
125
|
+
<full text, microcopy-locked>
|
|
126
|
+
components_added: ["UserCard"]
|
|
127
|
+
components_reused: ["Button"]
|
|
128
|
+
microcopy_lock: true
|
|
129
|
+
stack_corpus: # which corpora answered, or the degrade note
|
|
130
|
+
queried: ["svelte"]
|
|
131
|
+
cited: ["svelte.csv:12 — prefer stores over prop drilling"]
|
|
132
|
+
verify:
|
|
133
|
+
method: static_inspect # or playwright / screenshot
|
|
134
|
+
caveats: ["render not verified — no browser available"]
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Gotchas
|
|
138
|
+
|
|
139
|
+
- **Do not write framework prose into this skill.** The moment it explains how
|
|
140
|
+
Svelte stores work, it stops being one maintainable surface and starts being
|
|
141
|
+
sixteen. Framework knowledge belongs in the corpus.
|
|
142
|
+
- **`axes.reactivity: unknown` is not `none`.** `unknown` means a project has
|
|
143
|
+
manifests and no recognised reactivity layer — proceed, and say the stack was
|
|
144
|
+
not recognised. `none` means there genuinely is no reactivity layer, which is
|
|
145
|
+
the normal shape for a static page.
|
|
146
|
+
- **A meta-framework needs its own query.** Querying only `--stack vue` for a
|
|
147
|
+
Nuxt project silently drops the routing, data-fetching and SSR idioms that
|
|
148
|
+
are the whole reason Nuxt exists.
|
|
149
|
+
- **The generic contract is a floor, not a ceiling.** Where an overlay skill is
|
|
150
|
+
dispatched with this one, its framework rules win on their subject.
|
|
151
|
+
- **The floor now lives in exactly one place, and that is the point.** Overlay
|
|
152
|
+
composition applies this contract to every lane, so a future change to any of
|
|
153
|
+
its seven items is a one-point edit here rather than four lane patches that
|
|
154
|
+
drift apart. Concretely: the five-states requirement reaches every lane
|
|
155
|
+
through this skill. That requirement is deliberately unconditional — an
|
|
156
|
+
explicit `n/a` is the escape for a surface that genuinely has no such state
|
|
157
|
+
(asserted in `ui_lane_matrix.test.ts`), so this is not a defect being
|
|
158
|
+
broadened. But if the decision is ever revisited, revisit it here.
|
|
159
|
+
|
|
160
|
+
## Do NOT
|
|
161
|
+
|
|
162
|
+
- Emit framework code from memory and present it as grounded.
|
|
163
|
+
- Skip the corpus query because the stack "looks obvious".
|
|
164
|
+
- Claim a render or an interaction was verified when no primitive was available.
|
|
165
|
+
- Invent states, sections, or copy the brief does not carry.
|
|
166
|
+
- Treat a missing corpus as an evidence gap — it is a missing pack, and the two
|
|
167
|
+
must not be recorded the same way.
|
|
168
|
+
|
|
169
|
+
## See also
|
|
170
|
+
|
|
171
|
+
- [`design-intelligence`](../design-intelligence/SKILL.md) — the `--stack`
|
|
172
|
+
corpus this skill queries (16 stacks).
|
|
173
|
+
- [`existing-ui-audit`](../existing-ui-audit/SKILL.md) — the inventory Step 2
|
|
174
|
+
checks, and its once-per-state-file limit.
|
|
175
|
+
- [`design-tokens`](../design-tokens/SKILL.md) — `tokens validate`.
|
|
176
|
+
- [`fe-design`](../fe-design/SKILL.md) — where the brief's selection decisions
|
|
177
|
+
come from, and what it does when the corpus is not installed.
|
|
178
|
+
- [`ui-component-architect`](../ui-component-architect/SKILL.md),
|
|
179
|
+
[`tailwind-engineer`](../tailwind-engineer/SKILL.md) — stack-neutral
|
|
180
|
+
companions this skill composes with.
|
|
@@ -39,7 +39,7 @@ schema_version: 1
|
|
|
39
39
|
# CI guard: a release bump of `package.json` must update this value
|
|
40
40
|
# in lockstep — see scripts/check_template_pin_drift.ts (road-to-
|
|
41
41
|
# portable-runtime-and-update-check P3.3).
|
|
42
|
-
agent_config_version: "9.
|
|
42
|
+
agent_config_version: "9.12.0"
|
|
43
43
|
|
|
44
44
|
# --- Project identity ---
|
|
45
45
|
project:
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
} from '../../delivery_state.js';
|
|
20
20
|
import { placeholder_paths } from './design.js';
|
|
21
21
|
import {
|
|
22
|
-
|
|
22
|
+
is_ambiguous_stack,
|
|
23
23
|
bundle_line,
|
|
24
24
|
unsupported_stack_questions,
|
|
25
25
|
} from './stack_bundles.js';
|
|
@@ -132,11 +132,15 @@ function _placeholder_violations_in_output(envelope: Record<string, Any>): strin
|
|
|
132
132
|
function _delegate_to_stack_skill(state: DeliveryState): StepResult {
|
|
133
133
|
const directive = _resolve_directive(state);
|
|
134
134
|
const stack_label = _stack_label(state);
|
|
135
|
-
if (
|
|
135
|
+
if (is_ambiguous_stack(state.stack)) {
|
|
136
|
+
const conflicts = ((state.stack as Record<string, Any>)['ambiguity'] ??
|
|
137
|
+
[]) as ReadonlyArray<string>;
|
|
136
138
|
return new StepResult({
|
|
137
139
|
outcome: Outcome.BLOCKED,
|
|
138
|
-
questions: unsupported_stack_questions('apply'),
|
|
139
|
-
message:
|
|
140
|
+
questions: unsupported_stack_questions('apply', conflicts),
|
|
141
|
+
message:
|
|
142
|
+
`UI apply halted: detection is ambiguous (${conflicts.join('; ')}) — ` +
|
|
143
|
+
'the open question is which project to build for.',
|
|
140
144
|
});
|
|
141
145
|
}
|
|
142
146
|
return new StepResult({
|
|
@@ -144,7 +148,7 @@ function _delegate_to_stack_skill(state: DeliveryState): StepResult {
|
|
|
144
148
|
questions: [
|
|
145
149
|
agent_directive(directive),
|
|
146
150
|
`> Stack: \`${stack_label}\`. Implementing the locked design brief.`,
|
|
147
|
-
bundle_line(
|
|
151
|
+
bundle_line(state.stack, 'build', stack_label),
|
|
148
152
|
'> Microcopy is locked — every button label, empty-state ' +
|
|
149
153
|
'message, and validation message must come verbatim from ' +
|
|
150
154
|
'`state.ui_design.microcopy`.',
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
agent_directive,
|
|
17
17
|
} from '../../delivery_state.js';
|
|
18
18
|
import {
|
|
19
|
-
|
|
19
|
+
is_ambiguous_stack,
|
|
20
20
|
bundle_line,
|
|
21
21
|
unsupported_stack_questions,
|
|
22
22
|
} from './stack_bundles.js';
|
|
@@ -195,12 +195,15 @@ function _delegate_to_polish_skill(
|
|
|
195
195
|
ceiling: number,
|
|
196
196
|
): StepResult {
|
|
197
197
|
const directive = _resolve_directive(state);
|
|
198
|
-
|
|
199
|
-
|
|
198
|
+
if (is_ambiguous_stack(state.stack)) {
|
|
199
|
+
const conflicts = ((state.stack as Record<string, Any>)['ambiguity'] ??
|
|
200
|
+
[]) as ReadonlyArray<string>;
|
|
200
201
|
return new StepResult({
|
|
201
202
|
outcome: Outcome.BLOCKED,
|
|
202
|
-
questions: unsupported_stack_questions('polish'),
|
|
203
|
-
message:
|
|
203
|
+
questions: unsupported_stack_questions('polish', conflicts),
|
|
204
|
+
message:
|
|
205
|
+
`UI polish halted: detection is ambiguous (${conflicts.join('; ')}) — ` +
|
|
206
|
+
'the open question is which project to build for.',
|
|
204
207
|
});
|
|
205
208
|
}
|
|
206
209
|
const stack_label = _stack_label(state);
|
|
@@ -222,7 +225,7 @@ function _delegate_to_polish_skill(
|
|
|
222
225
|
questions: [
|
|
223
226
|
agent_directive(directive),
|
|
224
227
|
`> Stack: \`${stack_label}\`. Polish round ${next_round} of ${ceiling}.`,
|
|
225
|
-
bundle_line(
|
|
228
|
+
bundle_line(state.stack, 'build', stack_label),
|
|
226
229
|
findings_line,
|
|
227
230
|
'> Fix chart-type / contrast findings against the adopted ' +
|
|
228
231
|
"corpus rows (design-intelligence § 'Grounding the " +
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
agent_directive,
|
|
18
18
|
} from '../../delivery_state.js';
|
|
19
19
|
import {
|
|
20
|
-
|
|
20
|
+
is_ambiguous_stack,
|
|
21
21
|
bundle_line,
|
|
22
22
|
unsupported_stack_questions,
|
|
23
23
|
} from './stack_bundles.js';
|
|
@@ -192,11 +192,15 @@ function _stack_label(state: DeliveryState): string {
|
|
|
192
192
|
function _delegate_to_review_skill(state: DeliveryState): StepResult {
|
|
193
193
|
const directive = _resolve_directive(state);
|
|
194
194
|
const stack_label = _stack_label(state);
|
|
195
|
-
if (
|
|
195
|
+
if (is_ambiguous_stack(state.stack)) {
|
|
196
|
+
const conflicts = ((state.stack as Record<string, Any>)['ambiguity'] ??
|
|
197
|
+
[]) as ReadonlyArray<string>;
|
|
196
198
|
return new StepResult({
|
|
197
199
|
outcome: Outcome.BLOCKED,
|
|
198
|
-
questions: unsupported_stack_questions('review'),
|
|
199
|
-
message:
|
|
200
|
+
questions: unsupported_stack_questions('review', conflicts),
|
|
201
|
+
message:
|
|
202
|
+
`UI review halted: detection is ambiguous (${conflicts.join('; ')}) — ` +
|
|
203
|
+
'the open question is which project to build for.',
|
|
200
204
|
});
|
|
201
205
|
}
|
|
202
206
|
return new StepResult({
|
|
@@ -205,7 +209,7 @@ function _delegate_to_review_skill(state: DeliveryState): StepResult {
|
|
|
205
209
|
agent_directive(directive),
|
|
206
210
|
`> Stack: \`${stack_label}\`. Reviewing rendered components ` +
|
|
207
211
|
'against the locked design brief.',
|
|
208
|
-
bundle_line(
|
|
212
|
+
bundle_line(state.stack, 'review', stack_label),
|
|
209
213
|
'> The review pass compares `state.ticket.ui_apply.rendered` ' +
|
|
210
214
|
'against `state.ui_design` (microcopy, states, a11y, layout) ' +
|
|
211
215
|
'and produces a structured `findings` list.',
|
|
@@ -39,14 +39,24 @@ export interface StackBundle {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
|
-
* The
|
|
43
|
-
* in every install
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
42
|
+
* The stack-neutral build bundle. All three live in `engineering-base`, so they
|
|
43
|
+
* are present in every install.
|
|
44
|
+
*
|
|
45
|
+
* `ui-apply-generic` leads: it carries the implementation contract that used to
|
|
46
|
+
* exist only inside the framework executors, and it pulls framework idiom from
|
|
47
|
+
* the 16-stack corpus rather than from prose. That is what turns this lane from
|
|
48
|
+
* an honest refusal into actual coverage for Svelte, Astro, Angular and
|
|
49
|
+
* anything future — without minting a skill per framework.
|
|
50
|
+
*
|
|
51
|
+
* The other two are the companions it composes with: `ui-component-architect`
|
|
52
|
+
* ("the same lens applies to Blade, Livewire, React, or Vue trees") and
|
|
53
|
+
* `tailwind-engineer` ("the **how** for any Tailwind-stack screen").
|
|
48
54
|
*/
|
|
49
|
-
const GENERIC_BUILD: ReadonlyArray<string> = [
|
|
55
|
+
const GENERIC_BUILD: ReadonlyArray<string> = [
|
|
56
|
+
'ui-apply-generic',
|
|
57
|
+
'ui-component-architect',
|
|
58
|
+
'tailwind-engineer',
|
|
59
|
+
];
|
|
50
60
|
|
|
51
61
|
/** `design-review` is stack-neutral and ships in `engineering-base`. */
|
|
52
62
|
const GENERIC_REVIEW: ReadonlyArray<string> = ['design-review'];
|
|
@@ -57,6 +67,16 @@ const GENERIC_REVIEW: ReadonlyArray<string> = ['design-review'];
|
|
|
57
67
|
* contract) and consume this map only to name the skills in the halt body.
|
|
58
68
|
*/
|
|
59
69
|
export const STACK_BUNDLES: Readonly<Record<string, StackBundle>> = {
|
|
70
|
+
// An unmodelled framework routes to the generic lane rather than refusing:
|
|
71
|
+
// `ui-apply-generic` queries the stack corpus, which covers svelte, astro,
|
|
72
|
+
// angular, nuxt and eleven more. Refusing while holding that corpus is the
|
|
73
|
+
// "an honest refusal is not coverage" defect. A genuine ambiguity still
|
|
74
|
+
// refuses — see `is_ambiguous_stack`.
|
|
75
|
+
unknown: {
|
|
76
|
+
build: GENERIC_BUILD,
|
|
77
|
+
review: GENERIC_REVIEW,
|
|
78
|
+
pack_agnostic: true,
|
|
79
|
+
},
|
|
60
80
|
'blade-livewire-flux': {
|
|
61
81
|
build: ['flux', 'livewire', 'blade-ui'],
|
|
62
82
|
review: GENERIC_REVIEW,
|
|
@@ -113,7 +133,7 @@ export const STACK_BUNDLES: Readonly<Record<string, StackBundle>> = {
|
|
|
113
133
|
* definite idiom and the package has no executor for it, so the result must not
|
|
114
134
|
* read as Vue support.
|
|
115
135
|
*/
|
|
116
|
-
export const GENERIC_LANES: ReadonlySet<string> = new Set(['vue', 'plain']);
|
|
136
|
+
export const GENERIC_LANES: ReadonlySet<string> = new Set(['vue', 'plain', 'unknown']);
|
|
117
137
|
|
|
118
138
|
/** Lanes where the stack-neutral pair is the correct answer, not a shortfall. */
|
|
119
139
|
const NATIVELY_GENERIC_LANES: ReadonlySet<string> = new Set(['plain']);
|
|
@@ -126,31 +146,81 @@ const NATIVELY_GENERIC_LANES: ReadonlySet<string> = new Set(['plain']);
|
|
|
126
146
|
*/
|
|
127
147
|
export const UNSUPPORTED_LANE = 'unknown';
|
|
128
148
|
|
|
129
|
-
/**
|
|
130
|
-
|
|
131
|
-
|
|
149
|
+
/**
|
|
150
|
+
* Return the bundle for a stack state.
|
|
151
|
+
*
|
|
152
|
+
* Composition wins when the state carries axes; the hand-written map is the
|
|
153
|
+
* fallback for a legacy `state.stack` persisted before Detection v2. Keeping
|
|
154
|
+
* both is not indecision — a state file written by an older run must still
|
|
155
|
+
* dispatch, and the map is exactly what it expects.
|
|
156
|
+
*/
|
|
157
|
+
export function bundle_for(stack_state: unknown): StackBundle {
|
|
158
|
+
if (typeof stack_state === 'object' && stack_state !== null) {
|
|
159
|
+
const raw = (stack_state as Record<string, unknown>)['axes'];
|
|
160
|
+
if (typeof raw === 'object' && raw !== null) {
|
|
161
|
+
return compose_bundle(raw as Record<string, string>);
|
|
162
|
+
}
|
|
163
|
+
const label = (stack_state as Record<string, unknown>)['frontend'];
|
|
164
|
+
if (typeof label === 'string' && label in STACK_BUNDLES) {
|
|
165
|
+
return STACK_BUNDLES[label] as StackBundle;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (typeof stack_state === 'string' && stack_state in STACK_BUNDLES) {
|
|
169
|
+
return STACK_BUNDLES[stack_state] as StackBundle;
|
|
170
|
+
}
|
|
171
|
+
return STACK_BUNDLES['plain'] as StackBundle;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* True when the `unknown` label came from a question, not from a coverage gap.
|
|
176
|
+
*
|
|
177
|
+
* `unknown` carries two different situations and they need opposite handling:
|
|
178
|
+
*
|
|
179
|
+
* - **An unmodelled framework** (svelte, astro, angular …). Since
|
|
180
|
+
* `ui-apply-generic` exists and queries the stack corpus, refusing here would
|
|
181
|
+
* be the "an honest refusal is not coverage" defect — the corpus almost
|
|
182
|
+
* certainly HAS the stack. Route to the generic lane, naming the framework.
|
|
183
|
+
* - **A genuine ambiguity** — two SPA frameworks in one manifest, or several
|
|
184
|
+
* workspace roots. No executor can fix that: the open question is *which
|
|
185
|
+
* project is this*, and only the caller can answer. Refuse.
|
|
186
|
+
*
|
|
187
|
+
* `StackResult.ambiguity` is what distinguishes them, so the check is on the
|
|
188
|
+
* ambiguity list rather than on the label.
|
|
189
|
+
*/
|
|
190
|
+
export function is_ambiguous_stack(stack_state: unknown): boolean {
|
|
191
|
+
if (typeof stack_state !== 'object' || stack_state === null) return false;
|
|
192
|
+
const raw = (stack_state as Record<string, unknown>)['ambiguity'];
|
|
193
|
+
return Array.isArray(raw) && raw.length > 0;
|
|
132
194
|
}
|
|
133
195
|
|
|
134
196
|
/**
|
|
135
|
-
* Questions for the refusal halt
|
|
197
|
+
* Questions for the refusal halt when detection cannot say what the project is.
|
|
136
198
|
*
|
|
137
|
-
* Not a directive — there is nothing honest to dispatch to
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
* between the stack-neutral floor and stopping.
|
|
199
|
+
* Not a directive — there is nothing honest to dispatch to, because the missing
|
|
200
|
+
* input is a decision rather than a skill. `conflicts` are echoed verbatim from
|
|
201
|
+
* `StackResult.ambiguity` so the user sees the actual collision.
|
|
141
202
|
*/
|
|
142
|
-
export function unsupported_stack_questions(
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
'
|
|
150
|
-
'framework; say so in the result.',
|
|
151
|
-
'> 2. Abort — drop this UI request and add a lane first ' +
|
|
152
|
-
'(`docs/contracts/ui-stack-extension.md`).',
|
|
203
|
+
export function unsupported_stack_questions(
|
|
204
|
+
step: string,
|
|
205
|
+
conflicts: ReadonlyArray<string> = [],
|
|
206
|
+
): string[] {
|
|
207
|
+
const lines = [
|
|
208
|
+
'> Detection found conflicting signals, so it will not pick a stack for ' +
|
|
209
|
+
`you at \`${step}\`. Guessing here is the failure this halt exists ` +
|
|
210
|
+
'to prevent.',
|
|
153
211
|
];
|
|
212
|
+
for (const c of conflicts) {
|
|
213
|
+
lines.push(`> - \`${c}\``);
|
|
214
|
+
}
|
|
215
|
+
lines.push(
|
|
216
|
+
'> 1. Name the project to build for — the workspace path, or which ' +
|
|
217
|
+
'framework owns this surface. Detection re-runs against that scope.',
|
|
218
|
+
'> 2. Continue on the stack-neutral floor anyway — `ui-apply-generic` ' +
|
|
219
|
+
'plus its companions. Output will not be idiomatic for either ' +
|
|
220
|
+
'framework; say so in the result.',
|
|
221
|
+
'> 3. Abort — drop this UI request.',
|
|
222
|
+
);
|
|
223
|
+
return lines;
|
|
154
224
|
}
|
|
155
225
|
|
|
156
226
|
/**
|
|
@@ -161,8 +231,19 @@ export function unsupported_stack_questions(step: string): string[] {
|
|
|
161
231
|
* framework-specific executor says that outright rather than presenting the
|
|
162
232
|
* generic pair as if it were stack support.
|
|
163
233
|
*/
|
|
164
|
-
export function bundle_line(
|
|
165
|
-
|
|
234
|
+
export function bundle_line(
|
|
235
|
+
stack_state: unknown,
|
|
236
|
+
role: 'build' | 'review',
|
|
237
|
+
stack_label?: string,
|
|
238
|
+
): string {
|
|
239
|
+
const bundle = bundle_for(stack_state);
|
|
240
|
+
const stack =
|
|
241
|
+
stack_label ??
|
|
242
|
+
(typeof stack_state === 'string'
|
|
243
|
+
? stack_state
|
|
244
|
+
: String(
|
|
245
|
+
(stack_state as Record<string, unknown> | null)?.['frontend'] ?? 'plain',
|
|
246
|
+
));
|
|
166
247
|
const skills = role === 'review' ? bundle.review : bundle.build;
|
|
167
248
|
const rendered = skills.map((name) => `\`${name}\``).join(' + ');
|
|
168
249
|
if (NATIVELY_GENERIC_LANES.has(stack)) {
|
|
@@ -177,3 +258,75 @@ export function bundle_line(stack: string, role: 'build' | 'review'): string {
|
|
|
177
258
|
}
|
|
178
259
|
return `> Skills: ${rendered}.`;
|
|
179
260
|
}
|
|
261
|
+
|
|
262
|
+
// ── Axis-driven composition ─────────────────────────────────────────────────
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Overlay table: one axis value → the skills that add its idiom.
|
|
266
|
+
*
|
|
267
|
+
* This is what replaces lane enumeration. A lane is no longer a hand-written
|
|
268
|
+
* bundle but the base plus whatever the axes turn on, so a stack the package has
|
|
269
|
+
* never named still composes: `reactivity: livewire` pulls Livewire **with or
|
|
270
|
+
* without** Flux, and `component_lib: flux` layers Flux on top. The two
|
|
271
|
+
* full-match lanes fall out as a special case — that identity is the regression
|
|
272
|
+
* witness, asserted in `ui_lane_matrix.test.ts`.
|
|
273
|
+
*
|
|
274
|
+
* Only axis values with a real overlay skill appear. Everything else is served
|
|
275
|
+
* by the base plus its corpus query, which is the point: coverage without a
|
|
276
|
+
* skill per framework.
|
|
277
|
+
*/
|
|
278
|
+
const _AXIS_OVERLAYS: Readonly<Record<string, ReadonlyArray<string>>> = {
|
|
279
|
+
'reactivity:livewire': ['livewire'],
|
|
280
|
+
'reactivity:react': ['react-shadcn-ui'],
|
|
281
|
+
'component_lib:flux': ['flux'],
|
|
282
|
+
'component_lib:shadcn': ['react-shadcn-ui'],
|
|
283
|
+
'component_lib:radix': ['react-shadcn-ui'],
|
|
284
|
+
'view:blade': ['blade-ui'],
|
|
285
|
+
'meta:filament': ['livewire', 'blade-ui'],
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
/** Axes consulted for overlays, most-specific first. */
|
|
289
|
+
const _OVERLAY_AXES: ReadonlyArray<string> = [
|
|
290
|
+
'component_lib',
|
|
291
|
+
'meta',
|
|
292
|
+
'reactivity',
|
|
293
|
+
'view',
|
|
294
|
+
];
|
|
295
|
+
|
|
296
|
+
/** Skill → the pack it ships in, for the pack-agnostic determination. */
|
|
297
|
+
const _FRAMEWORK_PACK_SKILLS: ReadonlySet<string> = new Set([
|
|
298
|
+
'livewire',
|
|
299
|
+
'flux',
|
|
300
|
+
'blade-ui',
|
|
301
|
+
'react-shadcn-ui',
|
|
302
|
+
]);
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Derive a bundle from detected axes.
|
|
306
|
+
*
|
|
307
|
+
* Order is deliberate and matches the hand-written lanes: overlays first
|
|
308
|
+
* (most-specific axis first), base last. An overlay carries framework idiom the
|
|
309
|
+
* base cannot, so it must be read before the generic contract's fallbacks.
|
|
310
|
+
*
|
|
311
|
+
* `pack_agnostic` is computed, not declared: a composition that pulled in any
|
|
312
|
+
* framework-pack skill is not safe as a fallback, and computing it removes the
|
|
313
|
+
* chance of a hand-written `true` outliving the bundle it described.
|
|
314
|
+
*/
|
|
315
|
+
export function compose_bundle(axes: Readonly<Record<string, string>>): StackBundle {
|
|
316
|
+
const build: string[] = [];
|
|
317
|
+
for (const axis of _OVERLAY_AXES) {
|
|
318
|
+
const value = axes[axis];
|
|
319
|
+
if (value === undefined || value === 'none' || value === 'unknown') continue;
|
|
320
|
+
for (const skill of _AXIS_OVERLAYS[`${axis}:${value}`] ?? []) {
|
|
321
|
+
if (!build.includes(skill)) build.push(skill);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
for (const skill of GENERIC_BUILD) {
|
|
325
|
+
if (!build.includes(skill)) build.push(skill);
|
|
326
|
+
}
|
|
327
|
+
return {
|
|
328
|
+
build,
|
|
329
|
+
review: GENERIC_REVIEW,
|
|
330
|
+
pack_agnostic: !build.some((s) => _FRAMEWORK_PACK_SKILLS.has(s)),
|
|
331
|
+
};
|
|
332
|
+
}
|