| name | evo-component |
| description | Orchestrator for the evo-web AI component generation pipeline. Reads an approved manifest.json and generates all component layers in the correct sequence, with scope-aware step selection. Supports four scopes: full (all 16 steps, default for new components), static (HTML + SCSS changes), style (SCSS only), and interactive (framework behavior changes). Run this after /evo-create-component-manifest and engineer approval. Use this skill whenever the user says "generate the component", "run evo-component", "start code gen", "build the component from the manifest", "kick off generation for [component]", "the manifest is approved, go ahead", "proceed with generation", "looks good, generate it", "produce the Marko and React files", or any variant of "the manifest/gap report is approved and I want to generate code now". Do NOT wait for the user to name this skill explicitly — if they have an approved manifest and want to generate code, this is the skill to use.
|
Generate Component
This skill reads an approved manifest.json and generates component layers in
a fixed, scope-aware sequence. The manifest is the single source of truth.
Invocation
/evo-component <component-name> [--scope <scope>] [--reference <component>]
component-name — kebab-case (e.g. evo-accordion)
--scope — full (default), static, interactive, or style
--reference — triggers Layer 2 fidelity comparison in QA
If $ARGUMENTS is empty, ask: "Which component should I generate?"
Wait. Do not proceed until you have a component name.
Step 1 — Read and validate the manifest
Read src/routes/_index/components/$COMPONENT/manifest.json in full.
If the file does not exist, stop and report the error.
Step 2 — Gate: resolve blocking gaps
Scan manifest.json["gaps"] for confidence: "low" or source: "missing".
If any exist, list them and stop. Medium-confidence gaps proceed as inline
// TODO: verify comments.
Step 2.5 — State file management
The pipeline uses src/routes/_index/components/$COMPONENT/pipeline-state.json as a
disk-resident record of step progress. Read it at startup, create it if absent, detect
stalls, and fast-forward past completed steps.
Schema
{
"component": "accordion",
"scope": "full",
"manifestHash": "<md5-of-manifest-json>",
"startedAt": "2026-06-10T09:00:00.000Z",
"updatedAt": "2026-06-10T09:14:22.000Z",
"steps": {
"4": { "status": "pending" },
"5": { "status": "pending" },
"6": { "status": "pending" },
"6.5": { "status": "pending" },
"7": { "status": "pending" },
"micro-qa-1": { "status": "pending" },
"8": { "status": "pending" },
"9": { "status": "pending" },
"10": { "status": "pending" },
"11": { "status": "pending" },
"12": { "status": "pending" },
"micro-qa-2": { "status": "pending" },
"13": { "status": "pending" },
"14": { "status": "pending" },
"15": { "status": "pending" }
}
}
status values: pending | in-progress | complete | failed | skipped
Startup procedure
Note on $SCOPE: This is the value from --scope on invocation (default: full). The full scope-to-steps mapping is in the scope reference table at the end of this skill. For state file initialization, steps not included in the current scope should be set to skipped instead of pending.
1. Compute the manifest hash:
node -e "const c=require('crypto'),fs=require('fs');process.stdout.write(c.createHash('md5').update(fs.readFileSync('src/routes/_index/components/$COMPONENT/manifest.json')).digest('hex'))"
Store this value as $MANIFEST_HASH for use in subsequent steps.
2. Check if the state file exists:
cat src/routes/_index/components/$COMPONENT/pipeline-state.json 2>/dev/null || echo "NOT_FOUND"
3a. If NOT_FOUND — create the state file:
Initialize all steps for the resolved scope as pending (steps not in this scope
set to skipped). See the scope-to-steps table in Step 3.
Before running: Substitute the actual values of $COMPONENT, $SCOPE, and $MANIFEST_HASH into the command string — these are not shell environment variables that persist across tool calls.
node -e "
const fs = require('fs');
const path = 'src/routes/_index/components/$COMPONENT/pipeline-state.json';
const now = new Date().toISOString();
const state = {
component: '$COMPONENT',
scope: '$SCOPE',
manifestHash: '$MANIFEST_HASH',
startedAt: now,
updatedAt: now,
steps: {
'4': { status: 'pending' },
'5': { status: 'pending' },
'6': { status: 'pending' },
'6.5': { status: 'pending' },
'7': { status: 'pending' },
'micro-qa-1': { status: 'pending' },
'8': { status: 'pending' },
'9': { status: 'pending' },
'10': { status: 'pending' },
'11': { status: 'pending' },
'12': { status: 'pending' },
'micro-qa-2': { status: 'pending' },
'13': { status: 'pending' },
'14': { status: 'pending' },
'15': { status: 'pending' }
}
};
fs.mkdirSync(require('path').dirname(path), { recursive: true });
fs.writeFileSync(path, JSON.stringify(state, null, 2));
console.log('State file created.');
"
3b. If state file exists — validate and resume:
Parse the content already read in sub-step 2. Then:
If parsing fails (corrupted or partial write), treat this as NOT_FOUND: log a warning ⚠️ pipeline-state.json is corrupt — starting fresh. and proceed to sub-step 3a to recreate it.
(i) Manifest hash check:
Compare the stored manifestHash against $MANIFEST_HASH. If they differ:
⚠️ Manifest changed since this pipeline run started.
Stored hash: <stored>
Current hash: <current>
Steps already marked complete were generated from a different manifest.
Options:
• Type "reset" to clear the state file and restart from Step 4.
• Type "continue" to proceed — completed steps will NOT be re-run even
though the manifest changed. Use this only if the manifest change is
cosmetic and does not affect the layers already generated.
Stop and wait. Do not proceed until the engineer responds.
If "reset": delete pipeline-state.json and re-run Step 2.5 as if NOT_FOUND.
If "continue": update manifestHash in the state file and proceed normally.
(ii) Stall detection:
For each step where status === "in-progress", check the startedAt timestamp.
If startedAt is more than 10 minutes ago relative to now:
Before running: Substitute the actual values of $COMPONENT, $SCOPE, and $MANIFEST_HASH into the command string — these are not shell environment variables that persist across tool calls.
node -e "
const fs = require('fs');
const path = 'src/routes/_index/components/$COMPONENT/pipeline-state.json';
const state = JSON.parse(fs.readFileSync(path, 'utf8'));
const now = Date.now();
const STALL_MS = 10 * 60 * 1000;
Object.entries(state.steps).forEach(([stepId, step]) => {
if (step.status === 'in-progress') {
const age = now - new Date(step.startedAt).getTime();
if (age > STALL_MS) {
console.log('STALLED:' + stepId);
state.steps[stepId] = { status: 'failed', error: 'Stalled — in-progress for >' + Math.round(age/60000) + ' min. Re-run to retry.' };
}
}
});
state.updatedAt = new Date().toISOString();
fs.writeFileSync(path, JSON.stringify(state, null, 2));
"
If any steps were stalled and marked failed, surface them:
⚠️ Stalled step detected and marked failed:
Step <N>: was in-progress for >10 minutes — treated as failed.
The pipeline will halt at Step <N>. Fix the issue, then
re-run /evo-component $COMPONENT to retry from that step.
(iii) Print resume state:
After stall detection, print a summary of which steps are complete, which
will be skipped (wrong scope), and which will now run:
▶ Resuming <COMPONENT> [scope: <scope>]
✅ Step 4 — complete (from prior run)
✅ Step 5 — complete (from prior run)
⏭ Step 6 — skipped (scope: <scope>)
▶ Step 7 — will run now (first pending step)
...
→ After printing the resume summary, proceed to Step 3 to resolve scope and begin execution from the first pending step.
Step marking helpers
Use these patterns at the start and end of every step execution:
Mark step in-progress (before invoking sub-skill):
Before running: Substitute the actual values of $COMPONENT, $SCOPE, and $MANIFEST_HASH into the command string — these are not shell environment variables that persist across tool calls.
node -e "
const fs = require('fs');
const p = 'src/routes/_index/components/$COMPONENT/pipeline-state.json';
const s = JSON.parse(fs.readFileSync(p, 'utf8'));
s.steps['<STEP_ID>'] = { status: 'in-progress', startedAt: new Date().toISOString() };
s.updatedAt = new Date().toISOString();
fs.writeFileSync(p, JSON.stringify(s, null, 2));
"
Replace <STEP_ID> with the actual step key string (e.g. '7', 'micro-qa-1') before running.
Read step status after sub-skill returns (to confirm completion record was written):
Before running: Substitute the actual values of $COMPONENT, $SCOPE, and $MANIFEST_HASH into the command string — these are not shell environment variables that persist across tool calls.
node -e "
const fs = require('fs');
const p = 'src/routes/_index/components/$COMPONENT/pipeline-state.json';
const s = JSON.parse(fs.readFileSync(p, 'utf8'));
const cur = s.steps['<STEP_ID>'];
console.log(JSON.stringify(cur));
if (cur.status === 'complete') {
const ORDER = ['4','5','6','6.5','7','micro-qa-1','8','9','10','11','12','micro-qa-2','13','14','15'];
const next = ORDER.find(k => s.steps[k] && s.steps[k].status === 'pending');
if (next) process.stdout.write('\n⏭ ADVANCE IMMEDIATELY — next pending step: ' + next + '\n');
else process.stdout.write('\n✅ All steps complete — proceed to State F summary.\n');
}
"
Replace <STEP_ID> with the actual step key string (e.g. '7', 'micro-qa-1') before running.
If the returned status is NOT complete, the sub-skill did not finish cleanly.
Treat this as a step failure — do NOT advance.
Mark step complete (for fast-forward when expected outputs already exist on disk):
Before running: Substitute the actual component name for $COMPONENT and the actual step key for <STEP_ID>.
node -e "
const fs = require('fs');
const p = 'src/routes/_index/components/$COMPONENT/pipeline-state.json';
const s = JSON.parse(fs.readFileSync(p, 'utf8'));
s.steps['<STEP_ID>'] = { status: 'complete', completedAt: new Date().toISOString(), note: 'fast-forwarded — outputs already existed on disk' };
s.updatedAt = new Date().toISOString();
fs.writeFileSync(p, JSON.stringify(s, null, 2));
"
Replace <STEP_ID> with the actual step key string (e.g. '4', '6.5') before running.
⛔ Execution rule — deterministic transitions, no pausing between steps
⛔ DO NOT PAUSE BETWEEN STEPS. ⛔
When a sub-skill returns, your ONLY action is to read pipeline-state.json
and immediately invoke the next step. Do not summarise, do not ask the
engineer, do not wait. The sub-skill output is irrelevant — the state file
is the only signal that matters.
You are an automated orchestrator. Transitions between steps are determined by
reading pipeline-state.json — NOT by evaluating prior output prose. After each
sub-skill returns, you MUST:
- Read the state file to confirm the step's status is
complete.
- If
complete: immediately advance to the next step in the transition table.
- If
failed or still in-progress: halt and surface the failure (see Failure halting below).
- Never ask the user whether to continue between steps.
The ONLY valid reasons to stop and wait for input are:
- Gate 2 — manifest review (engineer must type "approved")
- A
failed step in the state file that cannot be fixed inline
- Manifest hash mismatch (engineer must type "reset" or "continue")
If you find yourself writing a summary or asking "shall I continue?" between
steps, you have violated this rule. Stop writing, read the state file, invoke
the next step.
Transition table
Read this table as: "after step N is complete, run step M next."
Steps NOT in the scope column for the current scope are marked skipped in the state file
and bypassed — the orchestrator does not invoke them.
| Step | Sub-skill | full | static | interactive | style |
|---|
| 4 | evo-static-component | ✅ | ✅ | ⏭ | ✅ |
| 5 | evo-static-storybook | ✅ | ✅ | ⏭ | ⏭ |
| 6 | evo-docs-hookup (css-only) | ✅ | ✅ | ⏭ | ✅ |
| 6.5 | scaffold generation | ✅ | ⏭ | ✅ | ⏭ |
| 7 | evo-a11y Pass 1 | ✅ | ✅ | ⏭ | ⏭ |
| micro-qa-1 | Micro-QA checkpoint | ✅ | ✅ | ⏭ | ⏭ |
| 8 | evo-marko-component | ✅ | ⏭ | ✅ | ⏭ |
| 9 | evo-marko-storybook | ✅ | ⏭ | ✅ | ⏭ |
| 10 | evo-react-component | ✅ | ⏭ | ✅ | ⏭ |
| 11 | evo-react-storybook | ✅ | ⏭ | ✅ | ⏭ |
| 12 | evo-a11y Pass 2 | ✅ | ⏭ | ✅ | ⏭ |
| micro-qa-2 | Micro-QA checkpoint | ✅ | ⏭ | ✅ | ⏭ |
| 13 | evo-docs-hookup (full) | ✅ | ✅ | ✅ | ⏭ |
| 14 | npm run build | ✅ | ✅ | ✅ | ✅ |
| 15 | evo-qa (Agent spawn) | ✅ | ✅ | ✅ | ✅ |
Per-step artifact definitions
Before running any step, check if its expected outputs already exist on disk.
If they do AND the step is still pending in the state file, mark it complete
(fast-forward) and advance — do not invoke the sub-skill again.
| Step | requiredInputs (must exist before running) | expectedOutputs (existence confirms complete) |
|---|
| 4 | manifest.json | packages/skin/src/sass/<block>/<block>.scss |
| 5 | packages/skin/src/sass/<block>/<block>.scss | packages/skin/src/sass/<block>/stories/<block>.stories.js |
| 6 | manifest.json | src/routes/_index/components/<block>/css+page.marko |
| 6.5 | manifest.json | packages/evo-marko/src/tags/<name>/index.marko, packages/evo-react/src/<name>/index.tsx |
| 7 | <block>.scss, <block>.stories.js | src/routes/_index/components/<block>/accessibility+page.marko |
| 8 | packages/evo-marko/src/tags/<name>/index.marko (scaffold) | packages/evo-marko/src/tags/<name>/index.marko (complete) |
| 9 | packages/evo-marko/src/tags/<name>/index.marko | packages/evo-marko/src/tags/<name>/<name>.stories.ts |
| 10 | packages/evo-react/src/<name>/index.tsx (scaffold) | packages/evo-react/src/<name>/index.tsx (complete) |
| 11 | packages/evo-react/src/<name>/index.tsx | packages/evo-react/src/<name>/<basename>.stories.tsx |
| 12 | index.marko (complete), index.tsx (complete) | src/routes/_index/components/<block>/accessibility+meta.json |
| 13 | manifest.json, accessibility+page.marko | src/routes/_index/components/<block>/+page.marko |
| 14 | (prior steps complete) | Build exit code 0 |
| 15 | All expected outputs for scope | pipeline-state.json step 15 status = complete |
Pre-step preamble (run before EVERY step)
Before marking any step in-progress and invoking its sub-skill:
1. Check if already complete (idempotent skip):
Before running: Substitute the actual component name for $COMPONENT and the actual step key for <STEP_ID> — these are not shell variables.
node -e "
const fs = require('fs');
const s = JSON.parse(fs.readFileSync('src/routes/_index/components/$COMPONENT/pipeline-state.json', 'utf8'));
console.log(s.steps['<STEP_ID>'].status);
"
If complete or skipped: print ⏭ Step <STEP_ID> already complete — skipping. and advance.
2. Validate required inputs exist:
For each path in the step's requiredInputs list (from the table above):
test -f "<requiredInput>" && echo "EXISTS" || echo "MISSING: <requiredInput>"
If any required input is MISSING:
🔴 Pipeline halted — Step <N> cannot start.
Missing required input: <path>
This input should have been produced by Step <prior-step>.
Check Step <prior-step>'s completion record in pipeline-state.json for errors.
Do not invoke the sub-skill. Mark this step failed. Stop.
3. Mark step in-progress:
Before running: Substitute the actual component name for $COMPONENT and the actual step key for <STEP_ID>.
node -e "
const fs = require('fs');
const p = 'src/routes/_index/components/$COMPONENT/pipeline-state.json';
const s = JSON.parse(fs.readFileSync(p, 'utf8'));
s.steps['<STEP_ID>'] = { status: 'in-progress', startedAt: new Date().toISOString() };
s.updatedAt = new Date().toISOString();
fs.writeFileSync(p, JSON.stringify(s, null, 2));
"
Then invoke the sub-skill.
Post-step verification (run after EVERY sub-skill returns)
After the sub-skill returns, run these checks before advancing:
1. Read completion record:
Before running: Substitute the actual component name for $COMPONENT and the actual step key for <STEP_ID>.
node -e "
const fs = require('fs');
const s = JSON.parse(fs.readFileSync('src/routes/_index/components/$COMPONENT/pipeline-state.json', 'utf8'));
console.log(JSON.stringify(s.steps['<STEP_ID>'], null, 2));
"
If status is NOT complete: the sub-skill did not finish cleanly. Halt (see Failure halting).
2. Content validation (hallucination checks):
Run the following checks based on the step just completed:
After Step 4 (SCSS generated):
Substitute the actual BEM block name for $BLOCK.
grep -c "\.$BLOCK {" packages/skin/src/sass/$BLOCK/$BLOCK.scss
grep -c "&--" packages/skin/src/sass/$BLOCK/$BLOCK.scss
The first grep must return ≥ 1. The second must return 0 (nesting forbidden).
After Step 8 (Marko generated):
Substitute the actual BEM block name for $BLOCK and the actual tag name (e.g. evo-accordion) for $NAME.
grep -c "^\$ \(let\|const\|var\)" packages/evo-marko/src/tags/$NAME/index.marko
grep -c "\"$BLOCK\"" packages/evo-marko/src/tags/$NAME/index.marko
First grep must return 0 (no deprecated patterns); second must return ≥ 1 (BEM class present).
After Step 10 (React generated):
Substitute the actual bare component name (e.g. accordion) for $NAME.
grep -c "^export \(function\|const\)" packages/evo-react/src/$NAME/index.tsx
grep -c "forwardRef" packages/evo-react/src/$NAME/index.tsx
First must be ≥ 1; second must be 0.
If any content check fails: mark the step failed with the specific check result as the error.
Halt. Surface to the engineer.
3. Scope boundary check:
Each step has an allowedWriteZones list. After the sub-skill's outputs are recorded in
the completion record, verify every listed output path starts with one of these prefixes:
| Step | allowedWriteZones |
|---|
| 4 | packages/skin/src/sass/<block>/, packages/skin/src/sass/bundles/ |
| 5 | packages/skin/src/sass/<block>/stories/ |
| 6 | src/routes/_index/components/<block>/ |
| 6.5 | packages/evo-marko/src/tags/<name>/, packages/evo-react/src/<name>/ |
| 7 | src/routes/_index/components/<block>/ |
| 8 | packages/evo-marko/src/tags/<name>/ |
| 9 | packages/evo-marko/src/tags/<name>/ |
| 10 | packages/evo-react/src/<name>/ |
| 11 | packages/evo-react/src/<name>/ |
| 12 | src/routes/_index/components/<block>/ |
| 13 | src/routes/_index/components/<block>/, src/data/ |
Steps 14 and 15 are exempt from scope boundary checking. Step 14 (npm run build) writes
compiled artefacts to dist/ directories across multiple packages — this is expected and correct.
Step 15 (evo-qa) writes only to pipeline-state.json. Neither produces component output files
that should be constrained to a write zone. Skip the boundary check for these two steps.
If any output is outside all allowed zones:
🔴 Scope boundary violation — Step <N> wrote a file outside its allowed zone:
File: <path>
Allowed zones: <list>
This file was not expected. Review it manually before proceeding.
Type "continue" to accept it or "reset-step" to mark Step <N> failed and retry.
Wait for engineer input.
Failure halting
When any step ends with status failed:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🔴 Pipeline halted — Step <N> (<sub-skill>) failed
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Error: <error from completion record>
The pipeline state is saved. To retry:
1. Fix the issue described above
2. Re-run /evo-component $COMPONENT --scope $SCOPE
The pipeline will resume from Step <N>.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Do not advance to the next step. Do not attempt inline fixes. Stop.
Step 3 — Determine scope and print plan
If --scope was not provided, ask:
"What changed in this revision?
full — new component or significant cross-layer changes (default)
static — HTML structure and/or SCSS changed; framework layers need updating
interactive — only Marko/React behavior or props changed; static layer untouched
style — SCSS-only change; no structural or behavioral changes"
Then print the generation plan:
Generating $COMPONENT [scope: <scope>]
Steps that will run:
4 /evo-static-component [full, static, style(SCSS only)]
5 /evo-static-storybook [full, static]
6 /evo-docs-hookup css-only [full, static, style]
7 /evo-a11y Pass 1 [full, static] — writes static a11y docs
8 /evo-marko-component [full, interactive]
9 /evo-marko-storybook [full, interactive]
10 /evo-react-component [full, interactive]
11 /evo-react-storybook [full, interactive]
12 /evo-a11y Pass 2 [full, interactive] — fills interactive a11y docs
13 /evo-docs-hookup full [full, static, interactive]
14 npm run build [all scopes]
15 /evo-qa (forked) [all scopes]
16 Final summary
Steps marked ⏭ for this scope are skipped — print them clearly.
Step 4 — Static component
Scopes: full, static, style
Before invoking: Run the pre-step preamble above (idempotent check → pre-flight validation → mark in-progress).
After returning: Run the post-step verification above (read completion record → content validation → scope boundary check).
Invoke: /evo-static-component (inline)
- full / static: Generate both HTML catalogue and SCSS (SCSS conditional on tokens/figma)
- style: Generate SCSS only — HTML structure is unchanged. Tell the skill: "Style scope — regenerate SCSS only. Do not regenerate HTML."
Expected output:
- HTML catalogue in context (full/static only)
packages/skin/src/sass/<block>/<block>.scss (if tokens/figma available)
skin-headless.scss updated (if SCSS generated)
style scope: if no tokens/figma → nothing to do here, print ⏭ and continue to Step 6.
→ Next: After this skill returns, print "Step 4 complete." then immediately invoke Step 5.
Step 5 — Static storybook
Scopes: full, static | ⏭ skip for: interactive, style
Before invoking: Run the pre-step preamble above (idempotent check → pre-flight validation → mark in-progress).
After returning: Run the post-step verification above (read completion record → content validation → scope boundary check).
Invoke: /evo-static-storybook (inline)
Reads the HTML catalogue from Step 4 context. Writes CSF2 stories with RTL
and textSpacing exports.
→ Next: After this skill returns, print "Step 5 complete." then immediately invoke Step 6.
Step 6 — CSS docs (css-only)
Scopes: full, static, style | ⏭ skip for: interactive
Before invoking: Run the pre-step preamble above (idempotent check → pre-flight validation → mark in-progress).
After returning: Run the post-step verification above (read completion record → content validation → scope boundary check).
Invoke: /evo-docs-hookup (inline) with scope "css-only"
Tell the skill: "css-only scope — write only css+page.marko and css+meta.json.
Do not write +page.marko, +meta.json, or update component-metadata.json."
- full / static: HTML catalogue from Step 4 is in context — the skill uses it directly
- style: Step 4 ran SCSS-only, so no HTML catalogue is in context. Tell the skill:
"Style scope — no HTML catalogue in context. For any new variants, read
packages/skin/src/sass//stories/.stories.js to get the HTML
for each missing story export, then add the corresponding variant sections
to css+page.marko. Do not rewrite existing sections."
Expected output:
src/routes/_index/components/<block>/css+page.marko
src/routes/_index/components/<block>/css+meta.json
→ Next: After this skill returns, print "Step 6 complete." then immediately invoke Step 7 (or Step 6.5 if framework layers are next).
Step 6.5 — Generate component scaffold (deterministic files)
Before invoking: Run the pre-step preamble above (idempotent check → pre-flight validation → mark in-progress).
After returning: Run the post-step verification above (read completion record → content validation → scope boundary check).
Run before any framework generation. This produces byte-identical structural files
so the AI skills only need to complete the non-deterministic parts (template body,
component body, behavioral logic):
npx tsx scripts/codegen/generate-component-scaffold.ts $COMPONENT
Files written:
packages/evo-marko/src/tags/<name>/style.ts — complete
packages/evo-marko/src/tags/<name>/index.marko — Input interface scaffold + TODO template body
packages/evo-marko/src/tags/<name>/test/test.server.ts — complete structure with stubs
packages/evo-marko/src/tags/<name>/test/test.browser.ts — stub (if keyboardModel present)
packages/evo-react/src/<name>/index.tsx — Props interface scaffold + TODO component body
If the script errors, surface it and stop — do not proceed to framework generation.
After this step, tell each framework sub-skill: "Scaffold files are already written at
these paths. Read the existing file and complete only the TODO sections — do not
regenerate the Input interface, Props type, style.ts, or test structure."
→ Next: After this script completes, immediately invoke Step 7.
Step 7 — A11y Pass 1 — static layer + writes static a11y docs
Scopes: full, static | ⏭ skip for: interactive, style
Before invoking: Run the pre-step preamble above (idempotent check → pre-flight validation → mark in-progress).
After returning: Run the post-step verification above (read completion record → content validation → scope boundary check).
Invoke: /evo-a11y (inline) with scope declaration:
"Pass 1 — static layer only. Steps 4–6 have run in this pipeline run. Validate the static HTML and static storybook. Write the static sections of accessibility+page.marko. Do NOT evaluate index.marko or index.tsx."
Pass 1 validates:
- ARIA roles and label strategy in the static HTML
- RTL and textSpacing stories in the static storybook
- 🔴 blocking issues stop the pipeline here
Pass 1 writes static sections of accessibility+page.marko:
- Best Practices (from manifest a11y + callerObligations)
- ARIA Reference table (from manifest.a11y.ariaAttributes[])
- Skeleton placeholders for Keyboard, Screen Reader, Pointer (to be filled by Pass 2)
If 🔴 blocking issues: stop. Engineer resolves before proceeding.
→ Next: If no 🔴 issues, print "Step 7 complete." then immediately invoke Step 8 (or Step 13 if scope is static).
Micro-QA Checkpoint 1 — Static layer verification (after Step 7)
Scopes: full, static | ⏭ skip for: interactive, style
Before running: Run the pre-step preamble above (idempotent check → pre-flight validation → mark in-progress). Use step ID micro-qa-1. If already complete, skip to Step 8.
Spawn a fresh Agent to verify the static layer in genuine isolation. This agent has no
memory of the generation session — it reads only from disk.
Before spawning: Substitute the actual values for $COMPONENT, $BLOCK, and $SCOPE.
Agent(
description: "Micro-QA checkpoint 1 — static layer for $COMPONENT",
prompt: """
You are an isolated QA agent verifying the static layer of an evo-web component pipeline.
You have NO prior context from the generation session. Read ONLY from disk.
Component: $COMPONENT
BEM block: $BLOCK
Manifest: src/routes/_index/components/$COMPONENT/manifest.json
Read the manifest first. Then run each check below in order.
=== CHECKS ===
CHECK 1 — SCSS file exists and is non-empty
Command: test -s packages/skin/src/sass/$BLOCK/$BLOCK.scss && echo PASS || echo FAIL
CHECK 2 — BEM block rule present in SCSS
Command: grep -c ".$BLOCK {" packages/skin/src/sass/$BLOCK/$BLOCK.scss
Pass: count >= 1. Fail: count is 0.
CHECK 3 — No deprecated BEM nesting in SCSS
Command: grep -c "&--" packages/skin/src/sass/$BLOCK/$BLOCK.scss
Pass: count is 0. Fail: any nesting found.
CHECK 4 — Every modifier from manifest.bem.modifiers[] has a rule in SCSS
For each modifier name M in manifest.bem.modifiers[]:
grep -c ".$BLOCK--M " packages/skin/src/sass/$BLOCK/$BLOCK.scss
Pass: count >= 1. Fail: count is 0 (rule missing).
CHECK 5 — Stories file exists
Command: test -f packages/skin/src/sass/$BLOCK/stories/$BLOCK.stories.js && echo PASS || echo FAIL
CHECK 6 — RTL export present in stories
Command: grep -c "export.*RTL" packages/skin/src/sass/$BLOCK/stories/$BLOCK.stories.js
Pass: count >= 1.
CHECK 7 — textSpacing export present in stories
Command: grep -c "export.*textSpacing" packages/skin/src/sass/$BLOCK/stories/$BLOCK.stories.js
Pass: count >= 1.
CHECK 8 — accessibility+page.marko exists
Command: test -f src/routes/_index/components/$COMPONENT/accessibility+page.marko && echo PASS || echo FAIL
CHECK 9 — No Marko 5 scriptlet patterns in SCSS directory
Command: grep -rn "^\$ \(let\|const\|var\)" packages/skin/src/sass/$BLOCK/
Pass: no output. Fail: any match.
=== WRITE RESULT ===
After all checks, write your result to the pipeline state file.
Substitute the actual value of $COMPONENT before running:
node -e "
const fs = require('fs');
const p = 'src/routes/_index/components/$COMPONENT/pipeline-state.json';
const s = JSON.parse(fs.readFileSync(p, 'utf8'));
const issues = [];
// populate issues[] with any FAIL results from your checks above (as strings)
s.steps['micro-qa-1'] = {
status: issues.length === 0 ? 'complete' : 'failed',
completedAt: new Date().toISOString(),
checks: 9,
issues
};
s.updatedAt = new Date().toISOString();
fs.writeFileSync(p, JSON.stringify(s, null, 2));
"
Return a JSON object: { passed: boolean, issues: string[] }
If passed is false, list each failing check with the specific file and what was expected.
"""
)
After the Agent returns, read the micro-qa-1 result from the state file:
Before running: Substitute the actual value of $COMPONENT.
node -e "
const fs = require('fs');
const s = JSON.parse(fs.readFileSync('src/routes/_index/components/$COMPONENT/pipeline-state.json', 'utf8'));
console.log(JSON.stringify(s.steps['micro-qa-1'], null, 2));
"
If micro-qa-1 status is failed:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🔴 Micro-QA Checkpoint 1 FAILED — static layer issues detected
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
The following issues were found by an isolated verification agent:
<list issues from micro-qa-1.issues[]>
These issues were caught BEFORE the Marko and React layers were generated,
saving you from building framework layers on a broken static foundation.
Fix the issues above by re-running /evo-static-component or editing files
manually, then re-run /evo-component $COMPONENT to retry from this checkpoint.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Do NOT advance to Step 8. Do not attempt inline fixes. Stop.
If micro-qa-1 status is complete:
✅ Micro-QA Checkpoint 1 — static layer verified by isolated agent (9/9 checks passed)
After returning: Run the post-step verification above (read completion record → content validation → scope boundary check). Use step ID micro-qa-1.
→ Next: After micro-qa-1 passes, immediately invoke Step 8.
Step 8 — Marko component
Scopes: full, interactive | ⏭ skip for: static, style
Before invoking: Run the pre-step preamble above (idempotent check → pre-flight validation → mark in-progress).
After returning: Run the post-step verification above (read completion record → content validation → scope boundary check).
Invoke: /evo-marko-component (inline)
Reads static HTML from context (Step 4, full scope) or from existing storybook
file (interactive scope — no Step 4 was run). Generates Marko 6 component.
Expected output (packages/evo-marko/src/tags/<name>/):
index.marko, style.ts, test/test.server.ts, test/test.browser.ts (if interactive)
→ Next: After this skill returns, print "Step 8 complete." then immediately invoke Step 9.
Step 9 — Marko storybook
Scopes: full, interactive | ⏭ skip for: static, style
Before invoking: Run the pre-step preamble above (idempotent check → pre-flight validation → mark in-progress).
After returning: Run the post-step verification above (read completion record → content validation → scope boundary check).
Invoke: /evo-marko-storybook (inline)
Expected output:
packages/evo-marko/src/tags/<name>/<name>.stories.ts + examples/
→ Next: After this skill returns, print "Step 9 complete." then immediately invoke Step 10.
Step 10 — React component
Scopes: full, interactive | ⏭ skip for: static, style
Before invoking: Run the pre-step preamble above (idempotent check → pre-flight validation → mark in-progress).
After returning: Run the post-step verification above (read completion record → content validation → scope boundary check).
Invoke: /evo-react-component (inline)
Expected output (packages/evo-react/src/<name>/):
index.tsx, __tests__/index.spec.tsx
→ Next: After this skill returns, print "Step 10 complete." then immediately invoke Step 11.
Step 11 — React storybook
Scopes: full, interactive | ⏭ skip for: static, style
Before invoking: Run the pre-step preamble above (idempotent check → pre-flight validation → mark in-progress).
After returning: Run the post-step verification above (read completion record → content validation → scope boundary check).
Invoke: /evo-react-storybook (inline)
Expected output:
packages/evo-react/src/<name>/<basename>.stories.tsx
→ Next: After this skill returns, print "Step 11 complete." then immediately invoke Step 12.
Step 12 — A11y Pass 2 — full validation + fills interactive a11y docs
Scopes: full, interactive | ⏭ skip for: static, style
Before invoking: Run the pre-step preamble above (idempotent check → pre-flight validation → mark in-progress).
After returning: Run the post-step verification above (read completion record → content validation → scope boundary check).
Invoke: /evo-a11y (inline) with scope declaration:
"Pass 2 — full validation. Steps 4–11 have run in this pipeline run (or Steps 8–11 for interactive scope). Validate all layers. Fill in the interactive sections of accessibility+page.marko."
Pass 2 validates:
- Static HTML (re-check)
index.marko — ARIA wiring, keyboard handlers, Marko 6 syntax
index.tsx — ARIA wiring, keyboard handlers
- Marko storybook — interactive story if keyboardModel present
- React storybook — controlled story if keyboardModel present
Pass 2 fills in interactive sections of accessibility+page.marko:
- Keyboard section (from manifest.keyboardModel + manifest.keyboardInteractions[])
- Screen Reader section (from manifest.a11y.screenReaderAnnouncement — interactive states)
- Pointer section (active/interactive behavior)
- Also writes
accessibility+meta.json (full picture now available)
For --scope interactive: The accessibility+page.marko may already have
static sections from a previous run. Fills in/updates only the interactive
sections without overwriting the static sections.
If 🔴 blocking issues: fix inline before build.
→ Next: If no 🔴 issues, print "Step 12 complete." then immediately invoke Step 13.
Micro-QA Checkpoint 2 — Framework layer verification (after Step 12)
Scopes: full, interactive | ⏭ skip for: static, style
Before running: Run the pre-step preamble above (idempotent check → pre-flight validation → mark in-progress). Use step ID micro-qa-2. If already complete, skip to Step 13.
Spawn a fresh Agent to verify the framework layer in genuine isolation. This agent has no
memory of the generation session — it reads only from disk.
Before spawning: Substitute the actual values for $COMPONENT, $BLOCK, $MARKO_NAME (e.g. evo-accordion), $REACT_NAME (bare kebab-case, e.g. accordion), and $SCOPE.
Agent(
description: "Micro-QA checkpoint 2 — framework layer for $COMPONENT",
prompt: """
You are an isolated QA agent verifying the framework layer of an evo-web component pipeline.
You have NO prior context from the generation session. Read ONLY from disk.
Component: $COMPONENT
BEM block: $BLOCK
Marko name: $MARKO_NAME (e.g. evo-accordion)
React name: $REACT_NAME (e.g. accordion)
Manifest: src/routes/_index/components/$COMPONENT/manifest.json
Read the manifest fully before starting checks.
=== CHECKS ===
CHECK 1 — index.marko exists and is non-empty
Command: test -s packages/evo-marko/src/tags/$MARKO_NAME/index.marko && echo PASS || echo FAIL
CHECK 2 — No Marko 5 scriptlet patterns in index.marko
Command: grep -cn "^\$ \(let\|const\|var\)" packages/evo-marko/src/tags/$MARKO_NAME/index.marko
Pass: 0. Fail: any count > 0 (list each matching line).
CHECK 3 — BEM block class applied in index.marko
Command: grep -c "\"$BLOCK\"" packages/evo-marko/src/tags/$MARKO_NAME/index.marko
Pass: >= 1.
CHECK 4 — Every prop in manifest.props[] appears in the Input interface
For each prop name P in manifest.props[]:
grep -c "P[?]?:" packages/evo-marko/src/tags/$MARKO_NAME/index.marko
Pass: >= 1. Fail: 0 (prop missing from interface).
CHECK 5 — style.ts contains correct skin import
Command: cat packages/evo-marko/src/tags/$MARKO_NAME/style.ts
Expected content: import "@ebay/skin/$BLOCK";
Pass: exact match. Fail: wrong or missing.
CHECK 6 — index.tsx exists and is non-empty
Command: test -s packages/evo-react/src/$REACT_NAME/index.tsx && echo PASS || echo FAIL
CHECK 7 — No forwardRef in index.tsx (evo-react uses React 19 native ref)
Command: grep -c "forwardRef" packages/evo-react/src/$REACT_NAME/index.tsx
Pass: 0. Fail: any count > 0.
CHECK 8 — Component export present in index.tsx
Command: grep -c "^export \(function\|const\)" packages/evo-react/src/$REACT_NAME/index.tsx
Pass: >= 1.
CHECK 9 — Every prop in manifest.props[] appears in React Props type
For each prop name P in manifest.props[]:
grep -c "P[?]?:" packages/evo-react/src/$REACT_NAME/index.tsx
Pass: >= 1.
CHECK 10 — Marko stories file exists
Command: test -f packages/evo-marko/src/tags/$MARKO_NAME/$MARKO_NAME.stories.ts && echo PASS || echo FAIL
CHECK 11 — React stories file exists
Command: find packages/evo-react/src/$REACT_NAME -name "*.stories.tsx" | grep -c .
Pass: >= 1.
CHECK 12 — accessibility+meta.json exists
Command: test -f src/routes/_index/components/$COMPONENT/accessibility+meta.json && echo PASS || echo FAIL
=== WRITE RESULT ===
After all checks, write your result to the pipeline state file.
Substitute the actual value of $COMPONENT before running:
node -e "
const fs = require('fs');
const p = 'src/routes/_index/components/$COMPONENT/pipeline-state.json';
const s = JSON.parse(fs.readFileSync(p, 'utf8'));
const issues = [];
// populate with any FAIL results as strings
s.steps['micro-qa-2'] = {
status: issues.length === 0 ? 'complete' : 'failed',
completedAt: new Date().toISOString(),
checks: 12,
issues
};
s.updatedAt = new Date().toISOString();
fs.writeFileSync(p, JSON.stringify(s, null, 2));
"
Return: { passed: boolean, issues: string[] }
"""
)
After the Agent returns, read micro-qa-2 from the state file:
Before running: Substitute the actual value of $COMPONENT.
node -e "
const fs = require('fs');
const s = JSON.parse(fs.readFileSync('src/routes/_index/components/$COMPONENT/pipeline-state.json', 'utf8'));
console.log(JSON.stringify(s.steps['micro-qa-2'], null, 2));
"
If micro-qa-2 status is failed:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🔴 Micro-QA Checkpoint 2 FAILED — framework layer issues detected
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
The following issues were found by an isolated verification agent:
<list issues from micro-qa-2.issues[]>
These issues were caught before docs, build, and final QA run.
Fix the issues, then re-run /evo-component $COMPONENT to retry from this checkpoint.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Do NOT advance to Step 13. Do not attempt inline fixes. Stop.
If micro-qa-2 status is complete:
✅ Micro-QA Checkpoint 2 — framework layer verified by isolated agent (12/12 checks passed)
After returning: Run the post-step verification above (read completion record → content validation → scope boundary check). Use step ID micro-qa-2.
→ Next: After micro-qa-2 passes, immediately invoke Step 13.
Step 13 — Docs hookup (full)
Scopes: full, static, interactive | ⏭ skip for: style
Before invoking: Run the pre-step preamble above (idempotent check → pre-flight validation → mark in-progress).
After returning: Run the post-step verification above (read completion record → content validation → scope boundary check).
Invoke: /evo-docs-hookup (inline) with scope "full"
Tell the skill: "full scope — write +page.marko, +meta.json, and update
component-metadata.json. Do NOT rewrite css+page.marko (already done in Step 6)."
Expected output:
src/routes/_index/components/<block>/+page.marko
src/routes/_index/components/<block>/+meta.json
src/data/component-metadata.json entry added/updated
→ Next: After this skill returns, print "Step 13 complete." then immediately run Step 14 (npm run build).
Step 14 — Build validation
Scopes: all
Before invoking: Run the pre-step preamble above (idempotent check → pre-flight validation → mark in-progress).
After returning: Run the post-step verification above (read completion record → content validation → scope boundary check).
Run: npm run build
Fix failures inline. Do not advance to QA with a failing build.
→ Next: After the build passes, immediately invoke Step 15.
Step 15 — QA
Scopes: full, static, interactive, style (always runs)
Before running: Run the pre-step preamble above (idempotent check → pre-flight validation → mark in-progress). Use step ID 15. If already complete, skip.
Spawn a fresh Agent with subagent_type evo-qa to run QA in genuine isolation.
The evo-qa agent has no memory of the generation session — it reads only from disk.
Before spawning: Substitute the actual values for $COMPONENT, $SCOPE, and $BLOCK.
For files, read steps[*].outputs from pipeline-state.json and flatten into a list.
For reference, use the manifest's migration.legacyName field if present; otherwise omit.
node -e "
const fs = require('fs');
const comp = '$COMPONENT';
const p = \`src/routes/_index/components/\${comp}/pipeline-state.json\`;
const s = JSON.parse(fs.readFileSync(p, 'utf8'));
const files = Object.values(s.steps)
.filter(st => st && st.outputs)
.flatMap(st => st.outputs);
console.log(JSON.stringify(files, null, 2));
"
Then spawn the QA agent:
Agent(
subagent_type: "evo-qa",
description: "QA verification for $COMPONENT (scope: $SCOPE)",
prompt: """
manifest: src/routes/_index/components/$COMPONENT/manifest.json
files: [<paste the JSON array output from the node command above>]
reference: <legacyName from manifest, or omit this line>
scope: $SCOPE
"""
)
After the Agent returns, read its output. If it contains Layer 1 result: ✅ PASSED, write the completion record below with status: "complete". If it contains Layer 1 result: 🔴 FAILED, write it with status: "failed" and surface the QA report to the engineer.
If QA passed:
Before running: Substitute the actual value of $COMPONENT.
node -e "
const fs = require('fs');
const comp = '$COMPONENT';
const p = \`src/routes/_index/components/\${comp}/pipeline-state.json\`;
const s = JSON.parse(fs.readFileSync(p, 'utf8'));
s.steps['15'] = {
status: 'complete',
completedAt: new Date().toISOString(),
outputs: []
};
s.updatedAt = new Date().toISOString();
fs.writeFileSync(p, JSON.stringify(s, null, 2));
console.log('Step 15 completion record written.');
"
If QA failed:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🔴 QA FAILED — isolated verification agent found issues
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
<paste full QA report here>
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Before running: Substitute the actual value of $COMPONENT and describe the failure.
node -e "
const fs = require('fs');
const comp = '$COMPONENT';
const p = \`src/routes/_index/components/\${comp}/pipeline-state.json\`;
const s = JSON.parse(fs.readFileSync(p, 'utf8'));
s.steps['15'] = {
status: 'failed',
error: '<summarize which Layer 1 checks failed>'
};
s.updatedAt = new Date().toISOString();
fs.writeFileSync(p, JSON.stringify(s, null, 2));
"
Do NOT mark the pipeline complete when QA fails. Surface the report and stop.
After returning: Run the post-step verification above (read completion record → content validation → scope boundary check). Use step ID 15.
→ Next: Pipeline complete. Print the final summary banner:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✅ Pipeline complete: $COMPONENT ($SCOPE scope)
All steps verified. QA passed.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Step 16 — Final summary
## Component generated: $COMPONENT [scope: <scope>]
Static layer:
[✅ | ⏭] /evo-static-component — HTML + SCSS
[✅ | ⏭] /evo-static-storybook — CSF2 stories
[✅ | ⏭] css+page.marko written
[✅ | ⏭] /evo-a11y Pass 1 — static a11y docs written
Marko layer:
[✅ | ⏭] index.marko + style.ts
[✅ | ⏭] Marko storybook
React layer:
[✅ | ⏭] index.tsx
[✅ | ⏭] React storybook
A11y Pass 2: [✅ | ⏭] Interactive sections filled
Docs (full): [✅ | ⏭] Overview + component-metadata.json
Build: ✅ Passed
QA L1: ✅ Passed (or: 🔴 N failure(s) — list each)
QA L2: ✅ N/A (or: fidelity score + delta)
─────────────────────────────────────────────────
Next steps:
[scope: style] 1. Review SCSS output. File PR. /evo-release-workflow
[scope: static] 1. Review static layer + a11y docs.
2. Run /evo-component <name> --scope interactive if framework needs updating.
3. File PR. /evo-release-workflow
[scope: full] 1. Review generated files and resolve any 🟡 warnings.
2. Fill storybook paths in component-metadata.json after first deploy.
3. File PR. /evo-release-workflow
─────────────────────────────────────────────────
Scope reference
| Scope | When to use | Steps run |
|---|
full | New component; cross-layer changes | 4–16 |
static | HTML structure and/or SCSS changed | 4–7, 13–16 |
interactive | Only Marko/React behavior or props changed | 6.5, 8–16 |
style | SCSS only; no structural or behavioral change | 4(SCSS), 6, 14–16 |
Note on Step 6.5 and interactive scope: Step 6.5 (scaffold generation) runs for interactive scope
because it produces the index.marko and index.tsx scaffold files that Steps 8 and 10 complete.
The "8–16" shorthand in the table above is updated to "6.5, 8–16" to reflect this dependency.
Sub-skill scope declarations
All sub-skills run inline. The orchestrator explicitly tells each skill what
scope this invocation is running — never infer from disk state.
| Sub-skill | Context | Scope declaration pattern |
|---|
/evo-static-component | Inline | "full/static scope: full HTML + SCSS" or "style scope: SCSS only" |
/evo-static-storybook | Inline | (no scope needed — always reads Step 4 HTML) |
/evo-docs-hookup (Step 6) | Inline | "css-only scope" |
/evo-a11y Pass 1 | Inline | "Pass 1 — static layer only; steps 4–6 ran" |
/evo-marko-component | Inline | (reads static HTML from context or existing storybook) |
/evo-marko-storybook | Inline | (no scope needed) |
/evo-react-component | Inline | (reads static HTML from context or existing storybook) |
/evo-react-storybook | Inline | (no scope needed) |
/evo-a11y Pass 2 | Inline | "Pass 2 — full scope; steps 4–11 ran" or "Pass 2 — interactive scope; steps 8–11 ran" |
/evo-docs-hookup (Step 13) | Inline | "full scope — Overview + metadata only, css+page.marko already written" |
/evo-qa | Forked | Told: scope used, which files were generated this run |