@capyclaw/ppt-skill-design 0.0.0-stage → 2.2.1

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,4 @@
1
+ # @capyclaw/ppt-skill-design(目录 dsh-ppt-skill-design) bundle patch:只注册 pptmaster-design 一个 skill provider。
2
+ - insert:
3
+ - id: ppt-skill-design
4
+ name: @capyclaw/ppt-skill-design
package/lib/index.mjs ADDED
@@ -0,0 +1,23 @@
1
+ /** @capyclaw/ppt-skill-design(目录 dsh-ppt-skill-design):单 skill provider。 */
2
+ import { fileURLToPath } from 'node:url'
3
+ import { createSkillProvider } from './provider.mjs'
4
+
5
+ export const name = 'ppt-skill-design'
6
+ export const inject = ['skills']
7
+ const SKILL = "pptmaster-design"
8
+
9
+ function defaultProvider(config = {}) {
10
+ return createSkillProvider({
11
+ assetRoot: config.assetRoot ?? fileURLToPath(new URL('../skills/', import.meta.url)),
12
+ contractRoot: config.contractRoot ?? fileURLToPath(new URL('../contract/', import.meta.url)),
13
+ contractFile: 'ppt-contract.json',
14
+ providerName: "dsh-ppt-design",
15
+ skillDirs: [SKILL],
16
+ contractNote: (version, skillName) => (String(skillName || '').startsWith('pptmaster-')
17
+ ? ''
18
+ : '契约:PPTist Workbench ' + version + '。动作前先调用 ppt_capabilities;工具缺失或 major 不一致时停止,并提示用户安装/升级 @capyclaw/ppt-suite。'),
19
+ })
20
+ }
21
+
22
+ export function createProvider(config = {}) { return defaultProvider(config) }
23
+ export function apply(ctx, config = {}) { ctx.skills.registerProvider(() => defaultProvider(config)) }
@@ -0,0 +1,82 @@
1
+ /** 通用 skill provider:从目录扫描 kebab-case SKILL.md,按需加载正文。 */
2
+ import { readFileSync } from 'node:fs'
3
+ import { readFile } from 'node:fs/promises'
4
+ import { isAbsolute, join, sep } from 'node:path'
5
+
6
+ const NAME_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/
7
+ const RANK = 600
8
+
9
+ function parseSkill(raw, path) {
10
+ const frontmatter = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/u.exec(raw)
11
+ if (!frontmatter?.[1]) throw new Error(`skill ${path} 缺少 YAML frontmatter`)
12
+ const meta = {}
13
+ for (const line of frontmatter[1].split(/\r?\n/)) {
14
+ const match = /^([A-Za-z0-9_-]+):\s*(.*)$/.exec(line)
15
+ if (match) meta[match[1]] = match[2].replace(/^['"]|['"]$/g, '').trim()
16
+ }
17
+ if (!meta.name || !meta.description) throw new Error(`skill ${path} 缺少 name/description`)
18
+ if (!NAME_RE.test(meta.name)) throw new Error(`skill ${path} name 必须是 kebab-case`)
19
+ return {
20
+ name: meta.name,
21
+ description: meta.description,
22
+ whenToUse: meta.whenToUse || undefined,
23
+ content: raw.slice(frontmatter[0].length).trim(),
24
+ }
25
+ }
26
+
27
+ /**
28
+ * @param {{
29
+ * assetRoot: string,
30
+ * skillDirs: string[],
31
+ * providerName?: string,
32
+ * contractRoot?: string,
33
+ * contractNote?: (version: string) => string
34
+ * }} config
35
+ */
36
+ export function createSkillProvider(config) {
37
+ const assetRoot = config.assetRoot
38
+ if (!assetRoot || !isAbsolute(assetRoot)) throw new Error('createSkillProvider: assetRoot 必须是绝对路径')
39
+ let contract = null
40
+ if (config.contractRoot) {
41
+ try {
42
+ contract = JSON.parse(readFileSync(join(config.contractRoot, config.contractFile || 'contract.json'), 'utf8'))
43
+ } catch { /* 非技能包可以没有契约快照 */ }
44
+ }
45
+ const dirs = config.skillDirs || []
46
+ if (!dirs.length) throw new Error('createSkillProvider: skillDirs 不能为空')
47
+ const candidates = dirs.map((dir) => {
48
+ const directory = join(assetRoot, dir)
49
+ const locator = join(directory, 'SKILL.md')
50
+ const parsed = parseSkill(readFileSync(locator, 'utf8'), locator)
51
+ if (parsed.name !== dir) throw new Error(`skill ${locator} name 必须是 ${dir}`)
52
+ return {
53
+ name: parsed.name,
54
+ description: parsed.description,
55
+ whenToUse: parsed.whenToUse,
56
+ invocation: { modelInvocable: true, userInvocable: true },
57
+ provider: config.providerName || 'dsh-plugin',
58
+ source: 'bundled',
59
+ rank: RANK,
60
+ resourceBase: { kind: 'directory', path: directory.split(sep).join('/') },
61
+ locator,
62
+ }
63
+ })
64
+ const byName = new Map(candidates.map((candidate) => [candidate.name, candidate]))
65
+ const noteFor = (name) => (contract && config.contractNote
66
+ ? config.contractNote(contract.contractVersion, name)
67
+ : '')
68
+
69
+ return {
70
+ name: config.providerName || 'dsh-plugin',
71
+ list: () => Promise.resolve(candidates),
72
+ async get(candidate, options = {}) {
73
+ const hit = byName.get(candidate.name)
74
+ if (!hit) throw new Error(`未知 skill ${candidate.name}`)
75
+ const raw = await readFile(hit.locator, { encoding: 'utf8', signal: options.signal })
76
+ const parsed = parseSkill(raw, hit.locator)
77
+ const { rank: _rank, locator: _locator, ...summary } = hit
78
+ const note = noteFor(candidate.name)
79
+ return { ...summary, content: parsed.content + (note ? `\n\n---\n\n${note}` : '') }
80
+ },
81
+ }
82
+ }
package/package.json CHANGED
@@ -1,6 +1,43 @@
1
1
  {
2
2
  "name": "@capyclaw/ppt-skill-design",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "2.2.1",
4
+ "description": "PPT skill:design token、版式、反 AI slop;只做视觉决策。",
5
+ "type": "module",
6
+ "main": "./lib/index.mjs",
7
+ "exports": {
8
+ ".": "./lib/index.mjs",
9
+ "./package.json": "./package.json"
10
+ },
11
+ "engines": {
12
+ "node": ">=18",
13
+ "dsh": "^0.2.0-rc.2"
14
+ },
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "dsh": {
19
+ "bundle": {
20
+ "patch": "./cordis.patch.yml"
21
+ },
22
+ "category": "skills",
23
+ "platform": "both"
24
+ },
25
+ "meta": {
26
+ "title": "PPT Skill · PPT 审美",
27
+ "description": "design token、版式、反 AI slop;只做视觉决策。"
28
+ },
29
+ "files": [
30
+ "lib/",
31
+ "skills/",
32
+ "contract/",
33
+ "cordis.patch.yml",
34
+ "README.md"
35
+ ],
36
+ "license": "MIT",
37
+ "scripts": {
38
+ "test": "node --test test/smoke.test.mjs"
39
+ },
40
+ "dependencies": {
41
+ "@capyclaw/plugin-kit": "^0.2.0"
42
+ }
43
+ }
@@ -0,0 +1,32 @@
1
+ ---
2
+ name: pptmaster-design
3
+ description: 方案 deck 设计系统:锁 design token(单一强调色、ink/paper、字体、12 列网格),按登记版式做视觉决策,反 AI slop;只做设计,不调用工具、不做审计、不改稿。
4
+ whenToUse: 用户明确要求配色、字体、版式、视觉调性、反 AI slop,或需要为成稿确定设计 token 时。
5
+ ---
6
+
7
+ # PPTMaster 设计系统
8
+
9
+ 本技能只负责 **视觉决策**。叙事由叙事技能负责,工具操作由执行技能负责,审计/截图由验收技能负责;本技能不调用 `ppt_*` 工具,也不修改 deck。
10
+
11
+ ## 硬纪律
12
+
13
+ | 纪律 | 治什么 |
14
+ |---|---|
15
+ | **全局锁一套 design token,单一强调色全程不换** | 配色跳变(橙突然变蓝) |
16
+ | **版式跟内容走,但吸附登记版式骨架,不自由发明** | 黑块遮挡 / 元素乱叠 |
17
+ | **几何图形里不写可见文字** | 坐标轴/图表文字翻转 |
18
+ | **内置简洁版式(封面/章节/要点/两栏/大数字/引用/结尾)视为登记版式** | 避免临时发明未登记结构 |
19
+ | **反 AI slop**:无渐变、无 emoji 图标、无圆角卡片+左 border 套路、无 SVG 人像 | 一眼 AI、掉档 |
20
+
21
+ ## 流程
22
+
23
+ 1. **锁 token**:从客户 VI 取色;没有就从预设主题选一套。写死 `{accent, ink, paper, font_sans, grid:12}`,全 deck 引用;不从记忆猜品牌色。
24
+ 2. **选版式**:数据→大数字/矩阵/条形;流程→时间线/循环;对比→左右对照;并列判断→等权卡片;结论→大字 statement。每页从登记版式选,不临时发明。
25
+ 3. **排版**:12 列网格、左对齐贴近内容轴;中文大标题按字数分档防溢出;主标题:正文字号 ≥ 8:1。
26
+ 4. **反 slop**:每个元素必须挣得位置;真图未就位用诚实 placeholder,不硬凑丑 SVG;同一张图不重复。
27
+
28
+ ## 输出
29
+
30
+ 输出 **design token + 版式选择 + 排版约束**,交给成稿/执行技能落地。不要在本技能内改 deck,也不要把审计结论当成设计决策。
31
+
32
+ 详细规则:`references/design-tokens.md`、`references/layout-system.md`、`references/anti-ai-slop.md`。
@@ -0,0 +1,18 @@
1
+ # 反 AI Slop
2
+
3
+ 这些是训练数据的“视觉最大公约数”,一眼 AI、抹掉品牌识别度——禁用:
4
+
5
+ - 紫色/多色渐变。
6
+ - emoji 当图标;用同一套线性图标或纯文字。
7
+ - 圆角卡片 + 左侧 border accent 的套路。
8
+ - 用 SVG/几何图形画人脸、插画或复杂图标。
9
+ - 满屏高亮色;强调色只占很小面积。
10
+ - 用内容填充凑数;每个元素必须挣得位置。
11
+
12
+ 替代方案:
13
+
14
+ - 严格使用客户 VI 或预设 token。
15
+ - 无衬线(瑞士风)或 serif display(杂志风),一份 deck 不混。
16
+ - 直角、纯色、不透明;只允许 1px hairline 分割线。
17
+ - 极致字号对比和大幅留白。
18
+ - 真图未就位时用带标签的诚实灰块,不硬凑丑图。
@@ -0,0 +1,36 @@
1
+ # Design Token 与主题
2
+
3
+ ## 先锁 token,再画任何一页
4
+
5
+ ```json
6
+ {
7
+ "accent": "#002FA7",
8
+ "ink": "#0a0a0a",
9
+ "paper": "#fafaf8",
10
+ "font_sans": "system-ui / 客户 VI 字体",
11
+ "grid": 12
12
+ }
13
+ ```
14
+
15
+ - 客户有 VI:用客户主色作为唯一 accent。
16
+ - 没有 VI:从预设主题选一套,不要临时调色。
17
+ - 绝不从记忆猜品牌色;必须来自客户 VI 或明确预设。
18
+
19
+ ## 单一强调色铁律
20
+
21
+ - 一份 deck 只用一套主题,全程不换 accent。
22
+ - 不混搭两个高亮色;accent 只用于关键数字/单字符/关键线条。
23
+ - 灰阶保持一致:paper / grey-1 / grey-2 / grey-3 / ink;不要用纯白纯黑替代全部灰阶。
24
+
25
+ ## 预设主题
26
+
27
+ | 主题 | accent | 适合 |
28
+ |---|---|---|
29
+ | 克莱因蓝 | `#002FA7` | 通用/商业/科技,默认 |
30
+ | 柠檬黄 | `#FFD500` | 年轻/运动/零售;accent 上文字用纯黑 |
31
+ | 柠檬绿 | `#C5E803` | 生态/未来/Z 世代;accent 上文字用纯黑 |
32
+ | 安全橙 | `#FF6B35` | 工业/警示/紧迫;白字加粗 |
33
+
34
+ ## 审计
35
+
36
+ 确定性审计应检查:非中性色种类 > 3 → 配色漂移;渐变/阴影/大圆角 → 风格风险。
@@ -0,0 +1,30 @@
1
+ # 版式系统
2
+
3
+ ## 内容 → 版式
4
+
5
+ | 内容 | 版式 |
6
+ |---|---|
7
+ | 数据/数字 | 大数字 Data Hero / 矩阵 / 横向条形 |
8
+ | 流程 | 时间线 / 循环 / 系统图 |
9
+ | 对比 | 左右对照 |
10
+ | 并列判断 | 等权卡片 / 网格 |
11
+ | 结论 | 大字 statement / manifesto |
12
+
13
+ ## 登记版式
14
+
15
+ 正文页从登记版式选并保持骨架,不要临时发明未登记结构。内置简洁版式(封面/目录/章节/单栏要点/双栏/大数字/矩阵/时间线/引用/结尾)本身属于登记版式。
16
+
17
+ ```text
18
+ 封面 / 目录 / Brief 开场
19
+ 章节过渡
20
+ 单栏要点 / 双栏 / 大数字 / 矩阵 / 时间线
21
+ 引用 / statement / 结尾行动
22
+ ```
23
+
24
+ 吸附网格槽位能避免黑块遮挡和元素乱叠。
25
+
26
+ ## 几何与文字
27
+
28
+ - 几何图形只画线、圆、箭头、路径;可见文字放在 HTML/文本框里,绝不塞进几何图形。
29
+ - 中文大标题按字数分档限高,避免溢出;主标题:正文 ≥ 8:1。
30
+ - 12 列网格,左对齐贴近左上内容轴;除 statement 类版式外不要居中。