@lark-apaas/coding-steering 0.1.18-dev.857e860 → 0.1.18-dev.9dbf919
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/pptx-style-extract/SKILL.md +40 -18
- package/steering/design-html/skills/pptx-style-extract/font-fallback.yaml +3 -3
- package/steering/design-html/skills/pptx-style-extract/scripts/census.py +18 -12
- package/steering/design-html/skills/pptx-style-extract/scripts/check_v2.py +153 -8
- package/steering/design-html/skills/pptx-style-extract/scripts/draft.py +973 -182
- package/steering/design-html/skills/pptx-style-extract/scripts/extract.py +319 -23
- package/steering/design-html/skills/pptx-style-extract/scripts/ooxml.py +1 -1
- package/steering/design-html/skills/pptx-style-extract/scripts/package.py +165 -19
- package/steering/design-html/skills/pptx-style-extract/scripts/parts.py +6 -3
- package/steering/design-html/skills/pptx-style-extract/scripts/query.py +2 -2
- package/steering/design-html/skills/pptx-style-extract/scripts/render_pages.py +14 -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_layout_css.py +98 -0
- package/steering/design-html/skills/pptx-style-extract/v2-format-spec.md +19 -12
- package/steering/design-html/skills/slide-deck/SKILL.md +18 -3
- package/steering/design-html/skills/slide-deck/scripts/check_local_references.py +179 -0
|
@@ -19,10 +19,9 @@ metadata:
|
|
|
19
19
|
|
|
20
20
|
## PPTX/POTX 模板附件前置步骤
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
如果对话里出现 `.pptx` 或 `.potx` 附件,并且当前任务是制作/生成/改做一个 PPT、演示文稿、slides、deck,**默认附件就是模板或视觉参考,先运行 `pptx-style-extract` skill**,读取产出的 `design.md`、`layouts.md` 和 `assets/`,再开始写 deck。不需要等用户再说“按这个模板”。
|
|
23
23
|
|
|
24
|
-
-
|
|
25
|
-
- 只总结、翻译、提取内容、审阅已有 PPTX 时,不需要抽取风格。
|
|
24
|
+
- 只有用户明确要求只总结、只翻译、只提取内容、只审阅或直接修改已有 PPTX 时,才跳过风格抽取。
|
|
26
25
|
- 不要用 `SummarizeAttachmentOrFile` 的 Markdown/文本摘要替代风格抽取;文本摘要不包含母版、色板、字体、版式坐标和素材角色。
|
|
27
26
|
|
|
28
27
|
## 动手前先问
|
|
@@ -51,6 +50,7 @@ deck-stage 组件会对每个 slotted 子元素做绝对定位——**绝不**
|
|
|
51
50
|
|
|
52
51
|
- 先读 `design.md` 的 Usage / Hard Rules / colors / typography / components / assets / safe-area。
|
|
53
52
|
- 再读 `layouts.md`,用其中的 layout archetype 和 slots 坐标生成页面;坐标单位已经是 px@1920,不需要 EMU 或 pt 换算。
|
|
53
|
+
- `layouts.md` 的 slot 中,`box` 只负责几何,机械展开为 `left/top/width/height`;`css` 是模板已转译好的最终渲染样式,原样写入元素 style。它优先于本 skill 的通用字号/间距建议,不得重新解释成另一套 token,也不得丢掉其中的 `padding`、对齐、行高、字距或旋转。
|
|
54
54
|
- 消费并落实 colors / typography / spacing / rounded / safe-area 等样式 token;可以用 CSS variables、类名或内联样式承载,但最终页面必须看得出这些 token 被系统性使用,而不是另起一套视觉系统。
|
|
55
55
|
- 背景和版式必须成对消费:选择某个 layout archetype 时,同时采用它声明的 `background`、`text_safe`、`avoid` / `pairing_rule`。标题、正文、图表、表格、卡片、时间线及其容器外接矩形不得进入背景禁放区;如果内容与背景主体冲突,换页型、拆页或缩小内容区域,不要只换背景色或把容器铺到禁放区。
|
|
56
56
|
- 把包内 `assets/` 复制到项目内相对目录并引用复制后的路径;最终 HTML 禁止引用 `/tmp` 或本机绝对路径。
|
|
@@ -144,6 +144,21 @@ deck-stage 组件会对每个 slotted 子元素做绝对定位——**绝不**
|
|
|
144
144
|
5. **把这套 token 当成每页的内容预算**:在上述数值下,一页正文区大约容纳 14 行正文、或 6 个两行 bullet——在 scratchpad 排内容时就按预算裁剪,而不是写完再看塞不塞得下。装不下的处置顺序是**拆页 > 删内容 > 换更省空间的版式**;缩小字号是最后手段,且绝不越过 24px 下限——靠缩字塞进去的页,只是把溢出换成了后排看不清。反过来,内容远少于预算的页按「视觉平衡」的出路增密或合并,而不是放大字号去撑面积。
|
|
145
145
|
6. 构建幻灯片,牢记每张幻灯片既是设计练习也是文案练习。在版式、文字内容和语调方面给予每张幻灯片应有的关注。遵循上述原则,确保每张幻灯片能独立成立;一个只看这一页的人,应当无需其他上下文就能理解其高层含义。
|
|
146
146
|
|
|
147
|
+
## 提交前资源完整性门禁
|
|
148
|
+
|
|
149
|
+
最终一次写入 HTML/CSS 后、调用 `run_commit` 前,必须从项目根目录运行:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
python3 <本skill目录>/scripts/check_local_references.py index.html
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
把 `<本skill目录>` 换成本 skill 的实际所在目录。脚本从最终 `index.html` 出发,递归检查 HTML/CSS 引用的每个项目内脚本、样式、图片、字体等文件是否真实存在;外部 URL 不做离线探测。
|
|
156
|
+
|
|
157
|
+
- 只有输出 `RESOURCE_CHECK: PASS` 才能提交。
|
|
158
|
+
- 输出 `RESOURCE_CHECK: FAIL` 时,先重新复制或修正列出的项目内文件,再原样重跑;不得删除引用来掩盖仍在使用的资源。
|
|
159
|
+
- 该检查针对最终工作区状态。较早执行过复制命令或检查,不能证明最终提交完整。
|
|
160
|
+
- `run_commit` 的静态检查跳过参数不能替代本门禁,也不能用来绕过失败;提交时必须包含检查通过所依赖的全部项目内文件。
|
|
161
|
+
|
|
147
162
|
## 验证要点
|
|
148
163
|
|
|
149
164
|
审阅时,用幻灯片构图规则——而非网页布局直觉——来检查版面。底部留白是不是缺陷,用「留白 ≠ 空洞」的归属判据:内容自身完整、下方是无边框的整块呼吸空间,这是正确的幻灯片构图——不要出于网页直觉把 `flex-start` 改成 `center`;空白被元素边界圈占的,是被动空洞,按「视觉平衡」的出路修。
|
|
@@ -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())
|