Skip to content
dsh.fish
Bundle

dsh-launch-card

Launch card generator for local repositories: renders hero SVG, a feature grid, and bilingual launch copy from README.md and package.json, and lints every unverifiable claim out of its own output.

Source
dingchenhui0618-arch
License
MIT
Updated
Updated 4 days ago

Readme

# dsh-launch-card

从一个本地仓库的真实产物(`README.md`、`package.json`、可选截图)生成发布用的 hero 图、特性网格和中英双语文案,并在写出之前把无法证实的说法 lint 掉。

它同时是一个 DSH 插件:CLI 负责生成,Web GUI 里多一个「发布卡片」面板,可以直接在会话里重新生成并看判定。

## 特性

- 零运行时依赖:只用 Node 内置模块,`package.json` 的 `dependencies` 是空对象,不引入任何图片库——图像产物是 SVG 与 HTML 纯文本,可 diff、可复现。
- 事实优先:项目名、版本、license、一句话简介、特性列表、安装片段全部来自 `README.md` 与 `package.json`,缺失的字段直接不渲染,不补占位文案。
- 可追溯:每条特性后面都带 `来源: README.md#L42` 这样的行号标注,`card.html` 里还有一张逐条来源表。
- 断言 lint:无法从本仓库产物证实的说法在写出之前就被拦掉,命中整行不写入产物,判定理由留在 `lint-report.json`。
- 双语同源:`copy.zh.md` 与 `copy.en.md` 由同一份结构化模型渲染,条目数一致,骨架文案各写一份,事实文本从仓库原样引用——不做机翻。
- 输出可复现:`manifest.json` 记录每个输入产物与每个输出文件的 sha256,`--now` 或 `SOURCE_DATE_EPOCH` 可让整次输出字节一致。
- DSH 插件双半区:host 半区挂 `/dsh-launch-card` 路由,client 半区在对话视图里注册「发布卡片」Tab。

## 安装

CLI 不需要安装,克隆后直接跑即可(本仓库没有发布到 npm):

```sh
node scripts/launch-card.mjs . --out examples
```

作为 DSH 插件使用时,把仓库目录加进 profile:

```sh
dsh plugin --profile <你的 profile> add <本仓库路径或 github:dingchenhui0618-arch/dsh-launch-card>
```

## 用法

```
node scripts/launch-card.mjs <仓库目录> --out <输出目录> [--lang zh,en] [--theme dark,light] [--now <ISO>]
```

产物:

| 文件 | 内容 |
| --- | --- |
| `hero.dark.svg` / `hero.light.svg` | 项目名 + 版本 + 一句话简介 + license |
| `features.dark.svg` / `features.light.svg` | 特性网格,逐条带来源行号 |
| `copy.zh.md` / `copy.en.md` | 一句话卖点、特性、安装片段、行动号召、来源标注 |
| `card.html` | 自包含预览页:hero、特性网格、事实表、安装片段、输入截图、来源表、双语文案 |
| `lint-report.json` | 每条命中的 `{rule, severity, file, line, text, why}`,以及被删条目 |
| `manifest.json` | 每个输入产物与输出文件的 sha256、生成时间、工具版本 |
| `summary.json` | 结构化事实与统计,供脚本消费 |

## 断言 lint

规则表在 `src/lint.js`。每条规则都带一句 `why`,写进 `lint-report.json`,措辞统一为「无法从本仓库产物证实」:

| rule | 拦什么 | 一句话理由 |
| --- | --- | --- |
| `stars` | 星标 / 收藏量 | 本仓库里没有任何星标数据,这类数字只能来自平台侧 |
| `popularity` | 用户量 / 下载量 / 客户数 / MAU | 本仓库没有用户或下载统计,规模数字都是编的 |
| `sla` | SLA / uptime / 可用性百分比 | 只有存在 `SLA.md`、`docs/SLA.md` 或 `docs/uptime.md` 时才放行 |
| `performance-claim` | `Nx faster`、`比 X 快 N 倍`、`speedup` | 性能对比必须来自本仓库的 benchmark 产物 |
| `performance-numbers` | 带数字的毫秒 / 秒 | 同上,没有 benchmark 文件就是命中 |
| `latency-claim` | 延迟 / 吞吐 / RPS 表述 | 同上 |
| `ranking` | `#1`、`leading`、`第一`、`最强` | 仓库里没有排名或横评依据 |
| `endorsement` | 奖项 / 背书 / 媒体报道 | 仓库里没有外部认可记录 |
| `business-claim` | 融资、营收、定价承诺 | 仓库里没有融资或定价信息 |
| `readiness-claim` | `production-ready`、`battle-tested`、`企业级` | 只有仓库里存在 CI 配置 + 测试目录,或 `Dockerfile`/`docker-compose.yml`/`Makefile` 时才放行 |

反面同样重要:`MIT`、`Apache-2.0`、版本号、`dependencies` 为 0、`test/*.test.mjs` 里数得出来的测试条数,都能在本仓库里找到依据,**不会被拦**。`test/lint.test.mjs` 里「该拦」与「不该拦」的用例是成对写的。

## 可追溯

`summary.json` 里每条特性都是 `{text, source:{file,line}}`;`copy.*.md` 里每条特性后面跟「来源: README.md#L42」;`card.html` 里有一张来源表。**没有来源的句子不会被渲染**——渲染器读不到文件系统,它只拿得到带着来源的结构化模型。

## 参考

骨架(零依赖、host 半区 ESM + `apply(ctx, config)`、client 半区 CJS 包在 `window.__ModuleLoader__.load()` 里、`cordis.patch.yml` 的 insert 行、`package.json` 的 `dsh` 段、测试写法)照的是本地已有的 `dsh-warden` 插件。参考了什么、不同在哪:

- **相同**:文件布局(`src/` 手写 + `lib/` 构建产物 + `test/` + `scripts/`)、`scripts/build.mjs` 的「拷贝即构建」思路、`scripts/wrap-client.mjs` 的包装形式、README 的章节结构、client 半区对 `ctx.effect` / `ctx.slots` 的守卫写法。
- **不同**:warden 的实质逻辑是一个审查门(LLM 判定 + 钩子拦截),这里没有 LLM、没有 `tools/pre-execute` 钩子,实质逻辑是一个纯函数生成器加一张 lint 规则表;warden 的构建把 CSS 用正则从源码里抠出来内联,这里改成 CSS 模块导出字符串 + 一个显式的 `__LAUNCH_CARD_CSS__` 占位符替换;这里多了 CLI、`manifest.json` 的哈希链和确定性输出。
- 代码是独立写的,没有从 `dsh-warden` 或任何其他仓库整段搬运。

> 设计动机(**未核实**):我们**没有**确认过「生态里不存在同类产出环节」这件事,所以不写成已核实的结论;这里只是这个插件存在的理由——发布素材通常靠手工攒,而手工攒的东西没人检查里面有没有编造的断言。

## 开发

```sh
node scripts/build.mjs   # 零依赖构建,产出 lib/
node --test              # 94 个用例,`node --test` 全绿
node scripts/launch-card.mjs . --out examples --now 2026-01-01T00:00:00.000Z
```

用例分布:`test/lint.test.mjs` 27 条(每条规则「该拦」+ 成对的「不该拦」)、`test/facts.test.mjs` 14 条(README / package.json 解析与来源行号)、`test/generate.test.mjs` 21 条(产物集合、确定性、哈希、双语一致、被删条目)、`test/host.test.mjs` 12 条(路由与卸载)、`test/client.test.mjs` 8 条(半区加载与插槽注册)、`test/cli.test.mjs` 12 条(真实子进程、退出码、只写 `--out`)。

`lib/` 是构建产物,改 `src/` 之后必须重新 build;`examples/` 是对本仓库自己跑一次 CLI 的真实产物,不是手写的。

## 它不做什么

- **不合成 PNG。** 图像产物只有 SVG 与 HTML;README 或 `screenshots.json` 里提到的截图原样引用进 `card.html`,不做二次加工。
- **不追求漂亮的字体渲染。** SVG 里的换行用的是一个字符宽度估算函数(CJK 记 1 em,其余 0.55 em),不加载任何字体度量库。跨浏览器、跨字体的实际换行效果没有实测过。
- **不做机翻。** 英文文件不是中文的翻译:两者都由同一份结构化模型渲染——小节标题、行动号召、表头这些框架文案各写一份,事实与特性文本从仓库原样引用。所以英文文件里的正文仍然是仓库原文的语言(本仓库是中文),只有骨架是英文。这是**明确的产出边界**:把中文句子逐词替换成英文只会在语法上产出胡话,所以本工具不做;英文正文要地道,得由人在 `docs/` 里提供译文,这件事没有做。
- **不替你核实外部事实。** 它只保证产物里不出现无法从本仓库证实的断言,不保证产物内容符合事实以外的判断。
- **lint 有两个已知盲区**,都写在测试里当作固定行为:标题行本身点名某条规则时(例如 `# 已有 1200 stars 的项目`)整行跳过,因为规则表自己的标题也在这一行;以及元语句标记(`这类说法会被拦掉`)后的行跳过。这两处是放宽而非漏判,凡是要写进产物的话都不走这两条路径——生成器自己的文案走的是逐行重渲染 + 定点循环。

## License

MIT

Install

dsh plugin --profile web add github:dingchenhui0618-arch/dsh-launch-card#4924397b5e98b026c4019bcdfb48aa0a7f530172

Profile: web

  • This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.
Source