@zhuxixi/pi-agent-board 0.9.0 → 0.10.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.
Files changed (37) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +10 -2
  3. package/VERIFY.md +2 -1
  4. package/docs/superpowers/plans/2026-09-29-issue-145-holding-semantic-state.md +1183 -0
  5. package/docs/superpowers/plans/2026-09-29-issue-145-user-verification.md +66 -0
  6. package/docs/superpowers/plans/2026-09-30-board-delete-key-x-y-confirm.md +649 -0
  7. package/docs/superpowers/plans/2026-09-30-issue-153-runner-stop-boot-window.md +618 -0
  8. package/docs/superpowers/plans/2026-09-30-issue-95-test-discipline.md +555 -0
  9. package/docs/superpowers/plans/2026-09-30-publish-dispatch-tag-check.md +259 -0
  10. package/docs/superpowers/specs/2026-09-29-issue-145-holding-semantic-state-design.md +394 -0
  11. package/docs/superpowers/specs/2026-09-30-board-delete-key-x-y-confirm-design.md +86 -0
  12. package/docs/superpowers/specs/2026-09-30-issue-153-runner-stop-boot-window-design.md +75 -0
  13. package/docs/superpowers/specs/2026-09-30-issue-95-test-discipline-design.md +226 -0
  14. package/docs/superpowers/specs/2026-09-30-publish-dispatch-tag-check-design.md +129 -0
  15. package/package.json +1 -1
  16. package/runner/job-runner.mjs +56 -22
  17. package/runner/pty-runner-legacy.mjs +1 -1
  18. package/runner/state-coordinator.mjs +1 -1
  19. package/runner/state-runner.mjs +2 -2
  20. package/scripts/budget-audit.mjs +135 -0
  21. package/src/core/auto-state.mjs +12 -10
  22. package/src/core/coordinator-protocol.mjs +3 -1
  23. package/src/core/derive.mjs +3 -0
  24. package/src/core/evidence.mjs +4 -1
  25. package/src/core/host-probe.mjs +12 -1
  26. package/src/core/pty-attach-reconnect.mjs +12 -1
  27. package/src/core/rows.mjs +4 -0
  28. package/src/core/state-commands.mjs +62 -8
  29. package/src/core/stop-latch.mjs +41 -0
  30. package/src/core/store.mjs +8 -1
  31. package/src/core/terminal-query-sequences.mjs +26 -0
  32. package/src/core/test-knobs.mjs +28 -0
  33. package/src/core/types.mjs +5 -2
  34. package/src/core/warm-host-sweeper.mjs +13 -0
  35. package/src/runtime/service.mjs +149 -28
  36. package/src/ui/dashboard.ts +46 -1
  37. package/src/ui/pty-attach.ts +49 -3
@@ -0,0 +1,259 @@
1
+ # publish.yml dispatch 恢复后门 tag 来源修复 Implementation Plan
2
+
3
+ > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4
+
5
+ **Goal:** 让 `publish.yml` 的 tag 自检步骤从事件负载 / dispatch 输入取 tag(而非 ref 名),使 `docs/RELEASE.md` 记载的手动恢复路径按字面操作即可成功。
6
+
7
+ **Architecture:** 三行 YAML 改动(自检步骤经 `env:` 接收与 checkout 同源的表达式)+ 一个新静态测试锁定不变量「publish.yml 不得从 ref 名派生 tag」。行为验证靠真实 `workflow_dispatch` run(正向 + 护栏负向),因为本仓无 YAML 级测试设施。
8
+
9
+ **Tech Stack:** GitHub Actions workflow YAML(`workflow_dispatch` + `release: published` 双触发)、`node:test` + `assert/strict`、`gh` CLI。
10
+
11
+ **Spec:** `docs/superpowers/specs/2026-09-30-publish-dispatch-tag-check-design.md`
12
+
13
+ **Worktree:** `/home/elling/work/git-repo/pi-agent-board/.pi/worktrees/issue-154-publish-workflow-dispatch-tag-check`(分支 `issue-154-publish-workflow-dispatch-tag-check`,基线 `444199d`)。下文所有路径以 `$WT` 代指该 worktree 根。
14
+
15
+ ## Global Constraints
16
+
17
+ - 只改 `.github/workflows/publish.yml` 的自检步骤(`:54-59`)与新增 `test/publish-workflow.test.mjs`;**不改** checkout 表达式、`scripts/release.mjs`、`docs/RELEASE.md`、release 事件路径行为。
18
+ - 表达式必须经 `env:` 中转,不得把 `${{ }}` 直接写进 `run` 脚本(避免表达式拼接进 shell)。
19
+ - tag 派生表达式逐字使用:`${{ github.event.release.tag_name || inputs.tag }}`;去 `v` 前缀方式保持 `${RELEASE_TAG#v}`。
20
+ - 测试文件遵循既有约定:`node:test` + `assert/strict`、**Tab 缩进**、文件名 `test/*.test.mjs`、仓库根用 `fileURLToPath(new URL("..", import.meta.url))`。
21
+ - 静态测试只断言「否定式不变量(剥整行注释后不得出现 `GITHUB_REF_NAME`)+ 正向表达式计数 ≥2」,不断言 YAML 结构 / 步骤名(避免重构假红)。
22
+ - TDD:Task 1 的测试必须在当前未修复文件上先红,Task 2 修完再绿。
23
+ - 提交按文件 `git add <file>`,不用 `git add -A`;commit message 用英文 conventional 格式。
24
+ - push / 开 PR 前必须获得用户明确许可(用户 AGENTS.md 硬规则)。
25
+ - 探针 tag 不得使用 `v*` 前缀(`release.mjs:207` / `release_helper.mjs:182` 以 `git describe --tags --match v*` 定 changelog 基线,`v*` 探针泄漏会污染下一次发版)。
26
+
27
+ ## Review Focus
28
+
29
+ 本改动的输入类别与失败模式,按最可能踩到的顺序:
30
+
31
+ 1. **dispatch 的 ref 是分支、而 tag 输入是 tag**(原始 bug 形状)→ 期望:自检通过。由 Task 3 的 A1 覆盖(`--ref issue-154-...` + `-f tag=v0.9.0`)。
32
+ 2. **tag 输入与 checkout 出来的 `package.json` 不一致** → 期望:自检 fail-closed,不得静默发布。由 Task 3 的 A2 覆盖(探针 tag `issue-154-mismatch-probe`——checkout 跟随 `inputs.tag`,错配只能人为制造)。
33
+ 3. **回归:有人把 tag 来源重新写成 ref 名派生** → 期望:静态测试红。由 Task 1 的 A3 覆盖。
34
+ 4. **release 事件自动路径被改坏** → 期望:行为不变(原 `GITHUB_REF_NAME` 在该路径恰好等于 tag)。本 fix 不改变该路径的输入来源,但**无法在本地验证**,只能由下一次真实发版观察(Task 3 的 U1,标注 pending)。
35
+ 5. **`-f tag=` 传了不带 `v` 前缀的值**(如 `0.9.0`):`${RELEASE_TAG#v}` 成为空操作。期望:因本仓所有 tag 均带 `v` 前缀,checkout 无法解析该 ref 而 fail-closed,不会走到自检或发布。此行为**不由本 fix 引入也不受其影响**,故不新增测试,记录于此以免被误认为遗漏。
36
+
37
+ ---
38
+
39
+ ### Task 1: 静态守卫测试(A3 · 先红)
40
+
41
+ **Files:**
42
+ - Create: `$WT/test/publish-workflow.test.mjs`
43
+
44
+ **Interfaces:**
45
+ - Consumes: 无(直接读仓库文件)
46
+ - Produces: 无(测试文件内部 helper,不被其他任务消费)
47
+
48
+ - [ ] **Step 1: 写测试文件**
49
+
50
+ 创建 `$WT/test/publish-workflow.test.mjs`(注意 Tab 缩进):
51
+
52
+ ```js
53
+ import assert from "node:assert/strict";
54
+ import { readFileSync } from "node:fs";
55
+ import { join } from "node:path";
56
+ import { test } from "node:test";
57
+ import { fileURLToPath } from "node:url";
58
+
59
+ const PACKAGE_ROOT = fileURLToPath(new URL("..", import.meta.url));
60
+
61
+ /**
62
+ * The tag a publish run works on comes from the trigger, not from the ref the
63
+ * run was started on: a published GitHub Release carries it in
64
+ * `github.event.release.tag_name`, a manual `workflow_dispatch` recovery run
65
+ * carries it in `inputs.tag`. Deriving it from `GITHUB_REF_NAME` breaks every
66
+ * manual recovery run, because that variable holds the *branch* the run was
67
+ * dispatched from (issue #154).
68
+ *
69
+ * The expression must appear in BOTH the checkout step and the tag-check
70
+ * step: deleting the check step's `env:` block must fail this suite, so the
71
+ * count is asserted, not just presence.
72
+ */
73
+ const TAG_SOURCE = /github\.event\.release\.tag_name\s*\|\|\s*inputs\.tag/g;
74
+
75
+ /**
76
+ * Read a workflow file as text -- the only side effect in this file. Full-line
77
+ * comments are stripped so prose about this invariant (e.g. a comment warning
78
+ * against ref-name derivation) cannot trip the guard.
79
+ */
80
+ function readWorkflowSource(name) {
81
+ const raw = readFileSync(join(PACKAGE_ROOT, ".github", "workflows", name), "utf8");
82
+ return raw
83
+ .split("\n")
84
+ .filter((line) => !line.trimStart().startsWith("#"))
85
+ .join("\n");
86
+ }
87
+
88
+ test("publish.yml never derives the release tag from the run's ref", () => {
89
+ const source = readWorkflowSource("publish.yml");
90
+ assert.equal(
91
+ source.includes("GITHUB_REF_NAME"),
92
+ false,
93
+ "publish.yml must not use GITHUB_REF_NAME: on a workflow_dispatch run it holds the branch name, not the tag (issue #154)",
94
+ );
95
+ });
96
+
97
+ test("publish.yml derives the release tag from the event payload or dispatch input in both places", () => {
98
+ const source = readWorkflowSource("publish.yml");
99
+ const uses = source.match(TAG_SOURCE)?.length ?? 0;
100
+ assert.ok(
101
+ uses >= 2,
102
+ `expected the tag source in both the checkout step and the tag-check step (>= 2 occurrences), found ${uses}`,
103
+ );
104
+ });
105
+ ```
106
+
107
+ - [ ] **Step 2: 运行,确认第一个测试红、第二个绿**
108
+
109
+ Run:
110
+ ```bash
111
+ cd $WT && node --test test/publish-workflow.test.mjs
112
+ ```
113
+ Expected: `pass 0` / `fail 2` —— 两条断言都红:`GITHUB_REF_NAME` 仍在 `:57`;表达式当前只在 checkout(`:32`)出现 1 次,不满足 ≥2。两条都转绿只能发生在 Task 2 修复之后。
114
+
115
+ - [ ] **Step 3: 提交(红态)**
116
+
117
+ ```bash
118
+ cd $WT && git add test/publish-workflow.test.mjs && git commit -m "test(ci): pin publish.yml tag source invariant (issue #154)"
119
+ ```
120
+
121
+ ---
122
+
123
+ ### Task 2: 修自检步骤的 tag 来源(A1 核心 · 转绿)
124
+
125
+ **Files:**
126
+ - Modify: `$WT/.github/workflows/publish.yml:54-59`
127
+
128
+ **Interfaces:**
129
+ - Consumes: Task 1 的静态测试(作为绿灯判据)
130
+ - Produces: 环境变量 `RELEASE_TAG`(仅该 step 内可见),值来自 `github.event.release.tag_name || inputs.tag`
131
+
132
+ - [ ] **Step 1: 改自检步骤**
133
+
134
+ 把 `.github/workflows/publish.yml` 的自检步骤替换为(原文 6 行 → 新文 8 行,新增 `env:` 块、`GITHUB_REF_NAME` 换成 `RELEASE_TAG`):
135
+
136
+ ```yaml
137
+ - name: Check the tag matches package.json
138
+ env:
139
+ RELEASE_TAG: ${{ github.event.release.tag_name || inputs.tag }}
140
+ run: |
141
+ PKG_VERSION=$(node -p "require('./package.json').version")
142
+ TAG_VERSION="${RELEASE_TAG#v}"
143
+ echo "package.json: ${PKG_VERSION}, tag: ${TAG_VERSION}"
144
+ [ "${PKG_VERSION}" = "${TAG_VERSION}" ]
145
+ ```
146
+
147
+ - [ ] **Step 2: 单独跑静态测试,确认双绿**
148
+
149
+ Run:
150
+ ```bash
151
+ cd $WT && node --test test/publish-workflow.test.mjs
152
+ ```
153
+ Expected: `pass 2` / `fail 0`。
154
+
155
+ - [ ] **Step 3: 跑全量测试,确认无回归**
156
+
157
+ Run:
158
+ ```bash
159
+ cd $WT && npm test
160
+ ```
161
+ Expected: 全绿(含既有 13 个 release-flow 测试)。
162
+
163
+ - [ ] **Step 4: 目视确认 diff 只碰这两处**
164
+
165
+ Run:
166
+ ```bash
167
+ cd $WT && git diff --stat && git diff .github/workflows/publish.yml
168
+ ```
169
+ Expected: 只有 `publish.yml` 一处改动(另加 Task 1 已提交的测试文件不在本次 diff 内);diff 中除 `env:` 块与 `${RELEASE_TAG#v}` 外无其他变化。
170
+
171
+ - [ ] **Step 5: 提交**
172
+
173
+ ```bash
174
+ cd $WT && git add .github/workflows/publish.yml && git commit -m "fix(ci): read the dispatch recovery tag from inputs, not the ref name (issue #154)"
175
+ ```
176
+
177
+ ---
178
+
179
+ ### Task 3: 验收取证(A4 · A1a · A2 · U1,A1b 合并后补验)
180
+
181
+ **Files:**
182
+ - 不修改任何文件;产出证据记录(写入 issue 评论,必要时追加到 spec 的验收结论段)
183
+
184
+ **Interfaces:**
185
+ - Consumes: Task 2 的分支状态(`--ref` 必须指向本分支,才能跑到修好的 workflow 定义)
186
+ - Produces: 每个验收 ID 的实际命令与结果字符串
187
+
188
+ > **机制说明(决定命令怎么写)**:`gh workflow run --ref <ref>` 运行的**是该 ref 上的 workflow 定义**(ref 在远端解析,本地分支不可见),而 checkout 用的是 `inputs.tag`。所以验收 dispatch 必须 `--ref issue-154-publish-workflow-dispatch-tag-check`(本分支,含修复);这正是原始 bug 的形状——ref 是分支、tag 来自输入。
189
+
190
+ - [ ] **Step 0: 推分支到 origin(用户许可门禁)**
191
+
192
+ ```bash
193
+ cd $WT && git push -u origin issue-154-publish-workflow-dispatch-tag-check
194
+ ```
195
+ Expected: 远端出现同名分支。**没有这一步,后续 `--ref issue-154-...` 会因远端无此 ref 报「workflow/ref not found」类错误。** 此 push 与后续开 PR 共用同一次用户许可。
196
+
197
+ - [ ] **Step 1: A4 —— 完整校验**
198
+
199
+ Run:
200
+ ```bash
201
+ cd $WT && npm run verify
202
+ ```
203
+ Expected: typecheck + perf gate + 全量测试 + 覆盖率阈值 + pack dry-run 全绿。记录覆盖率三行数字。
204
+
205
+ - [ ] **Step 2: A1a —— 正向:从分支 ref 触发、tag=v0.9.0 应通过并走 skip 分支**
206
+
207
+ Run:
208
+ ```bash
209
+ cd $WT && gh workflow run publish.yml -f tag=v0.9.0 --ref issue-154-publish-workflow-dispatch-tag-check
210
+ sleep 8 && gh run list --workflow=publish.yml --limit 4
211
+ ```
212
+ Expected: 新 run 出现;随后取该 run 的日志:
213
+ ```bash
214
+ gh run view <run-id> --log | grep -E "package.json:|already published|Publishing to"
215
+ ```
216
+ Expected: `package.json: 0.9.0, tag: 0.9.0`;`@zhuxixi/pi-agent-board@0.9.0 is already published — skipping the publish step`;**无** `Publishing to https://registry.npmjs.org`(证明零发布风险)。整 run `success`。注意:本条只证明修复生效 + skip 分支,**恢复路径的 `npm publish` 环节本次不执行**(与 release 路径共用、未改动,见 spec §5 A1a 行注记)。
217
+
218
+ - [ ] **Step 3: A2 —— 负向:护栏仍然会对不一致的 tag fail-closed**
219
+
220
+ 先建探针 tag(指向分支 HEAD:该提交的 `package.json` 是 0.9.0,而 tag 名不是版本号,二者必然不一致;**tag 名不带 `v` 前缀**,避免落入 release 工具链的 `v*` 基线通配):
221
+
222
+ ```bash
223
+ cd $WT && git tag issue-154-mismatch-probe && git push origin issue-154-mismatch-probe
224
+ ```
225
+ > ⚠️ 此 push 需要用户许可;与 Step 0 的分支 push 共用同一次许可。
226
+
227
+ ```bash
228
+ cd $WT && gh workflow run publish.yml -f tag=issue-154-mismatch-probe --ref issue-154-publish-workflow-dispatch-tag-check
229
+ sleep 8 && gh run list --workflow=publish.yml --limit 4
230
+ gh run view <run-id> --log-failed | grep -E "package.json:|error|exit code"
231
+ ```
232
+ Expected: 自检步骤 `package.json: 0.9.0, tag: issue-154-mismatch-probe`(`${RELEASE_TAG#v}` 对无 `v` 前缀的 tag 名是空操作)→ `##[error]Process completed with exit code 1`;run `failure`。**证明护栏没被改成「永远通过」。**
233
+
234
+ 清理探针 tag(本地 + 远端,远端以 ls-remote 为准):
235
+
236
+ ```bash
237
+ cd $WT && git push origin --delete issue-154-mismatch-probe && git tag -d issue-154-mismatch-probe
238
+ git ls-remote --tags origin | grep issue-154-mismatch-probe
239
+ ```
240
+ Expected: 第二条 grep **无输出**(远端已删);`git tag --list 'issue-154-*'` 本地为空。
241
+
242
+ - [ ] **Step 4: U1 —— release 事件路径(本任务不执行,标注 pending)**
243
+
244
+ 记录:release 事件路径的输入来源未被本 fix 改变(仍为 `github.event.release.tag_name`),但**无法在不真实发版的前提下验证**。因此 U1 在 PR 描述与 issue 评论中标记为 `pending`,由下一个版本(0.10.0)的真实发版观察 `tag: 0.10.0` 且 publish 成功。**不得宣称 U1 已通过。**
245
+
246
+ - [ ] **Step 5: 证据登记**
247
+
248
+ 把 Step 0–4 的实际命令与结果(含 run id、run URL、关键日志行)评论到 issue #154,并对每个验收 ID 标注 `passed` / `pending`。
249
+
250
+ - [ ] **Step 6: A1b —— 合并后按文档字面路径复跑一次(post-merge)**
251
+
252
+ PR 合并进 main 后(`git checkout main && git pull`),按 `docs/RELEASE.md:117-118` 的字面操作跑一遍:
253
+
254
+ ```bash
255
+ gh workflow run publish.yml -f tag=v0.9.0 --ref main
256
+ sleep 8 && gh run list --workflow=publish.yml --limit 4
257
+ gh run view <run-id> --log | grep -E "package.json:|already published"
258
+ ```
259
+ Expected: `package.json: 0.9.0, tag: 0.9.0`;命中 skip 分支;run `success`。这一步验证的不是修复本身,而是**文档承诺的路径从用户视角成立**(ref=main、只填 tag)。结果同样登记到 issue #154。