@xiaobuyu/nodesign 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +156 -106
- package/bin/nodesign.js +0 -0
- package/package.json +1 -1
- package/server/api/README.md +45 -45
- package/server/api/_guard.js +75 -75
- package/server/api/admin.js +187 -187
- package/server/api/assets/docx-page.js +103 -103
- package/server/api/assets/helpers.js +22 -22
- package/server/api/assets/notes.js +144 -144
- package/server/api/assets.js +896 -896
- package/server/api/board.js +57 -57
- package/server/api/browse.js +138 -138
- package/server/api/canvas.js +444 -444
- package/server/api/chatai.js +324 -324
- package/server/api/exports/build-standalone.js +980 -980
- package/server/api/exports/cards.js +118 -118
- package/server/api/exports/docx-pdf.js +46 -46
- package/server/api/exports/export-page.js +142 -142
- package/server/api/exports/handoff.js +147 -147
- package/server/api/exports.js +690 -690
- package/server/api/instruction.js +75 -75
- package/server/api/me.js +116 -116
- package/server/api/memory.js +185 -185
- package/server/api/pending-changes.js +366 -366
- package/server/api/plugins.js +164 -164
- package/server/api/projects.js +220 -220
- package/server/api/publish.js +64 -64
- package/server/api/recent.js +73 -73
- package/server/api/sessions.js +589 -589
- package/server/api/skills.js +77 -77
- package/server/api/standalone-fit.js +168 -168
- package/server/api/turn-compose.js +180 -180
- package/server/api/turn-inflight.js +44 -44
- package/server/auth/README.md +17 -17
- package/server/auth/middleware.js +138 -138
- package/server/auth/origin-guard.js +71 -71
- package/server/auth/session.js +108 -108
- package/server/auth/tier.js +113 -113
- package/server/auth/users-store.js +348 -348
- package/server/edit/README.md +29 -29
- package/server/engine/README.md +113 -113
- package/server/engine/agent/agent-shared.js +550 -550
- package/server/engine/agent/auto-mode-default-hard-deny.txt +4 -4
- package/server/engine/agent/auto-mode-rules.js +78 -78
- package/server/engine/agent/context.js +418 -418
- package/server/engine/agent/events.js +464 -464
- package/server/engine/agent/hooks/canvas-validate.js +199 -199
- package/server/engine/agent/hooks/failure.js +120 -120
- package/server/engine/agent/hooks/file-events.js +75 -75
- package/server/engine/agent/hooks/lifecycle.js +233 -233
- package/server/engine/agent/hooks/post-canvas-focus.js +80 -80
- package/server/engine/agent/hooks/post-guidance.js +206 -206
- package/server/engine/agent/hooks/post-subagent-report.js +104 -104
- package/server/engine/agent/hooks/post-trim.js +58 -58
- package/server/engine/agent/hooks/pre-board-neighborhood.js +34 -34
- package/server/engine/agent/hooks/pre-defaults.js +84 -84
- package/server/engine/agent/hooks/pre-injectors.js +277 -277
- package/server/engine/agent/hooks/pre-performance-log-guard.js +78 -78
- package/server/engine/agent/hooks/pre-starter-files.js +61 -61
- package/server/engine/agent/hooks/pre-workspace-scope-guard.js +78 -78
- package/server/engine/agent/hooks/site-validate.js +94 -94
- package/server/engine/agent/hooks/tool-prompts.js +31 -31
- package/server/engine/agent/hooks/turn-state-memory.js +56 -56
- package/server/engine/agent/hooks/user-prompt-submit.js +215 -215
- package/server/engine/agent/hooks.js +354 -354
- package/server/engine/agent/init-contract.js +69 -69
- package/server/engine/agent/isolation.js +159 -159
- package/server/engine/agent/model-table.js +292 -292
- package/server/engine/agent/plugin-loader.js +266 -266
- package/server/engine/agent/prompts/nodesign-prelude.md +336 -336
- package/server/engine/agent/prompts/tools/ask-user-question-protocol.md +71 -71
- package/server/engine/agent/prompts/tools/direct-edit-protocol.md +98 -98
- package/server/engine/agent/prompts/tools/generate-image-cookbook.gemini-gateway.md +583 -583
- package/server/engine/agent/prompts/tools/generate-image-cookbook.md +483 -483
- package/server/engine/agent/prompts/tools/hybrid-reference.md +100 -100
- package/server/engine/agent/prompts/tools/paint-still-cookbook.md +270 -270
- package/server/engine/agent/prompts/tools/roll-film-cookbook.md +70 -70
- package/server/engine/agent/prompts/tools/site-reference.md +106 -106
- package/server/engine/agent/prompts/tools/tweaks-syntax.md +125 -125
- package/server/engine/agent/prompts/tools/vision-checker-dispatch.md +48 -48
- package/server/engine/agent/session-loop.js +1024 -1024
- package/server/engine/agent/session-model.js +164 -164
- package/server/engine/agent/skill.js +204 -204
- package/server/engine/agent/task-events.js +78 -78
- package/server/engine/agents/ds-extractor.md +132 -132
- package/server/engine/agents/explorer.md +190 -190
- package/server/engine/agents/index.js +230 -230
- package/server/engine/agents/schemas/design-system.json +156 -156
- package/server/engine/agents/schemas/tweak-schema.json +113 -113
- package/server/engine/agents/tweak-proposer.md +127 -127
- package/server/engine/agents/vision-checker.md +254 -254
- package/server/engine/browse/capture.js +422 -422
- package/server/engine/browse/card.js +137 -137
- package/server/engine/browse/handover.js +59 -59
- package/server/engine/browse/page-digest.js +119 -119
- package/server/engine/browse/refs.js +196 -196
- package/server/engine/browse/registry.js +307 -307
- package/server/engine/browse/screencast.js +170 -170
- package/server/engine/browse/state.js +121 -121
- package/server/engine/chatai/chat-log.js +79 -79
- package/server/engine/chatai/index.js +193 -193
- package/server/engine/chatai/openai-compat.js +152 -152
- package/server/engine/chatai/orchestrate.js +309 -309
- package/server/engine/chatai/perform.js +40 -40
- package/server/engine/chatai/summarize.js +79 -79
- package/server/engine/mcp/capability-gate.js +52 -52
- package/server/engine/mcp/index.js +339 -339
- package/server/engine/mcp/param-sanitizer.js +142 -142
- package/server/engine/mcp/tools/arrange-on-board.js +124 -124
- package/server/engine/mcp/tools/artifact-session.js +278 -278
- package/server/engine/mcp/tools/browse-computer.js +421 -421
- package/server/engine/mcp/tools/browse-find-batch.js +183 -183
- package/server/engine/mcp/tools/browse-screenshot.js +157 -157
- package/server/engine/mcp/tools/browse.js +480 -480
- package/server/engine/mcp/tools/build-docx.js +101 -101
- package/server/engine/mcp/tools/clear-pending-changes.js +99 -99
- package/server/engine/mcp/tools/create-on-board.js +155 -155
- package/server/engine/mcp/tools/crystallize-skill.js +168 -168
- package/server/engine/mcp/tools/explain-style.js +237 -237
- package/server/engine/mcp/tools/export-handoff.js +133 -133
- package/server/engine/mcp/tools/expose-tweaks.js +149 -149
- package/server/engine/mcp/tools/generate-image.js +842 -842
- package/server/engine/mcp/tools/get-computed-styles.js +164 -164
- package/server/engine/mcp/tools/get-pending-changes.js +205 -205
- package/server/engine/mcp/tools/h3box-ssh.js +84 -84
- package/server/engine/mcp/tools/helpers/acquire-page.js +82 -82
- package/server/engine/mcp/tools/helpers/motion-lab.js +554 -554
- package/server/engine/mcp/tools/helpers/motion-scroll.js +112 -112
- package/server/engine/mcp/tools/helpers/perception-page.js +215 -215
- package/server/engine/mcp/tools/helpers/reference-download.js +103 -103
- package/server/engine/mcp/tools/helpers/rembg-bridge.py +80 -80
- package/server/engine/mcp/tools/helpers/shot-pipeline.js +280 -280
- package/server/engine/mcp/tools/highlight.js +68 -68
- package/server/engine/mcp/tools/list-pages.js +222 -222
- package/server/engine/mcp/tools/lookup-tags.js +59 -59
- package/server/engine/mcp/tools/navigate-to-page.js +60 -60
- package/server/engine/mcp/tools/organize-board.js +64 -64
- package/server/engine/mcp/tools/paint-still.js +434 -434
- package/server/engine/mcp/tools/pin-to-board.js +130 -130
- package/server/engine/mcp/tools/preview-deck.js +130 -130
- package/server/engine/mcp/tools/profile-scroll.js +305 -305
- package/server/engine/mcp/tools/publish-site.js +102 -102
- package/server/engine/mcp/tools/query-elements.js +195 -195
- package/server/engine/mcp/tools/read-board.js +103 -103
- package/server/engine/mcp/tools/read-document.js +93 -93
- package/server/engine/mcp/tools/read-page.js +264 -264
- package/server/engine/mcp/tools/read-tavern-json.js +143 -143
- package/server/engine/mcp/tools/record-decision.js +140 -140
- package/server/engine/mcp/tools/relate-on-board.js +110 -110
- package/server/engine/mcp/tools/report-issue.js +115 -115
- package/server/engine/mcp/tools/roll-film.js +266 -266
- package/server/engine/mcp/tools/screenshot-docx.js +138 -138
- package/server/engine/mcp/tools/screenshot-url.js +195 -195
- package/server/engine/mcp/tools/screenshot.js +583 -583
- package/server/engine/mcp/tools/tier-gate.js +96 -96
- package/server/engine/mcp/tools/trace-motion.js +215 -215
- package/server/engine/mcp/tools/web-search.js +548 -548
- package/server/engine/motion/inventory.js +349 -349
- package/server/engine/perception/session.js +235 -235
- package/server/engine/plugins/nodesign/.claude-plugin/plugin.json +5 -5
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/SKILL.md +192 -192
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/canvas.template.html +667 -667
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/hybrid-grid.md +22 -22
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/image-led-cover.md +25 -25
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/portrait.md +23 -23
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/quote-backdrop.md +21 -21
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/section-divider.md +23 -23
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/text-led.md +21 -21
- package/server/engine/plugins/nodesign/skills/docx-craft/SKILL.md +162 -162
- package/server/engine/plugins/nodesign/skills/docx-craft/references/token-schema.md +407 -407
- package/server/engine/plugins/nodesign/skills/docx-craft//346/226/207/346/241/243.template.json +80 -80
- package/server/engine/plugins/nodesign/skills/rp-craft/SKILL.md +228 -228
- package/server/engine/plugins/nodesign/skills/rp-craft/patterns//346/274/224/345/207/272/351/241/265-/346/234/200/345/260/217/345/256/236/347/216/260.html +78 -78
- package/server/engine/plugins/nodesign/skills/rp-craft//346/274/224/345/207/272.template.js +268 -268
- package/server/engine/plugins/nodesign/skills/site-craft/SKILL.md +323 -323
- package/server/engine/plugins/nodesign/skills/site-craft/patterns/build-lane.md +134 -134
- package/server/engine/plugins/nodesign/skills/site-craft/patterns/cutout-collage.md +61 -61
- package/server/engine/plugins/nodesign/skills/site-craft/patterns/mock-app.md +163 -163
- package/server/engine/plugins/nodesign/skills/site-craft/patterns/page-transitions.md +86 -86
- package/server/engine/runs/active-runs.js +698 -698
- package/server/engine/runs/live-turn.js +252 -252
- package/server/engine/runs/store.js +364 -364
- package/server/engine/runs/turn-relay.js +217 -217
- package/server/engine/runtime/workspace.js +164 -164
- package/server/lib/artifact-file-path.js +80 -80
- package/server/lib/artifact-target.js +284 -284
- package/server/lib/asset-refs.js +239 -239
- package/server/lib/async-queue.js +104 -104
- package/server/lib/auto-relations.js +130 -130
- package/server/lib/binding-types.js +60 -60
- package/server/lib/board-kind-sizes.js +62 -62
- package/server/lib/board-relations.js +133 -133
- package/server/lib/browse-proxy.js +180 -180
- package/server/lib/canvas-id.js +36 -36
- package/server/lib/cover.js +227 -227
- package/server/lib/danbooru-tags.js +291 -291
- package/server/lib/doc-extract.js +156 -156
- package/server/lib/docx/build-from-source.js +260 -260
- package/server/lib/docx/build.js +490 -490
- package/server/lib/docx/dump-styles.js +287 -287
- package/server/lib/docx/fonts/nodesign-cjk.conf +164 -164
- package/server/lib/docx/merge-runs.js +129 -129
- package/server/lib/docx/numbering.js +218 -218
- package/server/lib/docx/order.js +152 -152
- package/server/lib/docx/rawzip.js +172 -172
- package/server/lib/docx/render.js +92 -92
- package/server/lib/docx/text-lint.js +225 -225
- package/server/lib/docx/tokens.js +335 -335
- package/server/lib/docx/units.js +29 -29
- package/server/lib/docx/xml.js +291 -291
- package/server/lib/docx-pages.js +187 -187
- package/server/lib/export-collect.js +289 -289
- package/server/lib/export-package.js +210 -210
- package/server/lib/html-srcset.js +145 -145
- package/server/lib/image-variant.js +460 -460
- package/server/lib/ingress/forward-openai-chat.js +305 -305
- package/server/lib/ingress/openai-chat.js +491 -491
- package/server/lib/ingress/session-notice.js +66 -66
- package/server/lib/ingress/session-routes.js +68 -68
- package/server/lib/ingress/slot-probe.js +130 -130
- package/server/lib/ingress/upstream-billing.js +52 -52
- package/server/lib/ingress/upstream-fail-streak.js +72 -72
- package/server/lib/ingress/upstream-truncation.js +48 -48
- package/server/lib/issues-store.js +182 -182
- package/server/lib/kinds/deck.js +72 -72
- package/server/lib/kinds/docx.js +283 -283
- package/server/lib/kinds/file-kinds.js +113 -113
- package/server/lib/kinds/index.js +202 -202
- package/server/lib/kinds/site.js +161 -161
- package/server/lib/model-ingress.js +543 -543
- package/server/lib/moderation.js +255 -255
- package/server/lib/notice-store.js +103 -103
- package/server/lib/plugin-install.js +104 -104
- package/server/lib/plugin-validator.js +721 -721
- package/server/lib/publish-store.js +115 -115
- package/server/lib/quick-summary.js +52 -52
- package/server/lib/quota.js +270 -270
- package/server/lib/rate-window.js +29 -29
- package/server/lib/reference-assets.js +92 -92
- package/server/lib/region-shot.js +135 -135
- package/server/lib/safe-path.js +73 -73
- package/server/lib/sdk-session.js +33 -33
- package/server/lib/showcase-store.js +92 -92
- package/server/lib/site-publish.js +465 -465
- package/server/lib/ssrf-guard.js +432 -432
- package/server/lib/task-scan.js +158 -158
- package/server/lib/tavern-json.js +154 -154
- package/server/lib/video-variant.js +237 -237
- package/server/lib/workspace-path.js +33 -33
- package/server/ops/fix-sdk-musl.mjs +40 -40
- package/server/ops/install-macos-fonts.sh +114 -114
- package/server/ops/macos-fonts.conf +336 -336
- package/server/ops/sandbox-shim/bwrap +57 -57
- package/server/projects/assets-summary.js +119 -119
- package/server/projects/auto-name.js +53 -53
- package/server/projects/board-store.js +640 -640
- package/server/projects/move-entry.js +103 -103
- package/server/projects/store.js +270 -270
- package/server/projects/ui-config.js +54 -54
- package/server/projects/workspace-templates.js +53 -53
- package/server/projects/workspace.js +1025 -1025
- package/server/runtime/local-config.js +177 -177
- package/server/runtime/profile.js +72 -72
- package/server/services/rembg-service.py +334 -334
- package/server/shared/README.md +25 -25
- package/server/shared/deck.js +54 -54
- package/server/shared/time.js +49 -49
- package/server/style-pipeline/README.md +53 -53
- package/server/ws/broker.js +36 -36
- package/server/ws/browse-channel.js +177 -177
- package/server/ws/index.js +386 -386
- package/web/README.md +89 -89
- package/web/dist/assets/BrowserWindow-BYCgizHw.js +11 -0
- package/web/dist/assets/{DeckWindow-4G0xX7P3.js → DeckWindow-pNydEM4L.js} +1 -1
- package/web/dist/assets/{DocxWindow-BXQsoPkh.js → DocxWindow-PPgzh3Gq.js} +1 -1
- package/web/dist/assets/{SiteWindow-D6eS-2gM.js → SiteWindow-DM0LQPCO.js} +1 -1
- package/web/dist/assets/{index-Cbl3ATvp.js → index-BHTBJlpY.js} +32 -32
- package/web/dist/assets/{pending-edit-apply-DTjo2geW.js → pending-edit-apply-C0I13CbD.js} +1 -1
- package/web/dist/assets/{rotate-cw-LMT3x1Zj.js → rotate-cw-B1UQ8Hld.js} +1 -1
- package/web/dist/assets/{square-dashed-mouse-pointer-CBoAkfhi.js → square-dashed-mouse-pointer-8lrgqmfv.js} +1 -1
- package/web/dist/index.html +17 -17
- package/web/dist/assets/BrowserWindow-Dc7J7CYc.js +0 -11
|
@@ -1,199 +1,199 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Canvas 一致性校验(2026-05-08 范式重整 #1/#3/#5/#6 反馈通道)。
|
|
3
|
-
* (2026-08-14 可维护性行动:从 hooks.js 原样迁出,语义零改动)
|
|
4
|
-
*/
|
|
5
|
-
import fs from 'node:fs/promises';
|
|
6
|
-
import path from 'node:path';
|
|
7
|
-
|
|
8
|
-
/** 读 canvas.html 时的最大字节数(防大文件吞内存) */
|
|
9
|
-
const CANVAS_HTML_MAX_BYTES = 2 * 1024 * 1024;
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* 校验前预处理:strip HTML 注释 + CSS/JS block 注释
|
|
13
|
-
*
|
|
14
|
-
* 防止 false positive:模板里的 `<!-- ┄┄┄ 骨架范例 ... data-anchor="cover" ┄┄┄ -->`
|
|
15
|
-
* HTML 注释 + page-styles 里"取消注释切到 ppt mode"的 CSS 注释切片,原始 grep
|
|
16
|
-
* 都会误匹配。预先 strip 后再校验。
|
|
17
|
-
*
|
|
18
|
-
* 仅用于 validator 内部 regex 扫描;agent 看到的源文件不受影响。
|
|
19
|
-
*/
|
|
20
|
-
function stripCommentsForValidate(html) {
|
|
21
|
-
return html
|
|
22
|
-
.replace(/<!--[\s\S]*?-->/g, '') // HTML 注释
|
|
23
|
-
.replace(/\/\*[\s\S]*?\*\//g, ''); // CSS / JS block 注释(含 babel script)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* LAYOUT_COMPONENT_TRIGGERS — data-layout 值 → 推荐组件 import / detect 列表
|
|
28
|
-
*
|
|
29
|
-
* 校验项 4(#6)消费:data-layout ∈ keys + babel script 段所有 detect 都不命中
|
|
30
|
-
* → warn agent reach for 推荐组件(模板自带 inline 4 件 / 或 import @radix-ui)
|
|
31
|
-
*
|
|
32
|
-
* 形态:{ [layoutName]: { recommend: string[], detect: string[] regex sources } }
|
|
33
|
-
*/
|
|
34
|
-
const LAYOUT_COMPONENT_TRIGGERS = {
|
|
35
|
-
'comparison-table': { recommend: ['<Tabs>', '<Card>'], detect: ['\\bTabs\\s*[\\.<]', '<TabsList\\b', '<Card\\b', "import[^;]*['\"]@radix-ui/react-tabs['\"]"] },
|
|
36
|
-
'feature-cards': { recommend: ['<Card> 阵列'], detect: ['<Card\\b', '<CardHeader\\b', '<CardContent\\b', '<CardTitle\\b'] },
|
|
37
|
-
'use-cases': { recommend: ['<Tabs>', '<Card> 阵列'], detect: ['\\bTabs\\s*[\\.<]', '<Card\\b', "import[^;]*['\"]@radix-ui/react-tabs['\"]"] },
|
|
38
|
-
'core-products': { recommend: ['<Card> 阵列', '<Tabs>'], detect: ['<Card\\b', '\\bTabs\\s*[\\.<]'] },
|
|
39
|
-
'tech-highlights': { recommend: ['<Card> 阵列'], detect: ['<Card\\b', '<Badge\\b'] },
|
|
40
|
-
'feature-array': { recommend: ['<Tabs>', '<Card>'], detect: ['\\bTabs\\s*[\\.<]', '<Card\\b'] },
|
|
41
|
-
'variant-showcase': { recommend: ['<Tabs> (≤4) / embla-carousel-react (>4)'], detect: ['\\bTabs\\s*[\\.<]', 'embla-carousel-react', 'useEmblaCarousel'] },
|
|
42
|
-
'comparison': { recommend: ['<Tabs>', '<Card> 对比阵列'], detect: ['\\bTabs\\s*[\\.<]', '<Card\\b'] },
|
|
43
|
-
'step-switcher': { recommend: ['<Tabs>'], detect: ['\\bTabs\\s*[\\.<]', "import[^;]*['\"]@radix-ui/react-tabs['\"]"] },
|
|
44
|
-
'concept-vs-misconception': { recommend: ['<Tabs>', '<Card> 对照阵列'], detect: ['\\bTabs\\s*[\\.<]', '<Card\\b'] },
|
|
45
|
-
'config-switcher': { recommend: ['<Tabs>'], detect: ['\\bTabs\\s*[\\.<]'] },
|
|
46
|
-
'quadrant': { recommend: ['<Card> 4 格阵列'], detect: ['<Card\\b', 'grid-cols-2'] },
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* 校验项 1:data-anchor 唯一性
|
|
51
|
-
*
|
|
52
|
-
* 全文 grep `data-anchor="X"`,按值分组,重名 → 报冲突 + 列页号
|
|
53
|
-
*/
|
|
54
|
-
function validateAnchorUniqueness(html) {
|
|
55
|
-
const matches = [...html.matchAll(/data-anchor\s*=\s*['"]([^'"]+)['"]/g)];
|
|
56
|
-
if (matches.length === 0) return null;
|
|
57
|
-
|
|
58
|
-
const groups = new Map();
|
|
59
|
-
for (const m of matches) {
|
|
60
|
-
const value = m[1];
|
|
61
|
-
const idx = m.index;
|
|
62
|
-
// 反推所在 page:往前找最近的 <section data-page="N">
|
|
63
|
-
const before = html.slice(0, idx);
|
|
64
|
-
const lastSection = [...before.matchAll(/<section\b[^>]*data-page\s*=\s*['"](\d+)['"]/g)].pop();
|
|
65
|
-
const page = lastSection ? lastSection[1] : '?';
|
|
66
|
-
if (!groups.has(value)) groups.set(value, []);
|
|
67
|
-
groups.get(value).push(page);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const conflicts = [];
|
|
71
|
-
for (const [value, pages] of groups) {
|
|
72
|
-
if (pages.length > 1) {
|
|
73
|
-
conflicts.push(`"${value}" → 出现在 page ${[...new Set(pages)].join(', ')} (${pages.length} 次)`);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
if (conflicts.length === 0) return null;
|
|
77
|
-
return {
|
|
78
|
-
title: `data-anchor 重名 ${conflicts.length} 处`,
|
|
79
|
-
detail: conflicts.join('\n ') + '\n data-anchor 必须 deck 内唯一(重名加 -pN 页号或角色后缀,如 portrait-name-p3 / cover-sub-1)。findElementByAnchor 三层 fallback 第一层是按 data-anchor 查;重名时 querySelector 永远返第一个匹配,DirectEdit / 评论 pin 到错的元素。',
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* 校验项 2:data-layout 推荐组件 reach for 检查(#6)
|
|
85
|
-
*
|
|
86
|
-
* data-layout ∈ LAYOUT_COMPONENT_TRIGGERS keys 且整文件 babel script 段所有
|
|
87
|
-
* detect[i] regex 都不命中 → warn agent 用 inline 4 件
|
|
88
|
-
*/
|
|
89
|
-
function validateLayoutComponents(html) {
|
|
90
|
-
const layoutMatches = [...html.matchAll(/data-layout\s*=\s*['"]([^'"]+)['"]/g)];
|
|
91
|
-
if (layoutMatches.length === 0) return null;
|
|
92
|
-
|
|
93
|
-
// 抽 babel script 段(多个)拼一起
|
|
94
|
-
const babelBlocks = [...html.matchAll(/<script[^>]*type=["']text\/babel["'][^>]*>([\s\S]*?)<\/script>/gi)]
|
|
95
|
-
.map(m => m[1]).join('\n');
|
|
96
|
-
|
|
97
|
-
const issues = [];
|
|
98
|
-
const seen = new Set();
|
|
99
|
-
for (const m of layoutMatches) {
|
|
100
|
-
const layoutName = m[1];
|
|
101
|
-
if (seen.has(layoutName)) continue;
|
|
102
|
-
seen.add(layoutName);
|
|
103
|
-
const trigger = LAYOUT_COMPONENT_TRIGGERS[layoutName];
|
|
104
|
-
if (!trigger) continue;
|
|
105
|
-
const anyHit = trigger.detect.some(src => {
|
|
106
|
-
try { return new RegExp(src).test(babelBlocks); } catch { return false; }
|
|
107
|
-
});
|
|
108
|
-
if (!anyHit) {
|
|
109
|
-
issues.push(`data-layout="${layoutName}" 适合 ${trigger.recommend.join(' / ')},但 babel script 段没检测到对应组件`);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
if (issues.length === 0) return null;
|
|
113
|
-
return {
|
|
114
|
-
title: `${issues.length} 处 data-layout 漏用推荐组件`,
|
|
115
|
-
detail: issues.join('\n ') + '\n 模板 <script id="__nd-shadcn-lite"> 已自带 Card / Button / Badge / Tabs,0 import 直接 <Card> / <Tabs> 用即可。选型见首次写 deck 时注入的 hybrid 技术参考(inline shadcn 一节)。',
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* 校验项 3:data-layout-role 必装
|
|
121
|
-
*
|
|
122
|
-
* 每个 <section data-page> 必标 data-layout-role
|
|
123
|
-
*/
|
|
124
|
-
function validateLayoutRolePresence(html) {
|
|
125
|
-
const sections = [...html.matchAll(/<section\b[^>]*data-page\s*=\s*['"](\d+)['"][^>]*>/g)];
|
|
126
|
-
if (sections.length === 0) return null;
|
|
127
|
-
const missing = [];
|
|
128
|
-
for (const m of sections) {
|
|
129
|
-
const tag = m[0];
|
|
130
|
-
if (!/data-layout-role\s*=/.test(tag)) {
|
|
131
|
-
missing.push(m[1]);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
if (missing.length === 0) return null;
|
|
135
|
-
return {
|
|
136
|
-
title: `${missing.length} 个 section 缺 data-layout-role`,
|
|
137
|
-
detail: `Page ${missing.join(', ')} 没标 data-layout-role(image-led / text-led / data-led / hybrid 必选其一)。这字段决定页型分布 + 视觉判断;缺它系统按"未知"处理,patterns/<role>.md 也无法对应。`,
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Canvas validation 总入口(PostToolUse Edit|Write canvas.html 触发)
|
|
143
|
-
*
|
|
144
|
-
* matcher 第一行 path filter:仅对 canvas.html 跑校验,其他文件 noop。
|
|
145
|
-
* 单 hook 内 3 项串行校验(in-memory,~ms),有 issue 拼 systemMessage 注下一轮。
|
|
146
|
-
*
|
|
147
|
-
* 单 turn 反馈(不持久化):agent 不修则下次 Edit 自然惩罚再报,跨 turn 持续
|
|
148
|
-
* 延期下一轮(spec.json schema 扩展)。
|
|
149
|
-
*/
|
|
150
|
-
export function makePostToolUseCanvasValidationHandler({ ctx: _ctx, workspaceRoot }) {
|
|
151
|
-
return async (input, _toolUseId, _options) => {
|
|
152
|
-
try {
|
|
153
|
-
const fp = input?.tool_input?.file_path;
|
|
154
|
-
// deck 现在叫 <名>.html(canvas.html 只是常用名):先按扩展名放行,读到内容再按 deck wrap 标记认
|
|
155
|
-
if (!fp || !/\.html?$/i.test(fp)) return {};
|
|
156
|
-
if (!workspaceRoot) return {};
|
|
157
|
-
|
|
158
|
-
// 校验刚写的那份(2026-07-28):任务模型下 deck 在 tasks/<任务>/canvas.html,
|
|
159
|
-
// 这里以前固定读 cwd/canvas.html —— 文件不存在直接 ENOENT return,
|
|
160
|
-
// 于是整套一致性校验在任务模型下从来没跑过。
|
|
161
|
-
const canvasPath = path.resolve(workspaceRoot, fp);
|
|
162
|
-
let html;
|
|
163
|
-
try {
|
|
164
|
-
const stat = await fs.stat(canvasPath);
|
|
165
|
-
if (stat.size > CANVAS_HTML_MAX_BYTES) return {}; // 大文件 noop
|
|
166
|
-
html = await fs.readFile(canvasPath, 'utf8');
|
|
167
|
-
} catch (err) {
|
|
168
|
-
if (err.code === 'ENOENT') return {};
|
|
169
|
-
throw err;
|
|
170
|
-
}
|
|
171
|
-
if (!/canvas\.html$/i.test(fp) && !html.includes('__nd-deck-wrap')) return {}; // 不是 deck(站点页等)
|
|
172
|
-
|
|
173
|
-
// 预 strip 注释(HTML + CSS/JS block)防 false positive
|
|
174
|
-
const cleaned = stripCommentsForValidate(html);
|
|
175
|
-
// 依序调对应 validator。旧版含 mode-CSS / 必装 CSS 两项已删
|
|
176
|
-
// (2026-05-08 范式简化:不再有 deck-mode)
|
|
177
|
-
|
|
178
|
-
const issues = [
|
|
179
|
-
validateAnchorUniqueness(cleaned),
|
|
180
|
-
validateLayoutComponents(cleaned),
|
|
181
|
-
validateLayoutRolePresence(cleaned),
|
|
182
|
-
].filter(Boolean);
|
|
183
|
-
|
|
184
|
-
if (issues.length === 0) return {};
|
|
185
|
-
|
|
186
|
-
const body = issues.map((i, idx) => `${idx + 1}. ${i.title}\n ${i.detail}`).join('\n\n');
|
|
187
|
-
return {
|
|
188
|
-
systemMessage:
|
|
189
|
-
`<system-reminder>\n[canvas-validate] 你刚改完 ${fp},系统检测到 ${issues.length} 项可疑:\n\n`
|
|
190
|
-
+ body
|
|
191
|
-
+ `\n\n如果有意为之(custom mode / 故意命名重复 等)忽略;否则在下一轮主动修。\n`
|
|
192
|
-
+ `</system-reminder>`,
|
|
193
|
-
};
|
|
194
|
-
} catch (err) {
|
|
195
|
-
console.warn('[hooks/canvas-validate] threw:', err.message);
|
|
196
|
-
return {};
|
|
197
|
-
}
|
|
198
|
-
};
|
|
199
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Canvas 一致性校验(2026-05-08 范式重整 #1/#3/#5/#6 反馈通道)。
|
|
3
|
+
* (2026-08-14 可维护性行动:从 hooks.js 原样迁出,语义零改动)
|
|
4
|
+
*/
|
|
5
|
+
import fs from 'node:fs/promises';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
|
|
8
|
+
/** 读 canvas.html 时的最大字节数(防大文件吞内存) */
|
|
9
|
+
const CANVAS_HTML_MAX_BYTES = 2 * 1024 * 1024;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 校验前预处理:strip HTML 注释 + CSS/JS block 注释
|
|
13
|
+
*
|
|
14
|
+
* 防止 false positive:模板里的 `<!-- ┄┄┄ 骨架范例 ... data-anchor="cover" ┄┄┄ -->`
|
|
15
|
+
* HTML 注释 + page-styles 里"取消注释切到 ppt mode"的 CSS 注释切片,原始 grep
|
|
16
|
+
* 都会误匹配。预先 strip 后再校验。
|
|
17
|
+
*
|
|
18
|
+
* 仅用于 validator 内部 regex 扫描;agent 看到的源文件不受影响。
|
|
19
|
+
*/
|
|
20
|
+
function stripCommentsForValidate(html) {
|
|
21
|
+
return html
|
|
22
|
+
.replace(/<!--[\s\S]*?-->/g, '') // HTML 注释
|
|
23
|
+
.replace(/\/\*[\s\S]*?\*\//g, ''); // CSS / JS block 注释(含 babel script)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* LAYOUT_COMPONENT_TRIGGERS — data-layout 值 → 推荐组件 import / detect 列表
|
|
28
|
+
*
|
|
29
|
+
* 校验项 4(#6)消费:data-layout ∈ keys + babel script 段所有 detect 都不命中
|
|
30
|
+
* → warn agent reach for 推荐组件(模板自带 inline 4 件 / 或 import @radix-ui)
|
|
31
|
+
*
|
|
32
|
+
* 形态:{ [layoutName]: { recommend: string[], detect: string[] regex sources } }
|
|
33
|
+
*/
|
|
34
|
+
const LAYOUT_COMPONENT_TRIGGERS = {
|
|
35
|
+
'comparison-table': { recommend: ['<Tabs>', '<Card>'], detect: ['\\bTabs\\s*[\\.<]', '<TabsList\\b', '<Card\\b', "import[^;]*['\"]@radix-ui/react-tabs['\"]"] },
|
|
36
|
+
'feature-cards': { recommend: ['<Card> 阵列'], detect: ['<Card\\b', '<CardHeader\\b', '<CardContent\\b', '<CardTitle\\b'] },
|
|
37
|
+
'use-cases': { recommend: ['<Tabs>', '<Card> 阵列'], detect: ['\\bTabs\\s*[\\.<]', '<Card\\b', "import[^;]*['\"]@radix-ui/react-tabs['\"]"] },
|
|
38
|
+
'core-products': { recommend: ['<Card> 阵列', '<Tabs>'], detect: ['<Card\\b', '\\bTabs\\s*[\\.<]'] },
|
|
39
|
+
'tech-highlights': { recommend: ['<Card> 阵列'], detect: ['<Card\\b', '<Badge\\b'] },
|
|
40
|
+
'feature-array': { recommend: ['<Tabs>', '<Card>'], detect: ['\\bTabs\\s*[\\.<]', '<Card\\b'] },
|
|
41
|
+
'variant-showcase': { recommend: ['<Tabs> (≤4) / embla-carousel-react (>4)'], detect: ['\\bTabs\\s*[\\.<]', 'embla-carousel-react', 'useEmblaCarousel'] },
|
|
42
|
+
'comparison': { recommend: ['<Tabs>', '<Card> 对比阵列'], detect: ['\\bTabs\\s*[\\.<]', '<Card\\b'] },
|
|
43
|
+
'step-switcher': { recommend: ['<Tabs>'], detect: ['\\bTabs\\s*[\\.<]', "import[^;]*['\"]@radix-ui/react-tabs['\"]"] },
|
|
44
|
+
'concept-vs-misconception': { recommend: ['<Tabs>', '<Card> 对照阵列'], detect: ['\\bTabs\\s*[\\.<]', '<Card\\b'] },
|
|
45
|
+
'config-switcher': { recommend: ['<Tabs>'], detect: ['\\bTabs\\s*[\\.<]'] },
|
|
46
|
+
'quadrant': { recommend: ['<Card> 4 格阵列'], detect: ['<Card\\b', 'grid-cols-2'] },
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* 校验项 1:data-anchor 唯一性
|
|
51
|
+
*
|
|
52
|
+
* 全文 grep `data-anchor="X"`,按值分组,重名 → 报冲突 + 列页号
|
|
53
|
+
*/
|
|
54
|
+
function validateAnchorUniqueness(html) {
|
|
55
|
+
const matches = [...html.matchAll(/data-anchor\s*=\s*['"]([^'"]+)['"]/g)];
|
|
56
|
+
if (matches.length === 0) return null;
|
|
57
|
+
|
|
58
|
+
const groups = new Map();
|
|
59
|
+
for (const m of matches) {
|
|
60
|
+
const value = m[1];
|
|
61
|
+
const idx = m.index;
|
|
62
|
+
// 反推所在 page:往前找最近的 <section data-page="N">
|
|
63
|
+
const before = html.slice(0, idx);
|
|
64
|
+
const lastSection = [...before.matchAll(/<section\b[^>]*data-page\s*=\s*['"](\d+)['"]/g)].pop();
|
|
65
|
+
const page = lastSection ? lastSection[1] : '?';
|
|
66
|
+
if (!groups.has(value)) groups.set(value, []);
|
|
67
|
+
groups.get(value).push(page);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const conflicts = [];
|
|
71
|
+
for (const [value, pages] of groups) {
|
|
72
|
+
if (pages.length > 1) {
|
|
73
|
+
conflicts.push(`"${value}" → 出现在 page ${[...new Set(pages)].join(', ')} (${pages.length} 次)`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (conflicts.length === 0) return null;
|
|
77
|
+
return {
|
|
78
|
+
title: `data-anchor 重名 ${conflicts.length} 处`,
|
|
79
|
+
detail: conflicts.join('\n ') + '\n data-anchor 必须 deck 内唯一(重名加 -pN 页号或角色后缀,如 portrait-name-p3 / cover-sub-1)。findElementByAnchor 三层 fallback 第一层是按 data-anchor 查;重名时 querySelector 永远返第一个匹配,DirectEdit / 评论 pin 到错的元素。',
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 校验项 2:data-layout 推荐组件 reach for 检查(#6)
|
|
85
|
+
*
|
|
86
|
+
* data-layout ∈ LAYOUT_COMPONENT_TRIGGERS keys 且整文件 babel script 段所有
|
|
87
|
+
* detect[i] regex 都不命中 → warn agent 用 inline 4 件
|
|
88
|
+
*/
|
|
89
|
+
function validateLayoutComponents(html) {
|
|
90
|
+
const layoutMatches = [...html.matchAll(/data-layout\s*=\s*['"]([^'"]+)['"]/g)];
|
|
91
|
+
if (layoutMatches.length === 0) return null;
|
|
92
|
+
|
|
93
|
+
// 抽 babel script 段(多个)拼一起
|
|
94
|
+
const babelBlocks = [...html.matchAll(/<script[^>]*type=["']text\/babel["'][^>]*>([\s\S]*?)<\/script>/gi)]
|
|
95
|
+
.map(m => m[1]).join('\n');
|
|
96
|
+
|
|
97
|
+
const issues = [];
|
|
98
|
+
const seen = new Set();
|
|
99
|
+
for (const m of layoutMatches) {
|
|
100
|
+
const layoutName = m[1];
|
|
101
|
+
if (seen.has(layoutName)) continue;
|
|
102
|
+
seen.add(layoutName);
|
|
103
|
+
const trigger = LAYOUT_COMPONENT_TRIGGERS[layoutName];
|
|
104
|
+
if (!trigger) continue;
|
|
105
|
+
const anyHit = trigger.detect.some(src => {
|
|
106
|
+
try { return new RegExp(src).test(babelBlocks); } catch { return false; }
|
|
107
|
+
});
|
|
108
|
+
if (!anyHit) {
|
|
109
|
+
issues.push(`data-layout="${layoutName}" 适合 ${trigger.recommend.join(' / ')},但 babel script 段没检测到对应组件`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (issues.length === 0) return null;
|
|
113
|
+
return {
|
|
114
|
+
title: `${issues.length} 处 data-layout 漏用推荐组件`,
|
|
115
|
+
detail: issues.join('\n ') + '\n 模板 <script id="__nd-shadcn-lite"> 已自带 Card / Button / Badge / Tabs,0 import 直接 <Card> / <Tabs> 用即可。选型见首次写 deck 时注入的 hybrid 技术参考(inline shadcn 一节)。',
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* 校验项 3:data-layout-role 必装
|
|
121
|
+
*
|
|
122
|
+
* 每个 <section data-page> 必标 data-layout-role
|
|
123
|
+
*/
|
|
124
|
+
function validateLayoutRolePresence(html) {
|
|
125
|
+
const sections = [...html.matchAll(/<section\b[^>]*data-page\s*=\s*['"](\d+)['"][^>]*>/g)];
|
|
126
|
+
if (sections.length === 0) return null;
|
|
127
|
+
const missing = [];
|
|
128
|
+
for (const m of sections) {
|
|
129
|
+
const tag = m[0];
|
|
130
|
+
if (!/data-layout-role\s*=/.test(tag)) {
|
|
131
|
+
missing.push(m[1]);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (missing.length === 0) return null;
|
|
135
|
+
return {
|
|
136
|
+
title: `${missing.length} 个 section 缺 data-layout-role`,
|
|
137
|
+
detail: `Page ${missing.join(', ')} 没标 data-layout-role(image-led / text-led / data-led / hybrid 必选其一)。这字段决定页型分布 + 视觉判断;缺它系统按"未知"处理,patterns/<role>.md 也无法对应。`,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Canvas validation 总入口(PostToolUse Edit|Write canvas.html 触发)
|
|
143
|
+
*
|
|
144
|
+
* matcher 第一行 path filter:仅对 canvas.html 跑校验,其他文件 noop。
|
|
145
|
+
* 单 hook 内 3 项串行校验(in-memory,~ms),有 issue 拼 systemMessage 注下一轮。
|
|
146
|
+
*
|
|
147
|
+
* 单 turn 反馈(不持久化):agent 不修则下次 Edit 自然惩罚再报,跨 turn 持续
|
|
148
|
+
* 延期下一轮(spec.json schema 扩展)。
|
|
149
|
+
*/
|
|
150
|
+
export function makePostToolUseCanvasValidationHandler({ ctx: _ctx, workspaceRoot }) {
|
|
151
|
+
return async (input, _toolUseId, _options) => {
|
|
152
|
+
try {
|
|
153
|
+
const fp = input?.tool_input?.file_path;
|
|
154
|
+
// deck 现在叫 <名>.html(canvas.html 只是常用名):先按扩展名放行,读到内容再按 deck wrap 标记认
|
|
155
|
+
if (!fp || !/\.html?$/i.test(fp)) return {};
|
|
156
|
+
if (!workspaceRoot) return {};
|
|
157
|
+
|
|
158
|
+
// 校验刚写的那份(2026-07-28):任务模型下 deck 在 tasks/<任务>/canvas.html,
|
|
159
|
+
// 这里以前固定读 cwd/canvas.html —— 文件不存在直接 ENOENT return,
|
|
160
|
+
// 于是整套一致性校验在任务模型下从来没跑过。
|
|
161
|
+
const canvasPath = path.resolve(workspaceRoot, fp);
|
|
162
|
+
let html;
|
|
163
|
+
try {
|
|
164
|
+
const stat = await fs.stat(canvasPath);
|
|
165
|
+
if (stat.size > CANVAS_HTML_MAX_BYTES) return {}; // 大文件 noop
|
|
166
|
+
html = await fs.readFile(canvasPath, 'utf8');
|
|
167
|
+
} catch (err) {
|
|
168
|
+
if (err.code === 'ENOENT') return {};
|
|
169
|
+
throw err;
|
|
170
|
+
}
|
|
171
|
+
if (!/canvas\.html$/i.test(fp) && !html.includes('__nd-deck-wrap')) return {}; // 不是 deck(站点页等)
|
|
172
|
+
|
|
173
|
+
// 预 strip 注释(HTML + CSS/JS block)防 false positive
|
|
174
|
+
const cleaned = stripCommentsForValidate(html);
|
|
175
|
+
// 依序调对应 validator。旧版含 mode-CSS / 必装 CSS 两项已删
|
|
176
|
+
// (2026-05-08 范式简化:不再有 deck-mode)
|
|
177
|
+
|
|
178
|
+
const issues = [
|
|
179
|
+
validateAnchorUniqueness(cleaned),
|
|
180
|
+
validateLayoutComponents(cleaned),
|
|
181
|
+
validateLayoutRolePresence(cleaned),
|
|
182
|
+
].filter(Boolean);
|
|
183
|
+
|
|
184
|
+
if (issues.length === 0) return {};
|
|
185
|
+
|
|
186
|
+
const body = issues.map((i, idx) => `${idx + 1}. ${i.title}\n ${i.detail}`).join('\n\n');
|
|
187
|
+
return {
|
|
188
|
+
systemMessage:
|
|
189
|
+
`<system-reminder>\n[canvas-validate] 你刚改完 ${fp},系统检测到 ${issues.length} 项可疑:\n\n`
|
|
190
|
+
+ body
|
|
191
|
+
+ `\n\n如果有意为之(custom mode / 故意命名重复 等)忽略;否则在下一轮主动修。\n`
|
|
192
|
+
+ `</system-reminder>`,
|
|
193
|
+
};
|
|
194
|
+
} catch (err) {
|
|
195
|
+
console.warn('[hooks/canvas-validate] threw:', err.message);
|
|
196
|
+
return {};
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
}
|