dsh-thesis 2.1.0 → 2.1.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.
package/README.md CHANGED
@@ -73,7 +73,7 @@ node scripts/install-into-profile.ts --profile web # 没有 pnpm 时的兜
73
73
  | [docs/troubleshooting.md](./docs/troubleshooting.md) | 装了没反应、没弹选择题、PDF/pptx 失败等 |
74
74
  | [docs/design.md](./docs/design.md) | 设计文档与 ADR(架构、分层、质量策略、已知边界) |
75
75
  | [docs/host-api.md](./docs/host-api.md) | 宿主契约:用到的 DSH 接口与实测行为 |
76
- | [docs/release.md](./docs/release.md) | 发版:tag → GitHub Actions → npm(含 provenance)|
76
+ | [docs/release.md](./docs/release.md) | 发版:`npm run release:minor` + push → GitHub Actions → npm(含 provenance)|
77
77
 
78
78
  ## 开发
79
79
 
@@ -89,8 +89,9 @@ npm run verify # build + 两个 typecheck + 全部单测 + 审
89
89
  质量分层:单测/契约、真实场景端到端、真实 cordis 装配、真实 dsh 引擎装载、对外表面审计
90
90
  (见 [docs/design.md](./docs/design.md) §9)。
91
91
 
92
- **发版**:打 tag 即发布,由 GitHub Actions 跑构建、校验与 `npm publish --provenance`,
93
- 见 [docs/release.md](./docs/release.md)。
92
+ **发版**:`npm run release:minor`(改版本号并提交)+ `git push` 即发布——workflow 发现 npm 上没有这个版本
93
+ 就发布,发完自动补 tag 与 Release,**不需要手动打 tag**。由 GitHub Actions 跑构建、校验与
94
+ `npm publish --provenance`,见 [docs/release.md](./docs/release.md)。
94
95
 
95
96
  ## 许可
96
97
 
package/docs/design.md CHANGED
@@ -351,6 +351,28 @@ DSH 的 profile 预检(`dsh-app-boot` 的 `evaluatePluginCompatibility`,在
351
351
  **取舍**:内置过渡引擎只落实表格与页边距(A4、上 2.5/下 2.5/左 3.0/右 2.5 cm);
352
352
  分节页码、目录域、页眉依赖模板级排版,交给 `build_docx.py` 或 pandoc + 学校模板。
353
353
 
354
+ ### ADR-19 发布由「版本号」驱动,tag 是产物而不是开关
355
+
356
+ **背景**:原来发版要先打 tag 再推,tag 名还必须与 `package.json` 逐字一致——漏一步就是一次红 run,
357
+ 而 tag 本身没有携带任何信息,只是"我现在要发版了"的暗号。
358
+
359
+ **决定**:
360
+ 1. 触发条件改为 `push: branches: [main]`,同时保留 `tags: ['v*']` 兼容手动打 tag;
361
+ 2. 是否发布由「`package.json` 的版本在 npm 上是否已存在」决定——**版本号没变就不发**;
362
+ 3. 发布成功后由 workflow 自己 `git tag v<version>` 并推上去,再建 Release;
363
+ 4. 版本号只有一个来源:`package.json`。`npm run release:minor` 同时改
364
+ `package.json` 与 `package-lock.json`(顶层 + `packages[""]`)并提交,提交信息从版本号派生。
365
+
366
+ **为什么不从 commit message 解析版本**:发布包里写的是 `package.json` 的 version。
367
+ 让提交信息决定发什么版本,要么包与 tag 不一致,要么 CI 临时改包导致仓库与 npm 永久错位,
368
+ 要么机器人提交回 main 撞保护分支。三者都比"多跑一条 `npm run release:*`"更贵。
369
+
370
+ **取舍**:每次推 main 都会多跑一个 `npm ci` + `npm view`(约 40 秒)才判定跳过;
371
+ 换来的是"日常提交随便推、发版只需升版本号"。
372
+
373
+ **验证**:`npm run release:minor -- --dry-run` 只打印不落盘;`publish.yml` 用 PyYAML 解析校验结构与条件;
374
+ 真正的判定(已发布则跳过)由 npm 侧 `npm view` 保证,重复触发不会覆盖已发布版本。
375
+
354
376
  ---
355
377
 
356
378
  ## 7. 数据与产物
package/docs/release.md CHANGED
@@ -1,138 +1,147 @@
1
1
  # 发布
2
2
 
3
3
  发版由 GitHub Actions 完成([`.github/workflows/publish.yml`](../.github/workflows/publish.yml))。
4
- **打 tag 即发布**,本地不需要 `npm login`,也不需要任何 npm token。
5
4
 
6
- 认证走 **Trusted Publishing(OIDC 可信发布)**:npm 只接受「这个仓库的这个 workflow」发来的包,
7
- 凭据是每次运行临时签发的,不落盘、不过期、也没有 2FA 输 OTP 的问题;provenance 由 npm 自动附上。
5
+ **升版本号 + push 就发版,不用打 tag。** 推上去以后 workflow 会自己看 npm 上有没有这个版本,
6
+ 没有就发布,发完**自动补 tag 与 GitHub Release**——tag 是发布的产物,不再是发布的前提。
8
7
 
9
- ## 一次性设置(只做一次)
8
+ ```bash
9
+ npm run release:minor # 2.1.0 -> 2.2.0(改 package.json 与 package-lock.json 并提交)
10
+ git push origin main # 推完就自动发布
11
+ ```
10
12
 
11
- 1. 打开 <https://www.npmjs.com/package/dsh-thesis/access> → **Trusted Publishers** →
12
- **Select publisher: GitHub Actions**,填四个字段:
13
+ 就这两条。`npm run release:patch` / `release:major` 同理。
13
14
 
14
- | 字段 | 填什么 |
15
- |---|---|
16
- | Organization or user | `booondenz` |
17
- | Repository | `dsh-thesis` |
18
- | Workflow filename | `publish.yml`(只写文件名,不带路径)|
19
- | Environment name | **留空**(除非 workflow 里写了 `environment:`)|
15
+ 本地不需要 `npm login`,也不需要任何 npm token。
20
16
 
21
- 同一页下方的 **Allowed actions**:
17
+ ## 它怎么判断该不该发
22
18
 
23
- | 选项 | 勾不勾 | 为什么 |
24
- |---|---|---|
25
- | stage publish | 强制,改不了 | — |
26
- | **Allow npm publish** | **勾上** | 不勾的话 `npm publish` 会被拒(`403 OIDC permission denied for this action`),只接受 `npm stage publish`,每次发版还得你到网页上做 2FA 批准 |
27
- | Allow npm dist-tag | 不用勾 | `npm publish` 会自动把 `latest` 指过去;我们没有 `npm dist-tag` 操作 |
19
+ | 检查 | 结果 |
20
+ |---|---|
21
+ | push 到 `main` | 进入发布流程 |
22
+ | push 的 tag(`v*`) | 同样进入(兼容手动打 tag 的老做法) |
23
+ | `package.json` 的版本 npm 上**没有** | `npm pack --dry-run` → 守卫 → `npm publish` → 自动打 tag → 建 Release |
24
+ | `package.json` 的版本 npm 上**已有** | 只跑检查,跳过发布(不会红,也不会覆盖)|
25
+ | 手动触发(`workflow_dispatch`) | 只做打包预览,不发布、不打 tag |
28
26
 
29
- > 黄色提示条只说「provider 与必填字段建好后不能改」,`Allowed actions` 的两个勾选框随时可以改。
27
+ 所以:**版本号没变 = 这次 push 不发版**。日常提交随便推,只有 `release:*` 那次会发。
30
28
 
31
- 2. 确认仓库是 **public**:私有仓库发不出 provenance。
32
- 3. 不需要 `NPM_TOKEN`,**之前建过的话建议在 Settings → Secrets 里删掉**,免得以后误用。
29
+ ## 为什么版本号只认 `package.json`,不从 commit message 解析
33
30
 
34
- > 配置要先「验证」过一次才算生效:2026-10 起,长期没成功发布过的可信发布配置会自动过期。
35
- > 所以配完就立刻发一次版。
31
+ 发布包里真正写进去的是 `package.json` 的 `version`。如果让提交信息里的 `v2.2.0` 决定发什么版本,
32
+ 只有三条路,都不好:
36
33
 
37
- ## 备选:用 token(不推荐)
34
+ 1. **照抄提交信息 → 包里版本不对**:提交写 `v2.2.0`、`package.json` 还是 `2.1.0`,
35
+ npm 上会出现两个"2.1.0"(第二次直接被拒),或者发出去的包版本与 tag 不一致;
36
+ 2. **CI 里临时改 `package.json` 再发**:npm 上是 2.2.0,仓库里还是 2.1.0,
37
+ 下次谁从仓库装就是旧版本,provenance 里的 commit 也对不上;
38
+ 3. **CI 里改完再提交回 main**:机器人提交 + 可能撞保护分支 + 每次发版两个 commit。
38
39
 
39
- 如果哪天可信发布不可用,也可以在 npm 建 **Classic → Automation** 类型的 token
40
- (Granular token 在开了 2FA 的账号上会报 `EOTP`,除非它明确允许绕过 2FA),
41
- 存成仓库 secret `NPM_TOKEN`,然后在 workflow 的 publish 步骤加回:
40
+ 现在的做法是:**版本号只有一处真相(`package.json`),`npm run release:*` 负责同时改两处并提交**,
41
+ 提交信息与 tag 名都从它派生(`chore: release v2.2.0` → tag `v2.2.0`),三者不可能不一致。
42
42
 
43
- ```yaml
44
- env:
45
- NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
46
- ```
43
+ ## 一次性设置(只做一次,已完成)
47
44
 
48
- 并把 `setup-node` 的 `with:` 里加回 `registry-url: https://registry.npmjs.org`。
49
- Automation token 专为 CI 设计,不受 2FA 影响。
45
+ npm 侧认证走 **Trusted Publishing(OIDC)**:npm 只接受「这个仓库的这个 workflow」发来的包,
46
+ 凭据每次运行临时签发,不落盘、不过期、没有 2FA 输 OTP 的问题;provenance 自动附带。
50
47
 
51
- ## 发一次版
48
+ <https://www.npmjs.com/package/dsh-thesis/access> → **Trusted Publishers**:
52
49
 
53
- ```bash
54
- npm version patch --no-git-tag-version # 或 minor / major,或手动改 package.json
55
- git add package.json package-lock.json
56
- git commit -m "chore: release v2.0.1"
57
- git tag v2.0.1
58
- git push origin main --tags
59
- ```
50
+ | 字段 | 值 |
51
+ |---|---|
52
+ | Organization or user | `booondenz` |
53
+ | Repository | `dsh-thesis` |
54
+ | **Workflow filename** | **`publish.yml`**(只写文件名,不带路径)|
55
+ | Environment name | 留空 |
56
+
57
+ **`publish.yml` 这个文件名不能改**(也不能把发布逻辑挪进 `ci.yml`):npm 的配置绑的就是文件名,
58
+ 改名后第一次发布会 403。改了触发条件是安全的,改文件名不是。
59
+
60
+ 同一页 **Allowed actions** 里要勾 **Allow npm publish**,只勾 stage 的话每次发版还得去网页做 2FA 批准。
60
61
 
61
- Action 会依次做:
62
+ ## workflow 干了什么
62
63
 
63
64
  | 步骤 | 失败就停 |
64
65
  |---|---|
65
- | `npm ci` | 锁文件与 package.json 不同步会在这里红 |
66
- | tag 与 `package.json` 版本一致 | 防止「tag 写 v2.0.1、包里是 2.0.0」|
67
- | 该版本是否已存在于 npm | 已存在则**跳过发布**(不会红,也不会覆盖)|
68
- | `npm pack --dry-run` | 发布内容一目了然(日志里能看到文件清单)|
69
- | `npm publish` | 内部触发 `prepublishOnly` 守卫:构建 + 类型 + 单测 + 审计 + 端到端;provenance 由可信发布自动附带 |
70
- | 建 GitHub Release | 标题用 tag,正文自动生成 |
66
+ | `npm ci` | 锁文件与 `package.json` 不同步会红(所以别手改版本号,用 `npm run release:*`)|
67
+ | 读 `package.json` 版本 | — |
68
+ | 手动 tag 与版本一致(仅 tag 触发时)| 防止「tag 写 v2.2.0、包里是 2.1.0」|
69
+ | 该版本是否已发布 | 已存在则跳过发布 |
70
+ | `npm pack --dry-run` | 日志里能直接看到这次包里有什么 |
71
+ | `npm publish` | 内部跑 `prepublishOnly` 守卫:构建 + 类型 + 单测 + 审计 + 端到端;**main 红了在这里被拦下** |
72
+ | 自动打 tag | `git tag v<version>` + push;用 `GITHUB_TOKEN` 推的 tag 不会再触发 workflow,不会套娃 |
73
+ | 建 Release | 标题 `v<version>`,正文自动生成 |
71
74
 
72
- 发布成功后 npm 页面会带一个 **Provenance** 徽章,点进去能看到是哪次 commit、哪次 workflow 构建的。
75
+ ## 手动打 tag(仍然可以,但不再是发版的必要条件)
73
76
 
74
- ## 在网页上发版(完全不碰命令行)
77
+ ```bash
78
+ git tag v2.2.0
79
+ git push origin v2.2.0
80
+ ```
75
81
 
76
- 1. 打开 <https://github.com/booondenz/dsh-thesis/releases/new>;
77
- 2. **Choose a tag** 里输入 `v2.0.1`,点下拉第一条 **Create new tag: v2.0.1 on publish**;
78
- 3. **Target** 选 `main`,并确认它指向 bump 版本的那个 commit(否则 tag 会打在别的提交上,
79
- workflow 第一步的版本比对就会红);
80
- 4. 标题写 `v2.0.1`,正文可以留空——workflow 里是 `gh release view || gh release create`,
81
- 发现 Release 已存在会跳过;
82
- 5. **Publish release**。
82
+ 行为与以前完全一致:先校验 tag 与 `package.json` 一致,再发布。适合"版本号早就提交了、只是补发"的场景。
83
+
84
+ ## 备选:用 token(不推荐)
83
85
 
84
- 创建 tag 本身就是一次 `push`,所以 `publish.yml` 会被触发。两条路等价:也可以先
85
- `git push origin v2.0.1` 再建 Release,或者只推 tag 不建 Release(workflow 会替你建)。
86
+ 可信发布不可用时,在 npm 建 **Classic → Automation** token(Granular token 在开了 2FA 的账号上会报 `EOTP`),
87
+ 存成仓库 secret `NPM_TOKEN`,然后在 publish 步骤加回:
88
+
89
+ ```yaml
90
+ env:
91
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
92
+ ```
93
+
94
+ 并把 `setup-node` 的 `with:` 里加回 `registry-url: https://registry.npmjs.org`。
95
+ Automation token 专为 CI 设计,不受 2FA 影响。
86
96
 
87
97
  ## 确认发成功了
88
98
 
89
99
  | 看哪 | 期望 |
90
100
  |---|---|
91
- | Actions → **publish** | 这次 run 是绿的;Summary 里有一行 `https://www.npmjs.com/package/dsh-thesis/v/2.0.1` |
92
- | `npm view dsh-thesis version` | `2.0.1`(registry 立刻生效,网页可能还要一分钟)|
93
- | `npm view dsh-thesis dist-tags` | `{ latest: '2.0.1' }` |
94
- | <https://www.npmjs.com/package/dsh-thesis> | 版本号旁出现 **Provenance** 徽章 |
95
- | 仓库 **Releases** | 多了一条 `v2.0.1` |
96
-
97
- Actions 里**根本没出现 publish 这个 run**,说明 tag 没推上去(或名字不是 `v` 开头);
98
- 出现 run 但红在第一步,通常是 tag 与 `package.json` 版本对不上。
101
+ | Actions → **publish** | 绿;Summary 里有 `https://www.npmjs.com/package/dsh-thesis/v/2.2.0` |
102
+ | `npm view dsh-thesis version` | `2.2.0`(registry 立刻生效,网页可能慢一分钟)|
103
+ | `npm view dsh-thesis dist-tags` | `{ latest: '2.2.0' }` |
104
+ | 仓库 **Tags / Releases** | 自动多了 `v2.2.0` |
105
+ | <https://www.npmjs.com/package/dsh-thesis> | 版本号旁有 **Provenance** 徽章 |
99
106
 
100
107
  ## 发完之后:让 DSH 用上新版
101
108
 
102
- profile 把版本钉在 `~/.dsh/profiles/<profile>/package.json` 里(例如 `"dsh-thesis": "1.0.1"`),
103
- 所以 npm 上发了新版**不会自动生效**。到插件管理器里移除再装一次(会取 `latest`),或者把那条依赖
104
- 改成 `"^2.0.1"` 后重装,然后**重启 DSH**——2.0 删掉了全部斜杠命令,不是热更新能覆盖的。
109
+ profile 把版本钉在 `~/.dsh/profiles/<profile>/package.json` 里,npm 发了新版**不会自动生效**:
105
110
 
106
- ## 手动触发
111
+ ```bash
112
+ dsh plugin --profile web add dsh-thesis@2.2.0
113
+ dsh plugin --profile desktop add dsh-thesis@2.2.0
114
+ ```
107
115
 
108
- Actions → publish → Run workflow:只做打包预览,**不会发布**。用来确认「这次发出去的包里到底有什么」。
109
- 想真发版,还是打 tag。
116
+ 然后**重启 DSH**(2.0 删掉了全部斜杠命令,不是热更新能覆盖的)。
110
117
 
111
118
  ## 失败排查
112
119
 
113
120
  | 现象 | 原因 |
114
121
  |---|---|
115
- | `EOTP` / `This operation requires a one-time password` | 用的是需要 OTP 的 token(2FA 账号的 Granular / Publish token)。CI 里没人能输 OTP:改用可信发布,或换 Classic → Automation token |
116
- | 发布被拒,提示只能 `npm stage publish` | 可信发布里没勾 **Allow publish directly**(默认只允许 stage)|
117
- | `403 … OIDC permission denied for this action` | 同一条:OIDC 认证已经通过(组织/仓库/workflow 名字都对),只是没给「直接发布」权限。去包设置里编辑那条配置,勾上 **Allow npm publish** |
118
- | `401 Unauthorized` | token 过期(Automation token 有 90/180 天寿命),或可信发布配置里 workflow 文件名写错 |
119
- | `npm ci … not in sync` | 改了 `package.json` 的依赖/peer 却没跑 `npm install --package-lock-only` |
120
- | `403 … cannot publish over previously published version` | 版本号没升;这条在 workflow 里已被「已存在则跳过」挡住,手动发版时要注意 |
121
- | 守卫里 `单元/契约测试` 红 | 本地 `npm test` 先复现;守卫跑的就是它 |
122
+ | Actions 里**根本没有** publish run | push 的不是 `main`,也不是 `v*` tag |
123
+ | publish 绿但 Summary 写"已存在,未发布" | 版本号没升(`npm run release:*` 没跑)|
124
+ | `EOTP` / 要求一次性密码 | 用的是需要 OTP 的 token → 改用可信发布或 Automation token |
125
+ | 被拒、提示只能 `npm stage publish` | 可信发布里没勾 **Allow npm publish** |
126
+ | `403 … OIDC permission denied for this action` | 同上:认证过了,但没给「直接发布」权限 |
127
+ | `401 Unauthorized` | token 过期,或可信发布配置里 workflow 文件名写错 |
128
+ | `npm ci … not in sync` | 手改了 `package.json` 版本没同步锁文件 → 改用 `npm run release:*` |
129
+ | `403 … cannot publish over previously published version` | 版本号没升;workflow 里"已存在则跳过"会挡住,手动发版时要注意 |
130
+ | 守卫里单测红 / 端到端红 | 与 `npm run verify` 同一套;本地先复现 |
122
131
  | 守卫警告「未找到 DSH 安装」 | CI 里正常(没有宿主);**发版前请在本地跑一次 `npm run test:host-load` 与 `npm run test:host`** |
132
+ | 自动打 tag 失败但 npm 已发布 | tag 只是标记,事后手动 `git tag v2.2.0 && git push origin v2.2.0` 补上即可 |
123
133
 
124
134
  ## 撤回与补救
125
135
 
126
- - **已发布的版本不能改**,只能 `npm deprecate dsh-thesis@2.0.1 "原因与替代版本"`。
136
+ - **已发布的版本不能改**,只能 `npm deprecate dsh-thesis@2.2.0 "原因与替代版本"`。
127
137
  - 72 小时内理论上可以 `npm unpublish`,但那会让依赖它的人突然装不上,**不要用**。
128
- - 发错了就立刻发一个 patch:`2.0.2`。
138
+ - 发错了就立刻发一个 patch:`npm run release:patch` + `git push`。
129
139
 
130
- ## 仍然可以本地发布
140
+ ## 本地发布(应急)
131
141
 
132
142
  ```bash
133
143
  npm login
134
144
  npm publish --access public # 会跑同一个 prepublishOnly 守卫,会交互式要 OTP
135
145
  ```
136
146
 
137
- 本地发布用的是你登录后的凭据(可以输 OTP),不需要 token,也不需要可信发布配置。
138
- CI 只是把「构建 → 校验 → 发布」串起来,判据完全一样。
147
+ 本地发布用你登录后的凭据,不需要 token,也不需要可信发布配置。CI 只是把「构建 → 校验 → 发布」串起来,判据完全一样。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-thesis",
3
- "version": "2.1.0",
3
+ "version": "2.1.1",
4
4
  "description": "论文与答辩全流程助手(DeepSeek Harness 插件):材料摄取 → 逐题追问 → 文献检索 → 逐章写作与评审 → 原创性自查与合规 → Word/答辩幻灯生产,全流程留痕,并跨会话记住学校规范与导师要求。",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -60,6 +60,9 @@
60
60
  "test:host": "node scripts/host-smoke.ts && node .host-smoke/app/run.ts",
61
61
  "test:host-load": "node scripts/host-verify.ts",
62
62
  "audit": "node scripts/audit-surface.ts",
63
+ "release:patch": "node scripts/release.ts patch",
64
+ "release:minor": "node scripts/release.ts minor",
65
+ "release:major": "node scripts/release.ts major",
63
66
  "verify": "npm run build && npm run typecheck && npm run typecheck:scripts && npm run test:sandbox && npm run audit && npm run test:e2e",
64
67
  "prepublishOnly": "node scripts/prepublish-guard.ts"
65
68
  },