harveyz-skill 0.32.0 → 0.33.0
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/CHANGELOG.md +13 -0
- package/package.json +1 -1
- package/skills/coding/handoff/SKILL.md +122 -17
- package/skills/coding/handoff/assets/handoff-template.md +9 -3
- package/skills/coding/handoff/evals/evals.json +1 -1
- package/skills/coding/handoff/scripts/validate-handoff.sh +10 -0
- package/skills/coding/handoff/tests/validate-handoff.bats +65 -0
- package/skills/feed/sync-website/SKILL.md +14 -3
- package/skills/feed/sync-website/scripts/__pycache__/archive_articles.cpython-314.pyc +0 -0
- package/skills/feed/sync-website/scripts/__pycache__/articles_client.cpython-314.pyc +0 -0
- package/skills/feed/sync-website/scripts/__pycache__/browser_fetch_cli.cpython-314.pyc +0 -0
- package/skills/feed/sync-website/scripts/__pycache__/browser_fetch_locate.cpython-314.pyc +0 -0
- package/skills/feed/sync-website/scripts/__pycache__/calibration_gate.cpython-314.pyc +0 -0
- package/skills/feed/sync-website/scripts/__pycache__/config.cpython-314.pyc +0 -0
- package/skills/feed/sync-website/scripts/__pycache__/cursor.cpython-314.pyc +0 -0
- package/skills/feed/sync-website/scripts/__pycache__/digest.cpython-314.pyc +0 -0
- package/skills/feed/sync-website/scripts/__pycache__/fetch_new_articles.cpython-314.pyc +0 -0
- package/skills/feed/sync-website/scripts/__pycache__/roster_client.cpython-314.pyc +0 -0
- package/skills/feed/sync-website/scripts/__pycache__/roster_locate.cpython-314.pyc +0 -0
- package/skills/feed/sync-website/scripts/__pycache__/store_config.cpython-314.pyc +0 -0
- package/skills/feed/sync-website/scripts/store_config.py +84 -2
- package/skills/feed/sync-website/tests/__pycache__/conftest.cpython-314-pytest-9.1.1.pyc +0 -0
- package/skills/feed/sync-website/tests/__pycache__/test_archive_articles.cpython-314-pytest-9.1.1.pyc +0 -0
- package/skills/feed/sync-website/tests/__pycache__/test_articles_client.cpython-314-pytest-9.1.1.pyc +0 -0
- package/skills/feed/sync-website/tests/__pycache__/test_browser_fetch_locate.cpython-314-pytest-9.1.1.pyc +0 -0
- package/skills/feed/sync-website/tests/__pycache__/test_calibration_gate.cpython-314-pytest-9.1.1.pyc +0 -0
- package/skills/feed/sync-website/tests/__pycache__/test_cursor.cpython-314-pytest-9.1.1.pyc +0 -0
- package/skills/feed/sync-website/tests/__pycache__/test_digest.cpython-314-pytest-9.1.1.pyc +0 -0
- package/skills/feed/sync-website/tests/__pycache__/test_fetch_new_articles.cpython-314-pytest-9.1.1.pyc +0 -0
- package/skills/feed/sync-website/tests/__pycache__/test_roster_client.cpython-314-pytest-9.1.1.pyc +0 -0
- package/skills/feed/sync-website/tests/__pycache__/test_store_config.cpython-314-pytest-9.1.1.pyc +0 -0
- package/skills/feed/sync-website/tests/test_store_config.py +101 -0
- package/skills/feed/sync-xtimeline/SKILL.md +6 -2
- package/skills/feed/sync-xtimeline/scripts/__pycache__/store_config.cpython-314.pyc +0 -0
- package/skills/feed/sync-xtimeline/scripts/store_config.py +84 -2
- package/skills/feed/sync-xtimeline/tests/__pycache__/test_store_config.cpython-314-pytest-9.1.1.pyc +0 -0
- package/skills/feed/sync-xtimeline/tests/test_store_config.py +101 -0
- package/skills/feed/sync-ytchannel/SKILL.md +6 -2
- package/skills/feed/sync-ytchannel/scripts/__pycache__/store_config.cpython-314.pyc +0 -0
- package/skills/feed/sync-ytchannel/scripts/store_config.py +84 -2
- package/skills/feed/sync-ytchannel/tests/__pycache__/test_store_config.cpython-314-pytest-9.1.1.pyc +0 -0
- package/skills/feed/sync-ytchannel/tests/test_store_config.py +101 -0
- package/skills/mint/init-skill/SKILL.md +1 -1
- package/skills/mint/learn-skill/SKILL.md +1 -1
- package/skills/research/clip-url/SKILL.md +10 -3
- package/skills/research/clip-url/scripts/__pycache__/store_config.cpython-314.pyc +0 -0
- package/skills/research/clip-url/scripts/store_config.py +84 -2
- package/skills/research/clip-url/tests/__pycache__/test_store_config.cpython-314-pytest-9.1.1.pyc +0 -0
- package/skills/research/clip-url/tests/test_store_config.py +101 -0
- package/skills/research/learn-video/SKILL.md +8 -6
- package/skills/research/learn-video/scripts/__pycache__/archive.cpython-314.pyc +0 -0
- package/skills/research/learn-video/scripts/__pycache__/store_config.cpython-314.pyc +0 -0
- package/skills/research/learn-video/scripts/store_config.py +84 -2
- package/skills/research/learn-video/tests/__pycache__/test_archive.cpython-314-pytest-9.1.1.pyc +0 -0
- package/skills/research/learn-video/tests/__pycache__/test_store_config.cpython-314-pytest-9.1.1.pyc +0 -0
- package/skills/research/learn-video/tests/test_store_config.py +101 -0
- package/skills/research/survey-skillrepo/SKILL.md +1 -1
- package/skills-index.json +18 -18
- package/tools/browser-fetch/browser_fetch/__pycache__/cli.cpython-314.pyc +0 -0
- package/tools/browser-fetch/browser_fetch/__pycache__/core.cpython-314.pyc +0 -0
- package/tools/browser-fetch/browser_fetch/__pycache__/extractors.cpython-314.pyc +0 -0
- package/tools/browser-fetch/browser_fetch/__pycache__/normalize.cpython-314.pyc +0 -0
- package/tools/browser-fetch/browser_fetch/__pycache__/site_rules.cpython-314.pyc +0 -0
- package/tools/browser-fetch/tests/__pycache__/conftest.cpython-314-pytest-9.1.1.pyc +0 -0
- package/tools/browser-fetch/tests/__pycache__/test_cli_articles.cpython-314-pytest-9.1.1.pyc +0 -0
- package/tools/browser-fetch/tests/__pycache__/test_cli_articles_rule.cpython-314-pytest-9.1.1.pyc +0 -0
- package/tools/browser-fetch/tests/__pycache__/test_cli_articles_transform.cpython-314-pytest-9.1.1.pyc +0 -0
- package/tools/browser-fetch/tests/__pycache__/test_extractors_articles.cpython-314-pytest-9.1.1.pyc +0 -0
- package/tools/browser-fetch/tests/__pycache__/test_normalize.cpython-314-pytest-9.1.1.pyc +0 -0
- package/tools/browser-fetch/tests/__pycache__/test_site_rules.cpython-314-pytest-9.1.1.pyc +0 -0
- package/tools/browser-fetch/tests/__pycache__/test_site_rules_dirs.cpython-314-pytest-9.1.1.pyc +0 -0
- package/tools/roster/roster/__pycache__/urls.cpython-314.pyc +0 -0
- package/tools/roster/tests/__pycache__/test_cli.cpython-314-pytest-9.1.1.pyc +0 -0
- package/tools/roster/tests/__pycache__/test_urls.cpython-314-pytest-9.1.1.pyc +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.33.0] - 2026-09-15
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `store_config.py`(`learn-video`/`clip-url`/`sync-xtimeline`/`sync-ytchannel`/`sync-website` 共用):新增 `check-downstream`,核对统一存储根的产物与 vdl / scholia 两个下游读取方是否同步,五份 SKILL.md 的前置检查步骤接入该核对
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- `handoff`(1.8.0 → 1.11.0):新增 Phase 2.5 接手方自测闸门 + 打回复修回路(此前「接手方完工 → 待验收」这个状态转移毫无前置条件);建工作区改为显式给基线分支并 `EnterWorktree(path:)` 进去,三方指令统一,修复了两次因裸 git 命令落到主工作树而夹带他人未合并提交的翻车;关闭 `using-git-worktrees` 前置调用(它会另起一个 `.worktrees/` 下的第二工作区,与 handoff 自己的工作区约定冲突);平台专属指令(`EnterWorktree` 等 Claude Code harness 特性)拆进独立「平台适配」章节,避免 pi/hermes/Codex 上的接手方读到卡住
|
|
17
|
+
- `publish-skill` 审计:`init-skill`(1.2.0→1.2.1)、`learn-skill`(2.0.0→2.0.1)、`research/survey-skillrepo`(2.0.1→2.0.2)——内容已变但 version 未 bump,属于 F8 硬性检查项,现已按 patch 补齐;另有 6 个 skill(`handoff`/`sync-website`/`sync-xtimeline`/`sync-ytchannel`/`clip-url`/`learn-video`)此前 version 已 bump 但 `skills-index.json` 里的 `contentHash` 记录没跟上,已同步
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
- `learn-video` / `clip-url` / `sync-xtimeline` / `sync-ytchannel` / `sync-website` 共用的 `knowledgeRoot` 默认建议路径从 `~/Documents/knowledge` 改为 `~/knowledge`:前者在无 Full Disk Access 的 agent 执行环境下写入会静默失败,且此前没有护栏阻止 agent 擅自把配置改到别的路径绕过,导致产物在用户不知情的情况下分裂到两个根目录;五份 SKILL.md 加"写入失败必须如实报告并停下问用户"的护栏指令
|
|
21
|
+
- `tests/install.bats` / `tests/interactive.bats`:`survey-skillrepo` 测试夹具的硬编码版本号随上面的 F8 审计一并更新为 `2.0.2`,此前的 bump 一度导致 3 个断言失败
|
|
22
|
+
|
|
10
23
|
## [0.32.0] - 2026-09-07
|
|
11
24
|
|
|
12
25
|
### Added
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: handoff
|
|
3
|
-
description: Use when handing a task across sessions — writing a self-contained handoff doc for a fresh session to pick up (author), sanity-checking an inbound handoff before starting (verify), or accepting completed work against the criteria agreed at handoff time (accept). Triggers on phrases like "write a handoff", "hand this off", "pick up this task", "sign off on this work". Generic skill — project-specific conventions are read from .hskill/handoff/config.md.
|
|
4
|
-
version: "1.
|
|
3
|
+
description: Use when handing a task across sessions — writing a self-contained handoff doc for a fresh session to pick up (author), sanity-checking an inbound handoff before starting (verify), self-testing against the agreed criteria before reporting the work done or re-submitting after a rejection (self-test), or accepting completed work against the criteria agreed at handoff time (accept). Triggers on phrases like "write a handoff", "hand this off", "pick up this task", "sign off on this work". Generic skill — project-specific conventions are read from .hskill/handoff/config.md.
|
|
4
|
+
version: "1.11.0"
|
|
5
5
|
user_invocable: true
|
|
6
6
|
---
|
|
7
7
|
|
|
@@ -15,6 +15,10 @@ user_invocable: true
|
|
|
15
15
|
2. 文本未指明 → 看上下文:刚做完规划 → author;拿到别人的交接文档准备开工 → verify;接手方回报完成、要核收 → accept。
|
|
16
16
|
3. 仍不确定 → **问用户,不猜**。
|
|
17
17
|
|
|
18
|
+
**Phase 2.5(自测)没有对应的斜杠命令**,因为它不是谁"调"出来的:接手方干完活、准备把
|
|
19
|
+
`status` 推到「待验收」的那一刻就该执行它,被打回后复修完再执行一遍。它写在 Phase 2 与
|
|
20
|
+
Phase 3 之间。
|
|
21
|
+
|
|
18
22
|
判定后跳到对应 phase 段执行。
|
|
19
23
|
|
|
20
24
|
## Phase 1 — author(写交接)
|
|
@@ -30,8 +34,15 @@ user_invocable: true
|
|
|
30
34
|
分支和 worktree 都由**你**建好,不留给接手方建。这是整条交接链能闭环的关键——工作区是你建的,
|
|
31
35
|
你就一直知道它在哪;验收时直接回到这里读那份被接手方改过的文档,不必去别处找、更不必扫描。
|
|
32
36
|
- 已经在一条 feature worktree 里干活 → 就用它,别新建。
|
|
33
|
-
- 否则 `git worktree add <路径> -b
|
|
34
|
-
|
|
37
|
+
- 否则 `git worktree add <路径> -b <分支名> <基线分支>`;路径按 `.hskill/handoff/config.md`
|
|
38
|
+
的 workflow 段给的习惯,没给就用 `.claude/worktrees/<分支名把 / 换成 +>`。
|
|
39
|
+
**基线分支必须显式写出来**(通常是 `staging`)。不写就从主工作树当前 HEAD 拉分支,而主
|
|
40
|
+
工作树未必停在集成分支上——它可能正停在**别的 session 的分支**上。那样你的新分支会静默
|
|
41
|
+
夹带别人未合并的提交,合并时等于替对方把没写完的东西发布出去。这种错不会有任何报错,
|
|
42
|
+
merge 会干净利落地成功。
|
|
43
|
+
- **建完立刻进去**(怎么进见文末「平台适配」)。不进去的话,你后面每一条不带限定的
|
|
44
|
+
git 命令都作用在**主工作树**上而不是这个工作区。进不去的平台上,就把「限定到工作区」
|
|
45
|
+
当成每条命令的硬要求,别靠记性。
|
|
35
46
|
- **建完不要 `git worktree remove`。** 接手方要进来干活,你验收时还要再进来一次。这个工作区
|
|
36
47
|
在整条交接链上一直活着,直到验收通过。
|
|
37
48
|
- 把分支名填进 frontmatter 的 `branch`、工作区**绝对路径**填进 `worktree`。
|
|
@@ -57,7 +68,9 @@ user_invocable: true
|
|
|
57
68
|
|
|
58
69
|
```
|
|
59
70
|
接手任务。工作区已建好,不要自己建:
|
|
60
|
-
|
|
71
|
+
<worktree 绝对路径>
|
|
72
|
+
按你所在平台的方式进入它(Claude Code 用 EnterWorktree(path: "<路径>");
|
|
73
|
+
没有这类能力就每条命令都限定到这个路径,别靠裸 cd)。
|
|
61
74
|
分支 <branch>,已被这个工作区 checkout(git worktree add 会失败,属正常)。
|
|
62
75
|
<若 config.md 的 workflow 段有开工前置命令,原样列在这里>
|
|
63
76
|
交接文档:<相对于工作区的文档路径>
|
|
@@ -118,8 +131,9 @@ user_invocable: true
|
|
|
118
131
|
**只建这一条边。**不要顺带把自己挂到交接源节点实现的需求上(不建 `implements`),也不要调
|
|
119
132
|
`capture-requirement` 另立需求——需求已经挂在交接源节点上,接手节点该不该关联需求是另一个
|
|
120
133
|
问题,不在这次交接的范围内。
|
|
121
|
-
- **进 author 备好的工作区开工**(frontmatter 有 `worktree`
|
|
122
|
-
`git rev-parse --abbrev-ref HEAD` 与 `branch`
|
|
134
|
+
- **进 author 备好的工作区开工**(frontmatter 有 `worktree` 时):按文末「平台适配」进去,
|
|
135
|
+
然后核对 `git rev-parse --abbrev-ref HEAD` 与 `branch` 一致,就在这里干活。
|
|
136
|
+
- **绝不要销毁这个工作区**(`git worktree remove`,或平台的"退出并删除")——验收还要用。
|
|
123
137
|
- **不要自己 `git worktree add`。** 那条分支已经被这个工作区 checkout 了,再建会直接失败;
|
|
124
138
|
而且你另建一个,原 session 验收时会回到它自己建的那个,看不到你的改动。
|
|
125
139
|
- **不要 `git worktree remove`**,验收还要用。
|
|
@@ -128,13 +142,53 @@ user_invocable: true
|
|
|
128
142
|
改它等于单方面改约。
|
|
129
143
|
- 无缺口 → `status` 置 `执行中`,若文档有「工作流约定」章节按其开工,没有就直接开工。
|
|
130
144
|
|
|
145
|
+
## Phase 2.5 — 自测(接手方推 `status` 到「待验收」之前的硬闸门)
|
|
146
|
+
|
|
147
|
+
**「待验收」不是「我改完了」,是「我自己已经按锚点跑过一遍、拿得出结果」。** 这两件事之间
|
|
148
|
+
隔着一整轮 accept:没自测就推上来,红的那条要等 accept 方耗掉整轮才发现,而它本来可以由你
|
|
149
|
+
自己在几分钟内跑掉。
|
|
150
|
+
|
|
151
|
+
推 `status` 之前做完这四件事,缺一件就别推:
|
|
152
|
+
|
|
153
|
+
1. **逐条实跑「最小验收锚点」全集**——不是只跑你改动相关的那几条。`acceptance: hard`
|
|
154
|
+
逐条判对错;`soft` 按其描述做定性判断。文档若还点名了「必须复跑的既有资产」,一并跑。
|
|
155
|
+
项目自己的验证约定(跑什么测、要不要真产物 E2E)见 `.hskill/handoff/config.md` 的
|
|
156
|
+
`verification` 段。
|
|
157
|
+
2. **判成败的命令不接管道。** `cmd | tail` 的退出码是 `tail` 的,它恒为 0;输出还被截断,
|
|
158
|
+
连哪些用例根本没被发现都看不出来——两个信息一起丢。
|
|
159
|
+
3. **结果写成独立小节**,标题形如 `## 接手方自测记录(<日期>)`——**标题里必须有「自测」
|
|
160
|
+
二字**,`validate-handoff.sh` 按这个词认这一节。逐条列 pass/fail,附实际跑的命令。
|
|
161
|
+
写进你自己这一节,别混进原 session 的验收记录。
|
|
162
|
+
4. **有红就不许推 `status`。** 两条出路,没有第三条:
|
|
163
|
+
- 修到绿;
|
|
164
|
+
- 或者认为那条红不构成未达成(例如它在你动手之前就红),**在自测记录里那一条旁边写出
|
|
165
|
+
归因证据**(比如在分叉点上跑同一条命令的结果),并显式声明「带着这条红送验」。
|
|
166
|
+
**沉默地推上来不行**——accept 方一定会跑到它,届时你既丢了一轮,也丢了信用。
|
|
167
|
+
|
|
168
|
+
自测**不替代** accept:accept 方一律不采信接手方结论、逐条自己重跑(Phase 3)。自测的作用
|
|
169
|
+
是让「送验」这个动作有成本、有记录,不是让 accept 省事。
|
|
170
|
+
|
|
171
|
+
### 复修轮(被打回之后)
|
|
172
|
+
|
|
173
|
+
打回不是「改那一条就完事」。范围小、心态是「就改一行」,恰恰是二次打回最常见的来源。
|
|
174
|
+
|
|
175
|
+
- 复修完**重新走一遍本 phase**,`status` 才能再置回「待验收」。
|
|
176
|
+
- **自测范围默认是锚点全集,不是被打回的那几条。** 要缩小,必须在复修自测记录里给出两样
|
|
177
|
+
东西:改动范围证据(`git diff --name-only <打回点提交>..HEAD`)+「为什么其余条目不可能
|
|
178
|
+
被这些改动波及」的论证。给不出就跑全集。
|
|
179
|
+
- 记录**另起一节**,标题形如 `## 接手方复修自测记录(第 N 轮)`,同样必须含「自测」二字。
|
|
180
|
+
不覆盖、不合并前几轮——几轮并排放着,后来人才看得出哪条判据反复红过。
|
|
181
|
+
- 打回记录点名了修法就照做;**不认同要在复修记录里写出理由退回讨论,不要静默换一种改法**。
|
|
182
|
+
尤其不要把一条刚开始真的会红的断言改回恒真——那不是修复,是把判据废掉。
|
|
183
|
+
|
|
131
184
|
## Phase 3 — accept(原 session 验收)
|
|
132
185
|
|
|
133
186
|
**先到位,再验收。** 验收在**被验代码所在的工作区**跑——不在主工作树、不在核心分支
|
|
134
187
|
(staging / main / master)上跑。主工作树通常停在集成分支,那里根本没有接手方的改动:
|
|
135
188
|
在那里跑出来的绿是**别的代码的绿**,比不跑更有害,因为它看起来像验过了。
|
|
136
189
|
|
|
137
|
-
1.
|
|
190
|
+
1. **回到交接工作区**:按文末「平台适配」进 author 阶段第 4 步建的那个 worktree。
|
|
191
|
+
路径有三个可能来源,
|
|
138
192
|
哪个在手用哪个——你自己的上下文(你就是 author 时)、用户粘贴的验收指令(author 第 9 步
|
|
139
193
|
输出的那段)、或文档 frontmatter 的 `worktree`(文档已在手时)。三个都没有 → 用
|
|
140
194
|
`git worktree list` 按分支名查回来。确认这次交接根本不涉及独立工作区(同目录同分支续做)
|
|
@@ -142,31 +196,82 @@ user_invocable: true
|
|
|
142
196
|
- **在那里重读一遍交接文档。** 你手上这份可能是 author 时写的旧版;接手方的自测记录、
|
|
143
197
|
`status`、以及被修正过的字段,全都只存在于那个工作区里的那一份。读错版本不会报错,
|
|
144
198
|
只会让你对着过时的内容验收。
|
|
145
|
-
-
|
|
199
|
+
- 后面**每一条**验收命令都在这个工作区里跑。
|
|
146
200
|
- 路径不在了(被谁 remove 了)→ `git worktree list` 看这条分支现在挂在哪;都没有就用
|
|
147
201
|
`git worktree add --detach <临时路径> <branch>` 重建,验完 remove。**必须带 `--detach`**——
|
|
148
202
|
一条分支不能被两个 worktree 同时 checkout。
|
|
149
203
|
- 跑第一条验收命令之前确认到位:`git -C <验收目录> rev-parse --abbrev-ref HEAD` 落在
|
|
150
204
|
staging/main/master 上,说明你还在主工作树,停下来查,别接着跑。
|
|
151
|
-
2.
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
205
|
+
2. **先跑一次机器校验**:`bash <skill 目录>/scripts/validate-handoff.sh <文档路径>`。
|
|
206
|
+
`status` 是「待验收」却没有含「自测」的小节 → 脚本直接 exit 1。**这种情况按未达成处理,
|
|
207
|
+
当场置「打回」退回去,不必开始逐条重跑**:接手方连自己那一遍都没跑,你这一轮多半是在
|
|
208
|
+
替它跑第一遍。
|
|
209
|
+
|
|
210
|
+
3. 找文档里的**最小验收锚点**——这是唯一固定依据。**接手方自填的结论一律不采信,逐条自己跑**:验证产物"存在"不等于"跑得过",点得出脚本名不等于那个脚本此刻是绿的。frontmatter 的 `acceptance` 指明是哪一档:`hard`(逐条对/错)→ 按锚点描述**逐条实跑**(单测/E2E/核验);`soft`(定性描述)→ 按其描述做定性判断。字段与锚点正文不符时**以正文为准**并把字段改对——字段是索引,正文才是判据。
|
|
211
|
+
4. 把验收结果(每条 pass/fail,或整体达成/未达成)追加记录到最小验收锚点所在章节末尾。**接手方若已自填过验收记录,另起小节并列,不覆盖也不合并**——两份并排放着,后来人才看得出哪些结论被第二方复核过。其中若有与实跑不符的陈述,**显式写出更正**,不要静默改掉:静默改掉等于把同一个错误留给下一次。
|
|
212
|
+
5. 达成 → `status` 置 `已验收`;未达成 → `status` 置 `打回`。打回记录必须写清三件事,
|
|
213
|
+
缺了接手方只能猜:**哪几条未达成**、**为什么**(判据原文 vs 实跑结果)、以及
|
|
214
|
+
**复修后要重跑的范围**(默认全集;认为可以只跑子集就在这里点名)。退回接手方后,
|
|
215
|
+
它重新走 Phase 2.5 才能再置回「待验收」。
|
|
216
|
+
6. **达成才算真正完成**(硬判据要求逐条全绿;软判据按其描述定性判断是否达成)。
|
|
155
217
|
|
|
156
218
|
## 状态生命周期
|
|
157
219
|
|
|
158
|
-
`待执行`(author 写完)→ `执行中`(verify 通过 / 接手方开工)→
|
|
220
|
+
`待执行`(author 写完)→ `执行中`(verify 通过 / 接手方开工)→ `待验收`(接手方**自测通过后**
|
|
221
|
+
回报完成,见 Phase 2.5)→ `已验收`(accept 判定达成)/ `打回`(accept 判定未达成,退回执行中)。
|
|
222
|
+
|
|
223
|
+
`打回` 之后的回路是闭合的:**接手方复修 → 重新走一遍 Phase 2.5 → 才能再置回 `待验收`**。
|
|
224
|
+
直接从复修跳回「待验收」不算数。
|
|
159
225
|
|
|
160
|
-
`status`
|
|
226
|
+
`status` 是各 phase 间唯一协调锚点,无需外部状态存储。
|
|
161
227
|
|
|
162
228
|
要扫「哪些交接还没验收」,**别在当前工作树里 grep**——在途交接的文档都在各自的交接工作区里,
|
|
163
229
|
主工作树上那份(如果有)是合并后的历史归档,状态是旧的。正确做法是先 `git worktree list`
|
|
164
230
|
列出所有工作区,再到每个工作区的 `<output_dir>` 里 grep `status`。
|
|
165
231
|
|
|
166
|
-
**写入权按 phase 分:接手方最多只能把 `status`
|
|
232
|
+
**写入权按 phase 分:接手方最多只能把 `status` 推到「待验收」,且推之前必须过 Phase 2.5。`已验收` / `打回` 只能由原 session 在 accept 之后写。**
|
|
167
233
|
|
|
168
|
-
|
|
234
|
+
校验脚本查不了「谁写的」——它看不出某个值出自哪一方。这是约定,靠 accept 方兜底(见本节末)。
|
|
235
|
+
它查得了的是「有没有自测记录」:`status` 为「待验收」而正文没有含「自测」的小节,脚本 exit 1。
|
|
236
|
+
这一条堵的是「改完就推」,堵不住「自测跑了但结论是编的」——后者仍然只能靠 accept 方逐条重跑。
|
|
169
237
|
|
|
170
238
|
这条不是流程洁癖。「已验收」的全部信息量就是**做事的人之外的另一方查过**;做事的人一旦能自己写它,它就退化成「做事的人说做完了」——而这个信息「待验收」里已经有了,两个状态变成同义词,字段失效。风险还会反向放大:一份自填的「已验收」通常附着一张全 PASS 的表,比没有记录更难被怀疑。
|
|
171
239
|
|
|
172
240
|
接手方若跳档自填,accept 方**按未验收处理**,照常逐条重跑。
|
|
241
|
+
|
|
242
|
+
## 平台适配(怎么进出交接工作区)
|
|
243
|
+
|
|
244
|
+
author 之后的每个 phase 都要求"到被验代码所在的工作区里去"。**这个要求是平台无关的,实现手段不是**——
|
|
245
|
+
所以正文只说"进去",具体动作查本节。你所在的平台不在下表里,按「通用兜底」办。
|
|
246
|
+
|
|
247
|
+
判据只有一条:**后续命令的工作目录是不是那个工作区**。谁能做到都行。
|
|
248
|
+
|
|
249
|
+
### Claude Code
|
|
250
|
+
|
|
251
|
+
| 动作 | 手段 |
|
|
252
|
+
|---|---|
|
|
253
|
+
| 进入 | `EnterWorktree(path: "<工作区绝对路径>")` |
|
|
254
|
+
| 离开 | `ExitWorktree(action: "keep")` |
|
|
255
|
+
|
|
256
|
+
- **只用 `path` 模式,绝不用 `name`。** `name` 会**自己建**工作区和分支:分支名形如
|
|
257
|
+
`worktree-<name 把 / 换成 +>`(过不了多数仓库的分支命名 hook),基线由 `worktree.baseRef`
|
|
258
|
+
决定、默认 `fresh` 取 `origin/<默认分支>`——仓库若不常推远程,那可能落后几百个提交。
|
|
259
|
+
交接要的分支名和基线都得由 author 自己定,所以只能**先 `git worktree add` 建、再 `path` 进**。
|
|
260
|
+
- `path` 只认**当前仓库**(或嵌套在其中的仓库)注册过的工作区,跨仓库直接拒绝。
|
|
261
|
+
交接双方在同一仓库时不是问题;跨仓库按「通用兜底」办。
|
|
262
|
+
- 必须由当前会话**直接调**,不能交给 fork/subagent 代调。
|
|
263
|
+
- 离开一律 `action: "keep"`。以 `path` 进入的工作区它本来就不会删,但显式 `keep` 更稳;
|
|
264
|
+
**`remove` 在交接期间绝对不能用**——验收方还要回来。
|
|
265
|
+
|
|
266
|
+
### 通用兜底(无此类能力,或跨仓库)
|
|
267
|
+
|
|
268
|
+
每条命令自带限定,**不要靠裸 `cd`**:多数 agent harness 里 `cd` 只在单条命令内有效,
|
|
269
|
+
下一条又回到原目录,你会以为自己在工作区里、其实命令全打在主工作树上。
|
|
270
|
+
|
|
271
|
+
- git:`git -C <工作区> <命令>`
|
|
272
|
+
- 其他 shell 命令:`cd <工作区> && <命令>` 写在**同一条**命令里
|
|
273
|
+
- **读写文件一律用绝对路径。** 这条最容易漏:`git -C` 只管得住 git,管不住
|
|
274
|
+
Read / Edit / Write / Grep / Glob——那些按**会话当前目录**解析相对路径。会话在主工作树、
|
|
275
|
+
你以为在工作区里改文件,一个相对路径就改到了另一份同名文件上,**而且会成功**。
|
|
276
|
+
|
|
277
|
+
人类接手时同理:`cd <工作区>` 之后不要再切回去。
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
## 固定骨架:frontmatter + 两个锚点
|
|
6
6
|
|
|
7
|
-
frontmatter 的字段是给机器读的(`status`
|
|
7
|
+
frontmatter 的字段是给机器读的(`status` 驱动各 phase,`source_node`/`target_node` 驱动画布关系,`branch` 与 `worktree` 由 author 建好工作区后填,接手方照它进去开工、accept 方照它回来验收),由 `scripts/validate-handoff.sh` 校验;「交接目的」和「最小验收锚点」是给人读的,任何情况下不能省。文档必须包含以下开头结构和「最小验收锚点」一节:
|
|
8
8
|
|
|
9
9
|
```
|
|
10
10
|
---
|
|
@@ -22,11 +22,17 @@ target_node: # 留空,接手方在 verify 建完 hands-off-to 后
|
|
|
22
22
|
|
|
23
23
|
**交接目的**:<一句话,自由描述这次交接是为了什么,不受预设分类约束>
|
|
24
24
|
|
|
25
|
-
>
|
|
25
|
+
> **接手方须知**:你正在接手一个任务。本文档是完整交接与唯一权威入口:从头读到尾,若文档里有「工作流约定」章节按其开工,没有就直接开工。
|
|
26
|
+
>
|
|
27
|
+
> **完成后不是直接推状态,先自测**(skill 的 Phase 2.5):逐条实跑「最小验收锚点」**全集**(不是只跑你改动相关的那几条),外加文档点名要复跑的既有资产;判成败的命令不接管道(`| tail` 的退出码是 `tail` 的,恒为 0)。把结果写成**独立小节**,标题里必须有「自测」二字(如「接手方自测记录(日期)」),逐条列 pass/fail 并附实际命令——写你自己这一节,别混进原 session 的验收记录。**有红就别推状态**:要么修到绿,要么在那一条旁边写出归因证据并显式声明「带着这条红送验」,不许沉默推上来。`validate-handoff.sh` 会拦:`status` 是「待验收」而正文没有含「自测」的小节直接 exit 1。
|
|
28
|
+
>
|
|
29
|
+
> 自测过了,才把 frontmatter 的 `status` 置为「待验收」并**停在这里**——`已验收` / `打回` 由原 session 按「最小验收锚点」判定后写,不要代填。
|
|
30
|
+
>
|
|
31
|
+
> **被打回之后同理,而且默认跑全集**:打回轮范围小、容易觉得「就改一行」,恰恰是二次打回最常见的来源。复修完重新走一遍自测才能再置回「待验收」;要只跑子集,必须在记录里附改动范围证据(`git diff --name-only <打回点提交>..HEAD`)和「其余条目不可能被波及」的论证。复修记录另起一节(如「接手方复修自测记录(第 N 轮)」,同样含「自测」二字),不覆盖前几轮。
|
|
26
32
|
>
|
|
27
33
|
> **开工前**:若 frontmatter 有 `source_node`、且你也在 Agent Canvas 画布节点里,先建一条 `hands-off-to` 关系(`agent-canvas-ctl whoami` 取自己的 id,核对 `node-relations` 里没有后 `link-nodes --from <source_node> --to <自己> --type hands-off-to`),建完把自己的 id 填进 `target_node`。**只建这一条**——别建 `implements`、别另立需求节点。
|
|
28
34
|
>
|
|
29
|
-
> **在哪开工**:frontmatter 有 `worktree`
|
|
35
|
+
> **在哪开工**:frontmatter 有 `worktree` 时,进那个工作区干活,核对当前分支与 `branch` 一致。**怎么进按你所在平台来**(Claude Code 用 `EnterWorktree(path: <worktree>)` 的 `path` 模式;没有这类能力就每条命令自带限定 `git -C <worktree>`,读写文件用绝对路径,别靠裸 `cd`——它只在单条命令内有效)。判据只有一条:后续命令的工作目录确实是那个工作区。**不要自己 `git worktree add`**(那条分支已被这个工作区占用,再建会失败,而且原 session 验收时回的是它自己建的那个,看不到你的改动),**也绝不要销毁它**(`git worktree remove`,或平台的"退出并删除")——验收还要用。路径不存在就打回原 session 重建,别自己挑地方。
|
|
30
36
|
|
|
31
37
|
---
|
|
32
38
|
|
|
@@ -142,7 +142,7 @@
|
|
|
142
142
|
{
|
|
143
143
|
"id": "entered-author-worktree",
|
|
144
144
|
"source": "transcript",
|
|
145
|
-
"text": "执行轨迹显示验收命令是在交接工作区(setup 打印的 wt 目录)里跑的——cd 进去、或 git -C
|
|
145
|
+
"text": "执行轨迹显示验收命令是在交接工作区(setup 打印的 wt 目录)里跑的——EnterWorktree(path:) 进去、cd 进去、或 git -C / 命令 cwd 指向它——而不是全程只在仓库主工作树里操作。注意:本 fixture 的仓库刻意建在 jail 之外(见 setup.sh 的说明),对被测会话属跨仓库,EnterWorktree 会被拒;走 git -C / cd 兜底是 SKILL.md 明写的预期行为,不扣分。"
|
|
146
146
|
},
|
|
147
147
|
{
|
|
148
148
|
"id": "used-three-criteria-not-stale-two",
|
|
@@ -151,6 +151,16 @@ grep -qE '^##[[:space:]]*最小验收锚点' "$file" \
|
|
|
151
151
|
grep -qE '\*\*交接目的\*\*' "$file" \
|
|
152
152
|
|| err "正文缺「**交接目的**」行——任何情况下不能省"
|
|
153
153
|
|
|
154
|
+
# status=「待验收」时,正文必须有一节标题含「自测」的接手方自测记录。
|
|
155
|
+
# 「待验收」的全部信息量是「接手方声称做完了」;它若是在锚点还红着的时候推上来的,
|
|
156
|
+
# accept 方要耗掉一整轮才发现。这条把它压缩成 accept 开跑前的一条命令。
|
|
157
|
+
# 只认**标题行**,不认散文里顺嘴提到的「自测过了」——那不构成一份可复核的记录。
|
|
158
|
+
# 因此模板的 author 骨架里**不能**预置这个标题(预置=空标题也能过,规则当场恒真)。
|
|
159
|
+
if [[ -n "$fm" ]] && has_field status && [[ "$(field status)" == "待验收" ]]; then
|
|
160
|
+
grep -qE '^#{2,}[[:space:]]*.*自测' "$file" \
|
|
161
|
+
|| err "status 是「待验收」却没有标题含「自测」的小节——接手方须先逐条实跑最小验收锚点、把结果写成独立小节,才能推到这个状态(复修轮同理,另起「接手方复修自测记录」一节)"
|
|
162
|
+
fi
|
|
163
|
+
|
|
154
164
|
# ── 引用路径(只警告,不阻断:可能指向本次待创建的产物)────────────────────
|
|
155
165
|
while IFS= read -r link; do
|
|
156
166
|
[[ -z "$link" ]] && continue
|
|
@@ -285,3 +285,68 @@ branch: $BRANCH"
|
|
|
285
285
|
[[ "$output" == *WARN* ]]
|
|
286
286
|
[[ "$output" == *worktree* ]]
|
|
287
287
|
}
|
|
288
|
+
|
|
289
|
+
# ── status=待验收 时必须有接手方自测小节 ───────────────────────────────────
|
|
290
|
+
# 「待验收」的全部信息量是「接手方声称做完了」。接手方若没自测就推这个状态,accept 方
|
|
291
|
+
# 要花一整轮才发现锚点根本是红的。这条把它变成开跑前一条命令就能查出来的事。
|
|
292
|
+
# 只认**标题行**里的「自测」,不认正文里顺嘴提到的——散文提一句不构成一份记录。
|
|
293
|
+
# 其余 status 一律不受这条约束(30/31/32 是对照臂:规则确实按 status 分叉,不是恒真)。
|
|
294
|
+
|
|
295
|
+
@test "29 pending-accept status without self-test section -> exit 1" {
|
|
296
|
+
write_doc 'status: 待验收
|
|
297
|
+
date: 2026-09-06
|
|
298
|
+
author_model: opus-5
|
|
299
|
+
acceptance: hard'
|
|
300
|
+
run bash "$VALIDATOR" "$DOC"
|
|
301
|
+
[ "$status" -eq 1 ]
|
|
302
|
+
[[ "$output" == *自测* ]]
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
@test "30 pending-accept status with self-test section -> exit 0" {
|
|
306
|
+
write_doc 'status: 待验收
|
|
307
|
+
date: 2026-09-06
|
|
308
|
+
author_model: opus-5
|
|
309
|
+
acceptance: hard' "$(printf '# 交接:示例\n\n**交接目的**:把实现交给下一个 session\n\n## 最小验收锚点\n- demo() 返回 1\n\n## 接手方自测记录(2026-09-06)\n- demo() 返回 1 → PASS\n')"
|
|
310
|
+
run bash "$VALIDATOR" "$DOC"
|
|
311
|
+
[ "$status" -eq 0 ]
|
|
312
|
+
[[ "$output" != *ERROR* ]]
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
@test "31 pending-accept with selftest word in prose only -> exit 1" {
|
|
316
|
+
write_doc 'status: 待验收
|
|
317
|
+
date: 2026-09-06
|
|
318
|
+
author_model: opus-5
|
|
319
|
+
acceptance: hard' "$(printf '# 交接:示例\n\n**交接目的**:把实现交给下一个 session\n\n我已经自测过了,没问题。\n\n## 最小验收锚点\n- demo() 返回 1\n')"
|
|
320
|
+
run bash "$VALIDATOR" "$DOC"
|
|
321
|
+
[ "$status" -eq 1 ]
|
|
322
|
+
[[ "$output" == *自测* ]]
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
@test "32 todo status without self-test section -> exit 0" {
|
|
326
|
+
write_doc "$VALID_FM"
|
|
327
|
+
run bash "$VALIDATOR" "$DOC"
|
|
328
|
+
[ "$status" -eq 0 ]
|
|
329
|
+
[[ "$output" != *自测* ]]
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
@test "33 other statuses without self-test section -> exit 0" {
|
|
333
|
+
for s in 执行中 已验收 打回; do
|
|
334
|
+
write_doc "status: $s
|
|
335
|
+
date: 2026-09-06
|
|
336
|
+
author_model: opus-5
|
|
337
|
+
acceptance: hard"
|
|
338
|
+
run bash "$VALIDATOR" "$DOC"
|
|
339
|
+
[ "$status" -eq 0 ]
|
|
340
|
+
[[ "$output" != *自测* ]]
|
|
341
|
+
done
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
@test "34 pending-accept with rework self-test heading -> exit 0" {
|
|
345
|
+
write_doc 'status: 待验收
|
|
346
|
+
date: 2026-09-06
|
|
347
|
+
author_model: opus-5
|
|
348
|
+
acceptance: hard' "$(printf '# 交接:示例\n\n**交接目的**:把实现交给下一个 session\n\n## 最小验收锚点\n- demo() 返回 1\n\n### 接手方复修自测记录(第 2 轮)\n- demo() 返回 1 → PASS\n')"
|
|
349
|
+
run bash "$VALIDATOR" "$DOC"
|
|
350
|
+
[ "$status" -eq 0 ]
|
|
351
|
+
[[ "$output" != *ERROR* ]]
|
|
352
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: sync-website
|
|
3
|
-
version: "0.2.
|
|
3
|
+
version: "0.2.2"
|
|
4
4
|
description: "Run one incremental fetch over every website channel on the roster, produce a translated Markdown digest of what is new since last run, and archive each new article's title, translated title, publish date and URL to a per-channel JSON store. Trigger phrases: '/sync-website run', '/sync-website calibrate <handle>', '/sync-website', 'check my watched websites for new articles', or a request to run sync-website on a schedule via /loop or schedule. Adding or removing a watched website is manage-creators, not this skill. Listing only — never downloads article bodies or images, and never ingests into Obsidian (use clip-url for a single article). Display of archived articles is left to external tooling reading the JSON archive directly, not this skill."
|
|
5
5
|
user_invocable: true
|
|
6
6
|
---
|
|
@@ -41,8 +41,19 @@ python3 scripts/roster_locate.py
|
|
|
41
41
|
sync-xtimeline / sync-ytchannel 共用同一份 `knowledgeRoot` 配置(各自渠道
|
|
42
42
|
各占 `feeds/` 下一个子目录)。运行 `python3 scripts/store_config.py check`,
|
|
43
43
|
若输出 `MISSING:`,询问用户"抓取产物统一存到哪个目录?(直接回车使用默认:
|
|
44
|
-
`~/
|
|
45
|
-
字段(若已有 `skillDir` 等字段,只增改 `knowledgeRoot
|
|
44
|
+
`~/knowledge`)",写入 `~/.hskill/config.json` 的 `knowledgeRoot`
|
|
45
|
+
字段(若已有 `skillDir` 等字段,只增改 `knowledgeRoot`)。**默认值刻意不选
|
|
46
|
+
`~/Documents/...`、`~/Desktop/...`、`~/Downloads/...`**——这几个目录受 macOS
|
|
47
|
+
TCC 隐私保护,无 Full Disk Access 的 agent 执行环境写入会被拒绝;`$HOME` 下
|
|
48
|
+
的普通目录(如 `~/knowledge`)不受此限制。
|
|
49
|
+
|
|
50
|
+
**再核对下游是否同步。** 运行 `python3 scripts/store_config.py check-downstream` 核对 vdl、scholia 当前配置是否等于 `knowledgeRoot` 推出的期望值。输出 `DRIFT:` 时把对应的 `fix:` 命令原样报告给用户,由用户决定要不要执行——不要代替用户改这些下游程序的配置文件;输出 `SKIP:` 表示对应工具未安装,忽略即可。
|
|
51
|
+
|
|
52
|
+
**运行中写入失败时的处理。** 若 `store_config.py check` 通过、但实际写入
|
|
53
|
+
`<knowledgeRoot>/feeds/website/` 时仍然失败(权限不足、目录只读等),必须
|
|
54
|
+
原样把错误报告给用户并停下来,禁止擅自把 `knowledgeRoot` 改指到别的路径来
|
|
55
|
+
"绕过"——这是多个 skill 共用的配置,擅自改会让产物静默散落到不同目录,用户
|
|
56
|
+
毫不知情,且难以事后排查。
|
|
46
57
|
|
|
47
58
|
## 用法
|
|
48
59
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -9,13 +9,18 @@ clip-url / learn-video / sync-xtimeline / sync-ytchannel 四个 skill 提供
|
|
|
9
9
|
|
|
10
10
|
支持 HSKILL_CONFIG 环境变量覆盖 config 路径,供测试注入临时根;每次调用
|
|
11
11
|
时读取(不在 import 时绑定),进程内 monkeypatch 才能生效。
|
|
12
|
+
|
|
13
|
+
check-downstream 子命令核对 vdl / scholia 当前配置是否等于 knowledgeRoot
|
|
14
|
+
推出的期望值——knowledgeRoot 是唯一事实依据,vdl/scholia 的值只是被检查
|
|
15
|
+
的对象,不参与仲裁。只读,不改任何下游文件;发现漂移只打印修复命令。
|
|
12
16
|
"""
|
|
13
17
|
import json
|
|
14
18
|
import os
|
|
19
|
+
import subprocess
|
|
15
20
|
import sys
|
|
16
21
|
from pathlib import Path
|
|
17
22
|
|
|
18
|
-
_INIT_HINT = "抓取产物统一存到哪个目录?(直接回车使用默认:~/
|
|
23
|
+
_INIT_HINT = "抓取产物统一存到哪个目录?(直接回车使用默认:~/knowledge)"
|
|
19
24
|
|
|
20
25
|
|
|
21
26
|
def _config_path() -> Path:
|
|
@@ -45,6 +50,76 @@ def feeds_dir(channel: str) -> Path:
|
|
|
45
50
|
return get_root() / "feeds" / channel
|
|
46
51
|
|
|
47
52
|
|
|
53
|
+
def _run_downstream_command(cmd):
|
|
54
|
+
"""跑一个下游程序的 CLI,拿不到(未安装/非 0 退出)时返回 None。"""
|
|
55
|
+
try:
|
|
56
|
+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=10)
|
|
57
|
+
except FileNotFoundError:
|
|
58
|
+
return None
|
|
59
|
+
if result.returncode != 0:
|
|
60
|
+
return None
|
|
61
|
+
return result.stdout
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _check_vdl():
|
|
65
|
+
output = _run_downstream_command(["vdl", "config", "get"])
|
|
66
|
+
if output is None:
|
|
67
|
+
return [("SKIP", "vdl not installed (command not found)")]
|
|
68
|
+
actual = ""
|
|
69
|
+
for line in output.splitlines():
|
|
70
|
+
line = line.strip()
|
|
71
|
+
if line.startswith("workRoot:"):
|
|
72
|
+
actual = line.split(":", 1)[1].strip()
|
|
73
|
+
expected = str(videos_dir())
|
|
74
|
+
if actual == expected:
|
|
75
|
+
return [("OK", "vdl WORK_ROOT")]
|
|
76
|
+
return [
|
|
77
|
+
("DRIFT", f"vdl WORK_ROOT={actual} (expect {expected})"),
|
|
78
|
+
("FIX", f"vdl config set work-root {expected}"),
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _check_scholia():
|
|
83
|
+
output = _run_downstream_command(["scholia", "config", "list"])
|
|
84
|
+
if output is None:
|
|
85
|
+
return [("SKIP", "scholia not installed (command not found)")]
|
|
86
|
+
actual = {}
|
|
87
|
+
for line in output.splitlines():
|
|
88
|
+
if "=" not in line:
|
|
89
|
+
continue
|
|
90
|
+
key, _, value = line.partition("=")
|
|
91
|
+
actual[key.strip()] = value.strip()
|
|
92
|
+
expected = {
|
|
93
|
+
"work-dir": str(videos_dir() / "work"),
|
|
94
|
+
"content-dir": str(articles_dir()),
|
|
95
|
+
"x-dir": str(feeds_dir("tweets") / "creators"),
|
|
96
|
+
}
|
|
97
|
+
results = []
|
|
98
|
+
for key, exp in expected.items():
|
|
99
|
+
act = actual.get(key, "")
|
|
100
|
+
if act == exp:
|
|
101
|
+
results.append(("OK", f"scholia {key}"))
|
|
102
|
+
else:
|
|
103
|
+
results.append(("DRIFT", f"scholia {key}={act} (expect {exp})"))
|
|
104
|
+
results.append(("FIX", f"scholia config set {key} {exp}"))
|
|
105
|
+
return results
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def check_downstream() -> int:
|
|
109
|
+
get_root() # 触发 MISSING 异常,交给调用方处理
|
|
110
|
+
entries = _check_vdl() + _check_scholia()
|
|
111
|
+
has_drift = False
|
|
112
|
+
for kind, message in entries:
|
|
113
|
+
if kind == "FIX":
|
|
114
|
+
print(f" fix: {message}")
|
|
115
|
+
elif kind == "DRIFT":
|
|
116
|
+
has_drift = True
|
|
117
|
+
print(f"DRIFT: {message}")
|
|
118
|
+
else:
|
|
119
|
+
print(f"{kind}: {message}")
|
|
120
|
+
return 1 if has_drift else 0
|
|
121
|
+
|
|
122
|
+
|
|
48
123
|
def main():
|
|
49
124
|
if len(sys.argv) > 1 and sys.argv[1] == "check":
|
|
50
125
|
try:
|
|
@@ -53,7 +128,14 @@ def main():
|
|
|
53
128
|
print(f"MISSING: {e}", file=sys.stderr)
|
|
54
129
|
sys.exit(1)
|
|
55
130
|
return
|
|
56
|
-
|
|
131
|
+
if len(sys.argv) > 1 and sys.argv[1] == "check-downstream":
|
|
132
|
+
try:
|
|
133
|
+
sys.exit(check_downstream())
|
|
134
|
+
except (FileNotFoundError, KeyError) as e:
|
|
135
|
+
print(f"MISSING: {e}", file=sys.stderr)
|
|
136
|
+
sys.exit(1)
|
|
137
|
+
return
|
|
138
|
+
print("Usage: store_config.py check|check-downstream", file=sys.stderr)
|
|
57
139
|
sys.exit(1)
|
|
58
140
|
|
|
59
141
|
|
|
Binary file
|
|
Binary file
|
package/skills/feed/sync-website/tests/__pycache__/test_articles_client.cpython-314-pytest-9.1.1.pyc
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|