Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Code style conventions for Output SDK workflow projects. Use when writing or reviewing any TypeScript/JavaScript code. Discovers the project's own linting rules first; falls back to Output SDK conventions when no linter is configured.
.claude/skills/growthxai-output-dev-code-style/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 86% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 48% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 2034% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 115% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 179% | 0% |
Generated code must match the style of the project it lives in. Many projects use their own linter or formatter (ESLint, Prettier, Biome, etc.) with rule sets that differ from Output's defaults. Always discover and follow the project's rules first. Only fall back to the Output SDK conventions below when the project has no linter or formatter configured.
Before writing or reviewing code, determine the project's style rules:
eslint.config.js, .eslintrc.*, biome.json, deno.json, or similar in the project root..prettierrc*, .editorconfig, or formatter settings in package.json.package.json for lint, lint:fix, format, or similar scripts.These rules reflect the Output SDK's own ESLint config. Apply them only when the project has no linter configured and no conflicting conventions are evident in existing code.
Never use trailing commas in objects, arrays, function parameters, or type definitions.
typescript// CORRECT const config = { name: 'workflow', timeout: 30000 }; const items = [ 'a', 'b', 'c' ]; export const myStep = step( { name: 'myStep', inputSchema: MyInputSchema, outputSchema: MyOutputSchema, fn: async input => { return { result: input.value }; } } );
typescript// WRONG - trailing commas const config = { name: 'workflow', timeout: 30000, // <-- not allowed } const items = [ 'a', 'b', 'c', ] // <-- not allowed
let Declarationslet is banned. Use const exclusively. When a value needs conditional assignment, use a ternary, an IIFE, or restructure the logic.
typescript// CORRECT - ternary const label = count > 1 ? 'items' : 'item'; // CORRECT - named helper for complex cases const fetchWithFallback = async url => { try { return await fetchContent( url ); } catch { return '[Content unavailable]'; } }; const content = await fetchWithFallback( url ); // CORRECT - early return in a function function resolve( input ) { if ( input.mode === 'fast' ) { return fastPath( input ); } return standardPath( input ); }
typescript// WRONG let content; // <-- banned try { content = await fetchContent( url ); } catch { content = '[Content unavailable]'; } let label; // <-- banned if ( count > 1 ) { label = 'items'; } else { label = 'item'; }
Single-parameter arrow functions must not have parentheses. Use parens only for zero, multiple, destructured parameters, or when a TypeScript return type annotation is present.
typescript// CORRECT items.map( item => item.id ) items.filter( s => s.url ) items.forEach( x => console.log( x ) ) fn: async input => { ... } // Parens required for these cases: items.reduce( ( acc, item ) => acc + item, 0 ) const run = ( { name, id } ) => `${name}-${id}`; const noop = () => {}; fn: async ( input ): Promise<WorkflowOutput> => { ... } // return type annotation
typescript// WRONG - unnecessary parens on single param items.map( ( item ) => item.id ) items.filter( ( s ) => s.url ) fn: async ( input ) => { ... }
prefer-constAlways use const. If a binding is never reassigned, it must be const.
When an expression spans multiple lines, the operator stays on the first line.
typescript// CORRECT const result = longExpression + anotherExpression; const isValid = conditionA && conditionB && conditionC; const value = condition ? trueResult : falseResult;
typescript// WRONG - operator on next line const result = longExpression + anotherExpression;
fn( x ) not fn(x), except empty parens fn()[ 'a', 'b' ] not ['a', 'b']{ key: value } not {key: value}snake_case (e.g., fetch_data.ts, html_renderer.ts)snake_case (e.g., ai_hn_digest, shared_utils)vitest.config.js, eslint.config.js)| Rule | Correct | Wrong | |------|---------|-------| | Trailing comma | { a: 1 } | { a: 1, } | | Variable declaration | const x = 1 | let x = 1 | | Single-param arrow | x => x.id | ( x ) => x.id | | Operator linebreak | a +\n b | a\n + b | | Parens spacing | fn( x ) | fn(x) |
npm run lint, npx eslint, etc.), run it and fix any violations.npm run format, npx prettier --write, etc.), run it.| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-05 | pass→pass | 15,246 | 13,685 | -10% | 1 | 1 | 0% | 3,059 | 4,327 | +41% | 0 | 0 | — |
case-01 | fail→pass | 11,252 | 13,067 | +16% | 1 | 1 | 0% | 2,263 | 4,217 | +86% | 0 | 0 | — |
case-02 | fail→pass | 12,686 | 9,885 | -22% | 1 | 1 | 0% | 2,458 | 3,649 | +48% | 0 | 0 | — |
case-03 | fail→pass | 3,589 | 14,980 | +317% | 1 | 1 | 0% | 218 | 4,653 | +2034% | 0 | 0 | — |
case-04 | pass→fail | 13,024 | 17,803 | +37% | 1 | 1 | 0% | 2,314 | 5,071 | +119% | 0 | 0 | — |
case-06 | pass→pass | 12,440 | 7,334 | -41% | 1 | 1 | 0% | 2,330 | 3,048 | +31% | 0 | 0 | — |
case-07 | fail→pass | 6,731 | 6,080 | -10% | 1 | 1 | 0% | 1,271 | 2,731 | +115% | 0 | 0 | — |
case-08 | fail→fail | 4,726 | 4,436 | -6% | 1 | 1 | 0% | 827 | 2,419 | +193% | 0 | 0 | — |
case-09 | pass→pass | 8,704 | 6,419 | -26% | 1 | 1 | 0% | 1,753 | 2,720 | +55% | 0 | 0 | — |
case-10 | fail→pass | 4,251 | 3,380 | -20% | 1 | 1 | 0% | 791 | 2,206 | +179% | 0 | 0 | — |
case-11 | fail→pass | 5,588 | 2,963 | -47% | 1 | 1 | 0% | 1,090 | 2,159 | +98% | 0 | 0 | — |
case-12 | pass→pass | 4,607 | 3,567 | -23% | 1 | 1 | 0% | 810 | 2,298 | +184% | 0 | 0 | — |
case-13 | pass→pass | 4,309 | 5,145 | +19% | 1 | 1 | 0% | 667 | 2,556 | +283% | 0 | 0 | — |
case-18 | pass→pass | 5,109 | 2,922 | -43% | 1 | 1 | 0% | 775 | 2,116 | +173% | 0 | 0 | — |
case-14 | fail→pass | 13,808 | 3,038 | -78% | 1 | 1 | 0% | 2,214 | 2,068 | -7% | 0 | 0 | — |
case-15 | pass→pass | 5,463 | 2,188 | -60% | 1 | 1 | 0% | 993 | 1,980 | +99% | 0 | 0 | — |
case-16 | fail→pass | 7,057 | 2,792 | -60% | 1 | 1 | 0% | 1,142 | 2,083 | +82% | 0 | 0 | — |
case-17 | fail→pass | 8,844 | 5,267 | -40% | 1 | 1 | 0% | 1,417 | 2,553 | +80% | 0 | 0 | — |
case-19 | fail→pass | 4,416 | 6,626 | +50% | 1 | 1 | 0% | 766 | 2,919 | +281% | 0 | 0 | — |
case-20 | fail→pass | 6,191 | 5,411 | -13% | 1 | 1 | 0% | 1,132 | 2,625 | +132% | 0 | 0 | — |
case-21 | pass→pass | 9,643 | 7,574 | -21% | 1 | 1 | 0% | 1,826 | 3,051 | +67% | 0 | 0 | — |
case-22 | pass→pass | 4,640 | 3,998 | -14% | 1 | 1 | 0% | 825 | 2,283 | +177% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted, and 21 counted toward the lift figure. The other 1 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +45 percentage points is the difference between those two pass rates over the 21 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.