@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,139 @@
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 __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // packages/cli/src/templates/agent-contract.ts
22
+ var agent_contract_exports = {};
23
+ __export(agent_contract_exports, {
24
+ AGENT_CONTRACT: () => AGENT_CONTRACT
25
+ });
26
+ module.exports = __toCommonJS(agent_contract_exports);
27
+ var AGENT_CONTRACT = `# vobs \u6846\u67B6\u5951\u7EA6\uFF08\u5199\u4EE3\u7801\u524D\u5FC5\u8BFB\uFF09
28
+
29
+ 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
30
+ \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
31
+
32
+ > \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
33
+ > \u6BCF\u6761\u540E\u9762\u62EC\u53F7\u91CC\u662F**\u8FDD\u53CD\u65F6\u6846\u67B6\u4F1A\u62A5\u7684\u8BCA\u65AD\u7801**\u3002
34
+
35
+ ## \u4E00\u3001\u7EC4\u4EF6\u4F53\u53EA\u6267\u884C\u4E00\u6B21\uFF08run-once\uFF09
36
+
37
+ **\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
38
+
39
+ - \u274C \`const filtered = list.value.filter(...)\` \u2014\u2014 \u4E00\u6B21\u5FEB\u7167\uFF0C\u4E4B\u540E\u6C38\u4E0D\u66F4\u65B0
40
+ - \u2705 \u6D3E\u751F\u653E\u8FDB JSX\uFF1A\`<div>{list.value.filter(...).map(render)}</div>\`
41
+ - \u274C \u7EC4\u4EF6\u4F53\u91CC\u53D6\u5FEB\u7167\u7ED9\u56DE\u8C03\u7528\uFF1A\`const v = name.value; onClick={() => save(v)}\`
42
+ - \u2705 \u56DE\u8C03\u5185\u91CD\u8BFB\uFF1A\`onClick={() => save(name.value)}\`
43
+ - \u52A8\u6001 props \u7528 getter \u5F62\u6001\uFF0C\u4E0D\u8981\u4F20\u6C42\u503C\u540E\u7684\u503C
44
+
45
+ ## \u4E8C\u3001\u6761\u4EF6\u6E32\u67D3\uFF1A**\u4E0D\u8981\u5728\u7EC4\u4EF6\u4F53\u91CC \`return\` \u5206\u652F**\uFF08\`VOBS_C107\` / \`VOBS_C104\`\uFF09
46
+
47
+ \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
48
+
49
+ \`\`\`tsx
50
+ return cond.value ? <A/> : <B/> // \u274C \u51BB\u7ED3
51
+ if (cond.value) return <A/> // \u274C \u51BB\u7ED3
52
+ return cond.value ? <A/> : null // \u274C \u51BB\u7ED3
53
+ \`\`\`
54
+
55
+ \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
56
+
57
+ | \u573A\u666F | \u7528\u4EC0\u4E48 |
58
+ |---|---|
59
+ | **\u8DEF\u7531\u5206\u652F** | **\`<RouterView/>\`**\uFF08\u58F0\u660E\u5F0F\uFF0C\u5185\u90E8 \`insertDynamic\` + \`resetKey\`\uFF09 |
60
+ | \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 |
61
+ | \u53EA\u5207\u7C7B\u540D | \`class="base" classList={{ 'is-on': cond.value }}\` |
62
+ | \u666E\u901A\u6761\u4EF6\u6E32\u67D3 | \u653E\u8FDB **JSX \u5B50\u8282\u70B9\u4F4D\u7F6E**\uFF1A\`<div>{cond.value ? <A/> : <B/>}</div>\` |
63
+
64
+ ## \u4E09\u3001effect \u91CC\u4E0D\u8981\u5199\u81EA\u5DF1\u8BFB\u8FC7\u7684\u4FE1\u53F7\uFF08\`VOBS_C210\` / \`VOBS_C211\`\uFF09
65
+
66
+ **\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
67
+
68
+ - \u274C \`effect(() => { count.value++ })\` \u2014\u2014 \u8BFB+\u5199\u540C\u4E00\u4E2A\u4FE1\u53F7 = \u81EA\u8BA2\u9605\u5FAA\u73AF
69
+ - \u274C \`effect(() => { if (session.value) void sync() })\` \u2014\u2014 **\u300C\u53EA\u8C03\u4E86\u4E2A\u51FD\u6570\u300D\u4E0D\u7B49\u4E8E\u6CA1\u4F9D\u8D56**\uFF1A
70
+ \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
71
+ - \u2705 \u53EA\u60F3\u58F0\u660E\u4F9D\u8D56\uFF1A**\`effect(on(deps, () => { \u2026 }))\`** \u2014\u2014 \u56DE\u8C03\u5728 untrack \u4F5C\u7528\u57DF\u91CC\u8DD1\uFF0C
72
+ \u5B83\u8C03\u7528\u7684\u51FD\u6570\u78B0\u4EC0\u4E48\u4FE1\u53F7\u90FD\u4E0D\u4F1A\u53CD\u5411\u8BA2\u9605
73
+ - \u2705 \u6D3E\u751F\u503C\u7528 \`memo\`\uFF0C\u4E0D\u8981"\u8BFB A \u5199 B"
74
+ - \u515C\u5E95\u624D\u662F \`untrack(() => { X.value = next })\`
75
+
76
+ **\u522B\u5728 effect \u91CC\u8C03 async \u51FD\u6570**\uFF08\`VOBS_C106\`\uFF09\uFF1A\`effect\` \u4E0D\u7B49\u5B83\uFF0C
77
+ \u4E14\u9996\u4E2A \`await\` \u4E4B\u524D\u7684\u90E8\u5206\u662F\u540C\u6B65\u7684\u3002\u5F02\u6B65\u53D6\u6570\u7528 **\`@vobs/resource\`**\u3002
78
+
79
+ ## \u56DB\u3001\u5BA2\u6237\u7AEF\u526F\u4F5C\u7528\u4E0E SSR
80
+
81
+ - \u5B9A\u65F6\u5668 / \u76D1\u542C / \`matchMedia\` / \`localStorage\` \u2192 **\`onMount\` \u542F\u52A8\u3001\`onDestroy\` \u6E05\u7406**
82
+ \uFF08**\u4E0D\u8981\u624B\u5199 \`typeof window\` \u5B88\u536B**\uFF09
83
+ - **\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
84
+ \u2192 **\`<ClientOnly fallback={\u2026}>\`**\uFF08\u9996\u8F6E\u4E24\u4FA7\u90FD\u6E32\u67D3 fallback\uFF0C\u6C34\u5408\u5BF9\u5F97\u4E0A\uFF09
85
+ - \u6A21\u5757\u9876\u5C42**\u7981\u6B62** JSX\uFF08\`VOBS_C105\`\uFF09\uFF1Aimport \u6C42\u503C\u65E9\u4E8E\u6E32\u67D3\u5668\u5B89\u88C5
86
+
87
+ ## \u4E94\u3001\u5217\u8868\u4E0E\u6570\u636E
88
+
89
+ - **\u6570\u7EC4\u66F4\u65B0\u5FC5\u987B\u6362\u5F15\u7528**\uFF1A\`list.value = [...list.value, item]\`\uFF1B
90
+ \u539F\u5730 \`push\` / \u6539\u5B57\u6BB5**\u4E0D\u89E6\u53D1\u66F4\u65B0**
91
+ - 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()\`
92
+ - \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**
93
+ \uFF08SSG \u5E8F\u5217\u5316\u4F1A\u70B8\uFF0C\u4EA7\u7269\u51FA\u73B0 \`[object Xxx]\` \u65F6\u6846\u67B6\u4F1A\u76F4\u63A5\u62A5\u9519\uFF09
94
+
95
+ ## \u516D\u3001\u8F93\u5165
96
+
97
+ - **\u6570\u5B57\u8F93\u5165\u8D70 \`parseNumber\`**\uFF08\`<Field type="number">\` \u5DF2\u5185\u7F6E\uFF09\uFF1A
98
+ \u7A7A\u4E32 / \u975E\u6CD5 / \u8D85\u754C**\u4E0D\u63D0\u4EA4**\uFF0C\u4FDD\u6301\u539F\u503C
99
+ \u2014\u2014 \`Number('') === 0\` \u4F1A\u628A\u8F93\u5165\u6E05\u6210 0 \u5E76\u6CBF\u8054\u52A8\u94FE\u8DEF\u6E05\u96F6\u5144\u5F1F\u7EF4\u5EA6
100
+ - \`<select>\` \u7684 value \u76F4\u63A5\u7ED1\uFF0C**\u4E0D\u8981\u5199 ref \u515C\u5E95**\uFF081.5.1+ \u5DF2\u4FEE\u65F6\u5E8F\uFF09
101
+
102
+ ## \u4E03\u3001\u56FE\u6807\u4E0E SVG
103
+
104
+ - **SVG \u76F4\u63A5\u5199 JSX**\uFF08\`<svg><path/></svg>\`\uFF0C1.7.4+ \u6309 namespace \u521B\u5EFA\u4E0E\u6C34\u5408\uFF09
105
+ - \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
106
+
107
+ ## \u516B\u3001\u63D0\u4EA4\u524D\u81EA\u6D4B
108
+
109
+ \`\`\`bash
110
+ pnpm run check:source # = vobs check\uFF0C\u5168\u4ED3\u4E00\u6B21\u5217\u51FA\u5168\u90E8\u8BCA\u65AD
111
+ pnpm run check:runtime # \u771F\u5B9E\u6D4F\u89C8\u5668\u9010\u8DEF\u7531\u8DD1\u62A4\u680F\uFF08\u9700 Chrome\uFF09
112
+ pnpm run check:runtime:interact # \u518D\u70B9\u6240\u6709\u6309\u94AE\u3001\u89E6\u53D1\u6240\u6709\u8F93\u5165
113
+ \`\`\`
114
+
115
+ \`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
116
+ \`check:source\` \u624D\u662F\u5168\u4ED3\u5165\u53E3\u3002
117
+
118
+ ---
119
+
120
+ ## \u4E00\u53E5\u8BDD\u901F\u8BB0
121
+
122
+ | \u4E3B\u9898 | \u4E00\u53E5\u8BDD |
123
+ |---|---|
124
+ | \u7EC4\u4EF6\u4F53 | \u53EA\u8DD1\u4E00\u6B21\uFF0C\u6D3E\u751F\u8FDB JSX |
125
+ | \u56DE\u8C03 | \u89E6\u53D1\u65F6\u91CD\u8BFB \`.value\` |
126
+ | \u6761\u4EF6\u6E32\u67D3 | \u8DEF\u7531\u7528 \`RouterView\`\uFF0C\u663E\u9690\u7528 \`Show\`\uFF0C\u522B\u5728\u7EC4\u4EF6\u4F53 return \u5206\u652F |
127
+ | effect | \u522B\u5199\u81EA\u5DF1\u8BFB\u7684\u4FE1\u53F7\uFF0C\u7528 \`on(deps, fn)\` |
128
+ | async | \u522B\u585E\u8FDB effect\uFF0C\u7528 \`@vobs/resource\` |
129
+ | \u5BA2\u6237\u7AEF | \`onMount\`/\`onDestroy\`/\`ClientOnly\`\uFF0C\u522B\u624B\u5199 \`typeof window\` |
130
+ | \u5217\u8868 | \u6362\u5F15\u7528 |
131
+ | \u6570\u5B57 | \`parseNumber\` |
132
+ | SVG | \u76F4\u63A5\u5199 |
133
+ | \u81EA\u6D4B | \`check:source\` + \`check:runtime\` |
134
+ `;
135
+ // Annotate the CommonJS export names for ESM import in node:
136
+ 0 && (module.exports = {
137
+ AGENT_CONTRACT
138
+ });
139
+ //# sourceMappingURL=agent-contract.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/templates/agent-contract.ts"],"sourcesContent":["/**\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":";;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAWO,IAAM,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;","names":[]}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * vobs 框架契约正文 —— `vobs agent-doc` 的输出来源。
3
+ *
4
+ * 为什么是 TS 模块而不是 .md 文件:读文件要靠 `import.meta.url` 解析相对路径,
5
+ * 而**全量测试时 vitest 会转换模块**,`import.meta.url` 指向虚拟路径,
6
+ * `new URL('../../src/templates/…')` 就找不到文件(实测:单独跑 8 条全过、
7
+ * 全量跑 8 条全红)。内联成字符串**免疫打包与转换**,也省掉发布时拷模板。
8
+ *
9
+ * 内容由 `packages/cli/src/templates/agent-contract.md` 生成(保留 .md 作可读源);
10
+ * 改契约请改 .md 后重新生成,或直接改这里的字符串。
11
+ */
12
+ declare const AGENT_CONTRACT = "# vobs \u6846\u67B6\u5951\u7EA6\uFF08\u5199\u4EE3\u7801\u524D\u5FC5\u8BFB\uFF09\n\nvobs \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\n\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\n\n> \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\n> \u6BCF\u6761\u540E\u9762\u62EC\u53F7\u91CC\u662F**\u8FDD\u53CD\u65F6\u6846\u67B6\u4F1A\u62A5\u7684\u8BCA\u65AD\u7801**\u3002\n\n## \u4E00\u3001\u7EC4\u4EF6\u4F53\u53EA\u6267\u884C\u4E00\u6B21\uFF08run-once\uFF09\n\n**\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\n\n- \u274C `const filtered = list.value.filter(...)` \u2014\u2014 \u4E00\u6B21\u5FEB\u7167\uFF0C\u4E4B\u540E\u6C38\u4E0D\u66F4\u65B0\n- \u2705 \u6D3E\u751F\u653E\u8FDB JSX\uFF1A`<div>{list.value.filter(...).map(render)}</div>`\n- \u274C \u7EC4\u4EF6\u4F53\u91CC\u53D6\u5FEB\u7167\u7ED9\u56DE\u8C03\u7528\uFF1A`const v = name.value; onClick={() => save(v)}`\n- \u2705 \u56DE\u8C03\u5185\u91CD\u8BFB\uFF1A`onClick={() => save(name.value)}`\n- \u52A8\u6001 props \u7528 getter \u5F62\u6001\uFF0C\u4E0D\u8981\u4F20\u6C42\u503C\u540E\u7684\u503C\n\n## \u4E8C\u3001\u6761\u4EF6\u6E32\u67D3\uFF1A**\u4E0D\u8981\u5728\u7EC4\u4EF6\u4F53\u91CC `return` \u5206\u652F**\uFF08`VOBS_C107` / `VOBS_C104`\uFF09\n\n\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\n\n```tsx\nreturn cond.value ? <A/> : <B/> // \u274C \u51BB\u7ED3\nif (cond.value) return <A/> // \u274C \u51BB\u7ED3\nreturn cond.value ? <A/> : null // \u274C \u51BB\u7ED3\n```\n\n\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\n\n| \u573A\u666F | \u7528\u4EC0\u4E48 |\n|---|---|\n| **\u8DEF\u7531\u5206\u652F** | **`<RouterView/>`**\uFF08\u58F0\u660E\u5F0F\uFF0C\u5185\u90E8 `insertDynamic` + `resetKey`\uFF09 |\n| \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 |\n| \u53EA\u5207\u7C7B\u540D | `class=\"base\" classList={{ 'is-on': cond.value }}` |\n| \u666E\u901A\u6761\u4EF6\u6E32\u67D3 | \u653E\u8FDB **JSX \u5B50\u8282\u70B9\u4F4D\u7F6E**\uFF1A`<div>{cond.value ? <A/> : <B/>}</div>` |\n\n## \u4E09\u3001effect \u91CC\u4E0D\u8981\u5199\u81EA\u5DF1\u8BFB\u8FC7\u7684\u4FE1\u53F7\uFF08`VOBS_C210` / `VOBS_C211`\uFF09\n\n**\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\n\n- \u274C `effect(() => { count.value++ })` \u2014\u2014 \u8BFB+\u5199\u540C\u4E00\u4E2A\u4FE1\u53F7 = \u81EA\u8BA2\u9605\u5FAA\u73AF\n- \u274C `effect(() => { if (session.value) void sync() })` \u2014\u2014 **\u300C\u53EA\u8C03\u4E86\u4E2A\u51FD\u6570\u300D\u4E0D\u7B49\u4E8E\u6CA1\u4F9D\u8D56**\uFF1A\n \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\n- \u2705 \u53EA\u60F3\u58F0\u660E\u4F9D\u8D56\uFF1A**`effect(on(deps, () => { \u2026 }))`** \u2014\u2014 \u56DE\u8C03\u5728 untrack \u4F5C\u7528\u57DF\u91CC\u8DD1\uFF0C\n \u5B83\u8C03\u7528\u7684\u51FD\u6570\u78B0\u4EC0\u4E48\u4FE1\u53F7\u90FD\u4E0D\u4F1A\u53CD\u5411\u8BA2\u9605\n- \u2705 \u6D3E\u751F\u503C\u7528 `memo`\uFF0C\u4E0D\u8981\"\u8BFB A \u5199 B\"\n- \u515C\u5E95\u624D\u662F `untrack(() => { X.value = next })`\n\n**\u522B\u5728 effect \u91CC\u8C03 async \u51FD\u6570**\uFF08`VOBS_C106`\uFF09\uFF1A`effect` \u4E0D\u7B49\u5B83\uFF0C\n\u4E14\u9996\u4E2A `await` \u4E4B\u524D\u7684\u90E8\u5206\u662F\u540C\u6B65\u7684\u3002\u5F02\u6B65\u53D6\u6570\u7528 **`@vobs/resource`**\u3002\n\n## \u56DB\u3001\u5BA2\u6237\u7AEF\u526F\u4F5C\u7528\u4E0E SSR\n\n- \u5B9A\u65F6\u5668 / \u76D1\u542C / `matchMedia` / `localStorage` \u2192 **`onMount` \u542F\u52A8\u3001`onDestroy` \u6E05\u7406**\n \uFF08**\u4E0D\u8981\u624B\u5199 `typeof window` \u5B88\u536B**\uFF09\n- **\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\n \u2192 **`<ClientOnly fallback={\u2026}>`**\uFF08\u9996\u8F6E\u4E24\u4FA7\u90FD\u6E32\u67D3 fallback\uFF0C\u6C34\u5408\u5BF9\u5F97\u4E0A\uFF09\n- \u6A21\u5757\u9876\u5C42**\u7981\u6B62** JSX\uFF08`VOBS_C105`\uFF09\uFF1Aimport \u6C42\u503C\u65E9\u4E8E\u6E32\u67D3\u5668\u5B89\u88C5\n\n## \u4E94\u3001\u5217\u8868\u4E0E\u6570\u636E\n\n- **\u6570\u7EC4\u66F4\u65B0\u5FC5\u987B\u6362\u5F15\u7528**\uFF1A`list.value = [...list.value, item]`\uFF1B\n \u539F\u5730 `push` / \u6539\u5B57\u6BB5**\u4E0D\u89E6\u53D1\u66F4\u65B0**\n- 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()`\n- \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**\n \uFF08SSG \u5E8F\u5217\u5316\u4F1A\u70B8\uFF0C\u4EA7\u7269\u51FA\u73B0 `[object Xxx]` \u65F6\u6846\u67B6\u4F1A\u76F4\u63A5\u62A5\u9519\uFF09\n\n## \u516D\u3001\u8F93\u5165\n\n- **\u6570\u5B57\u8F93\u5165\u8D70 `parseNumber`**\uFF08`<Field type=\"number\">` \u5DF2\u5185\u7F6E\uFF09\uFF1A\n \u7A7A\u4E32 / \u975E\u6CD5 / \u8D85\u754C**\u4E0D\u63D0\u4EA4**\uFF0C\u4FDD\u6301\u539F\u503C\n \u2014\u2014 `Number('') === 0` \u4F1A\u628A\u8F93\u5165\u6E05\u6210 0 \u5E76\u6CBF\u8054\u52A8\u94FE\u8DEF\u6E05\u96F6\u5144\u5F1F\u7EF4\u5EA6\n- `<select>` \u7684 value \u76F4\u63A5\u7ED1\uFF0C**\u4E0D\u8981\u5199 ref \u515C\u5E95**\uFF081.5.1+ \u5DF2\u4FEE\u65F6\u5E8F\uFF09\n\n## \u4E03\u3001\u56FE\u6807\u4E0E SVG\n\n- **SVG \u76F4\u63A5\u5199 JSX**\uFF08`<svg><path/></svg>`\uFF0C1.7.4+ \u6309 namespace \u521B\u5EFA\u4E0E\u6C34\u5408\uFF09\n- \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\n\n## \u516B\u3001\u63D0\u4EA4\u524D\u81EA\u6D4B\n\n```bash\npnpm run check:source # = vobs check\uFF0C\u5168\u4ED3\u4E00\u6B21\u5217\u51FA\u5168\u90E8\u8BCA\u65AD\npnpm run check:runtime # \u771F\u5B9E\u6D4F\u89C8\u5668\u9010\u8DEF\u7531\u8DD1\u62A4\u680F\uFF08\u9700 Chrome\uFF09\npnpm run check:runtime:interact # \u518D\u70B9\u6240\u6709\u6309\u94AE\u3001\u89E6\u53D1\u6240\u6709\u8F93\u5165\n```\n\n`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\n`check:source` \u624D\u662F\u5168\u4ED3\u5165\u53E3\u3002\n\n---\n\n## \u4E00\u53E5\u8BDD\u901F\u8BB0\n\n| \u4E3B\u9898 | \u4E00\u53E5\u8BDD |\n|---|---|\n| \u7EC4\u4EF6\u4F53 | \u53EA\u8DD1\u4E00\u6B21\uFF0C\u6D3E\u751F\u8FDB JSX |\n| \u56DE\u8C03 | \u89E6\u53D1\u65F6\u91CD\u8BFB `.value` |\n| \u6761\u4EF6\u6E32\u67D3 | \u8DEF\u7531\u7528 `RouterView`\uFF0C\u663E\u9690\u7528 `Show`\uFF0C\u522B\u5728\u7EC4\u4EF6\u4F53 return \u5206\u652F |\n| effect | \u522B\u5199\u81EA\u5DF1\u8BFB\u7684\u4FE1\u53F7\uFF0C\u7528 `on(deps, fn)` |\n| async | \u522B\u585E\u8FDB effect\uFF0C\u7528 `@vobs/resource` |\n| \u5BA2\u6237\u7AEF | `onMount`/`onDestroy`/`ClientOnly`\uFF0C\u522B\u624B\u5199 `typeof window` |\n| \u5217\u8868 | \u6362\u5F15\u7528 |\n| \u6570\u5B57 | `parseNumber` |\n| SVG | \u76F4\u63A5\u5199 |\n| \u81EA\u6D4B | `check:source` + `check:runtime` |\n";
13
+
14
+ export { AGENT_CONTRACT };
@@ -0,0 +1,14 @@
1
+ /**
2
+ * vobs 框架契约正文 —— `vobs agent-doc` 的输出来源。
3
+ *
4
+ * 为什么是 TS 模块而不是 .md 文件:读文件要靠 `import.meta.url` 解析相对路径,
5
+ * 而**全量测试时 vitest 会转换模块**,`import.meta.url` 指向虚拟路径,
6
+ * `new URL('../../src/templates/…')` 就找不到文件(实测:单独跑 8 条全过、
7
+ * 全量跑 8 条全红)。内联成字符串**免疫打包与转换**,也省掉发布时拷模板。
8
+ *
9
+ * 内容由 `packages/cli/src/templates/agent-contract.md` 生成(保留 .md 作可读源);
10
+ * 改契约请改 .md 后重新生成,或直接改这里的字符串。
11
+ */
12
+ declare const AGENT_CONTRACT = "# vobs \u6846\u67B6\u5951\u7EA6\uFF08\u5199\u4EE3\u7801\u524D\u5FC5\u8BFB\uFF09\n\nvobs \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\n\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\n\n> \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\n> \u6BCF\u6761\u540E\u9762\u62EC\u53F7\u91CC\u662F**\u8FDD\u53CD\u65F6\u6846\u67B6\u4F1A\u62A5\u7684\u8BCA\u65AD\u7801**\u3002\n\n## \u4E00\u3001\u7EC4\u4EF6\u4F53\u53EA\u6267\u884C\u4E00\u6B21\uFF08run-once\uFF09\n\n**\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\n\n- \u274C `const filtered = list.value.filter(...)` \u2014\u2014 \u4E00\u6B21\u5FEB\u7167\uFF0C\u4E4B\u540E\u6C38\u4E0D\u66F4\u65B0\n- \u2705 \u6D3E\u751F\u653E\u8FDB JSX\uFF1A`<div>{list.value.filter(...).map(render)}</div>`\n- \u274C \u7EC4\u4EF6\u4F53\u91CC\u53D6\u5FEB\u7167\u7ED9\u56DE\u8C03\u7528\uFF1A`const v = name.value; onClick={() => save(v)}`\n- \u2705 \u56DE\u8C03\u5185\u91CD\u8BFB\uFF1A`onClick={() => save(name.value)}`\n- \u52A8\u6001 props \u7528 getter \u5F62\u6001\uFF0C\u4E0D\u8981\u4F20\u6C42\u503C\u540E\u7684\u503C\n\n## \u4E8C\u3001\u6761\u4EF6\u6E32\u67D3\uFF1A**\u4E0D\u8981\u5728\u7EC4\u4EF6\u4F53\u91CC `return` \u5206\u652F**\uFF08`VOBS_C107` / `VOBS_C104`\uFF09\n\n\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\n\n```tsx\nreturn cond.value ? <A/> : <B/> // \u274C \u51BB\u7ED3\nif (cond.value) return <A/> // \u274C \u51BB\u7ED3\nreturn cond.value ? <A/> : null // \u274C \u51BB\u7ED3\n```\n\n\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\n\n| \u573A\u666F | \u7528\u4EC0\u4E48 |\n|---|---|\n| **\u8DEF\u7531\u5206\u652F** | **`<RouterView/>`**\uFF08\u58F0\u660E\u5F0F\uFF0C\u5185\u90E8 `insertDynamic` + `resetKey`\uFF09 |\n| \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 |\n| \u53EA\u5207\u7C7B\u540D | `class=\"base\" classList={{ 'is-on': cond.value }}` |\n| \u666E\u901A\u6761\u4EF6\u6E32\u67D3 | \u653E\u8FDB **JSX \u5B50\u8282\u70B9\u4F4D\u7F6E**\uFF1A`<div>{cond.value ? <A/> : <B/>}</div>` |\n\n## \u4E09\u3001effect \u91CC\u4E0D\u8981\u5199\u81EA\u5DF1\u8BFB\u8FC7\u7684\u4FE1\u53F7\uFF08`VOBS_C210` / `VOBS_C211`\uFF09\n\n**\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\n\n- \u274C `effect(() => { count.value++ })` \u2014\u2014 \u8BFB+\u5199\u540C\u4E00\u4E2A\u4FE1\u53F7 = \u81EA\u8BA2\u9605\u5FAA\u73AF\n- \u274C `effect(() => { if (session.value) void sync() })` \u2014\u2014 **\u300C\u53EA\u8C03\u4E86\u4E2A\u51FD\u6570\u300D\u4E0D\u7B49\u4E8E\u6CA1\u4F9D\u8D56**\uFF1A\n \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\n- \u2705 \u53EA\u60F3\u58F0\u660E\u4F9D\u8D56\uFF1A**`effect(on(deps, () => { \u2026 }))`** \u2014\u2014 \u56DE\u8C03\u5728 untrack \u4F5C\u7528\u57DF\u91CC\u8DD1\uFF0C\n \u5B83\u8C03\u7528\u7684\u51FD\u6570\u78B0\u4EC0\u4E48\u4FE1\u53F7\u90FD\u4E0D\u4F1A\u53CD\u5411\u8BA2\u9605\n- \u2705 \u6D3E\u751F\u503C\u7528 `memo`\uFF0C\u4E0D\u8981\"\u8BFB A \u5199 B\"\n- \u515C\u5E95\u624D\u662F `untrack(() => { X.value = next })`\n\n**\u522B\u5728 effect \u91CC\u8C03 async \u51FD\u6570**\uFF08`VOBS_C106`\uFF09\uFF1A`effect` \u4E0D\u7B49\u5B83\uFF0C\n\u4E14\u9996\u4E2A `await` \u4E4B\u524D\u7684\u90E8\u5206\u662F\u540C\u6B65\u7684\u3002\u5F02\u6B65\u53D6\u6570\u7528 **`@vobs/resource`**\u3002\n\n## \u56DB\u3001\u5BA2\u6237\u7AEF\u526F\u4F5C\u7528\u4E0E SSR\n\n- \u5B9A\u65F6\u5668 / \u76D1\u542C / `matchMedia` / `localStorage` \u2192 **`onMount` \u542F\u52A8\u3001`onDestroy` \u6E05\u7406**\n \uFF08**\u4E0D\u8981\u624B\u5199 `typeof window` \u5B88\u536B**\uFF09\n- **\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\n \u2192 **`<ClientOnly fallback={\u2026}>`**\uFF08\u9996\u8F6E\u4E24\u4FA7\u90FD\u6E32\u67D3 fallback\uFF0C\u6C34\u5408\u5BF9\u5F97\u4E0A\uFF09\n- \u6A21\u5757\u9876\u5C42**\u7981\u6B62** JSX\uFF08`VOBS_C105`\uFF09\uFF1Aimport \u6C42\u503C\u65E9\u4E8E\u6E32\u67D3\u5668\u5B89\u88C5\n\n## \u4E94\u3001\u5217\u8868\u4E0E\u6570\u636E\n\n- **\u6570\u7EC4\u66F4\u65B0\u5FC5\u987B\u6362\u5F15\u7528**\uFF1A`list.value = [...list.value, item]`\uFF1B\n \u539F\u5730 `push` / \u6539\u5B57\u6BB5**\u4E0D\u89E6\u53D1\u66F4\u65B0**\n- 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()`\n- \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**\n \uFF08SSG \u5E8F\u5217\u5316\u4F1A\u70B8\uFF0C\u4EA7\u7269\u51FA\u73B0 `[object Xxx]` \u65F6\u6846\u67B6\u4F1A\u76F4\u63A5\u62A5\u9519\uFF09\n\n## \u516D\u3001\u8F93\u5165\n\n- **\u6570\u5B57\u8F93\u5165\u8D70 `parseNumber`**\uFF08`<Field type=\"number\">` \u5DF2\u5185\u7F6E\uFF09\uFF1A\n \u7A7A\u4E32 / \u975E\u6CD5 / \u8D85\u754C**\u4E0D\u63D0\u4EA4**\uFF0C\u4FDD\u6301\u539F\u503C\n \u2014\u2014 `Number('') === 0` \u4F1A\u628A\u8F93\u5165\u6E05\u6210 0 \u5E76\u6CBF\u8054\u52A8\u94FE\u8DEF\u6E05\u96F6\u5144\u5F1F\u7EF4\u5EA6\n- `<select>` \u7684 value \u76F4\u63A5\u7ED1\uFF0C**\u4E0D\u8981\u5199 ref \u515C\u5E95**\uFF081.5.1+ \u5DF2\u4FEE\u65F6\u5E8F\uFF09\n\n## \u4E03\u3001\u56FE\u6807\u4E0E SVG\n\n- **SVG \u76F4\u63A5\u5199 JSX**\uFF08`<svg><path/></svg>`\uFF0C1.7.4+ \u6309 namespace \u521B\u5EFA\u4E0E\u6C34\u5408\uFF09\n- \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\n\n## \u516B\u3001\u63D0\u4EA4\u524D\u81EA\u6D4B\n\n```bash\npnpm run check:source # = vobs check\uFF0C\u5168\u4ED3\u4E00\u6B21\u5217\u51FA\u5168\u90E8\u8BCA\u65AD\npnpm run check:runtime # \u771F\u5B9E\u6D4F\u89C8\u5668\u9010\u8DEF\u7531\u8DD1\u62A4\u680F\uFF08\u9700 Chrome\uFF09\npnpm run check:runtime:interact # \u518D\u70B9\u6240\u6709\u6309\u94AE\u3001\u89E6\u53D1\u6240\u6709\u8F93\u5165\n```\n\n`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\n`check:source` \u624D\u662F\u5168\u4ED3\u5165\u53E3\u3002\n\n---\n\n## \u4E00\u53E5\u8BDD\u901F\u8BB0\n\n| \u4E3B\u9898 | \u4E00\u53E5\u8BDD |\n|---|---|\n| \u7EC4\u4EF6\u4F53 | \u53EA\u8DD1\u4E00\u6B21\uFF0C\u6D3E\u751F\u8FDB JSX |\n| \u56DE\u8C03 | \u89E6\u53D1\u65F6\u91CD\u8BFB `.value` |\n| \u6761\u4EF6\u6E32\u67D3 | \u8DEF\u7531\u7528 `RouterView`\uFF0C\u663E\u9690\u7528 `Show`\uFF0C\u522B\u5728\u7EC4\u4EF6\u4F53 return \u5206\u652F |\n| effect | \u522B\u5199\u81EA\u5DF1\u8BFB\u7684\u4FE1\u53F7\uFF0C\u7528 `on(deps, fn)` |\n| async | \u522B\u585E\u8FDB effect\uFF0C\u7528 `@vobs/resource` |\n| \u5BA2\u6237\u7AEF | `onMount`/`onDestroy`/`ClientOnly`\uFF0C\u522B\u624B\u5199 `typeof window` |\n| \u5217\u8868 | \u6362\u5F15\u7528 |\n| \u6570\u5B57 | `parseNumber` |\n| SVG | \u76F4\u63A5\u5199 |\n| \u81EA\u6D4B | `check:source` + `check:runtime` |\n";
13
+
14
+ export { AGENT_CONTRACT };
@@ -0,0 +1,113 @@
1
+ // packages/cli/src/templates/agent-contract.ts
2
+ var AGENT_CONTRACT = `# vobs \u6846\u67B6\u5951\u7EA6\uFF08\u5199\u4EE3\u7801\u524D\u5FC5\u8BFB\uFF09
3
+
4
+ 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
5
+ \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
6
+
7
+ > \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
8
+ > \u6BCF\u6761\u540E\u9762\u62EC\u53F7\u91CC\u662F**\u8FDD\u53CD\u65F6\u6846\u67B6\u4F1A\u62A5\u7684\u8BCA\u65AD\u7801**\u3002
9
+
10
+ ## \u4E00\u3001\u7EC4\u4EF6\u4F53\u53EA\u6267\u884C\u4E00\u6B21\uFF08run-once\uFF09
11
+
12
+ **\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
13
+
14
+ - \u274C \`const filtered = list.value.filter(...)\` \u2014\u2014 \u4E00\u6B21\u5FEB\u7167\uFF0C\u4E4B\u540E\u6C38\u4E0D\u66F4\u65B0
15
+ - \u2705 \u6D3E\u751F\u653E\u8FDB JSX\uFF1A\`<div>{list.value.filter(...).map(render)}</div>\`
16
+ - \u274C \u7EC4\u4EF6\u4F53\u91CC\u53D6\u5FEB\u7167\u7ED9\u56DE\u8C03\u7528\uFF1A\`const v = name.value; onClick={() => save(v)}\`
17
+ - \u2705 \u56DE\u8C03\u5185\u91CD\u8BFB\uFF1A\`onClick={() => save(name.value)}\`
18
+ - \u52A8\u6001 props \u7528 getter \u5F62\u6001\uFF0C\u4E0D\u8981\u4F20\u6C42\u503C\u540E\u7684\u503C
19
+
20
+ ## \u4E8C\u3001\u6761\u4EF6\u6E32\u67D3\uFF1A**\u4E0D\u8981\u5728\u7EC4\u4EF6\u4F53\u91CC \`return\` \u5206\u652F**\uFF08\`VOBS_C107\` / \`VOBS_C104\`\uFF09
21
+
22
+ \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
23
+
24
+ \`\`\`tsx
25
+ return cond.value ? <A/> : <B/> // \u274C \u51BB\u7ED3
26
+ if (cond.value) return <A/> // \u274C \u51BB\u7ED3
27
+ return cond.value ? <A/> : null // \u274C \u51BB\u7ED3
28
+ \`\`\`
29
+
30
+ \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
31
+
32
+ | \u573A\u666F | \u7528\u4EC0\u4E48 |
33
+ |---|---|
34
+ | **\u8DEF\u7531\u5206\u652F** | **\`<RouterView/>\`**\uFF08\u58F0\u660E\u5F0F\uFF0C\u5185\u90E8 \`insertDynamic\` + \`resetKey\`\uFF09 |
35
+ | \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 |
36
+ | \u53EA\u5207\u7C7B\u540D | \`class="base" classList={{ 'is-on': cond.value }}\` |
37
+ | \u666E\u901A\u6761\u4EF6\u6E32\u67D3 | \u653E\u8FDB **JSX \u5B50\u8282\u70B9\u4F4D\u7F6E**\uFF1A\`<div>{cond.value ? <A/> : <B/>}</div>\` |
38
+
39
+ ## \u4E09\u3001effect \u91CC\u4E0D\u8981\u5199\u81EA\u5DF1\u8BFB\u8FC7\u7684\u4FE1\u53F7\uFF08\`VOBS_C210\` / \`VOBS_C211\`\uFF09
40
+
41
+ **\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
42
+
43
+ - \u274C \`effect(() => { count.value++ })\` \u2014\u2014 \u8BFB+\u5199\u540C\u4E00\u4E2A\u4FE1\u53F7 = \u81EA\u8BA2\u9605\u5FAA\u73AF
44
+ - \u274C \`effect(() => { if (session.value) void sync() })\` \u2014\u2014 **\u300C\u53EA\u8C03\u4E86\u4E2A\u51FD\u6570\u300D\u4E0D\u7B49\u4E8E\u6CA1\u4F9D\u8D56**\uFF1A
45
+ \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
46
+ - \u2705 \u53EA\u60F3\u58F0\u660E\u4F9D\u8D56\uFF1A**\`effect(on(deps, () => { \u2026 }))\`** \u2014\u2014 \u56DE\u8C03\u5728 untrack \u4F5C\u7528\u57DF\u91CC\u8DD1\uFF0C
47
+ \u5B83\u8C03\u7528\u7684\u51FD\u6570\u78B0\u4EC0\u4E48\u4FE1\u53F7\u90FD\u4E0D\u4F1A\u53CD\u5411\u8BA2\u9605
48
+ - \u2705 \u6D3E\u751F\u503C\u7528 \`memo\`\uFF0C\u4E0D\u8981"\u8BFB A \u5199 B"
49
+ - \u515C\u5E95\u624D\u662F \`untrack(() => { X.value = next })\`
50
+
51
+ **\u522B\u5728 effect \u91CC\u8C03 async \u51FD\u6570**\uFF08\`VOBS_C106\`\uFF09\uFF1A\`effect\` \u4E0D\u7B49\u5B83\uFF0C
52
+ \u4E14\u9996\u4E2A \`await\` \u4E4B\u524D\u7684\u90E8\u5206\u662F\u540C\u6B65\u7684\u3002\u5F02\u6B65\u53D6\u6570\u7528 **\`@vobs/resource\`**\u3002
53
+
54
+ ## \u56DB\u3001\u5BA2\u6237\u7AEF\u526F\u4F5C\u7528\u4E0E SSR
55
+
56
+ - \u5B9A\u65F6\u5668 / \u76D1\u542C / \`matchMedia\` / \`localStorage\` \u2192 **\`onMount\` \u542F\u52A8\u3001\`onDestroy\` \u6E05\u7406**
57
+ \uFF08**\u4E0D\u8981\u624B\u5199 \`typeof window\` \u5B88\u536B**\uFF09
58
+ - **\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
59
+ \u2192 **\`<ClientOnly fallback={\u2026}>\`**\uFF08\u9996\u8F6E\u4E24\u4FA7\u90FD\u6E32\u67D3 fallback\uFF0C\u6C34\u5408\u5BF9\u5F97\u4E0A\uFF09
60
+ - \u6A21\u5757\u9876\u5C42**\u7981\u6B62** JSX\uFF08\`VOBS_C105\`\uFF09\uFF1Aimport \u6C42\u503C\u65E9\u4E8E\u6E32\u67D3\u5668\u5B89\u88C5
61
+
62
+ ## \u4E94\u3001\u5217\u8868\u4E0E\u6570\u636E
63
+
64
+ - **\u6570\u7EC4\u66F4\u65B0\u5FC5\u987B\u6362\u5F15\u7528**\uFF1A\`list.value = [...list.value, item]\`\uFF1B
65
+ \u539F\u5730 \`push\` / \u6539\u5B57\u6BB5**\u4E0D\u89E6\u53D1\u66F4\u65B0**
66
+ - 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()\`
67
+ - \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**
68
+ \uFF08SSG \u5E8F\u5217\u5316\u4F1A\u70B8\uFF0C\u4EA7\u7269\u51FA\u73B0 \`[object Xxx]\` \u65F6\u6846\u67B6\u4F1A\u76F4\u63A5\u62A5\u9519\uFF09
69
+
70
+ ## \u516D\u3001\u8F93\u5165
71
+
72
+ - **\u6570\u5B57\u8F93\u5165\u8D70 \`parseNumber\`**\uFF08\`<Field type="number">\` \u5DF2\u5185\u7F6E\uFF09\uFF1A
73
+ \u7A7A\u4E32 / \u975E\u6CD5 / \u8D85\u754C**\u4E0D\u63D0\u4EA4**\uFF0C\u4FDD\u6301\u539F\u503C
74
+ \u2014\u2014 \`Number('') === 0\` \u4F1A\u628A\u8F93\u5165\u6E05\u6210 0 \u5E76\u6CBF\u8054\u52A8\u94FE\u8DEF\u6E05\u96F6\u5144\u5F1F\u7EF4\u5EA6
75
+ - \`<select>\` \u7684 value \u76F4\u63A5\u7ED1\uFF0C**\u4E0D\u8981\u5199 ref \u515C\u5E95**\uFF081.5.1+ \u5DF2\u4FEE\u65F6\u5E8F\uFF09
76
+
77
+ ## \u4E03\u3001\u56FE\u6807\u4E0E SVG
78
+
79
+ - **SVG \u76F4\u63A5\u5199 JSX**\uFF08\`<svg><path/></svg>\`\uFF0C1.7.4+ \u6309 namespace \u521B\u5EFA\u4E0E\u6C34\u5408\uFF09
80
+ - \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
81
+
82
+ ## \u516B\u3001\u63D0\u4EA4\u524D\u81EA\u6D4B
83
+
84
+ \`\`\`bash
85
+ pnpm run check:source # = vobs check\uFF0C\u5168\u4ED3\u4E00\u6B21\u5217\u51FA\u5168\u90E8\u8BCA\u65AD
86
+ pnpm run check:runtime # \u771F\u5B9E\u6D4F\u89C8\u5668\u9010\u8DEF\u7531\u8DD1\u62A4\u680F\uFF08\u9700 Chrome\uFF09
87
+ pnpm run check:runtime:interact # \u518D\u70B9\u6240\u6709\u6309\u94AE\u3001\u89E6\u53D1\u6240\u6709\u8F93\u5165
88
+ \`\`\`
89
+
90
+ \`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
91
+ \`check:source\` \u624D\u662F\u5168\u4ED3\u5165\u53E3\u3002
92
+
93
+ ---
94
+
95
+ ## \u4E00\u53E5\u8BDD\u901F\u8BB0
96
+
97
+ | \u4E3B\u9898 | \u4E00\u53E5\u8BDD |
98
+ |---|---|
99
+ | \u7EC4\u4EF6\u4F53 | \u53EA\u8DD1\u4E00\u6B21\uFF0C\u6D3E\u751F\u8FDB JSX |
100
+ | \u56DE\u8C03 | \u89E6\u53D1\u65F6\u91CD\u8BFB \`.value\` |
101
+ | \u6761\u4EF6\u6E32\u67D3 | \u8DEF\u7531\u7528 \`RouterView\`\uFF0C\u663E\u9690\u7528 \`Show\`\uFF0C\u522B\u5728\u7EC4\u4EF6\u4F53 return \u5206\u652F |
102
+ | effect | \u522B\u5199\u81EA\u5DF1\u8BFB\u7684\u4FE1\u53F7\uFF0C\u7528 \`on(deps, fn)\` |
103
+ | async | \u522B\u585E\u8FDB effect\uFF0C\u7528 \`@vobs/resource\` |
104
+ | \u5BA2\u6237\u7AEF | \`onMount\`/\`onDestroy\`/\`ClientOnly\`\uFF0C\u522B\u624B\u5199 \`typeof window\` |
105
+ | \u5217\u8868 | \u6362\u5F15\u7528 |
106
+ | \u6570\u5B57 | \`parseNumber\` |
107
+ | SVG | \u76F4\u63A5\u5199 |
108
+ | \u81EA\u6D4B | \`check:source\` + \`check:runtime\` |
109
+ `;
110
+ export {
111
+ AGENT_CONTRACT
112
+ };
113
+ //# sourceMappingURL=agent-contract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/templates/agent-contract.ts"],"sourcesContent":["/**\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":";AAWO,IAAM,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;","names":[]}
@@ -0,0 +1,107 @@
1
+ # vobs 框架契约(写代码前必读)
2
+
3
+ vobs 是**编译型 / 细粒度 / 响应式**框架,与 React 的心智模型有几处**根本不同**。
4
+ 下面的每一条都来自真实事故,**违反时通常编译通过、运行期才错**。
5
+
6
+ > 本文档由 `vobs agent-doc` 从框架版本生成 —— 不要手改生成的块。
7
+ > 每条后面括号里是**违反时框架会报的诊断码**。
8
+
9
+ ## 一、组件体只执行一次(run-once)
10
+
11
+ **组件函数体只在创建时跑一次**,之后只有 JSX 里订阅的位置更新。
12
+
13
+ - ❌ `const filtered = list.value.filter(...)` —— 一次快照,之后永不更新
14
+ - ✅ 派生放进 JSX:`<div>{list.value.filter(...).map(render)}</div>`
15
+ - ❌ 组件体里取快照给回调用:`const v = name.value; onClick={() => save(v)}`
16
+ - ✅ 回调内重读:`onClick={() => save(name.value)}`
17
+ - 动态 props 用 getter 形态,不要传求值后的值
18
+
19
+ ## 二、条件渲染:**不要在组件体里 `return` 分支**(`VOBS_C107` / `VOBS_C104`)
20
+
21
+ 组件体里读信号的 `return` 在挂载时固化,**三种写法坏得一模一样**:
22
+
23
+ ```tsx
24
+ return cond.value ? <A/> : <B/> // ❌ 冻结
25
+ if (cond.value) return <A/> // ❌ 冻结
26
+ return cond.value ? <A/> : null // ❌ 冻结
27
+ ```
28
+
29
+ 顶层 `return` 处**没有 parent/anchor**,所以两支都是 JSX 也一样不会重分支。
30
+
31
+ | 场景 | 用什么 |
32
+ |---|---|
33
+ | **路由分支** | **`<RouterView/>`**(声明式,内部 `insertDynamic` + `resetKey`) |
34
+ | 保留挂载、只切显隐 | **`<Show when={cond}>`**(切 `hidden` + `inert`,焦点/滚动/内部状态不丢) |
35
+ | 只切类名 | `class="base" classList={{ 'is-on': cond.value }}` |
36
+ | 普通条件渲染 | 放进 **JSX 子节点位置**:`<div>{cond.value ? <A/> : <B/>}</div>` |
37
+
38
+ ## 三、effect 里不要写自己读过的信号(`VOBS_C210` / `VOBS_C211`)
39
+
40
+ **依赖是自动收集的**:effect 运行期间读到的**任何**信号都会变成依赖。
41
+
42
+ - ❌ `effect(() => { count.value++ })` —— 读+写同一个信号 = 自订阅循环
43
+ - ❌ `effect(() => { if (session.value) void sync() })` —— **「只调了个函数」不等于没依赖**:
44
+ 被调函数在**首个 `await` 之前**的代码是**同步执行**的,它读的信号算在 effect 头上
45
+ - ✅ 只想声明依赖:**`effect(on(deps, () => { … }))`** —— 回调在 untrack 作用域里跑,
46
+ 它调用的函数碰什么信号都不会反向订阅
47
+ - ✅ 派生值用 `memo`,不要"读 A 写 B"
48
+ - 兜底才是 `untrack(() => { X.value = next })`
49
+
50
+ **别在 effect 里调 async 函数**(`VOBS_C106`):`effect` 不等它,
51
+ 且首个 `await` 之前的部分是同步的。异步取数用 **`@vobs/resource`**。
52
+
53
+ ## 四、客户端副作用与 SSR
54
+
55
+ - 定时器 / 监听 / `matchMedia` / `localStorage` → **`onMount` 启动、`onDestroy` 清理**
56
+ (**不要手写 `typeof window` 守卫**)
57
+ - **渲染输出本身**依赖浏览器(窗口尺寸 / `localStorage` 回填 / `Date.now` / 随机值)
58
+ → **`<ClientOnly fallback={…}>`**(首轮两侧都渲染 fallback,水合对得上)
59
+ - 模块顶层**禁止** JSX(`VOBS_C105`):import 求值早于渲染器安装
60
+
61
+ ## 五、列表与数据
62
+
63
+ - **数组更新必须换引用**:`list.value = [...list.value, item]`;
64
+ 原地 `push` / 改字段**不触发更新**
65
+ - JSX **子节点位置**只放四种形态:组件标签 / 元素 / 两分支三元 / `.map()`
66
+ - 数据结构里**只存纯描述**(字符串/样式/结构字段),**节点对象别进数据常量**
67
+ (SSG 序列化会炸,产物出现 `[object Xxx]` 时框架会直接报错)
68
+
69
+ ## 六、输入
70
+
71
+ - **数字输入走 `parseNumber`**(`<Field type="number">` 已内置):
72
+ 空串 / 非法 / 超界**不提交**,保持原值
73
+ —— `Number('') === 0` 会把输入清成 0 并沿联动链路清零兄弟维度
74
+ - `<select>` 的 value 直接绑,**不要写 ref 兜底**(1.5.1+ 已修时序)
75
+
76
+ ## 七、图标与 SVG
77
+
78
+ - **SVG 直接写 JSX**(`<svg><path/></svg>`,1.7.4+ 按 namespace 创建与水合)
79
+ - 图标要**登记进白名单**;查表 miss 时 `@vobs/icon-core` 会警告点名(但仍应登记)
80
+
81
+ ## 八、提交前自测
82
+
83
+ ```bash
84
+ pnpm run check:source # = vobs check,全仓一次列出全部诊断
85
+ pnpm run check:runtime # 真实浏览器逐路由跑护栏(需 Chrome)
86
+ pnpm run check:runtime:interact # 再点所有按钮、触发所有输入
87
+ ```
88
+
89
+ `vite build` **会**打印编译期警告(`C104`/`C105`/`C106`/`C107`),但只覆盖它编译到的文件;
90
+ `check:source` 才是全仓入口。
91
+
92
+ ---
93
+
94
+ ## 一句话速记
95
+
96
+ | 主题 | 一句话 |
97
+ |---|---|
98
+ | 组件体 | 只跑一次,派生进 JSX |
99
+ | 回调 | 触发时重读 `.value` |
100
+ | 条件渲染 | 路由用 `RouterView`,显隐用 `Show`,别在组件体 return 分支 |
101
+ | effect | 别写自己读的信号,用 `on(deps, fn)` |
102
+ | async | 别塞进 effect,用 `@vobs/resource` |
103
+ | 客户端 | `onMount`/`onDestroy`/`ClientOnly`,别手写 `typeof window` |
104
+ | 列表 | 换引用 |
105
+ | 数字 | `parseNumber` |
106
+ | SVG | 直接写 |
107
+ | 自测 | `check:source` + `check:runtime` |
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "type": "git",
5
5
  "url": "git+https://github.com/vobsjs/vobs.git"
6
6
  },
7
- "version": "1.8.5",
7
+ "version": "1.8.6",
8
8
  "publishConfig": {
9
9
  "access": "public"
10
10
  },
package/src/cli.ts CHANGED
@@ -6,6 +6,7 @@ import { generateCommand } from './commands/generate.js'
6
6
  import { addCommand } from './commands/add.js'
7
7
  import { dshCommand } from './commands/dsh.js'
8
8
  import { checkCommand } from './commands/check.js'
9
+ import { agentDocCommand } from './commands/agent-doc.js'
9
10
  import { showBanner, showLogo, showDevBanner } from './banner.js'
10
11
  import { cliVersion } from './version.js'
11
12
 
@@ -108,6 +109,21 @@ export function createCLI(): ReturnType<typeof cac> {
108
109
  })
109
110
  })
110
111
 
112
+ cli
113
+ .command('agent-doc', '生成/校验给 LLM 读的框架契约(AGENTS.md + CLAUDE.md)')
114
+ .option('--dir <dir>', '目标目录', { default: process.cwd() })
115
+ .option('--write', '写入文件(默认打到 stdout)')
116
+ .option('--check', '校验项目里那份是否与当前框架版本一致(可进 CI)')
117
+ .option('--body', '只输出契约正文(不含标记块外说明)')
118
+ .action(async (options: Record<string, unknown>) => {
119
+ await agentDocCommand({
120
+ dir: options.dir as string | undefined,
121
+ write: options.write === true,
122
+ check: options.check === true,
123
+ body: options.body === true
124
+ })
125
+ })
126
+
111
127
  cli
112
128
  .command('dsh [action] [target]', 'DSH 插件工具链(init / dev / build / check / install)')
113
129
  .option('--dir <dir>', 'init:目标目录')
@@ -160,5 +176,5 @@ export function createCLI(): ReturnType<typeof cac> {
160
176
  * 用这份清单把"跑某个命令"与"顶层用法错误"分开。
161
177
  */
162
178
  export const KNOWN_SUBCOMMANDS: readonly string[] = [
163
- 'init', 'create', 'dev', 'build', 'generate', 'g', 'add', 'check', 'dsh', 'logo'
179
+ 'init', 'create', 'dev', 'build', 'generate', 'g', 'add', 'check', 'dsh', 'agent-doc', 'logo'
164
180
  ]