@vobs/cli 1.8.5 → 1.8.6

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.
@@ -0,0 +1,240 @@
1
+ var __VOBS_CJS_FILE_URL = require("url").pathToFileURL(__filename).href;
2
+ "use strict";
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
21
+
22
+ // packages/cli/src/commands/agent-doc.ts
23
+ var agent_doc_exports = {};
24
+ __export(agent_doc_exports, {
25
+ agentDocCommand: () => agentDocCommand
26
+ });
27
+ module.exports = __toCommonJS(agent_doc_exports);
28
+ var import_node_fs = require("fs");
29
+ var import_node_path = require("path");
30
+
31
+ // packages/cli/src/templates/agent-contract.ts
32
+ var AGENT_CONTRACT = `# vobs \u6846\u67B6\u5951\u7EA6\uFF08\u5199\u4EE3\u7801\u524D\u5FC5\u8BFB\uFF09
33
+
34
+ vobs \u662F**\u7F16\u8BD1\u578B / \u7EC6\u7C92\u5EA6 / \u54CD\u5E94\u5F0F**\u6846\u67B6\uFF0C\u4E0E React \u7684\u5FC3\u667A\u6A21\u578B\u6709\u51E0\u5904**\u6839\u672C\u4E0D\u540C**\u3002
35
+ \u4E0B\u9762\u7684\u6BCF\u4E00\u6761\u90FD\u6765\u81EA\u771F\u5B9E\u4E8B\u6545\uFF0C**\u8FDD\u53CD\u65F6\u901A\u5E38\u7F16\u8BD1\u901A\u8FC7\u3001\u8FD0\u884C\u671F\u624D\u9519**\u3002
36
+
37
+ > \u672C\u6587\u6863\u7531 \`vobs agent-doc\` \u4ECE\u6846\u67B6\u7248\u672C\u751F\u6210 \u2014\u2014 \u4E0D\u8981\u624B\u6539\u751F\u6210\u7684\u5757\u3002
38
+ > \u6BCF\u6761\u540E\u9762\u62EC\u53F7\u91CC\u662F**\u8FDD\u53CD\u65F6\u6846\u67B6\u4F1A\u62A5\u7684\u8BCA\u65AD\u7801**\u3002
39
+
40
+ ## \u4E00\u3001\u7EC4\u4EF6\u4F53\u53EA\u6267\u884C\u4E00\u6B21\uFF08run-once\uFF09
41
+
42
+ **\u7EC4\u4EF6\u51FD\u6570\u4F53\u53EA\u5728\u521B\u5EFA\u65F6\u8DD1\u4E00\u6B21**\uFF0C\u4E4B\u540E\u53EA\u6709 JSX \u91CC\u8BA2\u9605\u7684\u4F4D\u7F6E\u66F4\u65B0\u3002
43
+
44
+ - \u274C \`const filtered = list.value.filter(...)\` \u2014\u2014 \u4E00\u6B21\u5FEB\u7167\uFF0C\u4E4B\u540E\u6C38\u4E0D\u66F4\u65B0
45
+ - \u2705 \u6D3E\u751F\u653E\u8FDB JSX\uFF1A\`<div>{list.value.filter(...).map(render)}</div>\`
46
+ - \u274C \u7EC4\u4EF6\u4F53\u91CC\u53D6\u5FEB\u7167\u7ED9\u56DE\u8C03\u7528\uFF1A\`const v = name.value; onClick={() => save(v)}\`
47
+ - \u2705 \u56DE\u8C03\u5185\u91CD\u8BFB\uFF1A\`onClick={() => save(name.value)}\`
48
+ - \u52A8\u6001 props \u7528 getter \u5F62\u6001\uFF0C\u4E0D\u8981\u4F20\u6C42\u503C\u540E\u7684\u503C
49
+
50
+ ## \u4E8C\u3001\u6761\u4EF6\u6E32\u67D3\uFF1A**\u4E0D\u8981\u5728\u7EC4\u4EF6\u4F53\u91CC \`return\` \u5206\u652F**\uFF08\`VOBS_C107\` / \`VOBS_C104\`\uFF09
51
+
52
+ \u7EC4\u4EF6\u4F53\u91CC\u8BFB\u4FE1\u53F7\u7684 \`return\` \u5728\u6302\u8F7D\u65F6\u56FA\u5316\uFF0C**\u4E09\u79CD\u5199\u6CD5\u574F\u5F97\u4E00\u6A21\u4E00\u6837**\uFF1A
53
+
54
+ \`\`\`tsx
55
+ return cond.value ? <A/> : <B/> // \u274C \u51BB\u7ED3
56
+ if (cond.value) return <A/> // \u274C \u51BB\u7ED3
57
+ return cond.value ? <A/> : null // \u274C \u51BB\u7ED3
58
+ \`\`\`
59
+
60
+ \u9876\u5C42 \`return\` \u5904**\u6CA1\u6709 parent/anchor**\uFF0C\u6240\u4EE5\u4E24\u652F\u90FD\u662F JSX \u4E5F\u4E00\u6837\u4E0D\u4F1A\u91CD\u5206\u652F\u3002
61
+
62
+ | \u573A\u666F | \u7528\u4EC0\u4E48 |
63
+ |---|---|
64
+ | **\u8DEF\u7531\u5206\u652F** | **\`<RouterView/>\`**\uFF08\u58F0\u660E\u5F0F\uFF0C\u5185\u90E8 \`insertDynamic\` + \`resetKey\`\uFF09 |
65
+ | \u4FDD\u7559\u6302\u8F7D\u3001\u53EA\u5207\u663E\u9690 | **\`<Show when={cond}>\`**\uFF08\u5207 \`hidden\` + \`inert\`\uFF0C\u7126\u70B9/\u6EDA\u52A8/\u5185\u90E8\u72B6\u6001\u4E0D\u4E22\uFF09 |
66
+ | \u53EA\u5207\u7C7B\u540D | \`class="base" classList={{ 'is-on': cond.value }}\` |
67
+ | \u666E\u901A\u6761\u4EF6\u6E32\u67D3 | \u653E\u8FDB **JSX \u5B50\u8282\u70B9\u4F4D\u7F6E**\uFF1A\`<div>{cond.value ? <A/> : <B/>}</div>\` |
68
+
69
+ ## \u4E09\u3001effect \u91CC\u4E0D\u8981\u5199\u81EA\u5DF1\u8BFB\u8FC7\u7684\u4FE1\u53F7\uFF08\`VOBS_C210\` / \`VOBS_C211\`\uFF09
70
+
71
+ **\u4F9D\u8D56\u662F\u81EA\u52A8\u6536\u96C6\u7684**\uFF1Aeffect \u8FD0\u884C\u671F\u95F4\u8BFB\u5230\u7684**\u4EFB\u4F55**\u4FE1\u53F7\u90FD\u4F1A\u53D8\u6210\u4F9D\u8D56\u3002
72
+
73
+ - \u274C \`effect(() => { count.value++ })\` \u2014\u2014 \u8BFB+\u5199\u540C\u4E00\u4E2A\u4FE1\u53F7 = \u81EA\u8BA2\u9605\u5FAA\u73AF
74
+ - \u274C \`effect(() => { if (session.value) void sync() })\` \u2014\u2014 **\u300C\u53EA\u8C03\u4E86\u4E2A\u51FD\u6570\u300D\u4E0D\u7B49\u4E8E\u6CA1\u4F9D\u8D56**\uFF1A
75
+ \u88AB\u8C03\u51FD\u6570\u5728**\u9996\u4E2A \`await\` \u4E4B\u524D**\u7684\u4EE3\u7801\u662F**\u540C\u6B65\u6267\u884C**\u7684\uFF0C\u5B83\u8BFB\u7684\u4FE1\u53F7\u7B97\u5728 effect \u5934\u4E0A
76
+ - \u2705 \u53EA\u60F3\u58F0\u660E\u4F9D\u8D56\uFF1A**\`effect(on(deps, () => { \u2026 }))\`** \u2014\u2014 \u56DE\u8C03\u5728 untrack \u4F5C\u7528\u57DF\u91CC\u8DD1\uFF0C
77
+ \u5B83\u8C03\u7528\u7684\u51FD\u6570\u78B0\u4EC0\u4E48\u4FE1\u53F7\u90FD\u4E0D\u4F1A\u53CD\u5411\u8BA2\u9605
78
+ - \u2705 \u6D3E\u751F\u503C\u7528 \`memo\`\uFF0C\u4E0D\u8981"\u8BFB A \u5199 B"
79
+ - \u515C\u5E95\u624D\u662F \`untrack(() => { X.value = next })\`
80
+
81
+ **\u522B\u5728 effect \u91CC\u8C03 async \u51FD\u6570**\uFF08\`VOBS_C106\`\uFF09\uFF1A\`effect\` \u4E0D\u7B49\u5B83\uFF0C
82
+ \u4E14\u9996\u4E2A \`await\` \u4E4B\u524D\u7684\u90E8\u5206\u662F\u540C\u6B65\u7684\u3002\u5F02\u6B65\u53D6\u6570\u7528 **\`@vobs/resource\`**\u3002
83
+
84
+ ## \u56DB\u3001\u5BA2\u6237\u7AEF\u526F\u4F5C\u7528\u4E0E SSR
85
+
86
+ - \u5B9A\u65F6\u5668 / \u76D1\u542C / \`matchMedia\` / \`localStorage\` \u2192 **\`onMount\` \u542F\u52A8\u3001\`onDestroy\` \u6E05\u7406**
87
+ \uFF08**\u4E0D\u8981\u624B\u5199 \`typeof window\` \u5B88\u536B**\uFF09
88
+ - **\u6E32\u67D3\u8F93\u51FA\u672C\u8EAB**\u4F9D\u8D56\u6D4F\u89C8\u5668\uFF08\u7A97\u53E3\u5C3A\u5BF8 / \`localStorage\` \u56DE\u586B / \`Date.now\` / \u968F\u673A\u503C\uFF09
89
+ \u2192 **\`<ClientOnly fallback={\u2026}>\`**\uFF08\u9996\u8F6E\u4E24\u4FA7\u90FD\u6E32\u67D3 fallback\uFF0C\u6C34\u5408\u5BF9\u5F97\u4E0A\uFF09
90
+ - \u6A21\u5757\u9876\u5C42**\u7981\u6B62** JSX\uFF08\`VOBS_C105\`\uFF09\uFF1Aimport \u6C42\u503C\u65E9\u4E8E\u6E32\u67D3\u5668\u5B89\u88C5
91
+
92
+ ## \u4E94\u3001\u5217\u8868\u4E0E\u6570\u636E
93
+
94
+ - **\u6570\u7EC4\u66F4\u65B0\u5FC5\u987B\u6362\u5F15\u7528**\uFF1A\`list.value = [...list.value, item]\`\uFF1B
95
+ \u539F\u5730 \`push\` / \u6539\u5B57\u6BB5**\u4E0D\u89E6\u53D1\u66F4\u65B0**
96
+ - JSX **\u5B50\u8282\u70B9\u4F4D\u7F6E**\u53EA\u653E\u56DB\u79CD\u5F62\u6001\uFF1A\u7EC4\u4EF6\u6807\u7B7E / \u5143\u7D20 / \u4E24\u5206\u652F\u4E09\u5143 / \`.map()\`
97
+ - \u6570\u636E\u7ED3\u6784\u91CC**\u53EA\u5B58\u7EAF\u63CF\u8FF0**\uFF08\u5B57\u7B26\u4E32/\u6837\u5F0F/\u7ED3\u6784\u5B57\u6BB5\uFF09\uFF0C**\u8282\u70B9\u5BF9\u8C61\u522B\u8FDB\u6570\u636E\u5E38\u91CF**
98
+ \uFF08SSG \u5E8F\u5217\u5316\u4F1A\u70B8\uFF0C\u4EA7\u7269\u51FA\u73B0 \`[object Xxx]\` \u65F6\u6846\u67B6\u4F1A\u76F4\u63A5\u62A5\u9519\uFF09
99
+
100
+ ## \u516D\u3001\u8F93\u5165
101
+
102
+ - **\u6570\u5B57\u8F93\u5165\u8D70 \`parseNumber\`**\uFF08\`<Field type="number">\` \u5DF2\u5185\u7F6E\uFF09\uFF1A
103
+ \u7A7A\u4E32 / \u975E\u6CD5 / \u8D85\u754C**\u4E0D\u63D0\u4EA4**\uFF0C\u4FDD\u6301\u539F\u503C
104
+ \u2014\u2014 \`Number('') === 0\` \u4F1A\u628A\u8F93\u5165\u6E05\u6210 0 \u5E76\u6CBF\u8054\u52A8\u94FE\u8DEF\u6E05\u96F6\u5144\u5F1F\u7EF4\u5EA6
105
+ - \`<select>\` \u7684 value \u76F4\u63A5\u7ED1\uFF0C**\u4E0D\u8981\u5199 ref \u515C\u5E95**\uFF081.5.1+ \u5DF2\u4FEE\u65F6\u5E8F\uFF09
106
+
107
+ ## \u4E03\u3001\u56FE\u6807\u4E0E SVG
108
+
109
+ - **SVG \u76F4\u63A5\u5199 JSX**\uFF08\`<svg><path/></svg>\`\uFF0C1.7.4+ \u6309 namespace \u521B\u5EFA\u4E0E\u6C34\u5408\uFF09
110
+ - \u56FE\u6807\u8981**\u767B\u8BB0\u8FDB\u767D\u540D\u5355**\uFF1B\u67E5\u8868 miss \u65F6 \`@vobs/icon-core\` \u4F1A\u8B66\u544A\u70B9\u540D\uFF08\u4F46\u4ECD\u5E94\u767B\u8BB0\uFF09
111
+
112
+ ## \u516B\u3001\u63D0\u4EA4\u524D\u81EA\u6D4B
113
+
114
+ \`\`\`bash
115
+ pnpm run check:source # = vobs check\uFF0C\u5168\u4ED3\u4E00\u6B21\u5217\u51FA\u5168\u90E8\u8BCA\u65AD
116
+ pnpm run check:runtime # \u771F\u5B9E\u6D4F\u89C8\u5668\u9010\u8DEF\u7531\u8DD1\u62A4\u680F\uFF08\u9700 Chrome\uFF09
117
+ pnpm run check:runtime:interact # \u518D\u70B9\u6240\u6709\u6309\u94AE\u3001\u89E6\u53D1\u6240\u6709\u8F93\u5165
118
+ \`\`\`
119
+
120
+ \`vite build\` **\u4F1A**\u6253\u5370\u7F16\u8BD1\u671F\u8B66\u544A\uFF08\`C104\`/\`C105\`/\`C106\`/\`C107\`\uFF09\uFF0C\u4F46\u53EA\u8986\u76D6\u5B83\u7F16\u8BD1\u5230\u7684\u6587\u4EF6\uFF1B
121
+ \`check:source\` \u624D\u662F\u5168\u4ED3\u5165\u53E3\u3002
122
+
123
+ ---
124
+
125
+ ## \u4E00\u53E5\u8BDD\u901F\u8BB0
126
+
127
+ | \u4E3B\u9898 | \u4E00\u53E5\u8BDD |
128
+ |---|---|
129
+ | \u7EC4\u4EF6\u4F53 | \u53EA\u8DD1\u4E00\u6B21\uFF0C\u6D3E\u751F\u8FDB JSX |
130
+ | \u56DE\u8C03 | \u89E6\u53D1\u65F6\u91CD\u8BFB \`.value\` |
131
+ | \u6761\u4EF6\u6E32\u67D3 | \u8DEF\u7531\u7528 \`RouterView\`\uFF0C\u663E\u9690\u7528 \`Show\`\uFF0C\u522B\u5728\u7EC4\u4EF6\u4F53 return \u5206\u652F |
132
+ | effect | \u522B\u5199\u81EA\u5DF1\u8BFB\u7684\u4FE1\u53F7\uFF0C\u7528 \`on(deps, fn)\` |
133
+ | async | \u522B\u585E\u8FDB effect\uFF0C\u7528 \`@vobs/resource\` |
134
+ | \u5BA2\u6237\u7AEF | \`onMount\`/\`onDestroy\`/\`ClientOnly\`\uFF0C\u522B\u624B\u5199 \`typeof window\` |
135
+ | \u5217\u8868 | \u6362\u5F15\u7528 |
136
+ | \u6570\u5B57 | \`parseNumber\` |
137
+ | SVG | \u76F4\u63A5\u5199 |
138
+ | \u81EA\u6D4B | \`check:source\` + \`check:runtime\` |
139
+ `;
140
+
141
+ // packages/cli/src/commands/agent-doc.ts
142
+ var BEGIN = "<!-- vobs:begin \u7531 `vobs agent-doc --write` \u751F\u6210\uFF0C\u8BF7\u52FF\u624B\u6539 -->";
143
+ var END = "<!-- vobs:end -->";
144
+ function readContract() {
145
+ return AGENT_CONTRACT.trim();
146
+ }
147
+ __name(readContract, "readContract");
148
+ function buildAgentsDoc(contract) {
149
+ return [
150
+ BEGIN,
151
+ "",
152
+ contract,
153
+ "",
154
+ END,
155
+ "",
156
+ "<!-- \u4EE5\u4E0B\u662F\u672C\u9879\u76EE\u7684\u81EA\u6709\u7EA6\u5B9A\uFF0C`vobs agent-doc --write` \u4E0D\u4F1A\u8986\u76D6\u3002 -->",
157
+ "",
158
+ "## \u672C\u9879\u76EE\u7EA6\u5B9A",
159
+ "",
160
+ "\uFF08\u5728\u8FD9\u91CC\u5199\u4F60\u81EA\u5DF1\u7684\u76EE\u5F55\u7ED3\u6784\u3001\u547D\u540D\u3001\u4E1A\u52A1\u6D41\u7A0B\u7B49\u89C4\u5219\u3002\uFF09",
161
+ ""
162
+ ].join("\n");
163
+ }
164
+ __name(buildAgentsDoc, "buildAgentsDoc");
165
+ function extractBlock(text) {
166
+ const start = text.indexOf(BEGIN);
167
+ const end = text.indexOf(END);
168
+ if (start < 0 || end < 0 || end < start) return null;
169
+ return text.slice(start, end + END.length);
170
+ }
171
+ __name(extractBlock, "extractBlock");
172
+ function mergeAgentsDoc(existing, generated) {
173
+ if (existing === null) return generated;
174
+ const block = extractBlock(generated);
175
+ if (block === null) return generated;
176
+ if (extractBlock(existing) === null) return `${existing.trimEnd()}
177
+
178
+ ${block}
179
+ `;
180
+ return existing.replace(extractBlock(existing), block);
181
+ }
182
+ __name(mergeAgentsDoc, "mergeAgentsDoc");
183
+ async function agentDocCommand(options = {}) {
184
+ const root = (0, import_node_path.resolve)(options.dir ?? process.cwd());
185
+ const contract = readContract();
186
+ if (options.body === true) {
187
+ process.stdout.write(`${contract}
188
+ `);
189
+ return;
190
+ }
191
+ const generated = buildAgentsDoc(contract);
192
+ const agentsPath = (0, import_node_path.resolve)(root, "AGENTS.md");
193
+ const claudePath = (0, import_node_path.resolve)(root, "CLAUDE.md");
194
+ const claudeDoc = "@AGENTS.md\n";
195
+ if (options.check === true) {
196
+ const problems = [];
197
+ if (!(0, import_node_fs.existsSync)(agentsPath)) {
198
+ problems.push("AGENTS.md \u4E0D\u5B58\u5728 \u2014\u2014 \u8DD1 `vobs agent-doc --write` \u751F\u6210");
199
+ } else {
200
+ const existing = (0, import_node_fs.readFileSync)(agentsPath, "utf8");
201
+ const block = extractBlock(existing);
202
+ if (block === null) {
203
+ problems.push("AGENTS.md \u91CC\u627E\u4E0D\u5230 vobs \u6807\u8BB0\u5757 \u2014\u2014 \u8DD1 `vobs agent-doc --write` \u8865\u4E0A");
204
+ } else if (block !== extractBlock(generated)) {
205
+ problems.push("AGENTS.md \u7684 vobs \u5951\u7EA6\u5757\u4E0E\u5F53\u524D\u6846\u67B6\u7248\u672C\u4E0D\u4E00\u81F4 \u2014\u2014 \u8DD1 `vobs agent-doc --write` \u540C\u6B65");
206
+ }
207
+ }
208
+ if (!(0, import_node_fs.existsSync)(claudePath)) {
209
+ problems.push("CLAUDE.md \u4E0D\u5B58\u5728 \u2014\u2014 \u8DD1 `vobs agent-doc --write` \u751F\u6210\uFF08\u5185\u5BB9\u53EA\u9700\u4E00\u884C `@AGENTS.md`\uFF09");
210
+ } else if (!(0, import_node_fs.readFileSync)(claudePath, "utf8").includes("@AGENTS.md")) {
211
+ problems.push("CLAUDE.md \u6CA1\u6709\u6307\u5411 @AGENTS.md");
212
+ }
213
+ if (problems.length > 0) {
214
+ console.error("[agent-doc] \u5951\u7EA6\u4E0E\u6846\u67B6\u7248\u672C\u4E0D\u540C\u6B65\uFF1A");
215
+ for (const problem of problems) console.error(` - ${problem}`);
216
+ process.exitCode = 1;
217
+ return;
218
+ }
219
+ console.log("[agent-doc] \u5951\u7EA6\u4E0E\u5F53\u524D\u6846\u67B6\u7248\u672C\u4E00\u81F4 \u2713");
220
+ return;
221
+ }
222
+ if (options.write !== true) {
223
+ process.stdout.write(generated);
224
+ return;
225
+ }
226
+ const existingAgents = (0, import_node_fs.existsSync)(agentsPath) ? (0, import_node_fs.readFileSync)(agentsPath, "utf8") : null;
227
+ (0, import_node_fs.writeFileSync)(agentsPath, mergeAgentsDoc(existingAgents, generated), "utf8");
228
+ (0, import_node_fs.writeFileSync)(claudePath, claudeDoc, "utf8");
229
+ console.log(`[agent-doc] \u5DF2\u5199\u5165 ${agentsPath}`);
230
+ console.log(`[agent-doc] \u5DF2\u5199\u5165 ${claudePath}\uFF08\u5185\u5BB9\u4E3A @AGENTS.md\uFF09`);
231
+ if (existingAgents !== null && extractBlock(existingAgents) === null) {
232
+ console.log("[agent-doc] \u4F60\u539F\u6709\u7684 AGENTS.md \u5185\u5BB9\u5DF2\u4FDD\u7559\uFF0Cvobs \u5951\u7EA6\u5757\u8FFD\u52A0\u5728\u540E\u3002");
233
+ }
234
+ }
235
+ __name(agentDocCommand, "agentDocCommand");
236
+ // Annotate the CommonJS export names for ESM import in node:
237
+ 0 && (module.exports = {
238
+ agentDocCommand
239
+ });
240
+ //# sourceMappingURL=agent-doc.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/commands/agent-doc.ts","../../src/templates/agent-contract.ts"],"sourcesContent":["import { existsSync, readFileSync, writeFileSync } from 'node:fs'\nimport { resolve } from 'node:path'\nimport { AGENT_CONTRACT } from '../templates/agent-contract.js'\n\n/**\n * `vobs agent-doc` —— 把框架契约**生成**到当前项目,供任何 LLM / agent 读取。\n *\n * ## 为什么是\"生成\"而不是\"手写\"\n *\n * 契约随框架演进(1.8.0 加 `Show`/`ClientOnly`、1.8.3 加 `on()` 与 `VOBS_C106`、\n * 1.8.4 加 `VOBS_C107`、1.8.5 改 fix 文案)。手写的话,每个项目一份副本,\n * **必然各自漂移**;而且写的人未必知道最新契约。\n *\n * 生成则天然与版本绑定:`@vobs/cli@1.8.x` 产出 1.8.x 的契约。\n *\n * ## 设计要点\n *\n * 1. **标记块**:只替换 `<!-- vobs:begin -->` 与 `<!-- vobs:end -->` 之间的内容,\n * 块外的**项目自有约定原样保留** —— 重新生成不会覆盖你自己的东西。\n * 2. **`--check`**:校验项目里那份是否与当前框架版本一致,不一致 exit 1。\n * 可以进 CI —— 这样\"文档漂移\"从\"靠人记得\"变成\"机器拦\"。\n * 3. **`CLAUDE.md` 只写一行 `@AGENTS.md`**:内容只有一份,避免两份副本漂移。\n * 4. 由**框架**产出,不由用户手抄;工具无关(内容里不提任何特定 LLM)。\n */\n\nconst BEGIN = '<!-- vobs:begin 由 `vobs agent-doc --write` 生成,请勿手改 -->'\nconst END = '<!-- vobs:end -->'\n\nexport interface AgentDocOptions {\n /** 目标目录,默认当前工作目录。 */\n readonly dir?: string\n /** 写入文件(否则打到 stdout)。 */\n readonly write?: boolean\n /** 校验项目里那份是否与当前框架版本一致;不一致 exit 1。 */\n readonly check?: boolean\n /** 只输出框架契约正文(不含标记块外的说明)。 */\n readonly body?: boolean\n}\n\n/** 契约正文内联在 `templates/agent-contract.ts`(见那里的注释:读文件在全量测试下会失败)。 */\nfunction readContract(): string {\n return AGENT_CONTRACT.trim()\n}\n\n/** 把契约包进标记块 —— 块外是项目自己的内容,重新生成不会动它。 */\nfunction buildAgentsDoc(contract: string): string {\n return [\n BEGIN,\n '',\n contract,\n '',\n END,\n '',\n '<!-- 以下是本项目的自有约定,`vobs agent-doc --write` 不会覆盖。 -->',\n '',\n '## 本项目约定',\n '',\n '(在这里写你自己的目录结构、命名、业务流程等规则。)',\n ''\n ].join('\\n')\n}\n\n/** 取出文件里的标记块内容(没有标记块时返回 null)。 */\nfunction extractBlock(text: string): string | null {\n const start = text.indexOf(BEGIN)\n const end = text.indexOf(END)\n if (start < 0 || end < 0 || end < start) return null\n return text.slice(start, end + END.length)\n}\n\n/**\n * 合并:保留块外内容,只替换块。\n *\n * 没有标记块的既有 `AGENTS.md`(用户手写的)**不直接覆盖** ——\n * 把生成块**追加在后**,用户原有内容全部保留。丢掉别人的规则比不生成更糟。\n */\nfunction mergeAgentsDoc(existing: string | null, generated: string): string {\n if (existing === null) return generated\n const block = extractBlock(generated)\n if (block === null) return generated\n if (extractBlock(existing) === null) return `${existing.trimEnd()}\\n\\n${block}\\n`\n return existing.replace(extractBlock(existing)!, block)\n}\n\nexport async function agentDocCommand(options: AgentDocOptions = {}): Promise<void> {\n const root = resolve(options.dir ?? process.cwd())\n const contract = readContract()\n\n if (options.body === true) {\n process.stdout.write(`${contract}\\n`)\n return\n }\n\n const generated = buildAgentsDoc(contract)\n const agentsPath = resolve(root, 'AGENTS.md')\n const claudePath = resolve(root, 'CLAUDE.md')\n // 内容只有一份:CLAUDE.md 只做入口,避免两份副本漂移\n const claudeDoc = '@AGENTS.md\\n'\n\n if (options.check === true) {\n const problems: string[] = []\n if (!existsSync(agentsPath)) {\n problems.push('AGENTS.md 不存在 —— 跑 `vobs agent-doc --write` 生成')\n } else {\n const existing = readFileSync(agentsPath, 'utf8')\n const block = extractBlock(existing)\n if (block === null) {\n problems.push('AGENTS.md 里找不到 vobs 标记块 —— 跑 `vobs agent-doc --write` 补上')\n } else if (block !== extractBlock(generated)) {\n problems.push('AGENTS.md 的 vobs 契约块与当前框架版本不一致 —— 跑 `vobs agent-doc --write` 同步')\n }\n }\n if (!existsSync(claudePath)) {\n problems.push('CLAUDE.md 不存在 —— 跑 `vobs agent-doc --write` 生成(内容只需一行 `@AGENTS.md`)')\n } else if (!readFileSync(claudePath, 'utf8').includes('@AGENTS.md')) {\n problems.push('CLAUDE.md 没有指向 @AGENTS.md')\n }\n\n if (problems.length > 0) {\n console.error('[agent-doc] 契约与框架版本不同步:')\n for (const problem of problems) console.error(` - ${problem}`)\n process.exitCode = 1\n return\n }\n console.log('[agent-doc] 契约与当前框架版本一致 ✓')\n return\n }\n\n if (options.write !== true) {\n process.stdout.write(generated)\n return\n }\n\n const existingAgents = existsSync(agentsPath) ? readFileSync(agentsPath, 'utf8') : null\n writeFileSync(agentsPath, mergeAgentsDoc(existingAgents, generated), 'utf8')\n writeFileSync(claudePath, claudeDoc, 'utf8')\n console.log(`[agent-doc] 已写入 ${agentsPath}`)\n console.log(`[agent-doc] 已写入 ${claudePath}(内容为 @AGENTS.md)`)\n if (existingAgents !== null && extractBlock(existingAgents) === null) {\n console.log('[agent-doc] 你原有的 AGENTS.md 内容已保留,vobs 契约块追加在后。')\n }\n}\n","/**\n * vobs 框架契约正文 —— `vobs agent-doc` 的输出来源。\n *\n * 为什么是 TS 模块而不是 .md 文件:读文件要靠 `import.meta.url` 解析相对路径,\n * 而**全量测试时 vitest 会转换模块**,`import.meta.url` 指向虚拟路径,\n * `new URL('../../src/templates/…')` 就找不到文件(实测:单独跑 8 条全过、\n * 全量跑 8 条全红)。内联成字符串**免疫打包与转换**,也省掉发布时拷模板。\n *\n * 内容由 `packages/cli/src/templates/agent-contract.md` 生成(保留 .md 作可读源);\n * 改契约请改 .md 后重新生成,或直接改这里的字符串。\n */\nexport const AGENT_CONTRACT = `# vobs 框架契约(写代码前必读)\n\nvobs 是**编译型 / 细粒度 / 响应式**框架,与 React 的心智模型有几处**根本不同**。\n下面的每一条都来自真实事故,**违反时通常编译通过、运行期才错**。\n\n> 本文档由 \\`vobs agent-doc\\` 从框架版本生成 —— 不要手改生成的块。\n> 每条后面括号里是**违反时框架会报的诊断码**。\n\n## 一、组件体只执行一次(run-once)\n\n**组件函数体只在创建时跑一次**,之后只有 JSX 里订阅的位置更新。\n\n- ❌ \\`const filtered = list.value.filter(...)\\` —— 一次快照,之后永不更新\n- ✅ 派生放进 JSX:\\`<div>{list.value.filter(...).map(render)}</div>\\`\n- ❌ 组件体里取快照给回调用:\\`const v = name.value; onClick={() => save(v)}\\`\n- ✅ 回调内重读:\\`onClick={() => save(name.value)}\\`\n- 动态 props 用 getter 形态,不要传求值后的值\n\n## 二、条件渲染:**不要在组件体里 \\`return\\` 分支**(\\`VOBS_C107\\` / \\`VOBS_C104\\`)\n\n组件体里读信号的 \\`return\\` 在挂载时固化,**三种写法坏得一模一样**:\n\n\\`\\`\\`tsx\nreturn cond.value ? <A/> : <B/> // ❌ 冻结\nif (cond.value) return <A/> // ❌ 冻结\nreturn cond.value ? <A/> : null // ❌ 冻结\n\\`\\`\\`\n\n顶层 \\`return\\` 处**没有 parent/anchor**,所以两支都是 JSX 也一样不会重分支。\n\n| 场景 | 用什么 |\n|---|---|\n| **路由分支** | **\\`<RouterView/>\\`**(声明式,内部 \\`insertDynamic\\` + \\`resetKey\\`) |\n| 保留挂载、只切显隐 | **\\`<Show when={cond}>\\`**(切 \\`hidden\\` + \\`inert\\`,焦点/滚动/内部状态不丢) |\n| 只切类名 | \\`class=\"base\" classList={{ 'is-on': cond.value }}\\` |\n| 普通条件渲染 | 放进 **JSX 子节点位置**:\\`<div>{cond.value ? <A/> : <B/>}</div>\\` |\n\n## 三、effect 里不要写自己读过的信号(\\`VOBS_C210\\` / \\`VOBS_C211\\`)\n\n**依赖是自动收集的**:effect 运行期间读到的**任何**信号都会变成依赖。\n\n- ❌ \\`effect(() => { count.value++ })\\` —— 读+写同一个信号 = 自订阅循环\n- ❌ \\`effect(() => { if (session.value) void sync() })\\` —— **「只调了个函数」不等于没依赖**:\n 被调函数在**首个 \\`await\\` 之前**的代码是**同步执行**的,它读的信号算在 effect 头上\n- ✅ 只想声明依赖:**\\`effect(on(deps, () => { … }))\\`** —— 回调在 untrack 作用域里跑,\n 它调用的函数碰什么信号都不会反向订阅\n- ✅ 派生值用 \\`memo\\`,不要\"读 A 写 B\"\n- 兜底才是 \\`untrack(() => { X.value = next })\\`\n\n**别在 effect 里调 async 函数**(\\`VOBS_C106\\`):\\`effect\\` 不等它,\n且首个 \\`await\\` 之前的部分是同步的。异步取数用 **\\`@vobs/resource\\`**。\n\n## 四、客户端副作用与 SSR\n\n- 定时器 / 监听 / \\`matchMedia\\` / \\`localStorage\\` → **\\`onMount\\` 启动、\\`onDestroy\\` 清理**\n (**不要手写 \\`typeof window\\` 守卫**)\n- **渲染输出本身**依赖浏览器(窗口尺寸 / \\`localStorage\\` 回填 / \\`Date.now\\` / 随机值)\n → **\\`<ClientOnly fallback={…}>\\`**(首轮两侧都渲染 fallback,水合对得上)\n- 模块顶层**禁止** JSX(\\`VOBS_C105\\`):import 求值早于渲染器安装\n\n## 五、列表与数据\n\n- **数组更新必须换引用**:\\`list.value = [...list.value, item]\\`;\n 原地 \\`push\\` / 改字段**不触发更新**\n- JSX **子节点位置**只放四种形态:组件标签 / 元素 / 两分支三元 / \\`.map()\\`\n- 数据结构里**只存纯描述**(字符串/样式/结构字段),**节点对象别进数据常量**\n (SSG 序列化会炸,产物出现 \\`[object Xxx]\\` 时框架会直接报错)\n\n## 六、输入\n\n- **数字输入走 \\`parseNumber\\`**(\\`<Field type=\"number\">\\` 已内置):\n 空串 / 非法 / 超界**不提交**,保持原值\n —— \\`Number('') === 0\\` 会把输入清成 0 并沿联动链路清零兄弟维度\n- \\`<select>\\` 的 value 直接绑,**不要写 ref 兜底**(1.5.1+ 已修时序)\n\n## 七、图标与 SVG\n\n- **SVG 直接写 JSX**(\\`<svg><path/></svg>\\`,1.7.4+ 按 namespace 创建与水合)\n- 图标要**登记进白名单**;查表 miss 时 \\`@vobs/icon-core\\` 会警告点名(但仍应登记)\n\n## 八、提交前自测\n\n\\`\\`\\`bash\npnpm run check:source # = vobs check,全仓一次列出全部诊断\npnpm run check:runtime # 真实浏览器逐路由跑护栏(需 Chrome)\npnpm run check:runtime:interact # 再点所有按钮、触发所有输入\n\\`\\`\\`\n\n\\`vite build\\` **会**打印编译期警告(\\`C104\\`/\\`C105\\`/\\`C106\\`/\\`C107\\`),但只覆盖它编译到的文件;\n\\`check:source\\` 才是全仓入口。\n\n---\n\n## 一句话速记\n\n| 主题 | 一句话 |\n|---|---|\n| 组件体 | 只跑一次,派生进 JSX |\n| 回调 | 触发时重读 \\`.value\\` |\n| 条件渲染 | 路由用 \\`RouterView\\`,显隐用 \\`Show\\`,别在组件体 return 分支 |\n| effect | 别写自己读的信号,用 \\`on(deps, fn)\\` |\n| async | 别塞进 effect,用 \\`@vobs/resource\\` |\n| 客户端 | \\`onMount\\`/\\`onDestroy\\`/\\`ClientOnly\\`,别手写 \\`typeof window\\` |\n| 列表 | 换引用 |\n| 数字 | \\`parseNumber\\` |\n| SVG | 直接写 |\n| 自测 | \\`check:source\\` + \\`check:runtime\\` |\n`\n"],"mappings":"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","names":[]}
@@ -0,0 +1,13 @@
1
+ interface AgentDocOptions {
2
+ /** 目标目录,默认当前工作目录。 */
3
+ readonly dir?: string;
4
+ /** 写入文件(否则打到 stdout)。 */
5
+ readonly write?: boolean;
6
+ /** 校验项目里那份是否与当前框架版本一致;不一致 exit 1。 */
7
+ readonly check?: boolean;
8
+ /** 只输出框架契约正文(不含标记块外的说明)。 */
9
+ readonly body?: boolean;
10
+ }
11
+ declare function agentDocCommand(options?: AgentDocOptions): Promise<void>;
12
+
13
+ export { type AgentDocOptions, agentDocCommand };
@@ -0,0 +1,13 @@
1
+ interface AgentDocOptions {
2
+ /** 目标目录,默认当前工作目录。 */
3
+ readonly dir?: string;
4
+ /** 写入文件(否则打到 stdout)。 */
5
+ readonly write?: boolean;
6
+ /** 校验项目里那份是否与当前框架版本一致;不一致 exit 1。 */
7
+ readonly check?: boolean;
8
+ /** 只输出框架契约正文(不含标记块外的说明)。 */
9
+ readonly body?: boolean;
10
+ }
11
+ declare function agentDocCommand(options?: AgentDocOptions): Promise<void>;
12
+
13
+ export { type AgentDocOptions, agentDocCommand };
@@ -0,0 +1,216 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+
4
+ // packages/cli/src/commands/agent-doc.ts
5
+ import { existsSync, readFileSync, writeFileSync } from "fs";
6
+ import { resolve } from "path";
7
+
8
+ // packages/cli/src/templates/agent-contract.ts
9
+ var AGENT_CONTRACT = `# vobs \u6846\u67B6\u5951\u7EA6\uFF08\u5199\u4EE3\u7801\u524D\u5FC5\u8BFB\uFF09
10
+
11
+ vobs \u662F**\u7F16\u8BD1\u578B / \u7EC6\u7C92\u5EA6 / \u54CD\u5E94\u5F0F**\u6846\u67B6\uFF0C\u4E0E React \u7684\u5FC3\u667A\u6A21\u578B\u6709\u51E0\u5904**\u6839\u672C\u4E0D\u540C**\u3002
12
+ \u4E0B\u9762\u7684\u6BCF\u4E00\u6761\u90FD\u6765\u81EA\u771F\u5B9E\u4E8B\u6545\uFF0C**\u8FDD\u53CD\u65F6\u901A\u5E38\u7F16\u8BD1\u901A\u8FC7\u3001\u8FD0\u884C\u671F\u624D\u9519**\u3002
13
+
14
+ > \u672C\u6587\u6863\u7531 \`vobs agent-doc\` \u4ECE\u6846\u67B6\u7248\u672C\u751F\u6210 \u2014\u2014 \u4E0D\u8981\u624B\u6539\u751F\u6210\u7684\u5757\u3002
15
+ > \u6BCF\u6761\u540E\u9762\u62EC\u53F7\u91CC\u662F**\u8FDD\u53CD\u65F6\u6846\u67B6\u4F1A\u62A5\u7684\u8BCA\u65AD\u7801**\u3002
16
+
17
+ ## \u4E00\u3001\u7EC4\u4EF6\u4F53\u53EA\u6267\u884C\u4E00\u6B21\uFF08run-once\uFF09
18
+
19
+ **\u7EC4\u4EF6\u51FD\u6570\u4F53\u53EA\u5728\u521B\u5EFA\u65F6\u8DD1\u4E00\u6B21**\uFF0C\u4E4B\u540E\u53EA\u6709 JSX \u91CC\u8BA2\u9605\u7684\u4F4D\u7F6E\u66F4\u65B0\u3002
20
+
21
+ - \u274C \`const filtered = list.value.filter(...)\` \u2014\u2014 \u4E00\u6B21\u5FEB\u7167\uFF0C\u4E4B\u540E\u6C38\u4E0D\u66F4\u65B0
22
+ - \u2705 \u6D3E\u751F\u653E\u8FDB JSX\uFF1A\`<div>{list.value.filter(...).map(render)}</div>\`
23
+ - \u274C \u7EC4\u4EF6\u4F53\u91CC\u53D6\u5FEB\u7167\u7ED9\u56DE\u8C03\u7528\uFF1A\`const v = name.value; onClick={() => save(v)}\`
24
+ - \u2705 \u56DE\u8C03\u5185\u91CD\u8BFB\uFF1A\`onClick={() => save(name.value)}\`
25
+ - \u52A8\u6001 props \u7528 getter \u5F62\u6001\uFF0C\u4E0D\u8981\u4F20\u6C42\u503C\u540E\u7684\u503C
26
+
27
+ ## \u4E8C\u3001\u6761\u4EF6\u6E32\u67D3\uFF1A**\u4E0D\u8981\u5728\u7EC4\u4EF6\u4F53\u91CC \`return\` \u5206\u652F**\uFF08\`VOBS_C107\` / \`VOBS_C104\`\uFF09
28
+
29
+ \u7EC4\u4EF6\u4F53\u91CC\u8BFB\u4FE1\u53F7\u7684 \`return\` \u5728\u6302\u8F7D\u65F6\u56FA\u5316\uFF0C**\u4E09\u79CD\u5199\u6CD5\u574F\u5F97\u4E00\u6A21\u4E00\u6837**\uFF1A
30
+
31
+ \`\`\`tsx
32
+ return cond.value ? <A/> : <B/> // \u274C \u51BB\u7ED3
33
+ if (cond.value) return <A/> // \u274C \u51BB\u7ED3
34
+ return cond.value ? <A/> : null // \u274C \u51BB\u7ED3
35
+ \`\`\`
36
+
37
+ \u9876\u5C42 \`return\` \u5904**\u6CA1\u6709 parent/anchor**\uFF0C\u6240\u4EE5\u4E24\u652F\u90FD\u662F JSX \u4E5F\u4E00\u6837\u4E0D\u4F1A\u91CD\u5206\u652F\u3002
38
+
39
+ | \u573A\u666F | \u7528\u4EC0\u4E48 |
40
+ |---|---|
41
+ | **\u8DEF\u7531\u5206\u652F** | **\`<RouterView/>\`**\uFF08\u58F0\u660E\u5F0F\uFF0C\u5185\u90E8 \`insertDynamic\` + \`resetKey\`\uFF09 |
42
+ | \u4FDD\u7559\u6302\u8F7D\u3001\u53EA\u5207\u663E\u9690 | **\`<Show when={cond}>\`**\uFF08\u5207 \`hidden\` + \`inert\`\uFF0C\u7126\u70B9/\u6EDA\u52A8/\u5185\u90E8\u72B6\u6001\u4E0D\u4E22\uFF09 |
43
+ | \u53EA\u5207\u7C7B\u540D | \`class="base" classList={{ 'is-on': cond.value }}\` |
44
+ | \u666E\u901A\u6761\u4EF6\u6E32\u67D3 | \u653E\u8FDB **JSX \u5B50\u8282\u70B9\u4F4D\u7F6E**\uFF1A\`<div>{cond.value ? <A/> : <B/>}</div>\` |
45
+
46
+ ## \u4E09\u3001effect \u91CC\u4E0D\u8981\u5199\u81EA\u5DF1\u8BFB\u8FC7\u7684\u4FE1\u53F7\uFF08\`VOBS_C210\` / \`VOBS_C211\`\uFF09
47
+
48
+ **\u4F9D\u8D56\u662F\u81EA\u52A8\u6536\u96C6\u7684**\uFF1Aeffect \u8FD0\u884C\u671F\u95F4\u8BFB\u5230\u7684**\u4EFB\u4F55**\u4FE1\u53F7\u90FD\u4F1A\u53D8\u6210\u4F9D\u8D56\u3002
49
+
50
+ - \u274C \`effect(() => { count.value++ })\` \u2014\u2014 \u8BFB+\u5199\u540C\u4E00\u4E2A\u4FE1\u53F7 = \u81EA\u8BA2\u9605\u5FAA\u73AF
51
+ - \u274C \`effect(() => { if (session.value) void sync() })\` \u2014\u2014 **\u300C\u53EA\u8C03\u4E86\u4E2A\u51FD\u6570\u300D\u4E0D\u7B49\u4E8E\u6CA1\u4F9D\u8D56**\uFF1A
52
+ \u88AB\u8C03\u51FD\u6570\u5728**\u9996\u4E2A \`await\` \u4E4B\u524D**\u7684\u4EE3\u7801\u662F**\u540C\u6B65\u6267\u884C**\u7684\uFF0C\u5B83\u8BFB\u7684\u4FE1\u53F7\u7B97\u5728 effect \u5934\u4E0A
53
+ - \u2705 \u53EA\u60F3\u58F0\u660E\u4F9D\u8D56\uFF1A**\`effect(on(deps, () => { \u2026 }))\`** \u2014\u2014 \u56DE\u8C03\u5728 untrack \u4F5C\u7528\u57DF\u91CC\u8DD1\uFF0C
54
+ \u5B83\u8C03\u7528\u7684\u51FD\u6570\u78B0\u4EC0\u4E48\u4FE1\u53F7\u90FD\u4E0D\u4F1A\u53CD\u5411\u8BA2\u9605
55
+ - \u2705 \u6D3E\u751F\u503C\u7528 \`memo\`\uFF0C\u4E0D\u8981"\u8BFB A \u5199 B"
56
+ - \u515C\u5E95\u624D\u662F \`untrack(() => { X.value = next })\`
57
+
58
+ **\u522B\u5728 effect \u91CC\u8C03 async \u51FD\u6570**\uFF08\`VOBS_C106\`\uFF09\uFF1A\`effect\` \u4E0D\u7B49\u5B83\uFF0C
59
+ \u4E14\u9996\u4E2A \`await\` \u4E4B\u524D\u7684\u90E8\u5206\u662F\u540C\u6B65\u7684\u3002\u5F02\u6B65\u53D6\u6570\u7528 **\`@vobs/resource\`**\u3002
60
+
61
+ ## \u56DB\u3001\u5BA2\u6237\u7AEF\u526F\u4F5C\u7528\u4E0E SSR
62
+
63
+ - \u5B9A\u65F6\u5668 / \u76D1\u542C / \`matchMedia\` / \`localStorage\` \u2192 **\`onMount\` \u542F\u52A8\u3001\`onDestroy\` \u6E05\u7406**
64
+ \uFF08**\u4E0D\u8981\u624B\u5199 \`typeof window\` \u5B88\u536B**\uFF09
65
+ - **\u6E32\u67D3\u8F93\u51FA\u672C\u8EAB**\u4F9D\u8D56\u6D4F\u89C8\u5668\uFF08\u7A97\u53E3\u5C3A\u5BF8 / \`localStorage\` \u56DE\u586B / \`Date.now\` / \u968F\u673A\u503C\uFF09
66
+ \u2192 **\`<ClientOnly fallback={\u2026}>\`**\uFF08\u9996\u8F6E\u4E24\u4FA7\u90FD\u6E32\u67D3 fallback\uFF0C\u6C34\u5408\u5BF9\u5F97\u4E0A\uFF09
67
+ - \u6A21\u5757\u9876\u5C42**\u7981\u6B62** JSX\uFF08\`VOBS_C105\`\uFF09\uFF1Aimport \u6C42\u503C\u65E9\u4E8E\u6E32\u67D3\u5668\u5B89\u88C5
68
+
69
+ ## \u4E94\u3001\u5217\u8868\u4E0E\u6570\u636E
70
+
71
+ - **\u6570\u7EC4\u66F4\u65B0\u5FC5\u987B\u6362\u5F15\u7528**\uFF1A\`list.value = [...list.value, item]\`\uFF1B
72
+ \u539F\u5730 \`push\` / \u6539\u5B57\u6BB5**\u4E0D\u89E6\u53D1\u66F4\u65B0**
73
+ - JSX **\u5B50\u8282\u70B9\u4F4D\u7F6E**\u53EA\u653E\u56DB\u79CD\u5F62\u6001\uFF1A\u7EC4\u4EF6\u6807\u7B7E / \u5143\u7D20 / \u4E24\u5206\u652F\u4E09\u5143 / \`.map()\`
74
+ - \u6570\u636E\u7ED3\u6784\u91CC**\u53EA\u5B58\u7EAF\u63CF\u8FF0**\uFF08\u5B57\u7B26\u4E32/\u6837\u5F0F/\u7ED3\u6784\u5B57\u6BB5\uFF09\uFF0C**\u8282\u70B9\u5BF9\u8C61\u522B\u8FDB\u6570\u636E\u5E38\u91CF**
75
+ \uFF08SSG \u5E8F\u5217\u5316\u4F1A\u70B8\uFF0C\u4EA7\u7269\u51FA\u73B0 \`[object Xxx]\` \u65F6\u6846\u67B6\u4F1A\u76F4\u63A5\u62A5\u9519\uFF09
76
+
77
+ ## \u516D\u3001\u8F93\u5165
78
+
79
+ - **\u6570\u5B57\u8F93\u5165\u8D70 \`parseNumber\`**\uFF08\`<Field type="number">\` \u5DF2\u5185\u7F6E\uFF09\uFF1A
80
+ \u7A7A\u4E32 / \u975E\u6CD5 / \u8D85\u754C**\u4E0D\u63D0\u4EA4**\uFF0C\u4FDD\u6301\u539F\u503C
81
+ \u2014\u2014 \`Number('') === 0\` \u4F1A\u628A\u8F93\u5165\u6E05\u6210 0 \u5E76\u6CBF\u8054\u52A8\u94FE\u8DEF\u6E05\u96F6\u5144\u5F1F\u7EF4\u5EA6
82
+ - \`<select>\` \u7684 value \u76F4\u63A5\u7ED1\uFF0C**\u4E0D\u8981\u5199 ref \u515C\u5E95**\uFF081.5.1+ \u5DF2\u4FEE\u65F6\u5E8F\uFF09
83
+
84
+ ## \u4E03\u3001\u56FE\u6807\u4E0E SVG
85
+
86
+ - **SVG \u76F4\u63A5\u5199 JSX**\uFF08\`<svg><path/></svg>\`\uFF0C1.7.4+ \u6309 namespace \u521B\u5EFA\u4E0E\u6C34\u5408\uFF09
87
+ - \u56FE\u6807\u8981**\u767B\u8BB0\u8FDB\u767D\u540D\u5355**\uFF1B\u67E5\u8868 miss \u65F6 \`@vobs/icon-core\` \u4F1A\u8B66\u544A\u70B9\u540D\uFF08\u4F46\u4ECD\u5E94\u767B\u8BB0\uFF09
88
+
89
+ ## \u516B\u3001\u63D0\u4EA4\u524D\u81EA\u6D4B
90
+
91
+ \`\`\`bash
92
+ pnpm run check:source # = vobs check\uFF0C\u5168\u4ED3\u4E00\u6B21\u5217\u51FA\u5168\u90E8\u8BCA\u65AD
93
+ pnpm run check:runtime # \u771F\u5B9E\u6D4F\u89C8\u5668\u9010\u8DEF\u7531\u8DD1\u62A4\u680F\uFF08\u9700 Chrome\uFF09
94
+ pnpm run check:runtime:interact # \u518D\u70B9\u6240\u6709\u6309\u94AE\u3001\u89E6\u53D1\u6240\u6709\u8F93\u5165
95
+ \`\`\`
96
+
97
+ \`vite build\` **\u4F1A**\u6253\u5370\u7F16\u8BD1\u671F\u8B66\u544A\uFF08\`C104\`/\`C105\`/\`C106\`/\`C107\`\uFF09\uFF0C\u4F46\u53EA\u8986\u76D6\u5B83\u7F16\u8BD1\u5230\u7684\u6587\u4EF6\uFF1B
98
+ \`check:source\` \u624D\u662F\u5168\u4ED3\u5165\u53E3\u3002
99
+
100
+ ---
101
+
102
+ ## \u4E00\u53E5\u8BDD\u901F\u8BB0
103
+
104
+ | \u4E3B\u9898 | \u4E00\u53E5\u8BDD |
105
+ |---|---|
106
+ | \u7EC4\u4EF6\u4F53 | \u53EA\u8DD1\u4E00\u6B21\uFF0C\u6D3E\u751F\u8FDB JSX |
107
+ | \u56DE\u8C03 | \u89E6\u53D1\u65F6\u91CD\u8BFB \`.value\` |
108
+ | \u6761\u4EF6\u6E32\u67D3 | \u8DEF\u7531\u7528 \`RouterView\`\uFF0C\u663E\u9690\u7528 \`Show\`\uFF0C\u522B\u5728\u7EC4\u4EF6\u4F53 return \u5206\u652F |
109
+ | effect | \u522B\u5199\u81EA\u5DF1\u8BFB\u7684\u4FE1\u53F7\uFF0C\u7528 \`on(deps, fn)\` |
110
+ | async | \u522B\u585E\u8FDB effect\uFF0C\u7528 \`@vobs/resource\` |
111
+ | \u5BA2\u6237\u7AEF | \`onMount\`/\`onDestroy\`/\`ClientOnly\`\uFF0C\u522B\u624B\u5199 \`typeof window\` |
112
+ | \u5217\u8868 | \u6362\u5F15\u7528 |
113
+ | \u6570\u5B57 | \`parseNumber\` |
114
+ | SVG | \u76F4\u63A5\u5199 |
115
+ | \u81EA\u6D4B | \`check:source\` + \`check:runtime\` |
116
+ `;
117
+
118
+ // packages/cli/src/commands/agent-doc.ts
119
+ var BEGIN = "<!-- vobs:begin \u7531 `vobs agent-doc --write` \u751F\u6210\uFF0C\u8BF7\u52FF\u624B\u6539 -->";
120
+ var END = "<!-- vobs:end -->";
121
+ function readContract() {
122
+ return AGENT_CONTRACT.trim();
123
+ }
124
+ __name(readContract, "readContract");
125
+ function buildAgentsDoc(contract) {
126
+ return [
127
+ BEGIN,
128
+ "",
129
+ contract,
130
+ "",
131
+ END,
132
+ "",
133
+ "<!-- \u4EE5\u4E0B\u662F\u672C\u9879\u76EE\u7684\u81EA\u6709\u7EA6\u5B9A\uFF0C`vobs agent-doc --write` \u4E0D\u4F1A\u8986\u76D6\u3002 -->",
134
+ "",
135
+ "## \u672C\u9879\u76EE\u7EA6\u5B9A",
136
+ "",
137
+ "\uFF08\u5728\u8FD9\u91CC\u5199\u4F60\u81EA\u5DF1\u7684\u76EE\u5F55\u7ED3\u6784\u3001\u547D\u540D\u3001\u4E1A\u52A1\u6D41\u7A0B\u7B49\u89C4\u5219\u3002\uFF09",
138
+ ""
139
+ ].join("\n");
140
+ }
141
+ __name(buildAgentsDoc, "buildAgentsDoc");
142
+ function extractBlock(text) {
143
+ const start = text.indexOf(BEGIN);
144
+ const end = text.indexOf(END);
145
+ if (start < 0 || end < 0 || end < start) return null;
146
+ return text.slice(start, end + END.length);
147
+ }
148
+ __name(extractBlock, "extractBlock");
149
+ function mergeAgentsDoc(existing, generated) {
150
+ if (existing === null) return generated;
151
+ const block = extractBlock(generated);
152
+ if (block === null) return generated;
153
+ if (extractBlock(existing) === null) return `${existing.trimEnd()}
154
+
155
+ ${block}
156
+ `;
157
+ return existing.replace(extractBlock(existing), block);
158
+ }
159
+ __name(mergeAgentsDoc, "mergeAgentsDoc");
160
+ async function agentDocCommand(options = {}) {
161
+ const root = resolve(options.dir ?? process.cwd());
162
+ const contract = readContract();
163
+ if (options.body === true) {
164
+ process.stdout.write(`${contract}
165
+ `);
166
+ return;
167
+ }
168
+ const generated = buildAgentsDoc(contract);
169
+ const agentsPath = resolve(root, "AGENTS.md");
170
+ const claudePath = resolve(root, "CLAUDE.md");
171
+ const claudeDoc = "@AGENTS.md\n";
172
+ if (options.check === true) {
173
+ const problems = [];
174
+ if (!existsSync(agentsPath)) {
175
+ problems.push("AGENTS.md \u4E0D\u5B58\u5728 \u2014\u2014 \u8DD1 `vobs agent-doc --write` \u751F\u6210");
176
+ } else {
177
+ const existing = readFileSync(agentsPath, "utf8");
178
+ const block = extractBlock(existing);
179
+ if (block === null) {
180
+ problems.push("AGENTS.md \u91CC\u627E\u4E0D\u5230 vobs \u6807\u8BB0\u5757 \u2014\u2014 \u8DD1 `vobs agent-doc --write` \u8865\u4E0A");
181
+ } else if (block !== extractBlock(generated)) {
182
+ problems.push("AGENTS.md \u7684 vobs \u5951\u7EA6\u5757\u4E0E\u5F53\u524D\u6846\u67B6\u7248\u672C\u4E0D\u4E00\u81F4 \u2014\u2014 \u8DD1 `vobs agent-doc --write` \u540C\u6B65");
183
+ }
184
+ }
185
+ if (!existsSync(claudePath)) {
186
+ problems.push("CLAUDE.md \u4E0D\u5B58\u5728 \u2014\u2014 \u8DD1 `vobs agent-doc --write` \u751F\u6210\uFF08\u5185\u5BB9\u53EA\u9700\u4E00\u884C `@AGENTS.md`\uFF09");
187
+ } else if (!readFileSync(claudePath, "utf8").includes("@AGENTS.md")) {
188
+ problems.push("CLAUDE.md \u6CA1\u6709\u6307\u5411 @AGENTS.md");
189
+ }
190
+ if (problems.length > 0) {
191
+ console.error("[agent-doc] \u5951\u7EA6\u4E0E\u6846\u67B6\u7248\u672C\u4E0D\u540C\u6B65\uFF1A");
192
+ for (const problem of problems) console.error(` - ${problem}`);
193
+ process.exitCode = 1;
194
+ return;
195
+ }
196
+ console.log("[agent-doc] \u5951\u7EA6\u4E0E\u5F53\u524D\u6846\u67B6\u7248\u672C\u4E00\u81F4 \u2713");
197
+ return;
198
+ }
199
+ if (options.write !== true) {
200
+ process.stdout.write(generated);
201
+ return;
202
+ }
203
+ const existingAgents = existsSync(agentsPath) ? readFileSync(agentsPath, "utf8") : null;
204
+ writeFileSync(agentsPath, mergeAgentsDoc(existingAgents, generated), "utf8");
205
+ writeFileSync(claudePath, claudeDoc, "utf8");
206
+ console.log(`[agent-doc] \u5DF2\u5199\u5165 ${agentsPath}`);
207
+ console.log(`[agent-doc] \u5DF2\u5199\u5165 ${claudePath}\uFF08\u5185\u5BB9\u4E3A @AGENTS.md\uFF09`);
208
+ if (existingAgents !== null && extractBlock(existingAgents) === null) {
209
+ console.log("[agent-doc] \u4F60\u539F\u6709\u7684 AGENTS.md \u5185\u5BB9\u5DF2\u4FDD\u7559\uFF0Cvobs \u5951\u7EA6\u5757\u8FFD\u52A0\u5728\u540E\u3002");
210
+ }
211
+ }
212
+ __name(agentDocCommand, "agentDocCommand");
213
+ export {
214
+ agentDocCommand
215
+ };
216
+ //# sourceMappingURL=agent-doc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/commands/agent-doc.ts","../../src/templates/agent-contract.ts"],"sourcesContent":["import { existsSync, readFileSync, writeFileSync } from 'node:fs'\nimport { resolve } from 'node:path'\nimport { AGENT_CONTRACT } from '../templates/agent-contract.js'\n\n/**\n * `vobs agent-doc` —— 把框架契约**生成**到当前项目,供任何 LLM / agent 读取。\n *\n * ## 为什么是\"生成\"而不是\"手写\"\n *\n * 契约随框架演进(1.8.0 加 `Show`/`ClientOnly`、1.8.3 加 `on()` 与 `VOBS_C106`、\n * 1.8.4 加 `VOBS_C107`、1.8.5 改 fix 文案)。手写的话,每个项目一份副本,\n * **必然各自漂移**;而且写的人未必知道最新契约。\n *\n * 生成则天然与版本绑定:`@vobs/cli@1.8.x` 产出 1.8.x 的契约。\n *\n * ## 设计要点\n *\n * 1. **标记块**:只替换 `<!-- vobs:begin -->` 与 `<!-- vobs:end -->` 之间的内容,\n * 块外的**项目自有约定原样保留** —— 重新生成不会覆盖你自己的东西。\n * 2. **`--check`**:校验项目里那份是否与当前框架版本一致,不一致 exit 1。\n * 可以进 CI —— 这样\"文档漂移\"从\"靠人记得\"变成\"机器拦\"。\n * 3. **`CLAUDE.md` 只写一行 `@AGENTS.md`**:内容只有一份,避免两份副本漂移。\n * 4. 由**框架**产出,不由用户手抄;工具无关(内容里不提任何特定 LLM)。\n */\n\nconst BEGIN = '<!-- vobs:begin 由 `vobs agent-doc --write` 生成,请勿手改 -->'\nconst END = '<!-- vobs:end -->'\n\nexport interface AgentDocOptions {\n /** 目标目录,默认当前工作目录。 */\n readonly dir?: string\n /** 写入文件(否则打到 stdout)。 */\n readonly write?: boolean\n /** 校验项目里那份是否与当前框架版本一致;不一致 exit 1。 */\n readonly check?: boolean\n /** 只输出框架契约正文(不含标记块外的说明)。 */\n readonly body?: boolean\n}\n\n/** 契约正文内联在 `templates/agent-contract.ts`(见那里的注释:读文件在全量测试下会失败)。 */\nfunction readContract(): string {\n return AGENT_CONTRACT.trim()\n}\n\n/** 把契约包进标记块 —— 块外是项目自己的内容,重新生成不会动它。 */\nfunction buildAgentsDoc(contract: string): string {\n return [\n BEGIN,\n '',\n contract,\n '',\n END,\n '',\n '<!-- 以下是本项目的自有约定,`vobs agent-doc --write` 不会覆盖。 -->',\n '',\n '## 本项目约定',\n '',\n '(在这里写你自己的目录结构、命名、业务流程等规则。)',\n ''\n ].join('\\n')\n}\n\n/** 取出文件里的标记块内容(没有标记块时返回 null)。 */\nfunction extractBlock(text: string): string | null {\n const start = text.indexOf(BEGIN)\n const end = text.indexOf(END)\n if (start < 0 || end < 0 || end < start) return null\n return text.slice(start, end + END.length)\n}\n\n/**\n * 合并:保留块外内容,只替换块。\n *\n * 没有标记块的既有 `AGENTS.md`(用户手写的)**不直接覆盖** ——\n * 把生成块**追加在后**,用户原有内容全部保留。丢掉别人的规则比不生成更糟。\n */\nfunction mergeAgentsDoc(existing: string | null, generated: string): string {\n if (existing === null) return generated\n const block = extractBlock(generated)\n if (block === null) return generated\n if (extractBlock(existing) === null) return `${existing.trimEnd()}\\n\\n${block}\\n`\n return existing.replace(extractBlock(existing)!, block)\n}\n\nexport async function agentDocCommand(options: AgentDocOptions = {}): Promise<void> {\n const root = resolve(options.dir ?? process.cwd())\n const contract = readContract()\n\n if (options.body === true) {\n process.stdout.write(`${contract}\\n`)\n return\n }\n\n const generated = buildAgentsDoc(contract)\n const agentsPath = resolve(root, 'AGENTS.md')\n const claudePath = resolve(root, 'CLAUDE.md')\n // 内容只有一份:CLAUDE.md 只做入口,避免两份副本漂移\n const claudeDoc = '@AGENTS.md\\n'\n\n if (options.check === true) {\n const problems: string[] = []\n if (!existsSync(agentsPath)) {\n problems.push('AGENTS.md 不存在 —— 跑 `vobs agent-doc --write` 生成')\n } else {\n const existing = readFileSync(agentsPath, 'utf8')\n const block = extractBlock(existing)\n if (block === null) {\n problems.push('AGENTS.md 里找不到 vobs 标记块 —— 跑 `vobs agent-doc --write` 补上')\n } else if (block !== extractBlock(generated)) {\n problems.push('AGENTS.md 的 vobs 契约块与当前框架版本不一致 —— 跑 `vobs agent-doc --write` 同步')\n }\n }\n if (!existsSync(claudePath)) {\n problems.push('CLAUDE.md 不存在 —— 跑 `vobs agent-doc --write` 生成(内容只需一行 `@AGENTS.md`)')\n } else if (!readFileSync(claudePath, 'utf8').includes('@AGENTS.md')) {\n problems.push('CLAUDE.md 没有指向 @AGENTS.md')\n }\n\n if (problems.length > 0) {\n console.error('[agent-doc] 契约与框架版本不同步:')\n for (const problem of problems) console.error(` - ${problem}`)\n process.exitCode = 1\n return\n }\n console.log('[agent-doc] 契约与当前框架版本一致 ✓')\n return\n }\n\n if (options.write !== true) {\n process.stdout.write(generated)\n return\n }\n\n const existingAgents = existsSync(agentsPath) ? readFileSync(agentsPath, 'utf8') : null\n writeFileSync(agentsPath, mergeAgentsDoc(existingAgents, generated), 'utf8')\n writeFileSync(claudePath, claudeDoc, 'utf8')\n console.log(`[agent-doc] 已写入 ${agentsPath}`)\n console.log(`[agent-doc] 已写入 ${claudePath}(内容为 @AGENTS.md)`)\n if (existingAgents !== null && extractBlock(existingAgents) === null) {\n console.log('[agent-doc] 你原有的 AGENTS.md 内容已保留,vobs 契约块追加在后。')\n }\n}\n","/**\n * vobs 框架契约正文 —— `vobs agent-doc` 的输出来源。\n *\n * 为什么是 TS 模块而不是 .md 文件:读文件要靠 `import.meta.url` 解析相对路径,\n * 而**全量测试时 vitest 会转换模块**,`import.meta.url` 指向虚拟路径,\n * `new URL('../../src/templates/…')` 就找不到文件(实测:单独跑 8 条全过、\n * 全量跑 8 条全红)。内联成字符串**免疫打包与转换**,也省掉发布时拷模板。\n *\n * 内容由 `packages/cli/src/templates/agent-contract.md` 生成(保留 .md 作可读源);\n * 改契约请改 .md 后重新生成,或直接改这里的字符串。\n */\nexport const AGENT_CONTRACT = `# vobs 框架契约(写代码前必读)\n\nvobs 是**编译型 / 细粒度 / 响应式**框架,与 React 的心智模型有几处**根本不同**。\n下面的每一条都来自真实事故,**违反时通常编译通过、运行期才错**。\n\n> 本文档由 \\`vobs agent-doc\\` 从框架版本生成 —— 不要手改生成的块。\n> 每条后面括号里是**违反时框架会报的诊断码**。\n\n## 一、组件体只执行一次(run-once)\n\n**组件函数体只在创建时跑一次**,之后只有 JSX 里订阅的位置更新。\n\n- ❌ \\`const filtered = list.value.filter(...)\\` —— 一次快照,之后永不更新\n- ✅ 派生放进 JSX:\\`<div>{list.value.filter(...).map(render)}</div>\\`\n- ❌ 组件体里取快照给回调用:\\`const v = name.value; onClick={() => save(v)}\\`\n- ✅ 回调内重读:\\`onClick={() => save(name.value)}\\`\n- 动态 props 用 getter 形态,不要传求值后的值\n\n## 二、条件渲染:**不要在组件体里 \\`return\\` 分支**(\\`VOBS_C107\\` / \\`VOBS_C104\\`)\n\n组件体里读信号的 \\`return\\` 在挂载时固化,**三种写法坏得一模一样**:\n\n\\`\\`\\`tsx\nreturn cond.value ? <A/> : <B/> // ❌ 冻结\nif (cond.value) return <A/> // ❌ 冻结\nreturn cond.value ? <A/> : null // ❌ 冻结\n\\`\\`\\`\n\n顶层 \\`return\\` 处**没有 parent/anchor**,所以两支都是 JSX 也一样不会重分支。\n\n| 场景 | 用什么 |\n|---|---|\n| **路由分支** | **\\`<RouterView/>\\`**(声明式,内部 \\`insertDynamic\\` + \\`resetKey\\`) |\n| 保留挂载、只切显隐 | **\\`<Show when={cond}>\\`**(切 \\`hidden\\` + \\`inert\\`,焦点/滚动/内部状态不丢) |\n| 只切类名 | \\`class=\"base\" classList={{ 'is-on': cond.value }}\\` |\n| 普通条件渲染 | 放进 **JSX 子节点位置**:\\`<div>{cond.value ? <A/> : <B/>}</div>\\` |\n\n## 三、effect 里不要写自己读过的信号(\\`VOBS_C210\\` / \\`VOBS_C211\\`)\n\n**依赖是自动收集的**:effect 运行期间读到的**任何**信号都会变成依赖。\n\n- ❌ \\`effect(() => { count.value++ })\\` —— 读+写同一个信号 = 自订阅循环\n- ❌ \\`effect(() => { if (session.value) void sync() })\\` —— **「只调了个函数」不等于没依赖**:\n 被调函数在**首个 \\`await\\` 之前**的代码是**同步执行**的,它读的信号算在 effect 头上\n- ✅ 只想声明依赖:**\\`effect(on(deps, () => { … }))\\`** —— 回调在 untrack 作用域里跑,\n 它调用的函数碰什么信号都不会反向订阅\n- ✅ 派生值用 \\`memo\\`,不要\"读 A 写 B\"\n- 兜底才是 \\`untrack(() => { X.value = next })\\`\n\n**别在 effect 里调 async 函数**(\\`VOBS_C106\\`):\\`effect\\` 不等它,\n且首个 \\`await\\` 之前的部分是同步的。异步取数用 **\\`@vobs/resource\\`**。\n\n## 四、客户端副作用与 SSR\n\n- 定时器 / 监听 / \\`matchMedia\\` / \\`localStorage\\` → **\\`onMount\\` 启动、\\`onDestroy\\` 清理**\n (**不要手写 \\`typeof window\\` 守卫**)\n- **渲染输出本身**依赖浏览器(窗口尺寸 / \\`localStorage\\` 回填 / \\`Date.now\\` / 随机值)\n → **\\`<ClientOnly fallback={…}>\\`**(首轮两侧都渲染 fallback,水合对得上)\n- 模块顶层**禁止** JSX(\\`VOBS_C105\\`):import 求值早于渲染器安装\n\n## 五、列表与数据\n\n- **数组更新必须换引用**:\\`list.value = [...list.value, item]\\`;\n 原地 \\`push\\` / 改字段**不触发更新**\n- JSX **子节点位置**只放四种形态:组件标签 / 元素 / 两分支三元 / \\`.map()\\`\n- 数据结构里**只存纯描述**(字符串/样式/结构字段),**节点对象别进数据常量**\n (SSG 序列化会炸,产物出现 \\`[object Xxx]\\` 时框架会直接报错)\n\n## 六、输入\n\n- **数字输入走 \\`parseNumber\\`**(\\`<Field type=\"number\">\\` 已内置):\n 空串 / 非法 / 超界**不提交**,保持原值\n —— \\`Number('') === 0\\` 会把输入清成 0 并沿联动链路清零兄弟维度\n- \\`<select>\\` 的 value 直接绑,**不要写 ref 兜底**(1.5.1+ 已修时序)\n\n## 七、图标与 SVG\n\n- **SVG 直接写 JSX**(\\`<svg><path/></svg>\\`,1.7.4+ 按 namespace 创建与水合)\n- 图标要**登记进白名单**;查表 miss 时 \\`@vobs/icon-core\\` 会警告点名(但仍应登记)\n\n## 八、提交前自测\n\n\\`\\`\\`bash\npnpm run check:source # = vobs check,全仓一次列出全部诊断\npnpm run check:runtime # 真实浏览器逐路由跑护栏(需 Chrome)\npnpm run check:runtime:interact # 再点所有按钮、触发所有输入\n\\`\\`\\`\n\n\\`vite build\\` **会**打印编译期警告(\\`C104\\`/\\`C105\\`/\\`C106\\`/\\`C107\\`),但只覆盖它编译到的文件;\n\\`check:source\\` 才是全仓入口。\n\n---\n\n## 一句话速记\n\n| 主题 | 一句话 |\n|---|---|\n| 组件体 | 只跑一次,派生进 JSX |\n| 回调 | 触发时重读 \\`.value\\` |\n| 条件渲染 | 路由用 \\`RouterView\\`,显隐用 \\`Show\\`,别在组件体 return 分支 |\n| effect | 别写自己读的信号,用 \\`on(deps, fn)\\` |\n| async | 别塞进 effect,用 \\`@vobs/resource\\` |\n| 客户端 | \\`onMount\\`/\\`onDestroy\\`/\\`ClientOnly\\`,别手写 \\`typeof window\\` |\n| 列表 | 换引用 |\n| 数字 | \\`parseNumber\\` |\n| SVG | 直接写 |\n| 自测 | \\`check:source\\` + \\`check:runtime\\` |\n`\n"],"mappings":"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","names":[]}