@lark-apaas/coding-steering 0.1.18-dev.21ea0ba → 0.1.18-dev.28c4f05
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 +27 -13
- 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 +13 -6
- package/steering/design-html/skills/pptx-style-extract/scripts/draft.py +957 -197
- package/steering/design-html/skills/pptx-style-extract/scripts/extract.py +48 -7
- package/steering/design-html/skills/pptx-style-extract/scripts/ooxml.py +1 -1
- package/steering/design-html/skills/pptx-style-extract/scripts/package.py +115 -149
- package/steering/design-html/skills/pptx-style-extract/scripts/parts.py +3 -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 +16 -10
- package/steering/design-html/skills/pptx-style-extract/v2-format-spec.md +14 -11
package/package.json
CHANGED
|
@@ -23,8 +23,8 @@ If the user only asks to summarize, translate, extract text, or review an existi
|
|
|
23
23
|
- Pillow may be present and improves image hashing, palette extraction, contact sheets, and asset transcoding. If Pillow is missing or fails, continue with degraded extraction and record the degradation in outputs.
|
|
24
24
|
- Do not run `pip install`, `uv pip install`, `npm install`, or any other dependency installation in the user session.
|
|
25
25
|
- Run Python with bytecode disabled: `PYTHONDONTWRITEBYTECODE=1 python3 -B ...`.
|
|
26
|
-
- Default working directory:
|
|
27
|
-
-
|
|
26
|
+
- Default working directory: `tmp/pptx-style-extract/<unique-id>/`, **relative to the project root**. The sandbox file tools (`read_file` / `write` / `multi_edit`) only accept project-relative paths — an absolute `/tmp/...` workdir makes every draft edit fall back to hand-written shell scripts.
|
|
27
|
+
- Intermediates stay under the project's `tmp/`; they are scratch, not deliverables. Only the final deck assets copied by the deck authoring step enter the app source tree.
|
|
28
28
|
- `package.py` only runs v1 checks when `--check-v1 <path>` is passed or `DSM_V1_DIR` points to a sibling checker. It must not rely on developer-machine paths in the sandbox.
|
|
29
29
|
|
|
30
30
|
## Fast Path Contract
|
|
@@ -39,11 +39,19 @@ Generated packages must make the attachment-consumption path explicit in `design
|
|
|
39
39
|
|
|
40
40
|
## 1. 抽取
|
|
41
41
|
|
|
42
|
+
先拿到附件的**真实**文件路径——列目录,用列出来的名字,不要按附件标题猜文件名:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
ls -la .agent/<conversation_id>/attachments/
|
|
46
|
+
```
|
|
47
|
+
|
|
42
48
|
```bash
|
|
43
|
-
mkdir -p
|
|
44
|
-
PYTHONDONTWRITEBYTECODE=1 python3 -B scripts/extract.py
|
|
49
|
+
mkdir -p tmp/pptx-style-extract/<unique-id>
|
|
50
|
+
PYTHONDONTWRITEBYTECODE=1 python3 -B scripts/extract.py <上一步列出的真实路径> tmp/pptx-style-extract/<unique-id>/stage1
|
|
45
51
|
```
|
|
46
52
|
|
|
53
|
+
**目录里没有 `.pptx` / `.potx`**(只有 `skills/` 之类)说明这次上传没有落成沙箱本地文件,本 skill 无法执行:如实告诉用户模板文件在沙箱里取不到、请重新上传或反馈平台,然后停下。此时**不要**用 `summarize_attachment_or_file` 的文本摘要当风格来源,也不要用 `generate_image` 自造配图充当模板视觉——那样产出的 deck 与模板无关,却看起来像成功了。
|
|
54
|
+
|
|
47
55
|
一条命令出全部:`extract.json`(普查数值)、`media-out/`(候选图)、`ref/`(审计层)、`l-out/`(**四件判断单草案 + BRIEF.md + contact-sheet.png + layout-sheet.png**)。
|
|
48
56
|
|
|
49
57
|
非 OOXML / 损坏 / 加密 → 报告调用方,不产半成品。
|
|
@@ -52,16 +60,20 @@ PYTHONDONTWRITEBYTECODE=1 python3 -B scripts/extract.py <pptx_path> /tmp/pptx-st
|
|
|
52
60
|
|
|
53
61
|
1. 读 `<outdir>/l-out/BRIEF.md` —— 事实、草案依据、待判断清单一页看完。
|
|
54
62
|
2. 看 `<outdir>/l-out/contact-sheet.png` 与 `<outdir>/l-out/layout-sheet.png` —— 候选图和代表页型都已拼好,对应 BRIEF 的表。
|
|
55
|
-
3.
|
|
56
|
-
4.
|
|
63
|
+
3. 用 `multi_edit` 一次批量改掉四件草案里的每一处 `TODO`:风格命名与气质、模板特有硬规则、以及下面第 4 条的禁放区。草案就在项目内相对路径下,直接编辑,**不要写脚本去改 YAML**。
|
|
64
|
+
4. `layouts.yaml` 顶部 `bg_rules:` 按**背景资产**分组(不是按页型),逐张背景填 `text_safe` / `avoid` / `pairing_rule`:看图标清视觉主体、强光斑、深色透明区,打包时自动并入用该背景的所有页型。标题、正文、关键数字、图表、卡片、时间线及其容器的外接矩形都不得压住背景主体,透明容器也不能跨进禁放区。
|
|
65
|
+
|
|
66
|
+
`layouts.yaml` 的 `layouts:` 段是普查数值——页型、坐标、字号、色值、对齐、以及各页型自带的 `role: logo` 资产槽全部直读,**一个字都不要动**。模板自己给版式起过名时 `names:` 已经填好,读一遍确认表意即可。
|
|
57
67
|
|
|
58
68
|
判断口径:
|
|
59
69
|
|
|
60
70
|
- **满屏图默认属背景族**——整幅替换底图的艺术图/摄影图就是背景(首页那张记 `role: cover`);「内容图不进包」只指内容区里的图表、截图、配图。
|
|
61
71
|
- **logo 宁缺勿错**——看图确认有品牌文字或标志图形才留;拿不准就把该条从 `manifest.yaml` 删掉,在 `gaps` 写一句,候选图留在 `media-out/`。
|
|
62
72
|
- **数值只改名不改值**——草案里的坐标/色值/字号来自普查;确需推导值(CJK 行高转译、投影尺度上抬)在 manifest 写 `derived:` 声明理由,机检认声明。
|
|
63
|
-
-
|
|
64
|
-
-
|
|
73
|
+
- **页型命名**——模板自带版式名时草案已填好;只有靠样张聚类的模板(`names` 里还是 TODO)才需要按 BRIEF 的 slot 原文起中文名。
|
|
74
|
+
- **页型角色**——`roles:` 段有 TODO 时,看 `layout-sheet.png` 上对应的代表页定 `cover|section|content|quote|closing|blank|custom`。草案只把客观事实摆在注释里(代表页页码、页数、文字块数、字号序列、图片数、有无满屏底图),不替你下结论:字号多大算章节页、文字块多少算密集页,每套模板的答案都不一样。
|
|
75
|
+
- **资产位置不要写成全局规则**——同一个 logo 常按页型换位换尺寸(封面一个位置一个尺寸、内容页另一个)。位置只存在于 `layouts.md` 各页型的 `slots`,正文里只说「按该页型的 slot 摆放,没有就不放」。
|
|
76
|
+
- **背景和版式要配对**——真实背景不是纯色底。看 `contact-sheet.png` 判断每张背景的视觉主体/禁放区,看 `layout-sheet.png` 判断文字与卡片落点;`background` 草案已按版式直读填好,你只需在 `bg_rules` 里逐张背景补 `text_safe`、`avoid`、`pairing_rule`,避免消费 Agent 把文字、图表、卡片、时间线、标题容器、正文容器或宽透明容器的外接矩形压到金字塔、人物、产品图、强光斑或深色区域上。
|
|
65
77
|
- 双主题只认 BRIEF 的 `themes`;单主题包不写 `theme` 字段。
|
|
66
78
|
|
|
67
79
|
要更多依据时用 `PYTHONDONTWRITEBYTECODE=1 python3 -B scripts/query.py <outdir> <子命令>`(`shapes` / `colors` / `fonts` / `text-scale` / `images` / `clusters` / `media` / `slides` / `layouts` / `recipes` / `grids` / `get <点路径>`)。读数一律走它,**不写解析脚本、不读 XML、不开浏览器**。
|
|
@@ -69,10 +81,12 @@ PYTHONDONTWRITEBYTECODE=1 python3 -B scripts/extract.py <pptx_path> /tmp/pptx-st
|
|
|
69
81
|
## 3. 打包
|
|
70
82
|
|
|
71
83
|
```bash
|
|
72
|
-
PYTHONDONTWRITEBYTECODE=1 python3 -B scripts/package.py <outdir> <outdir>/l-out <包输出目录>
|
|
84
|
+
PYTHONDONTWRITEBYTECODE=1 python3 -B scripts/package.py <outdir> <outdir>/l-out <包输出目录> --force
|
|
73
85
|
```
|
|
74
86
|
|
|
75
|
-
exit 0 即交付。FAIL 会指名道姓(TODO 未改 / 数值不可追溯 / 枚举误用 / 引用断链 /
|
|
87
|
+
exit 0 即交付。FAIL 会指名道姓(TODO 未改 / 数值不可追溯 / 枚举误用 / 引用断链 / 体量超标),改判断单后**用同一个包目录重跑**(`--force` 就是为回修准备的),不要去改产物;体量 FAIL 走降级链(审计出包 → sidecar 化 → archetype 收缩)。
|
|
88
|
+
|
|
89
|
+
跑之前先确认四件判断单里 `TODO` 已经全部改完(`grep -rn TODO <outdir>/l-out`)——`body.md` 最容易漏。
|
|
76
90
|
|
|
77
91
|
## 完成判据
|
|
78
92
|
|
|
@@ -93,10 +107,10 @@ Read `<pack_dir>/design.md` first, especially `## Usage`, `## Hard Rules`, color
|
|
|
93
107
|
When generating a deck:
|
|
94
108
|
|
|
95
109
|
1. Call `copy_starter_component` with `kind: "deck-stage.js"`.
|
|
96
|
-
2. Build `<deck-stage width="
|
|
110
|
+
2. Build `<deck-stage width="<canvas width>" height="<canvas height>">` using the `canvas` declared in `layouts.md` — source decks are not always 16:9, and a default-sized stage shifts every coordinate on the page. Each slide is one static `<section>`.
|
|
97
111
|
3. Inline CSS variables from `design.md` into the HTML `<style>` block using a `--ppt-*` prefix.
|
|
98
|
-
4. Map `layouts.md` slots `[x,y,w,h]`
|
|
99
|
-
5. Copy `<pack_dir>/assets/` into the project, for example `assets/pptx-style/<pack-name>/`, and reference those copied files with relative URLs.
|
|
112
|
+
4. Map `layouts.md` slots to absolute-positioned elements inside each section: `box` is `[x,y,w,h]`, and each slot also carries the template's own `size` / `weight` / `color` / `align` / `valign` — use them as-is instead of picking your own type scale. A slot with `asset` is a fixed image element (logo, mark): place that asset at that box on that archetype only. Do not reflow any of this as generic web grids.
|
|
113
|
+
5. Copy `<pack_dir>/assets/` into the project, for example `assets/pptx-style/<pack-name>/`, and reference those copied files with relative URLs. The scratch `tmp/pptx-style-extract/...` path must never appear in the final HTML.
|
|
100
114
|
6. Use the font stacks and fallback rules from `design.md`; do not install fonts or dependencies at runtime.
|
|
101
115
|
7. Run the slide preflight checks: no resource failures, no section overflow, and sampled screenshots follow the package colors, typography, layouts, assets, and Hard Rules.
|
|
102
116
|
|
|
@@ -10,10 +10,10 @@
|
|
|
10
10
|
|
|
11
11
|
version: 2
|
|
12
12
|
|
|
13
|
-
# fallback 用到的 web
|
|
14
|
-
# verified: true = 该镜像已 curl
|
|
13
|
+
# fallback 用到的 web 字体探测状态(镜像 https://miaoda.feishu.cn/fonts/css2)。
|
|
14
|
+
# verified: true = 该镜像已 curl 验证返回 @font-face(2026-08-06)。
|
|
15
15
|
# 状态过期不影响正确性:L4 对 verified 为 false 或存疑的字体按 dsm-v1-spec 字体条款
|
|
16
|
-
# 重新 curl
|
|
16
|
+
# 重新 curl 验证后再用,加载不到降到下一档。
|
|
17
17
|
web-fonts:
|
|
18
18
|
"Noto Sans SC": {verified: true}
|
|
19
19
|
"Noto Serif SC": {verified: true}
|
|
@@ -11,11 +11,16 @@ from ooxml import (NS, alias_group, family_of, local, luminance, raw_token, read
|
|
|
11
11
|
# 1920px canvas; fullscreen detection has its own independent threshold.
|
|
12
12
|
EPS_PX = 0.005 * 1920 # 9.6 px
|
|
13
13
|
FULLSCREEN_MIN_PCT = 95.0 # 上不封顶(尺寸维度,兼容旧口径)
|
|
14
|
-
FULLSCREEN_COVERAGE = 0.95 #
|
|
15
|
-
REPEAT_MIN = 2
|
|
14
|
+
FULLSCREEN_COVERAGE = 0.95 # 画布覆盖率判据:溢出/略未贴边都算背景,偏移出画布的大图不算
|
|
15
|
+
REPEAT_MIN = 2 # 出现 <2 次谈不上「重复位」
|
|
16
16
|
|
|
17
|
+
# 下面三个跨模块共用,import 处不要再抄一份字面量
|
|
18
|
+
SMALL_IMG_W_PCT = 25.0 # 占画布宽小于此值算小图(图标/角标/logo),不是内容配图
|
|
19
|
+
LUM_MID = 0.5 # 深/浅分界,Rec.709 相对亮度
|
|
20
|
+
ASSET_WARN_SINGLE = 500 * 1024 # 单张压缩后体积的 WARN 线,对齐 v2-format-spec §5 V2-6
|
|
17
21
|
|
|
18
|
-
|
|
22
|
+
|
|
23
|
+
def canvas_coverage(box, cw, ch):
|
|
19
24
|
"""图片与画布交集面积 / 画布面积。溢出(bleed)交集封顶于画布,天然 ≤1。"""
|
|
20
25
|
if not box:
|
|
21
26
|
return 0.0
|
|
@@ -99,8 +104,10 @@ def image_census(shape_recs, bg_images, units):
|
|
|
99
104
|
'in_group': any(o.get('in_group') for o in c['occs']),
|
|
100
105
|
})
|
|
101
106
|
clusters.sort(key=lambda c: -c['count'])
|
|
102
|
-
|
|
103
|
-
|
|
107
|
+
cw, ch = float(units.w), float(units.h)
|
|
108
|
+
fs = [o for o in occs if canvas_coverage(o.get('box'), cw, ch) >= FULLSCREEN_COVERAGE]
|
|
109
|
+
fs_clusters = [c for c in clusters
|
|
110
|
+
if canvas_coverage(c.get('box'), cw, ch) >= FULLSCREEN_COVERAGE]
|
|
104
111
|
crop_sigs = {_crop_sig(o.get('crop')) for o in occs} - {''}
|
|
105
112
|
images.append({
|
|
106
113
|
'media': media,
|
|
@@ -157,11 +164,10 @@ def _crop_sig(crop):
|
|
|
157
164
|
# ------------------------------------------ S5b media content clustering (素材聚类)
|
|
158
165
|
# Two levels: sha256 byte identity, then perceptual identity. The perceptual level
|
|
159
166
|
# is a dHash *prefilter* followed by a pixel confirmation, because dHash alone does
|
|
160
|
-
# not separate
|
|
161
|
-
#
|
|
162
|
-
#
|
|
163
|
-
#
|
|
164
|
-
# thumbnail difference decides. Calibration table is in reports/agent-extract-round2.md.
|
|
167
|
+
# not separate them on its own: distinct images can land at hamming distance 0
|
|
168
|
+
# while identical ones land several bits apart, so no single hamming threshold
|
|
169
|
+
# splits the two populations. The hash therefore only narrows the candidate set
|
|
170
|
+
# and the thumbnail pixel difference makes the call.
|
|
165
171
|
DHASH_PREFILTER_MAX = 10 # hamming distance over the 64-bit dHash
|
|
166
172
|
PIXDIFF_MAX = 5.0 # mean per-channel |Δ| over the 64x64 thumbnail
|
|
167
173
|
THUMB_PX = 64
|
|
@@ -546,7 +552,7 @@ def font_census(shape_recs, txstyles_by_master, themes, scheme_by_part=None):
|
|
|
546
552
|
# A paragraph with no runs at all (only <a:endParaRPr/>) declares a
|
|
547
553
|
# default that renders no glyph, so it is counted under its own source
|
|
548
554
|
# and kept out of `rendered_n` — otherwise a face backing zero visible
|
|
549
|
-
# text can outrank the deck's actual typeface
|
|
555
|
+
# text can outrank the deck's actual typeface.
|
|
550
556
|
dr = p.get('defRPr') or {}
|
|
551
557
|
if dr:
|
|
552
558
|
covered = {slot for slot in ('latin', 'ea', 'cs')
|
|
@@ -875,7 +881,7 @@ def fit_grids(shape_recs):
|
|
|
875
881
|
"""逐页把同类形状拟合成栅格:列数/列起点/列步距 + 行数/行步距。
|
|
876
882
|
|
|
877
883
|
按**中心**分档而不是按左上角——一排 logo 尺寸各不相同却居中对齐于等宽格,
|
|
878
|
-
|
|
884
|
+
用左上角看不出列。
|
|
879
885
|
因此这里也不要求同尺寸,只要求同页同 kind 同层级。
|
|
880
886
|
"""
|
|
881
887
|
buckets = defaultdict(list)
|
|
@@ -36,7 +36,7 @@ V2_SECTIONS = ['themes', 'default-theme', 'theme-mechanism', 'color-confidence',
|
|
|
36
36
|
# —— V2-11 YAML 1.1 布尔字面量(PyYAML 会把这些键名解析成 True/False)——
|
|
37
37
|
BOOL_LITERALS = {'y', 'yes', 'n', 'no', 'true', 'false', 'on', 'off'}
|
|
38
38
|
|
|
39
|
-
# —— V2-6 体积上限(
|
|
39
|
+
# —— V2-6 体积上限(KB = 1024)——
|
|
40
40
|
ASSET_WARN_SINGLE = 500 * 1024 # 压缩图单张(受检对象 = 条目 path 指向的包内文件)
|
|
41
41
|
ASSET_MAX_TOTAL = 20 * 1024 * 1024 # 包内资产总量(assets/** ∪ 条目 path/full 并集)
|
|
42
42
|
|
|
@@ -305,7 +305,7 @@ class MdFile:
|
|
|
305
305
|
self.keys += [(k, n + offset) for k, n in sub.keys]
|
|
306
306
|
|
|
307
307
|
def section(self, key):
|
|
308
|
-
"""先取 frontmatter
|
|
308
|
+
"""先取 frontmatter,再取正文围栏块——存量风格包有把 layouts 写在正文围栏块里的。"""
|
|
309
309
|
if isinstance(self.data.get(key), (dict, list)):
|
|
310
310
|
return self.data[key]
|
|
311
311
|
for block in self.fenced:
|
|
@@ -632,7 +632,7 @@ def _pack_asset_files(pack):
|
|
|
632
632
|
|
|
633
633
|
|
|
634
634
|
def rule_v2_6(pack):
|
|
635
|
-
"""V2-6 压缩图单张 >500KB WARN;包内资产总量 >20MB FAIL
|
|
635
|
+
"""V2-6 压缩图单张 >500KB WARN;包内资产总量 >20MB FAIL"""
|
|
636
636
|
res = Result('V2-6', '压缩图单张 >500KB WARN;包内资产总量 >20MB FAIL')
|
|
637
637
|
# 500KB 只管压缩图 = 条目 path 指向的包内文件;
|
|
638
638
|
# full(原图)天然大,豁免单张 WARN 但计入总量;url 条目包内无文件,不适用。
|
|
@@ -883,9 +883,10 @@ def main():
|
|
|
883
883
|
pack = Pack(sys.argv[1])
|
|
884
884
|
print(f'包: {pack.root}')
|
|
885
885
|
print('文件: ' + ', '.join(md.name for md in pack.files))
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
886
|
+
# 这个解析器只服务于机检取数,它读不懂不代表内容有问题——产物是逐字节写出去的,
|
|
887
|
+
# 消费模型读多行 CSS 之类毫无障碍。所以这里既不静默跳过,也不拦住流水线:
|
|
888
|
+
# 把原文交出来,由模型看一眼确认内容没丢。
|
|
889
|
+
unread = [(md.name, lineno, text) for md in pack.files for lineno, text in md.anomalies]
|
|
889
890
|
print('-' * 72)
|
|
890
891
|
failed = 0
|
|
891
892
|
for rule in RULES:
|
|
@@ -900,6 +901,12 @@ def main():
|
|
|
900
901
|
failed += len(res.fails)
|
|
901
902
|
print('-' * 72)
|
|
902
903
|
print(f'结论: {"FAIL" if failed else "PASS"}({failed} 条 FAIL)')
|
|
904
|
+
if unread:
|
|
905
|
+
print('-' * 72)
|
|
906
|
+
print(f'待你确认({len(unread)} 行机检解析器没读懂,产物里内容仍在,'
|
|
907
|
+
f'请对照原文确认没丢东西):')
|
|
908
|
+
for name, lineno, text in unread:
|
|
909
|
+
print(f' {name}:{lineno} {text.strip()[:100]}')
|
|
903
910
|
sys.exit(1 if failed else 0)
|
|
904
911
|
|
|
905
912
|
|