@aibyzero/byz 0.1.1 → 0.1.3

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.
Files changed (114) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +34 -24
  3. package/dist/cli.js +22 -14
  4. package/dist/core/export-html/template.css +1066 -0
  5. package/dist/core/export-html/template.html +55 -0
  6. package/dist/core/export-html/template.js +1864 -0
  7. package/dist/core/export-html/vendor/highlight.min.js +1213 -0
  8. package/dist/core/export-html/vendor/marked.min.js +78 -0
  9. package/dist/fast.js +64 -0
  10. package/dist/modes/interactive/assets/clankolas.png +0 -0
  11. package/dist/modes/interactive/theme/dark.json +90 -0
  12. package/dist/modes/interactive/theme/light.json +89 -0
  13. package/dist/modes/interactive/theme/theme-schema.json +352 -0
  14. package/dist/runtime/bundle/chunks/{chunk-CCRJHU72.js → chunk-QY7DJQRI.js} +2 -2
  15. package/dist/runtime/bundle/chunks/github-copilot.js +1 -1
  16. package/dist/runtime/bundle/cli.js +1 -1
  17. package/dist/runtime/bundle/index.js +1 -1
  18. package/dist/runtime/bundle/rpc-entry.js +1 -1
  19. package/dist/runtime/core/resource-loader.d.ts +1 -0
  20. package/dist/runtime/core/resource-loader.d.ts.map +1 -1
  21. package/dist/runtime/core/resource-loader.js +8 -3
  22. package/dist/runtime/core/resource-loader.js.map +1 -1
  23. package/dist/workflows.js +4 -131
  24. package/package.json +2 -1
  25. package/workflows/cm-plugin/LICENSE +21 -0
  26. package/workflows/cm-plugin/README.md +195 -0
  27. package/workflows/cm-plugin/VERSION +1 -0
  28. package/workflows/cm-plugin/agents/cm-plugin-backend-agent.md +34 -0
  29. package/workflows/cm-plugin/agents/cm-plugin-extension-agent.md +35 -0
  30. package/workflows/cm-plugin/agents/cm-plugin-ui-agent.md +34 -0
  31. package/workflows/cm-plugin/commands/cm-plugin-ai-nodes/N1-init.md +60 -0
  32. package/workflows/cm-plugin/commands/cm-plugin-ai-nodes/N2-enter-feature.md +51 -0
  33. package/workflows/cm-plugin/commands/cm-plugin-ai-nodes/N3-execute-task.md +32 -0
  34. package/workflows/cm-plugin/commands/cm-plugin-ai-nodes/N4-review.md +58 -0
  35. package/workflows/cm-plugin/commands/cm-plugin-ai-nodes/N5-mark-done.md +80 -0
  36. package/workflows/cm-plugin/commands/cm-plugin-ai-nodes/N6-qa-eval.md +69 -0
  37. package/workflows/cm-plugin/commands/cm-plugin-ai-nodes/N7-context.md +23 -0
  38. package/workflows/cm-plugin/commands/cm-plugin-ai-nodes/N8-finish.md +61 -0
  39. package/workflows/cm-plugin/commands/cm-plugin-prd-modes/brownfield.md +13 -0
  40. package/workflows/cm-plugin/commands/cm-plugin-prd-modes/change-mode.md +130 -0
  41. package/workflows/cm-plugin/commands/cm-plugin-prd-modes/greenfield.md +82 -0
  42. package/workflows/cm-plugin/commands/cm-plugin:ai.md +75 -0
  43. package/workflows/cm-plugin/commands/cm-plugin:check.md +66 -0
  44. package/workflows/cm-plugin/commands/cm-plugin:fix.md +100 -0
  45. package/workflows/cm-plugin/commands/cm-plugin:idea.md +29 -0
  46. package/workflows/cm-plugin/commands/cm-plugin:init.md +145 -0
  47. package/workflows/cm-plugin/commands/cm-plugin:prd.md +403 -0
  48. package/workflows/cm-plugin/commands/cm-plugin:refactor.md +147 -0
  49. package/workflows/cm-plugin/commands/cm-plugin:rewrite.md +90 -0
  50. package/workflows/cm-plugin/commands/cm-plugin:scout.md +202 -0
  51. package/workflows/cm-plugin/docs//346/265/213/350/257/225/346/214/207/345/274/225-/345/277/253/351/200/237/344/270/212/346/211/213.md +189 -0
  52. package/workflows/cm-plugin/docs//351/207/215/346/236/204/346/265/201/347/250/213/350/256/276/350/256/241/README.md +17 -0
  53. package/workflows/cm-plugin/docs//351/207/215/346/236/204/346/265/201/347/250/213/350/256/276/350/256/241/refactor-flow.excalidraw +3650 -0
  54. package/workflows/cm-plugin/docs//351/207/215/346/236/204/346/265/201/347/250/213/350/256/276/350/256/241/refactor-flow.mp4 +0 -0
  55. package/workflows/cm-plugin/docs//351/207/215/346/236/204/346/265/201/347/250/213/350/256/276/350/256/241/refactor-flow.png +0 -0
  56. package/workflows/cm-plugin/docs//351/207/215/346/236/204/346/265/201/347/250/213/350/256/276/350/256/241/refactor-flow.spec.json +223 -0
  57. package/workflows/cm-plugin/package.json +33 -0
  58. package/workflows/cm-plugin/skills/cm-plugin-backend-engineer/SKILL.md +100 -0
  59. package/workflows/cm-plugin/skills/cm-plugin-devops-engineer/NOTICE.md +14 -0
  60. package/workflows/cm-plugin/skills/cm-plugin-devops-engineer/SKILL.md +120 -0
  61. package/workflows/cm-plugin/skills/cm-plugin-devops-engineer/references/cws-ci-cd.md +139 -0
  62. package/workflows/cm-plugin/skills/cm-plugin-devops-engineer/references/cws-submission-checklist.md +87 -0
  63. package/workflows/cm-plugin/skills/cm-plugin-doc-syncer/SKILL.md +149 -0
  64. package/workflows/cm-plugin/skills/cm-plugin-extension-engineer/SKILL.md +105 -0
  65. package/workflows/cm-plugin/skills/cm-plugin-product-manager/SKILL.md +83 -0
  66. package/workflows/cm-plugin/skills/cm-plugin-qa-engineer/NOTICE.md +14 -0
  67. package/workflows/cm-plugin/skills/cm-plugin-qa-engineer/SKILL.md +134 -0
  68. package/workflows/cm-plugin/skills/cm-plugin-qa-engineer/references/cws-scan-checklist.md +136 -0
  69. package/workflows/cm-plugin/skills/cm-plugin-qa-engineer/references/cws-violation-codes.md +68 -0
  70. package/workflows/cm-plugin/skills/cm-plugin-ui-engineer/SKILL.md +94 -0
  71. package/workflows/cm-plugin/skills/codebase-context/SKILL.md +489 -0
  72. package/workflows/cm-plugin/skills/darwin-skill/NOTICE.md +24 -0
  73. package/workflows/cm-plugin/skills/darwin-skill/README.md +272 -0
  74. package/workflows/cm-plugin/skills/darwin-skill/SKILL.md +492 -0
  75. package/workflows/cm-plugin/skills/darwin-skill/references/runtime-neutrality.md +68 -0
  76. package/workflows/cm-plugin/skills/darwin-skill/references/skilllens-evidence.md +142 -0
  77. package/workflows/cm-plugin/skills/darwin-skill/scripts/screenshot.mjs +71 -0
  78. package/workflows/cm-plugin/skills/darwin-skill/templates/result-card-dark.html +698 -0
  79. package/workflows/cm-plugin/skills/darwin-skill/templates/result-card-white.html +444 -0
  80. package/workflows/cm-plugin/skills/darwin-skill/templates/result-card.html +616 -0
  81. package/workflows/cm-plugin/skills/idea-to-prd/SKILL.md +289 -0
  82. package/workflows/cm-plugin/skills/idea-to-prd/references/domains/trading.md +97 -0
  83. package/workflows/cm-plugin/skills/idea-to-prd/references/example-prd.md +92 -0
  84. package/workflows/cm-plugin/templates/arch-reference.md +55 -0
  85. package/workflows/cm-plugin/templates/auto-update/cm-announce.sh +19 -0
  86. package/workflows/cm-plugin/templates/auto-update/cm-update.sh +251 -0
  87. package/workflows/cm-plugin/templates/dashboard/dashboard.html +153 -0
  88. package/workflows/cm-plugin/templates/dashboard/serve.sh +10 -0
  89. package/workflows/cm-plugin/templates/e2e/extension-harness.ts +110 -0
  90. package/workflows/cm-plugin/templates/e2e/smoke.spec.example.ts +51 -0
  91. package/workflows/cm-plugin/templates/hooks/pre-commit-cm-task-check +33 -0
  92. package/workflows/cm-plugin/templates/pixel/cm-pixel.html +388 -0
  93. package/workflows/cm-plugin/templates/pixel/cm-pixel.sh +212 -0
  94. package/workflows/cm-plugin/templates/pixel/dev/README.md +20 -0
  95. package/workflows/cm-plugin/templates/pixel/dev/atlas-preview.png +0 -0
  96. package/workflows/cm-plugin/templates/pixel/dev/build.py +55 -0
  97. package/workflows/cm-plugin/templates/pixel/dev/sheets/roguelikeChar_transparent.png +0 -0
  98. package/workflows/cm-plugin/templates/pixel/dev/sheets/roguelikeCity_tilemap.png +0 -0
  99. package/workflows/cm-plugin/templates/pixel/dev/sheets/roguelikeIndoor_transparent.png +0 -0
  100. package/workflows/cm-plugin/templates/pixel/dev/template.html +388 -0
  101. package/workflows/cm-plugin/templates/pixel/serve.sh +12 -0
  102. package/workflows/cm-plugin/templates/refactor/cm-refactor-denies.json +14 -0
  103. package/workflows/cm-plugin/templates/rules/backend-api.md +36 -0
  104. package/workflows/cm-plugin/templates/rules/chrome-extension.md +53 -0
  105. package/workflows/cm-plugin/templates/rules/coding-style.md +44 -0
  106. package/workflows/cm-plugin/templates/rules/frontend.md +32 -0
  107. package/workflows/cm-plugin/templates/rules/git-workflow.md +25 -0
  108. package/workflows/cm-plugin/templates/rules/security.md +31 -0
  109. package/workflows/cm-plugin/templates/rules/testing.md +36 -0
  110. package/workflows/cm-plugin/templates/scripts/cm-plugin-codex.sh +52 -0
  111. package/workflows/cm-plugin/templates/scripts/cm-plugin-log.sh +41 -0
  112. package/workflows/cm-plugin/templates/scripts/cm-plugin-preflight.sh +60 -0
  113. package/workflows/cm-plugin/templates/statusline/cm-plugin-statusline.sh +73 -0
  114. package/workflows.lock.json +4 -3
@@ -0,0 +1,55 @@
1
+ from PIL import Image
2
+ import base64, io, json
3
+ import os
4
+ D = os.path.dirname(os.path.abspath(__file__))
5
+ ind = Image.open(D+"/sheets/roguelikeIndoor_transparent.png").convert("RGBA")
6
+ cty = Image.open(D+"/sheets/roguelikeCity_tilemap.png").convert("RGBA")
7
+ chs = Image.open(D+"/sheets/roguelikeChar_transparent.png").convert("RGBA")
8
+ def grab(im,c,r): return im.crop((c*17,r*17,c*17+16,r*17+16))
9
+ def person(body, shirt, hair, pants=(3,0)):
10
+ t = Image.new("RGBA",(16,16),(0,0,0,0))
11
+ for c,r in [body, pants, shirt, hair]:
12
+ t.alpha_composite(grab(chs,c,r))
13
+ return t
14
+ TILES = {
15
+ "wall":grab(cty,3,19),"wall2":grab(cty,3,20),"wallB":grab(cty,3,22),
16
+ "flr":grab(cty,6,19),"flr2":grab(cty,6,20),"flr3":grab(cty,7,19),
17
+ "desk":grab(ind,4,3),"chairL":grab(ind,3,2),"chairR":grab(ind,2,2),
18
+ "mon":grab(cty,34,4),"mon2":grab(cty,34,6),
19
+ "sofaL":grab(ind,0,10),"sofaM":grab(ind,1,10),"sofaR":grab(ind,2,10),
20
+ "rugL":grab(ind,23,5),"rugM":grab(ind,24,5),"rugM2":grab(ind,25,4),"rugR":grab(ind,26,5),
21
+ "plant":grab(ind,16,0),"plant2":grab(ind,17,0),
22
+ "art1":grab(ind,16,12),"art2":grab(ind,17,12),"art3":grab(ind,18,12),
23
+ "gold":grab(ind,19,13),"mirror":grab(ind,22,17),
24
+ "shelfA":grab(cty,10,18),"shelfB":grab(cty,12,18),"dresser":grab(cty,9,18),
25
+ "vend":grab(cty,23,5),"fridge":grab(cty,23,6),"bin":grab(cty,12,16),"crate":grab(cty,13,16),
26
+ "lamp":grab(cty,36,9),"lamp2":grab(cty,35,9),
27
+ "cntL":grab(cty,19,8),"cntM":grab(cty,20,8),"cntR":grab(cty,21,8),
28
+ "brick":grab(cty,0,11),"brick2":grab(cty,1,11),
29
+ "wood":grab(cty,3,19),"wood2":grab(cty,4,19),"wood3":grab(cty,3,20),
30
+ "rgA":grab(ind,12,4),"rgB":grab(ind,13,4),"rgC":grab(ind,12,5),"rgD":grab(ind,13,5),
31
+ "chairF":grab(ind,0,2),"tblO1":grab(ind,3,0),"tblO2":grab(ind,3,1),
32
+ "worker":person((1,0),(12,1),(20,1)),
33
+ "codex":person((1,1),(14,5),(19,8)),
34
+ "sleep":person((1,1),(12,0),(22,0)),
35
+ "celA":person((1,1),(15,0),(20,0)),
36
+ "celB":person((1,0),(13,1),(23,0)),
37
+ "celC":person((1,0),(9,1),(21,9)),
38
+ }
39
+ COLS = 8
40
+ names = list(TILES)
41
+ rows = (len(names)+COLS-1)//COLS
42
+ atlas = Image.new("RGBA",(COLS*16, rows*16),(0,0,0,0))
43
+ tmap = {}
44
+ for i,n in enumerate(names):
45
+ atlas.paste(TILES[n], ((i%COLS)*16,(i//COLS)*16))
46
+ tmap[n] = [i%COLS, i//COLS]
47
+ buf = io.BytesIO(); atlas.save(buf,"PNG",optimize=True)
48
+ b64 = base64.b64encode(buf.getvalue()).decode()
49
+ atlas.resize((COLS*16*4, rows*16*4), Image.NEAREST).save(D+"/atlas-preview.png")
50
+
51
+ html = open(D+"/template.html", encoding="utf-8").read()
52
+ html = html.replace("@B64@", b64).replace("@TMAP@", json.dumps(tmap))
53
+ out = os.path.join(D, "..", "cm-pixel.html")
54
+ open(out, "w", encoding="utf-8").write(html)
55
+ print("built", out, len(html), "bytes; atlas", len(b64))
@@ -0,0 +1,388 @@
1
+ <!doctype html>
2
+ <html lang="zh">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <title>cm 工作流 · 像素办公室</title>
7
+ <style>
8
+ * { margin:0; padding:0; box-sizing:border-box; }
9
+ body { background:#131120; color:#e8e2d0; font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
10
+ min-height:100vh; display:flex; flex-direction:column; align-items:center; padding:18px 12px; }
11
+ #wrap { width:min(980px, 100%); }
12
+ #hud { display:flex; align-items:center; gap:12px; padding:8px 2px; font-size:14px; }
13
+ #logo { background:#f0b03c; color:#1c1a14; font-weight:800; padding:3px 9px; border-radius:3px;
14
+ letter-spacing:2px; box-shadow:0 3px 0 #8a6210; }
15
+ #phase { background:#232033; padding:4px 12px; border-radius:3px; letter-spacing:1px; font-weight:700; }
16
+ #phase.paused { background:#f5d95c; color:#1c1a14; }
17
+ #phase.stale { background:#d25050; color:#fff; }
18
+ #task { color:#9a93b8; font-size:13px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; flex:1; }
19
+ #coins { color:#f0b03c; font-weight:700; }
20
+ #stage { position:relative; border:4px solid #2a2438; border-radius:8px; overflow:hidden;
21
+ box-shadow:0 18px 50px rgba(0,0,0,.55), inset 0 0 0 2px #0d0b16; background:#0d0b16; }
22
+ canvas { display:block; width:100%; image-rendering:pixelated; image-rendering:crisp-edges; }
23
+ #bubble { position:absolute; transform:translate(-50%,-115%); max-width:380px; padding:8px 12px;
24
+ background:#faf6ec; color:#1c1a14; border:2px solid #1c1a14; border-radius:6px;
25
+ font-size:13px; line-height:1.5; display:none; box-shadow:3px 4px 0 rgba(0,0,0,.4); z-index:2; }
26
+ #bubble.paused { background:#f5d95c; transform:translate(-12%,-115%); }
27
+ #bubble.paused:after { left:12%; }
28
+ #bubble:after { content:""; position:absolute; left:50%; bottom:-10px; margin-left:-5px;
29
+ border:5px solid transparent; border-top-color:#1c1a14; }
30
+ #note { position:absolute; right:14px; bottom:16px; width:250px; display:none;
31
+ background:#f7ecc0; color:#4a3c14; border:2px solid #c8b478; padding:9px 11px;
32
+ font-size:12px; line-height:1.65; white-space:pre-wrap; transform:rotate(-1.6deg);
33
+ box-shadow:4px 5px 0 rgba(0,0,0,.35); border-radius:2px; }
34
+ #note b { color:#8a6210; }
35
+ #barrow { display:flex; align-items:center; gap:10px; margin-top:10px; }
36
+ #bar { flex:1; height:16px; background:#232033; border:2px solid #2a2438; border-radius:4px; overflow:hidden; }
37
+ #fill { height:100%; width:0; transition:width .6s;
38
+ background:repeating-linear-gradient(45deg,#f0b03c 0 10px,#d99a28 10px 20px); }
39
+ #count { font-size:13px; color:#f0b03c; min-width:44px; text-align:right; font-weight:700; }
40
+ #hint { margin-top:8px; font-size:12px; color:#6f6890; }
41
+ </style>
42
+ </head>
43
+ <body>
44
+ <div id="wrap">
45
+ <div id="hud">
46
+ <span id="logo">CM</span><span id="phase">连接中…</span>
47
+ <span id="task"></span><span id="coins"></span>
48
+ </div>
49
+ <div id="stage">
50
+ <canvas id="cv" width="744" height="420"></canvas>
51
+ <div id="bubble"></div>
52
+ <div id="note"></div>
53
+ </div>
54
+ <div id="barrow"><div id="bar"><div id="fill"></div></div><span id="count"></span></div>
55
+ <div id="hint"></div>
56
+ </div>
57
+ <script>
58
+ const ATLAS = "data:image/png;base64,@B64@";
59
+ const T = @TMAP@;
60
+ const IMG = new Image(); let loaded = false;
61
+ IMG.onload = () => loaded = true; IMG.src = ATLAS;
62
+
63
+ const SKY = {
64
+ dawn:["#c98d78","#dba98a","#e8c49a"], day:["#7fa8c8","#93b9d4","#a9cbdd"],
65
+ dusk:["#8a5f7a","#b07a6a","#d0996a"], night:["#242138","#2e2a48","#3a3560"],
66
+ };
67
+ const TINT = { dawn:"rgba(230,150,100,.09)", day:"rgba(255,180,90,.05)",
68
+ dusk:"rgba(210,120,70,.12)", night:"rgba(30,34,90,.17)" };
69
+ const NODES = ["N1","N2","N3","N4","N5","N6","N7","N8"];
70
+ const PHASE = { N1:"启动准备", N2:"规划任务", N3:"开发中", N4:"检查代码",
71
+ N5:"保存进度", N6:"质量检测", N7:"整理现场", N8:"收尾总结" };
72
+ const ZONE = i => i<2 ? "dawn" : i<5 ? "day" : i<7 ? "dusk" : "night";
73
+ const PROPS = { 0:"plant2", 1:"crate", 2:"bin", 4:"dresser", 5:"fridge", 6:"shelfB", 7:"plant" };
74
+
75
+ const cv = document.getElementById("cv"), cx = cv.getContext("2d");
76
+ cx.imageSmoothingEnabled = false;
77
+ const W = cv.width, H = cv.height, TOPW = 48; // 顶墙高度
78
+ // 工位:两排四列,像真办公室
79
+ const POD = []; for (let i=0;i<8;i++)
80
+ POD.push({ x: 128 + (i%4)*162, y: 168 + ((i/4)|0)*150 });
81
+ const BENCH = { x:20, y:252, cx:44 }; // 等候长椅(左墙)
82
+ const LOUNGE = { x:W/2, y:252 }; // 中央过道=休息区
83
+ const seed = n => { let x = Math.sin(n*127.1)*43758.5; return x - Math.floor(x); };
84
+
85
+ function rect(x,y,w,h,c){ cx.fillStyle = c; cx.fillRect(x|0, y|0, w, h); }
86
+ function tile(n,dx,dy,z=3){ const [c,r] = T[n];
87
+ cx.drawImage(IMG, c*16, r*16, 16, 16, dx|0, dy|0, 16*z, 16*z); }
88
+ function shadow(x,y,rx,ry,a=.26){ cx.fillStyle = "rgba(12,8,20,"+a+")";
89
+ cx.beginPath(); cx.ellipse(x,y,rx,ry,0,0,7); cx.fill(); }
90
+
91
+ let cur = { node:"N1", state:"running", detail:"", feature:"", task:"" };
92
+ let mode = "wait", staleRun = false, firstSnap = true;
93
+ let wk = { x:POD[0].x-58, y:POD[0].y+18 }; // 小人(2D 自由走动)
94
+ let frame = 0, confetti = [];
95
+
96
+ // ---------- 房间(俯视) ----------
97
+ function room(zone) {
98
+ for (let r=0; r<Math.ceil((H-TOPW)/48); r++) // 暖木地板
99
+ for (let c=0; c<Math.ceil(W/48); c++)
100
+ tile(seed(c*3+r*11)>.62 ? "wood2" : (seed(c*5+r)>.85 ? "wood3" : "wood"), c*48, TOPW + r*48);
101
+ for (let c=0; c<Math.ceil(W/48); c++) tile("brick", c*48, 0); // 红砖顶墙
102
+ rect(0,0,W,6,"#171310");
103
+ rect(0,TOPW-7,W,7,"#4a3626"); rect(0,TOPW-7,W,2,"#5f4630"); // 木梁压边
104
+ rect(0,TOPW,W,5,"rgba(12,8,20,.35)"); // 墙脚阴影
105
+ rect(0,TOPW,4,H-TOPW,"rgba(12,8,20,.30)"); // 左右墙沿
106
+ rect(W-4,TOPW,4,H-TOPW,"rgba(12,8,20,.30)");
107
+ const p = SKY[zone]; // 4 扇窗,窗外=当前进度时辰
108
+ for (let i=0;i<4;i++) {
109
+ const x = 96 + i*168, y = 7, w = 52, h = 32;
110
+ rect(x-3,y-3,w+6,h+6,"#241f1a");
111
+ for (let b=0;b<3;b++) rect(x, y+b*h/3, w, h/3+1, p[b]);
112
+ if (zone==="day" && i===1) { rect(x+34,y+5,9,9,"#f5e8b0"); }
113
+ if (zone==="dawn" && i===0) { rect(x+8,y+h-12,10,5,"#f0c890"); }
114
+ if (zone==="dusk" && i===2) { rect(x+30,y+h-14,11,6,"#e8a060"); }
115
+ if (zone==="night") { if (i===1) { rect(x+34,y+5,8,8,"#e8e2d0");
116
+ rect(x+35,y+6,2,2,"#c8c2b0"); rect(x+38,y+9,2,2,"#c8c2b0"); }
117
+ for (let s2=0;s2<3;s2++) if ((frame>>4)%3 !== s2)
118
+ rect(x+4+seed(s2+i)*44, y+3+seed(s2+9)*24, 2,2,"#e8e2d0"); }
119
+ if (zone==="day" || zone==="dawn") {
120
+ cx.save(); cx.beginPath(); cx.rect(x,y,w,h); cx.clip();
121
+ const cxp = x + ((frame*0.22 + i*53) % (w+30)) - 15;
122
+ rect(cxp, y+6+(i%2)*8, 14, 4, "rgba(250,246,236,.85)"); cx.restore(); }
123
+ rect(x-3, y+h/2-1, w+6, 2, "#241f1a");
124
+ if (i%2===0) tile("plant2", x+w-6, y+h-8, 1); // 窗台盆栽
125
+ else tile("plant", x-12, y+h-8, 1);
126
+ }
127
+ tile("art1", 40, 10, 2); tile("gold", 660, 8, 2); // 顶墙挂画
128
+ const ckx = 24, cky = 12; // 挂钟
129
+ rect(ckx-2,cky-2,16,16,"#241f1a"); rect(ckx,cky,12,12,"#e8e2d0");
130
+ rect(ckx+5,cky+2,2,5,"#241f1a"); rect(ckx+6,cky+6,4,2,"#241f1a");
131
+ }
132
+
133
+ // ---------- 深度排序渲染(y 小在后,y 大在前=离观众近) ----------
134
+ function drawSorted(ents) {
135
+ ents.sort((a,b) => a.by - b.by);
136
+ for (const e of ents) e.f();
137
+ }
138
+
139
+ function podEnts(ents, idx, done) {
140
+ for (let i=0;i<8;i++) {
141
+ const P = POD[i], x = P.x, y = P.y,
142
+ active = i===idx && !done && mode==="run", passed = done || i<idx;
143
+ ents.push({ by: y+30, f: () => {
144
+ tile("rgA", x-32, y-14, 2); tile("rgB", x, y-14, 2); // 奶油地垫
145
+ tile("rgC", x-32, y+18, 2); tile("rgD", x, y+18, 2);
146
+ shadow(x, y+30, 30, 6);
147
+ tile("desk", x-24, y-18);
148
+ tile("lamp2", x+13, y-30, 1.1); // 桌灯
149
+ tile(active ? "mon" : "mon2", x-14, y-38, 1.75);
150
+ if (passed) { rect(x-11, y-35, 22, 17, "rgba(80,170,90,.85)");
151
+ cx.fillStyle="#eaf6ea"; cx.font="bold 11px monospace"; cx.textAlign="center";
152
+ cx.fillText("✓", x, y-22); }
153
+ else if (!active) rect(x-11, y-35, 22, 17, "rgba(24,20,34,.88)");
154
+ else if ((frame>>4)%4===0) rect(x-11, y-35, 22, 17, "rgba(159,216,240,.25)");
155
+ tile("chairF", x-14, y+16, 1.75); // 桌前椅(背对观众)
156
+ }});
157
+ if (i===3) ents.push({ by: P.y+26, f: () => { // Codex 审查员常驻 N4
158
+ shadow(P.x+44, P.y+26, 13, 4); tile("codex", P.x+22, P.y-22); }});
159
+ else if (i===5) ents.push({ by: P.y+28, f: () => { // 服务器机架(CI/测试机)
160
+ shadow(P.x+46, P.y+28, 13, 4);
161
+ tile("mon2", P.x+30, P.y-36, 2); tile("mon2", P.x+30, P.y-4, 2);
162
+ for (let k=0;k<4;k++) if ((frame>>3)%4!==k)
163
+ rect(P.x+36+k*6, P.y-30+((k%2)*32), 3, 3, k%2?"#7fdc6a":"#63b8b0"); }});
164
+ else if (PROPS[i]) ents.push({ by: P.y+28, f: () => {
165
+ shadow(P.x+46, P.y+28, 13, 4); tile(PROPS[i], P.x+30, P.y-4, 2); }});
166
+ }
167
+ }
168
+
169
+ function furnitureEnts(ents) {
170
+ ents.push({ by: 96, f: () => { // 咖啡台(靠顶墙)
171
+ shadow(608, 98, 52, 6);
172
+ tile("cntL", 560, 64, 2); tile("cntM", 592, 64, 2); tile("cntR", 624, 64, 2); }});
173
+ ents.push({ by: 98, f: () => { shadow(40, 98, 20, 5); tile("shelfA", 16, 56, 2.5); }}); // 书架
174
+ ents.push({ by: BENCH.y+34, f: () => { // 等候区:售货机+长椅
175
+ shadow(36, BENCH.y-14, 18, 5); tile("vend", 16, BENCH.y-62, 2);
176
+ shadow(BENCH.cx+22, BENCH.y+32, 26, 5);
177
+ tile("sofaM", BENCH.x, BENCH.y, 2); tile("sofaR", BENCH.x+32, BENCH.y, 2); }});
178
+ ents.push({ by: H-8, f: () => { // 角落绿植
179
+ tile("plant", 12, H-56, 2); tile("plant2", W-46, H-58, 2); }});
180
+ }
181
+
182
+ function person(n, x, y, bob) { // x,y = 脚底中心
183
+ return { by: y, f: () => { shadow(x, y+2, 14, 4, .32);
184
+ tile(n, x-24, y-46 + (bob ? ((frame>>3)%2) : 0)); } };
185
+ }
186
+
187
+ function lighting(idx, paused, done, zone) {
188
+ const lit = [];
189
+ if (mode==="run" && !done && !paused)
190
+ lit.push({x:POD[idx].x, y:POD[idx].y-10, c: staleRun ? "255,95,80" : "255,205,120", a:.26});
191
+ if (paused) lit.push({x:BENCH.cx+16, y:BENCH.y-10, c:"255,205,120", a:.24});
192
+ if (done || mode==="wait") lit.push({x:LOUNGE.x, y:LOUNGE.y, c:"255,205,120", a:.10});
193
+ cx.globalCompositeOperation = "lighter";
194
+ for (const L of lit) {
195
+ const g = cx.createRadialGradient(L.x,L.y,10, L.x,L.y,130);
196
+ g.addColorStop(0,"rgba("+L.c+","+L.a+")"); g.addColorStop(1,"rgba("+L.c+",0)");
197
+ cx.fillStyle = g; cx.fillRect(L.x-130, L.y-130, 260, 260);
198
+ }
199
+ cx.globalCompositeOperation = "source-over";
200
+ rect(0,0,W,H, TINT[zone]); // 时辰环境色
201
+ const v = cx.createRadialGradient(W/2, H*.44, H*.42, W/2, H*.52, W*.62);
202
+ v.addColorStop(0,"rgba(8,6,18,0)"); v.addColorStop(1,"rgba(8,6,18,.48)");
203
+ cx.fillStyle = v; cx.fillRect(0,0,W,H);
204
+ }
205
+
206
+ function labels(idx, done) {
207
+ cx.font = "bold 11px ui-monospace,monospace"; cx.textAlign = "center";
208
+ for (let i=0;i<8;i++) {
209
+ const P = POD[i], active = i===idx && !done && mode==="run", passed = done || i<idx;
210
+ const t = NODES[i]+" "+PHASE[NODES[i]].slice(0,2);
211
+ cx.fillStyle = "rgba(20,14,8,.75)"; cx.fillText(t, P.x+1, P.y+53);
212
+ cx.fillStyle = active ? "#faf6ec" : passed ? "#8fdc78" : "#b8a888";
213
+ cx.fillText(t, P.x, P.y+52);
214
+ }
215
+ const name = (cur.feature ? cur.feature.replace(/^\d+\./,"") : "cm 工作流") + " 的办公室";
216
+ cx.font = "bold 12px ui-monospace,monospace";
217
+ const tw = cx.measureText("★ "+name+" ★").width + 24;
218
+ rect(W/2-tw/2, H-24, tw, 19, "#241f1a"); rect(W/2-tw/2+2, H-22, tw-4, 15, "#3a2c1e");
219
+ cx.fillStyle = "#f0c05a"; cx.fillText("★ "+name+" ★", W/2, H-10);
220
+ }
221
+
222
+ function emote(ch, x, y, col) {
223
+ if ((frame>>4)%2) return;
224
+ cx.fillStyle = col; cx.font = "bold 17px monospace"; cx.textAlign = "center";
225
+ cx.fillText(ch, x, y);
226
+ }
227
+
228
+ function drawIdle() {
229
+ room("night");
230
+ const ents = []; podEnts(ents, 0, false); furnitureEnts(ents);
231
+ ents.push({ by: LOUNGE.y+34, f: () => { // 中央沙发打盹
232
+ shadow(LOUNGE.x, LOUNGE.y+34, 66, 7);
233
+ tile("sofaL", LOUNGE.x-72, LOUNGE.y-6); tile("sofaM", LOUNGE.x-24, LOUNGE.y-6);
234
+ tile("sofaR", LOUNGE.x+24, LOUNGE.y-6); }});
235
+ ents.push(person("sleep", LOUNGE.x, LOUNGE.y+2, false));
236
+ drawSorted(ents); labels(0, false);
237
+ const zi = (frame>>4)%3;
238
+ cx.fillStyle="#cfc8e8"; cx.font="bold 13px monospace"; cx.textAlign="left";
239
+ cx.fillText("z".repeat(zi+1), LOUNGE.x+26, LOUNGE.y-40-zi*4);
240
+ lighting(0,false,false,"night");
241
+ }
242
+
243
+ function draw() {
244
+ frame++;
245
+ if (!loaded) { rect(0,0,W,H,"#0d0b16"); requestAnimationFrame(draw); return; }
246
+ if (mode==="wait") { drawIdle(); requestAnimationFrame(draw); return; }
247
+ const idx = Math.max(0, NODES.indexOf(cur.node)),
248
+ paused = cur.state==="paused_for_human", done = mode==="done",
249
+ zone = done ? "night" : ZONE(idx);
250
+ room(zone);
251
+ const ents = []; podEnts(ents, idx, done); furnitureEnts(ents);
252
+ let sat = false;
253
+ if (!done) {
254
+ const t = paused ? {x:BENCH.cx+16, y:BENCH.y+26} : {x:POD[idx].x-52, y:POD[idx].y+20};
255
+ const dx = t.x-wk.x, dy = t.y-wk.y, d = Math.hypot(dx,dy);
256
+ const spd = isDemo ? 8 : 2.6;
257
+ if (d>3) { wk.x += dx/d*Math.min(spd,d); wk.y += dy/d*Math.min(spd,d); }
258
+ const walking = d>3;
259
+ sat = paused && !walking;
260
+ ents.push(person("worker", wk.x, wk.y, walking));
261
+ if (sat) ents.push({ by: wk.y+1, f: () => { // 补画椅面盖腿=坐下
262
+ tile("sofaM", BENCH.x, BENCH.y, 2); tile("sofaR", BENCH.x+32, BENCH.y, 2); }});
263
+ } else {
264
+ ents.push({ by: LOUNGE.y+18, f: () => { shadow(LOUNGE.x, LOUNGE.y+18, 34, 7);
265
+ tile("tblO1", LOUNGE.x-24, LOUNGE.y-54); tile("tblO2", LOUNGE.x-24, LOUNGE.y-8); }});
266
+ ents.push(person("worker", LOUNGE.x-64, LOUNGE.y+6, true));
267
+ ents.push(person("celA", LOUNGE.x+64, LOUNGE.y+6, true));
268
+ ents.push(person("celB", LOUNGE.x-40, LOUNGE.y+52, true));
269
+ ents.push(person("celC", LOUNGE.x+40, LOUNGE.y+52, true));
270
+ }
271
+ if (paused) { drawSorted(ents.filter(e=>true)); rect(0,0,W,H,"rgba(10,8,22,.32)");
272
+ const again = []; again.push(person("worker", wk.x, wk.y, false));
273
+ if (sat) again.push({ by: wk.y+1, f: () => {
274
+ tile("sofaM", BENCH.x, BENCH.y, 2); tile("sofaR", BENCH.x+32, BENCH.y, 2); }});
275
+ drawSorted(again);
276
+ if (sat) emote("!", wk.x, wk.y-56, "#f5d95c");
277
+ } else {
278
+ drawSorted(ents);
279
+ if (staleRun && !done) emote("?", wk.x, wk.y-56, "#f08a80");
280
+ }
281
+ labels(idx, done);
282
+ if (done) {
283
+ if (frame%3===0) confetti.push({ x:Math.random()*W, y:-6, v:1+Math.random()*1.6,
284
+ w:Math.random()>.5?4:3, c:["#f0b03c","#f06a3c","#63b8b0","#95c86a","#8c78c8","#faf6ec"][Math.random()*6|0] });
285
+ confetti = confetti.filter(p => p.y < H-8);
286
+ for (const p of confetti) { p.y += p.v; rect(p.x + Math.sin((p.y+p.x)/14)*5, p.y, p.w, p.w+2, p.c); }
287
+ rect(W/2-104, 66, 208, 34, "#faf6ec"); rect(W/2-104, 66, 208, 4, "#241f1a");
288
+ rect(W/2-104, 96, 208, 4, "#241f1a"); rect(W/2-104, 66, 4, 34, "#241f1a"); rect(W/2+100, 66, 4, 34, "#241f1a");
289
+ cx.fillStyle="#1c1a14"; cx.font="bold 19px ui-monospace,monospace"; cx.textAlign="center";
290
+ cx.fillText("STAGE CLEAR!", W/2, 90);
291
+ }
292
+ lighting(idx, paused, done, zone);
293
+ requestAnimationFrame(draw);
294
+ }
295
+
296
+ function hud() {
297
+ const paused = cur.state==="paused_for_human", done = mode==="done";
298
+ const ph = document.getElementById("phase");
299
+ if (mode==="wait") { ph.textContent = "😴 休息中(等 /cm-plugin:ai 启动)"; ph.className=""; }
300
+ else if (staleRun) { ph.textContent = "⚠ 可能已中断(去会话确认)"; ph.className="stale"; }
301
+ else { ph.textContent = done ? "STAGE CLEAR — 全部完成" :
302
+ (paused ? "🖐 等你确认" : "STAGE "+cur.node+" · "+(PHASE[cur.node]||"运行中"));
303
+ ph.className = paused ? "paused" : ""; }
304
+ document.getElementById("task").textContent =
305
+ mode==="wait" ? "" : [cur.feature.replace(/^\d+\./,""), cur.task].filter(Boolean).join(" · ");
306
+ const b = document.getElementById("bubble");
307
+ if (mode==="run" && cur.detail) {
308
+ b.style.display="block"; b.className = paused ? "paused" : "";
309
+ b.textContent = (paused ? "🖐 " : "") + cur.detail;
310
+ const idx = Math.max(0, NODES.indexOf(cur.node));
311
+ b.style.left = paused ? (BENCH.cx/W*100+"%")
312
+ : (Math.min(86, Math.max(14, POD[idx].x/W*100)) + "%");
313
+ b.style.top = paused ? ((BENCH.y-58)/H*100+"%") : ((POD[idx].y-34)/H*100 + "%");
314
+ } else b.style.display = "none";
315
+ }
316
+
317
+ // 上次收工小记:读度量汇总.md 最后一节
318
+ async function loadNote() {
319
+ try {
320
+ const md = await (await fetch("度量汇总.md?ts="+Date.now())).text();
321
+ if (md.startsWith("<")) return;
322
+ const secs = md.trim().split(/\n(?=#)/), last = secs[secs.length-1];
323
+ const lines = last.split("\n").filter(l=>l.trim().startsWith("-")).slice(0,4);
324
+ if (!lines.length) return;
325
+ const n = document.getElementById("note");
326
+ n.innerHTML = "<b>📋 上次收工小记</b>\n" + lines.join("\n").replace(/</g,"&lt;");
327
+ n.style.display = "block";
328
+ } catch {}
329
+ }
330
+
331
+ const DEMO = [
332
+ ["N1","running","正在检查项目环境和版本控制","",""],
333
+ ["N2","running","正在读需求、排任务","1.market-board",""],
334
+ ["N3","running","正在开发行情数据接口","1.market-board","T-005"],
335
+ ["N4","running","第1轮代码审查进行中","1.market-board","T-005"],
336
+ ["N4","paused_for_human","确认一下:原型里有3个按钮点了没反应,要做吗?","1.market-board","T-006"],
337
+ ["N5","running","保存进度:行情页面完成","1.market-board","T-006"],
338
+ ["N6","running","质量检测和业务走查","1.market-board",""],
339
+ ["N7","running","整理现场,准备下一个任务","1.market-board",""],
340
+ ["N8","running","收尾:整理文档和发布清单","",""],
341
+ ["N8","done","全部完成","",""]];
342
+ const q = new URLSearchParams(location.search);
343
+ const isDemo = q.has("demo"), isIdleDemo = q.has("idle");
344
+
345
+ async function poll() {
346
+ if (isIdleDemo) { mode="wait"; hud(); loadNote(); return; }
347
+ if (isDemo) {
348
+ const [node,state,detail,feature,task] = DEMO[(Math.floor(Date.now()/2600))%DEMO.length];
349
+ cur = {node,state,detail,feature,task};
350
+ mode = state==="done" ? "done" : "run"; staleRun = false; setBar(4,6);
351
+ } else {
352
+ try {
353
+ const res = await fetch(".cm-status.json?ts="+Date.now());
354
+ if (!res.ok) throw 0;
355
+ const s = await res.json();
356
+ cur = { node:s.node||"N1", state:s.state||"running", detail:s.detail||"",
357
+ feature:s.feature||"", task:s.task||"" };
358
+ mode = cur.state==="done" ? "done" : "run";
359
+ if (firstSnap && mode==="run") { firstSnap = false;
360
+ if (cur.state==="paused_for_human") { wk.x = BENCH.cx+16; wk.y = BENCH.y+26; }
361
+ else { const P = POD[Math.max(0,NODES.indexOf(cur.node))];
362
+ wk.x = P.x-52; wk.y = P.y+20; } }
363
+ const lm = res.headers.get("last-modified"); // 中断检测(阈值 10 分钟)
364
+ staleRun = !!(lm && mode==="run" && cur.state!=="paused_for_human"
365
+ && (Date.now()-new Date(lm).getTime()) > 600_000);
366
+ if (cur.feature && mode!=="wait") {
367
+ const md = await (await fetch(cur.feature+"/tasks.md?ts="+Date.now())).text();
368
+ const items = md.match(/^- \[( |x)\] T-\d+/gm)||[];
369
+ setBar(items.filter(l=>l.includes("[x]")).length, items.length);
370
+ } else setBar(0,0);
371
+ document.getElementById("hint").textContent = "";
372
+ document.getElementById("note").style.display = mode==="done" ? "" : "none";
373
+ if (mode==="done") loadNote();
374
+ } catch { mode = "wait"; setBar(0,0); loadNote();
375
+ document.getElementById("hint").textContent = "😴 值班中"; }
376
+ }
377
+ hud(); setTimeout(poll, isDemo?400:2000);
378
+ }
379
+ function setBar(d,t){
380
+ document.getElementById("fill").style.width = t ? (100*d/t)+"%" : "0";
381
+ document.getElementById("count").textContent = t ? d+"/"+t : "";
382
+ document.getElementById("coins").textContent = d ? "⛁ "+d : "";
383
+ }
384
+
385
+ draw(); poll();
386
+ </script>
387
+ </body>
388
+ </html>
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env bash
2
+ # cm 像素流水线(浏览器版) · 纯只读,零侵入(只消费 specs 落盘文件)
3
+ # 用法: ./serve.sh /path/to/specs [端口,默认 8799]
4
+ # 演示模式(不需要 specs 在跑): 打开 http://localhost:8799/.cm-pixel.html?demo
5
+ set -euo pipefail
6
+ SPECS="${1:?用法: ./serve.sh /path/to/specs [端口]}"
7
+ PORT="${2:-8799}"
8
+ [ -d "$SPECS" ] || { echo "specs 目录不存在: $SPECS"; exit 1; }
9
+ cp "$(cd "$(dirname "$0")" && pwd)/cm-pixel.html" "$SPECS/.cm-pixel.html"
10
+ echo "✓ 像素流水线已启动: http://localhost:$PORT/.cm-pixel.html (Ctrl+C 停止)"
11
+ echo " 演示模式: http://localhost:$PORT/.cm-pixel.html?demo"
12
+ cd "$SPECS" && exec python3 -m http.server "$PORT" --bind 127.0.0.1
@@ -0,0 +1,14 @@
1
+ {
2
+ "_comment": "cm-plugin:refactor 批量道循环内禁令模板。用法:把 permissions.deny 合并进代码项目 .claude/settings.json(人工安装,命令只验证);测试命令按项目实际替换;收口后撤销。这是防误触护栏,不是安全边界。",
3
+ "permissions": {
4
+ "deny": [
5
+ "Bash(git reset:*)",
6
+ "Bash(git checkout:*)",
7
+ "Bash(git clean:*)",
8
+ "Bash(git commit:*)",
9
+ "Bash(git rebase:*)",
10
+ "Bash(npm test:*)",
11
+ "Bash(npm run test:*)"
12
+ ]
13
+ }
14
+ }
@@ -0,0 +1,36 @@
1
+ ---
2
+ description: {一句话:后端 API 层开发约定}
3
+ globs: {如 "src/api/**",按实际目录}
4
+ ---
5
+
6
+ <!-- 模板骨架 · 生成时遵守四原则,{占位符} 结合项目填充。
7
+ cm-plugin-backend-engineer 的默认值(超时/分页等)以本文件为准 -->
8
+
9
+ # 后端 API 规范
10
+
11
+ ## 分层与路由
12
+
13
+ - 分层:{controller/service/repository 或项目实际分层,附目录}
14
+ - 路由组织:{按资源分组的实际方式};新路由必须显式挂鉴权中间件或声明公开
15
+
16
+ ## 错误处理
17
+
18
+ - 统一错误体结构:`{ code, message, ... }`({附项目实际结构})
19
+ - 全局错误处理器统一出口,禁止 handler 内裸造错误体
20
+ - 错误码清单位置:{路径}
21
+
22
+ ## 输入与幂等
23
+
24
+ - 边界层校验:{校验库};内层信任已校验数据
25
+ - 写操作幂等:{幂等键方案};支付类接口强制
26
+
27
+ ## 性能与可靠性默认值
28
+
29
+ - 外部调用超时:{5}s;重试上限:{N} 次
30
+ - 列表接口必须分页,默认页大小 {20},上限 {100}
31
+ - 缓存策略:{Redis/内存缓存的使用边界}
32
+
33
+ ## 可观测
34
+
35
+ - 日志格式与脱敏:{约定,敏感字段清单}
36
+ - {链路追踪/健康检查端点约定}
@@ -0,0 +1,53 @@
1
+ ---
2
+ description: {一句话:浏览器扩展(MV3)开发铁律——权限、后台、注入、存储、发布}
3
+ globs: {如 "src/**", "entrypoints/**",按脚手架实际目录}
4
+ ---
5
+
6
+ <!-- 模板骨架 · 生成时遵守四原则,{占位符} 结合项目填充 -->
7
+
8
+ # 浏览器扩展规范
9
+
10
+ ## 项目形态(生成时填死,变更过人工确认)
11
+
12
+ - 脚手架: {WXT / Plasmo / CRXJS+Vite / 原生};manifest 源头: {wxt.config.ts / 文件约定 / 手写 manifest.json 路径}
13
+ - 表面组合: {popup / options / side panel / content script / devtools}
14
+ - 目标浏览器: {仅 Chrome / Chrome+Edge / 含 Firefox(经 webextension-polyfill)}
15
+
16
+ ## 权限(最大红线)
17
+
18
+ - manifest 中每个 permission / host_permission 必须能对到一个已实现功能;**新增权限 = 契约变更**,提交信息单独说明并附用途理由,不与业务代码混提
19
+ - 能用 `activeTab` 不申请 host_permissions;能窄匹配不用 `<all_urls>`
20
+
21
+ ```jsonc
22
+ // Bad:为"读当前页标题"申请全站常驻权限
23
+ "host_permissions": ["<all_urls>"]
24
+ // Good:用户点击触发的读取用 activeTab 即可
25
+ "permissions": ["activeTab"]
26
+ ```
27
+
28
+ - 禁止 eval / new Function / 远程加载可执行代码(MV3 CSP 硬约束,也是商店红线);远程只取数据,不取代码
29
+ - API 密钥不进扩展包(扩展包=公开源码)——需要密钥的调用走 {后端代理地址/方案}
30
+
31
+ ## service worker(后台)
32
+
33
+ - **会随时休眠**:跨事件状态一律落 `chrome.storage.{session/local}`,禁止依赖全局变量存活
34
+ - 事件监听器顶层同步注册,不包进 async 初始化
35
+ - 定时用 `chrome.alarms`,禁止 `setTimeout` 跨事件计时;DOM/canvas/音频需求走 offscreen document
36
+
37
+ ## content script
38
+
39
+ - 注入 UI 一律 {shadow DOM / 前缀方案}——不裸放 DOM、不污染宿主页样式
40
+ - 宿主页 DOM 选择器集中在 {选择器常量文件路径},禁止散落在业务逻辑里
41
+ - 初始化必须幂等(SPA 路由变化会重复触发);宿主页 JS 状态需经 main world 桥接,不假装 isolated world 能直接读
42
+
43
+ ## 消息与存储
44
+
45
+ - 消息契约(type/payload/响应)集中定义在 {契约文件路径},收发都走 {封装层路径},禁止裸调 `chrome.runtime.sendMessage`
46
+ - 异步响应风格统一为 {Promise / return true 回调}(二选一,全项目一致)
47
+ - storage 分区约定:sync = {用户设置类},local = {大数据类},session = {SW 临时态};schema 变更必须附迁移逻辑(老用户数据不会自己变形状)
48
+
49
+ ## 构建与发布
50
+
51
+ - 版本号唯一源: {package.json / manifest},发布前必递增
52
+ - 打包命令: {npx wxt zip 等实际命令};产物加载冒烟后才算构建通过(编译过 ≠ 能跑)
53
+ - manifest 变更、权限 diff 在 PR/提交描述中显式列出
@@ -0,0 +1,44 @@
1
+ ---
2
+ description: {一句话:本项目的编码风格约定}
3
+ ---
4
+
5
+ <!-- 模板骨架 · 由 /cm-plugin:init 或 bootstrap 以此为骨架生成,生成时必须遵守四原则:
6
+ ① 可执行——具体指令,禁止"使用恰当的 XX"类模糊话
7
+ ② 关键规则带 Bad/Good 对比示例
8
+ ③ 量化——用数字不用形容词
9
+ ④ 现代实践——锚定项目当前版本的惯用法
10
+ 所有 {占位符} 结合项目实际填充;不适用章节删除;禁止保留空洞通用话术 -->
11
+
12
+ # 编码风格
13
+
14
+ ## 量化标准(默认值,按团队口味调整)
15
+
16
+ | 项 | 上限 |
17
+ | ---- | ---- |
18
+ | 函数长度 | {30} 行 |
19
+ | 文件长度 | {300} 行 |
20
+ | 嵌套深度 | {3} 层 |
21
+ | 函数参数 | {4} 个 |
22
+
23
+ ## 命名
24
+
25
+ - 文件命名:{从项目现有文件推断,如 kebab-case / PascalCase 组件}
26
+ - 变量/函数:{camelCase 等,附一个项目内真实示例}
27
+ - 常量与枚举:{约定}
28
+
29
+ ## Import 与文件组织
30
+
31
+ - Import 顺序:{如 内置 → 第三方 → 别名 → 相对路径,从 lint 配置推断}
32
+ - 路径别名:{tsconfig/构建配置中的实际别名清单}
33
+
34
+ ## 注释
35
+
36
+ - 只解释"为什么",不复述"做了什么"
37
+ - {公共 API 的文档注释要求,如 JSDoc / docstring}
38
+
39
+ ## Bad / Good 示例
40
+
41
+ ```{lang}
42
+ // Bad: {从项目痛点提炼}
43
+ // Good: {对应正确写法}
44
+ ```
@@ -0,0 +1,32 @@
1
+ ---
2
+ description: {一句话:Web 前端开发约定}
3
+ globs: {如 "src/web/**",按实际目录}
4
+ ---
5
+
6
+ <!-- 模板骨架 · 生成时遵守四原则,{占位符} 结合项目填充 -->
7
+
8
+ # 前端规范
9
+
10
+ ## 组件
11
+
12
+ - 公共 UI 组件目录:{components/ui/ 等实际路径}——新通用组件必须入此目录
13
+ - 组件模式:{函数组件 + hooks / 组合式 API——从现有代码推断}
14
+ - UI 组件纯 props 驱动不含业务逻辑;业务组件组合 UI 组件
15
+ - 第三方组件库:{shadcn/Antd/Element…}——库内有的不自造轮子
16
+
17
+ ## 样式与 Design Token
18
+
19
+ - 方案:{Tailwind vN / CSS Modules…}
20
+ - 颜色/间距/字号/圆角一律引用 token({tailwind.config / CSS 变量位置}),**禁止裸写 hex/px 魔法值**
21
+ - 修改既有 token 值 = 契约变更,走上报流程(波及已验收页面)
22
+
23
+ ## 状态与数据
24
+
25
+ - 状态管理:{Zustand/Pinia…};简单局部状态用框架原生
26
+ - 请求层:{统一封装位置},错误处理与 loading 状态必须处理
27
+ - 接口未就绪:mock + `// TODO: replace mock when API ready`
28
+
29
+ ## 路由与性能
30
+
31
+ - 路由约定:{file-based / config-based,附目录}
32
+ - {懒加载/分包要求、图片处理约定}