@lark-apaas/coding-steering 0.1.18-dev.4e64c13 → 0.1.18-dev.61b3ece
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/steering/design-html/skills/charts/SKILL.md +4 -0
- package/steering/design-html/skills/pptx-style-extract/SKILL.md +11 -8
- package/steering/design-html/skills/pptx-style-extract/scripts/check_v2.py +33 -3
- package/steering/design-html/skills/pptx-style-extract/scripts/draft.py +526 -110
- package/steering/design-html/skills/pptx-style-extract/scripts/extract.py +226 -6
- package/steering/design-html/skills/pptx-style-extract/scripts/ooxml.py +18 -1
- package/steering/design-html/skills/pptx-style-extract/scripts/package.py +167 -28
- package/steering/design-html/skills/pptx-style-extract/scripts/parts.py +3 -0
- package/steering/design-html/skills/pptx-style-extract/scripts/query.py +3 -8
- package/steering/design-html/skills/pptx-style-extract/scripts/test_background_composite.py +57 -0
- package/steering/design-html/skills/pptx-style-extract/scripts/test_color_contract.py +60 -0
- package/steering/design-html/skills/pptx-style-extract/scripts/test_design_consumer_contract.py +63 -0
- package/steering/design-html/skills/pptx-style-extract/scripts/test_flow_layout_contract.py +468 -0
- package/steering/design-html/skills/pptx-style-extract/scripts/test_layout_css.py +127 -0
- package/steering/design-html/skills/pptx-style-extract/scripts/test_rounded_contract.py +112 -0
- package/steering/design-html/skills/pptx-style-extract/scripts/test_text_role_contract.py +208 -0
- package/steering/design-html/skills/pptx-style-extract/v2-format-spec.md +14 -7
- package/steering/design-html/skills/slide-deck/SKILL.md +15 -20
- package/steering/design-html/skills/slide-deck/scripts/check_local_references.py +179 -0
- package/steering/nestjs-react-fullstack/skills/plugin-guide/SKILL.md +5 -3
- package/steering/nestjs-react-fullstack/skills_local/plugin-guide/SKILL.md +4 -0
- package/steering/vite-react/skills/plugin-guide/SKILL.md +3 -1
- package/steering/vite-react/skills/react-three-fiber/SKILL.md +4 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
import sys
|
|
5
|
+
from html.parser import HTMLParser
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from urllib.parse import unquote, urlsplit
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
CSS_URL_PATTERN = re.compile(r"""url\(\s*(['"]?)(.*?)\1\s*\)""", re.IGNORECASE)
|
|
11
|
+
CSS_IMPORT_PATTERN = re.compile(
|
|
12
|
+
r"""@import\s+(?:url\(\s*)?(['"])(.*?)\1\s*\)?""",
|
|
13
|
+
re.IGNORECASE,
|
|
14
|
+
)
|
|
15
|
+
CSS_COMMENT_PATTERN = re.compile(r"/\*.*?\*/", re.DOTALL)
|
|
16
|
+
HREF_RESOURCE_TAGS = {"image", "link", "use"}
|
|
17
|
+
SRC_RESOURCE_TAGS = {
|
|
18
|
+
"audio",
|
|
19
|
+
"embed",
|
|
20
|
+
"iframe",
|
|
21
|
+
"img",
|
|
22
|
+
"input",
|
|
23
|
+
"script",
|
|
24
|
+
"source",
|
|
25
|
+
"track",
|
|
26
|
+
"video",
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ReferenceParser(HTMLParser):
|
|
31
|
+
def __init__(self) -> None:
|
|
32
|
+
super().__init__()
|
|
33
|
+
self.references: set[str] = set()
|
|
34
|
+
self.in_style = False
|
|
35
|
+
|
|
36
|
+
def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None:
|
|
37
|
+
tag = tag.lower()
|
|
38
|
+
if tag == "style":
|
|
39
|
+
self.in_style = True
|
|
40
|
+
attributes = dict(attrs)
|
|
41
|
+
if tag in HREF_RESOURCE_TAGS and attributes.get("href"):
|
|
42
|
+
self.references.add(attributes["href"])
|
|
43
|
+
if tag in SRC_RESOURCE_TAGS and attributes.get("src"):
|
|
44
|
+
self.references.add(attributes["src"])
|
|
45
|
+
if tag == "object" and attributes.get("data"):
|
|
46
|
+
self.references.add(attributes["data"])
|
|
47
|
+
if tag == "video" and attributes.get("poster"):
|
|
48
|
+
self.references.add(attributes["poster"])
|
|
49
|
+
|
|
50
|
+
srcset = attributes.get("srcset")
|
|
51
|
+
if tag in {"img", "source"} and srcset:
|
|
52
|
+
self.references.update(
|
|
53
|
+
candidate.strip().split()[0]
|
|
54
|
+
for candidate in srcset.split(",")
|
|
55
|
+
if candidate.strip()
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
style = attributes.get("style")
|
|
59
|
+
if style:
|
|
60
|
+
self.references.update(css_references(style))
|
|
61
|
+
|
|
62
|
+
def handle_endtag(self, tag: str) -> None:
|
|
63
|
+
if tag.lower() == "style":
|
|
64
|
+
self.in_style = False
|
|
65
|
+
|
|
66
|
+
def handle_data(self, data: str) -> None:
|
|
67
|
+
if self.in_style:
|
|
68
|
+
self.references.update(css_references(data))
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def css_references(content: str) -> set[str]:
|
|
72
|
+
content_without_comments = CSS_COMMENT_PATTERN.sub("", content)
|
|
73
|
+
references = {
|
|
74
|
+
match.group(2).strip() for match in CSS_URL_PATTERN.finditer(content_without_comments)
|
|
75
|
+
}
|
|
76
|
+
references.update(
|
|
77
|
+
match.group(2).strip() for match in CSS_IMPORT_PATTERN.finditer(content_without_comments)
|
|
78
|
+
)
|
|
79
|
+
return references
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def resolve_local_reference(
|
|
83
|
+
raw_reference: str,
|
|
84
|
+
source: Path,
|
|
85
|
+
project_root: Path,
|
|
86
|
+
) -> Path | None:
|
|
87
|
+
reference = raw_reference.strip()
|
|
88
|
+
parsed = urlsplit(reference)
|
|
89
|
+
if (
|
|
90
|
+
not reference
|
|
91
|
+
or reference.startswith(("#", "//"))
|
|
92
|
+
or (parsed.scheme and parsed.scheme.lower() != "file")
|
|
93
|
+
or parsed.netloc
|
|
94
|
+
):
|
|
95
|
+
return None
|
|
96
|
+
|
|
97
|
+
path_text = unquote(parsed.path)
|
|
98
|
+
if not path_text:
|
|
99
|
+
return None
|
|
100
|
+
|
|
101
|
+
if parsed.scheme.lower() == "file":
|
|
102
|
+
return Path(path_text).resolve()
|
|
103
|
+
if path_text.startswith("/"):
|
|
104
|
+
return (project_root / path_text.lstrip("/")).resolve()
|
|
105
|
+
return (source.parent / path_text).resolve()
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def collect_references(entry: Path, project_root: Path) -> list[tuple[Path, str, Path]]:
|
|
109
|
+
pending = [entry]
|
|
110
|
+
visited: set[Path] = set()
|
|
111
|
+
local_references: list[tuple[Path, str, Path]] = []
|
|
112
|
+
|
|
113
|
+
while pending:
|
|
114
|
+
source = pending.pop()
|
|
115
|
+
if source in visited or not source.is_file():
|
|
116
|
+
continue
|
|
117
|
+
visited.add(source)
|
|
118
|
+
|
|
119
|
+
content = source.read_text(encoding="utf-8")
|
|
120
|
+
if source.suffix.lower() == ".css":
|
|
121
|
+
references = css_references(content)
|
|
122
|
+
else:
|
|
123
|
+
parser = ReferenceParser()
|
|
124
|
+
parser.feed(content)
|
|
125
|
+
references = parser.references
|
|
126
|
+
|
|
127
|
+
for raw_reference in sorted(references):
|
|
128
|
+
target = resolve_local_reference(raw_reference, source, project_root)
|
|
129
|
+
if target is None:
|
|
130
|
+
continue
|
|
131
|
+
local_references.append((source, raw_reference, target))
|
|
132
|
+
if target.suffix.lower() == ".css" and target.is_file():
|
|
133
|
+
pending.append(target)
|
|
134
|
+
|
|
135
|
+
return local_references
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def display_path(path: Path, project_root: Path) -> str:
|
|
139
|
+
try:
|
|
140
|
+
return path.relative_to(project_root).as_posix()
|
|
141
|
+
except ValueError:
|
|
142
|
+
return str(path)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def main() -> int:
|
|
146
|
+
if len(sys.argv) != 2:
|
|
147
|
+
print("usage: check_local_references.py <entry.html>", file=sys.stderr)
|
|
148
|
+
return 2
|
|
149
|
+
|
|
150
|
+
project_root = Path.cwd().resolve()
|
|
151
|
+
entry = (project_root / sys.argv[1]).resolve()
|
|
152
|
+
if not entry.is_file():
|
|
153
|
+
print(f"RESOURCE_CHECK: FAIL entryNotFound={sys.argv[1]}")
|
|
154
|
+
return 1
|
|
155
|
+
|
|
156
|
+
missing: list[tuple[Path, str, Path]] = []
|
|
157
|
+
for source, raw_reference, target in collect_references(entry, project_root):
|
|
158
|
+
try:
|
|
159
|
+
target.relative_to(project_root)
|
|
160
|
+
except ValueError:
|
|
161
|
+
missing.append((source, raw_reference, target))
|
|
162
|
+
continue
|
|
163
|
+
if not target.is_file():
|
|
164
|
+
missing.append((source, raw_reference, target))
|
|
165
|
+
|
|
166
|
+
if not missing:
|
|
167
|
+
print("RESOURCE_CHECK: PASS")
|
|
168
|
+
return 0
|
|
169
|
+
|
|
170
|
+
print(f"RESOURCE_CHECK: FAIL missingLocalReferences={len(missing)}")
|
|
171
|
+
for source, raw_reference, target in missing:
|
|
172
|
+
source_name = display_path(source, project_root)
|
|
173
|
+
target_name = display_path(target, project_root)
|
|
174
|
+
print(f"- {source_name}: {raw_reference} -> {target_name}")
|
|
175
|
+
return 1
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
if __name__ == "__main__":
|
|
179
|
+
raise SystemExit(main())
|
|
@@ -215,7 +215,7 @@ const structured = await capabilityClient
|
|
|
215
215
|
- **Plugin(插件)**:底层承载单元,包含插件元信息与表单定义(form.schema)。模型侧只感知插件及其表单字段,不感知插件内部实现细节。
|
|
216
216
|
- **PluginInstance(插件实例配置)**:基于某个 Plugin 的表单做"业务封装",以 **单文件 JSON** 的形式存储(每个插件实例一个文件,语义化 id)。
|
|
217
217
|
- 通过 `paramsSchema` 暴露业务入参
|
|
218
|
-
- 通过 `formValue` 将业务入参映射到插件表单字段(可常量或引用 `{{input.xxx}}`)
|
|
218
|
+
- 通过 `formValue` 将业务入参映射到插件表单字段(可常量或引用 `{% raw %}{{input.xxx}}{% endraw %}`)
|
|
219
219
|
- **PluginInstanceAIJson(pluginInstance.ai.json)**:工程转化层产物,是 pluginInstance 的**运行时投影 / 调用合同(Runtime Spec)**。
|
|
220
220
|
- 包含插件定位信息、actions 入口列表、input/output schema、outputMode、readme 等
|
|
221
221
|
- Code Agent 在生成**调用代码**前,必须读取它作为权威依据(Server 侧用 `CapabilityService`,Client 侧用 `capabilityClient`)
|
|
@@ -258,6 +258,7 @@ Plugin 的具体内容以JSON格式给出,例如:
|
|
|
258
258
|
|
|
259
259
|
|
|
260
260
|
PluginInstance 的配置以 JSON 形式输出,例如:
|
|
261
|
+
{% raw %}
|
|
261
262
|
```json
|
|
262
263
|
{
|
|
263
264
|
"id": "create_feishu_group", // 全局唯一语义化 ID
|
|
@@ -279,6 +280,7 @@ PluginInstance 的配置以 JSON 形式输出,例如:
|
|
|
279
280
|
}
|
|
280
281
|
}
|
|
281
282
|
```
|
|
283
|
+
{% endraw %}
|
|
282
284
|
|
|
283
285
|
**注意**paramsSchema 支持以下 4 种参数类型,需要按下面规定的格式进行填充:
|
|
284
286
|
|
|
@@ -560,7 +562,7 @@ PluginInstanceAIJson 的配置以 JSON 形式输出,例如:
|
|
|
560
562
|
| 未按 `outputSchema` 解析返回值,猜测返回结构 | 严格按 `get_plugin_ai_json` 返回的 `outputSchema` 读取字段,流式和非流式均适用 |
|
|
561
563
|
| 未输出 Schema 摘录卡就直接写调用代码 | 先完成“编码前闸门”中的摘录卡,再开始编码 |
|
|
562
564
|
| 改完未做真实调用冒烟就宣告完成 | 至少完成一次 unary/stream 真实调用验证,并附最小日志字段 |
|
|
563
|
-
| formValue 中用 `["{{input.xxx}}"]` 包装已经是 `type: array` 的 paramsSchema 参数 | 当 paramsSchema 定义为 array 时,formValue 应透传 `"{{input.xxx}}"`,不要再包一层数组 |
|
|
565
|
+
| formValue 中用 `{% raw %}["{{input.xxx}}"]{% endraw %}` 包装已经是 `type: array` 的 paramsSchema 参数 | 当 paramsSchema 定义为 array 时,formValue 应透传 `{% raw %}"{{input.xxx}}"{% endraw %}`,不要再包一层数组 |
|
|
564
566
|
| 通过 `getDataloom().capability` 或 `(dataloom as any).capability` 调用插件 | `capabilityClient` 是独立导入,不通过 dataloom 访问。dataloom 仅提供 storage 和 service |
|
|
565
567
|
| Client 侧调用插件时,先通过 dataloom 上传文件拿 URL 再传给插件 | Client 侧可直接传 File/Blob 对象给 `capabilityClient`,SDK 自动处理上传。适用于所有文件类型字段(`format` 为 `file`/`picture`/`plugin-file-url`)。Server 侧仍需传 URL |
|
|
566
568
|
| 前端调用插件后不保存结果到数据库,导致页面刷新后数据丢失 | 需要持久化时:优先在 Server 侧调用并直接落库(方案A);若在 Client 侧调用,必须通过已有 CRUD 接口立即保存结果(方案B) |
|
|
@@ -642,7 +644,7 @@ try {
|
|
|
642
644
|
| 场景 | 正确做法 | 示例 |
|
|
643
645
|
|------|---------|------|
|
|
644
646
|
| 需求明确的**固定**接收人/配置 | 在 `plugin_instance CREATE` 的 `formValue` 中直接写死 | `formValue.receiverUserList: ["1854102143505690"]` |
|
|
645
|
-
| **动态**接收人/配置(按角色/条件变化) | 从配置/平台 API/DB 获取,传入 `input` 参数 | `formValue.receiverUserList: "{{input.receiverIds}}"` |
|
|
647
|
+
| **动态**接收人/配置(按角色/条件变化) | 从配置/平台 API/DB 获取,传入 `input` 参数 | `{% raw %}formValue.receiverUserList: "{{input.receiverIds}}"{% endraw %}` |
|
|
646
648
|
|
|
647
649
|
> **关键区分**:`formValue` 中配置固定值 ≠ 代码中硬编码。`formValue` 是插件实例的声明式配置,修改不需要改代码;而代码中硬编码的值散落在业务逻辑中,难以维护。
|
|
648
650
|
|
|
@@ -8,6 +8,8 @@ steering-topic: plugin_guide
|
|
|
8
8
|
match-template-name: nestjs-react-fullstack
|
|
9
9
|
---
|
|
10
10
|
|
|
11
|
+
{% raw %}
|
|
12
|
+
|
|
11
13
|
# Plugin 集成指南(本地开发)
|
|
12
14
|
|
|
13
15
|
AI 插件集成规范,使用 lark-cli 命令管理插件包与实例,通过 capabilityClient / CapabilityService 生成调用代码。
|
|
@@ -578,3 +580,5 @@ npx @lark-apaas/miaoda-cli plugin list --id <instance_id>
|
|
|
578
580
|
5. **禁止用 `npm install` 安装插件包** — 插件包和 npm 包是两套独立机制。
|
|
579
581
|
6. **禁止 Mock** — 必须走真实插件实例调用链路。
|
|
580
582
|
7. **formValue 禁止 Handlebars 控制语法** — 仅允许 `{{input.xxx}}`。
|
|
583
|
+
|
|
584
|
+
{% endraw %}
|
|
@@ -180,7 +180,7 @@ const structured = await (jsonExtractor as any).call('textToJson', { text: rawRe
|
|
|
180
180
|
- **Plugin(插件)**:底层承载单元,包含插件元信息与表单定义(form.schema)。模型侧只感知插件及其表单字段,不感知插件内部实现细节。
|
|
181
181
|
- **PluginInstance(插件实例配置)**:基于某个 Plugin 的表单做"业务封装",以 **单文件 JSON** 的形式存储(每个插件实例一个文件,语义化 id)。
|
|
182
182
|
- 通过 `paramsSchema` 暴露业务入参
|
|
183
|
-
- 通过 `formValue` 将业务入参映射到插件表单字段(可常量或引用 `{{input.xxx}}`)
|
|
183
|
+
- 通过 `formValue` 将业务入参映射到插件表单字段(可常量或引用 `{% raw %}{{input.xxx}}{% endraw %}`)
|
|
184
184
|
- **PluginInstanceAIJson(pluginInstance.ai.json)**:工程转化层产物,是 pluginInstance 的**运行时投影 / 调用合同(Runtime Spec)**。
|
|
185
185
|
- 包含插件定位信息、actions 入口列表、input/output schema、outputMode、readme 等
|
|
186
186
|
- Code Agent 在生成**调用代码**前,必须读取它作为权威依据(使用 `capabilityClient` 调用)
|
|
@@ -224,6 +224,7 @@ Plugin 的具体内容以JSON格式给出,例如:
|
|
|
224
224
|
|
|
225
225
|
PluginInstance 的配置以 JSON 形式输出,例如:
|
|
226
226
|
|
|
227
|
+
{% raw %}
|
|
227
228
|
```json
|
|
228
229
|
{
|
|
229
230
|
"id": "create_feishu_group", // 全局唯一语义化 ID
|
|
@@ -245,6 +246,7 @@ PluginInstance 的配置以 JSON 形式输出,例如:
|
|
|
245
246
|
}
|
|
246
247
|
}
|
|
247
248
|
```
|
|
249
|
+
{% endraw %}
|
|
248
250
|
|
|
249
251
|
**注意**paramsSchema 支持以下 4 种参数类型,需要按下面规定的格式进行填充:
|
|
250
252
|
|
|
@@ -5,6 +5,8 @@ steering: true
|
|
|
5
5
|
steering-topic: react_three_fiber
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
+
{% raw %}
|
|
9
|
+
|
|
8
10
|
# React Three Fiber (R3F) 编码指南
|
|
9
11
|
|
|
10
12
|
实现 3D 场景 / 3D 游戏 / 3D 数据可视化时, MUST 用 **react-three-fiber + drei** 声明式栈, 严禁用 React + CSS / SVG / `transform: rotateX` 伪 3D.
|
|
@@ -220,3 +222,5 @@ npm install react-error-boundary # 必装! Canvas 外包 ErrorBoundary
|
|
|
220
222
|
|
|
221
223
|
- `client-coding-guide` - vite-react 通用编码规范
|
|
222
224
|
- `component-conventions` - React 组件命名 / 文件结构
|
|
225
|
+
|
|
226
|
+
{% endraw %}
|