feat: complete WorkBuddy parity and Windows release workflow

This commit is contained in:
xiaohan.yuan committed 2026-10-18 12:45:21 +08:00
1 parent 143516b3a8
commit d5512df549
434 files changed
+54977 -1854

No files matched your search

@@ -0,0 +1,70 @@
/** Code-native artwork for YCode's five general development roles. No remote image service. */
import fs from 'node:fs/promises'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import { Resvg } from '@resvg/resvg-js'
const project = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
const palettes = {
explorer: ['#E4F3ED', '#9DCCB9', '#558E79', '#D8EEE4', '#1D3E37'],
implementer: ['#E7EFFB', '#A6BCE2', '#668CC3', '#E0EBFA', '#253D60'],
verifier: ['#FAF0DE', '#E6C384', '#B98B42', '#F8E9CB', '#55412B'],
reviewer: ['#EDEAF7', '#B9AADC', '#8770B5', '#E9E1F6', '#3D3258'],
debugger: ['#F7E9E6', '#DBADA5', '#B6786C', '#F5DDD6', '#533730']
}
const marks = {
explorer: '<circle cx="204" cy="202" r="8"/><path d="m210 208 8 8"/>',
implementer: '<path d="m199 195-7 8 7 8m13-16 7 8-7 8m-7-19-3 22"/>',
verifier: '<path d="m194 203 8 8 15-17"/>',
reviewer: '<path d="m205 190 13 5v9c0 8-8 13-13 15-5-2-13-7-13-15v-9z"/><path d="m199 203 4 4 8-9"/>',
debugger: '<path d="M191 204h7l4-10 7 19 4-9h7"/>'
}
function avatar(role) {
const [background, accent, edge, highlight, ink] = palettes[role]
return `<svg xmlns="http://www.w3.org/2000/svg" width="256" height="256" viewBox="0 0 256 256">
<defs>
<linearGradient id="${role}-bg" x2=".8" y2="1"><stop stop-color="${background}"/><stop offset="1" stop-color="${accent}"/></linearGradient>
<linearGradient id="${role}-shell" x2=".3" y2="1"><stop stop-color="#FFFFFF"/><stop offset="1" stop-color="${highlight}"/></linearGradient>
<linearGradient id="${role}-screen" x2=".7" y2="1"><stop stop-color="${ink}"/><stop offset="1" stop-color="#172A31"/></linearGradient>
<clipPath id="${role}-clip"><rect width="256" height="256" rx="48"/></clipPath>
</defs>
<g clip-path="url(#${role}-clip)">
<rect width="256" height="256" fill="url(#${role}-bg)"/>
<circle cx="128" cy="95" r="94" fill="#FFFFFF" opacity=".2"/>
<ellipse cx="128" cy="254" rx="94" ry="20" fill="${edge}" opacity=".16"/>
<path d="M48 256v-23c0-31 32-49 80-49s80 18 80 49v23" fill="${edge}"/>
<path d="M62 256v-23c0-24 26-40 66-40s66 16 66 40v23" fill="url(#${role}-shell)"/>
<path d="M106 175h44v24c-11 9-33 9-44 0z" fill="${accent}"/>
<path d="M128 55V34" stroke="${edge}" stroke-width="8" stroke-linecap="round"/>
<circle cx="128" cy="30" r="10" fill="${edge}"/>
<circle cx="125" cy="27" r="4" fill="#FFFFFF" opacity=".7"/>
<rect x="43" y="97" width="22" height="44" rx="11" fill="${edge}"/>
<rect x="191" y="97" width="22" height="44" rx="11" fill="${edge}"/>
<rect x="54" y="55" width="148" height="128" rx="42" fill="${edge}" opacity=".18" transform="translate(0 5)"/>
<rect x="54" y="51" width="148" height="128" rx="42" fill="url(#${role}-shell)"/>
<path d="M78 72c12-8 25-10 50-10" stroke="#FFFFFF" stroke-width="6" stroke-linecap="round" opacity=".9"/>
<rect x="69" y="87" width="118" height="66" rx="25" fill="url(#${role}-screen)"/>
<rect x="87" y="105" width="16" height="23" rx="8" fill="${highlight}"/>
<rect x="153" y="105" width="16" height="23" rx="8" fill="${highlight}"/>
<path d="M119 136q9 6 18 0" fill="none" stroke="${highlight}" stroke-width="3.5" stroke-linecap="round"/>
<circle cx="79" cy="137" r="5" fill="${accent}" opacity=".55"/>
<circle cx="177" cy="137" r="5" fill="${accent}" opacity=".55"/>
<path d="M102 225h52" stroke="${accent}" stroke-width="7" stroke-linecap="round"/>
<path d="M114 239h28" stroke="${accent}" stroke-width="5" stroke-linecap="round" opacity=".65"/>
<circle cx="205" cy="204" r="29" fill="#FFFFFF" opacity=".28"/>
<circle cx="205" cy="203" r="25" fill="${ink}"/>
<g fill="none" stroke="${highlight}" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round">${marks[role]}</g>
</g>
</svg>\n`
}
const directory = path.join(project, 'resources/agents/avatars')
await fs.mkdir(directory, { recursive: true })
for (const role of Object.keys(palettes)) {
const source = avatar(role)
await fs.writeFile(path.join(directory, `${role}.svg`), source)
await fs.writeFile(path.join(directory, `${role}.png`), new Resvg(source).render().asPng())
}
await fs.mkdir(path.join(project, '.tmp'), { recursive: true })
const preview = `<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="216" viewBox="0 0 1000 216"><rect width="1000" height="216" fill="#F8F9FA"/>${Object.keys(palettes).map((role, index) => `<g transform="translate(${index * 200 + 12},20) scale(.6875)">${avatar(role)}</g>`).join('')}</svg>`
await fs.writeFile(path.join(project, '.tmp/general-agent-avatars.png'), new Resvg(preview).render().asPng())
console.log('Generated five SVG sources, five 256px PNG avatars and .tmp/general-agent-avatars.png')