issue-map 0.3.0 → 0.4.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/README.zh-CN.md CHANGED
@@ -6,130 +6,124 @@
6
6
 
7
7
  把 GitHub Issues 的阻挡关系画成一页开发地图:**哪几张票现在可以动、哪几张在等谁、关键路径是哪一条。**
8
8
 
9
- 状态的权威永远是 GitHub Issues。这一页只是快照,页面上不能改状态——所以不会长出第二个事实来源。
10
-
11
- ## 为什么有这个项目
12
-
13
- 起点是 [mattpocock/skills](https://github.com/mattpocock/skills)。团队照它那套「把工作流写成
14
- skill、让 agent 照着跑」开始做事之后,开票变得很便宜:想到一件事就开一张票,交给 skill 去接。
15
- 票因此长得很快——那是流程在运作的证据,不是问题。
16
-
17
- 问题在下一步。Agent 一轮吃一张票,所以每一轮真正要决定的是**派哪一张**,而这个答案不在任何
18
- 单一张票里,它在票与票之间:谁挡着谁、哪一组子票还差几张、最长的那条链有多长。GitHub Issues
19
- 一次只让你读一张票,要凑出那张图就得一张一张点开来,而且每天都要重凑一次。
20
-
21
- 这一页就是那张图。
22
-
23
- ## 用法
24
-
25
- 在**要看的那个 repo** 里跑:
26
-
27
9
  ```bash
28
10
  bunx issue-map@latest
29
11
  ```
30
12
 
31
- `@latest` npm 上最新的一版;要钉住特定版本就写 `bunx issue-map@0.2.0`。
32
-
33
- 起在 `http://localhost:4747` **并直接开浏览器**。每次刷新都重抓 GitHub,看到的一定是现在的状态。repo 是 `gh` 从 cwd 的 git 推断的,不必填。
13
+ 在要看的那个 repo 里跑。会直接开浏览器标签页,每次刷新都重抓 GitHub。状态的权威永远是 GitHub
14
+ Issues——这一页只是快照,不能改状态。
34
15
 
35
- 不要自动开标签页就设 `ISSUE_MAP_OPEN=0`。
16
+ ## 为什么有这个项目
36
17
 
37
- 只要一份静态 HTML 的话(`-p` 是用来选另一个 bin 的,少了它会变成起 server):
18
+ Agent 一轮吃一张票,所以每一轮真正要决定的是**派哪一张**。这个答案不在任何单一张票里,它在票与
19
+ 票之间:谁挡着谁、哪一组子票还差几张、最长的那条链有多长。GitHub 一次只让你读一张票。这一页就是
20
+ 那张图。
38
21
 
39
- ```bash
40
- bunx -p issue-map@latest issue-map-build # 写到 dist/issue-map.html
41
- bunx -p issue-map@latest issue-map-build out.html
42
- ```
22
+ 它是绕着 [mattpocock/skills](https://github.com/mattpocock/skills) 那套做法建的——把工作流写成
23
+ skill、让 agent 照着跑——默认的标签与指令也是从那里来的。
43
24
 
44
- 快照就是快照——状态会过期,要看现在的状态就用上面的 server。
25
+ ## 用法
45
26
 
46
- ## 语言
27
+ | 指令 | 得到什么 |
28
+ | --------------------------------------------------- | -------------------------------- |
29
+ | `bunx issue-map@latest` | server 起在空端口,自动开浏览器 |
30
+ | `bunx -p issue-map@latest issue-map-build` | 静态文件到 `dist/issue-map.html` |
31
+ | `bunx -p issue-map@latest issue-map-build out.html` | 静态文件到你指定的路径 |
47
32
 
48
- 页面右上角切换,**默认英文**,另外支持繁体中文、简体中文、日文。选了哪一种记在浏览器
49
- (localStorage `issue-map:locale`),跟 repo 无关——语言是看的人的偏好,不是某个项目的设置。
50
- 刻意不看 `navigator.language`:默认就是英文,猜错了反而每次进来都要改回去。
33
+ - repo 是 `gh` 从 cwd 的 git 推断的,不必填。
34
+ - `ISSUE_MAP_PORT` 固定端口。固定就是严格的:被占住时直接失败,不会偷偷换一个。
35
+ - `ISSUE_MAP_OPEN=0` 不自动开标签页。
36
+ - 静态文件会过期,要看现在的状态就用 server。
37
+ - 在跑不了 script 的地方(严格 CSP、某些预览窗),文件会退回一份纯文字的票清单,而不是一片空白。
38
+ - `@latest` 取 npm 上最新的一版;要钉住就写 `bunx issue-map@0.2.0`。
51
39
 
52
- 文案全部在 `scripts/issue-map-i18n.ts`,那是页面上每一句话的唯一来源:
40
+ ## 前置条件
53
41
 
54
- - `EN` 是原稿,也是键的定义处。三份翻译的类型由它推导,少翻一个键 `bun run typecheck` 就会红。
55
- - 句子里的代入名(`{n}`、`{issues}`)也是类型的一部分,少传一个编不过——不然缺的那个会以
56
- `{n}` 的样子印在画面上,而那要真的跑到那一格才看得到。
57
- - 英文要分单复数的键写成 `{ one, other }`,中日文写一句字符串就好(`Intl.PluralRules` 对这几种
58
- 语言只有 `other`)。
42
+ - **Bun**——这几支用了 `Bun.build`、`Bun.serve`、`Bun.file`,Node 跑不起来。
43
+ - **`gh` CLI 已登录**,而且对目标 repo 有读取权。
44
+ - 目标 repo 有 git remote 指向 GitHub。
59
45
 
60
- 模型与抓取那一侧**不再算好句子**:`nextStep` `{ kind: 'waitChildren', count: 2 }` 这种结构化
61
- 的值,分组名字也一样,话在 i18n 那一层才组出来。快照里存中文句子的话,换一次语言就得重抓一次
62
- GitHub。
46
+ 没有 runtime 依赖。
63
47
 
64
- CLI 那一侧(产文件消息、错误)刻意留中文:那是给开发者看的,不是页面的一部分。
48
+ ## 语言
65
49
 
66
- ## 前置条件
50
+ 页面右上角切换:英文(默认)、繁体中文、简体中文、日文。选了哪一种记在浏览器,跟 repo 无关——语言
51
+ 是看的人的偏好,不是某个项目的设置。
67
52
 
68
- - **Bun**。这几支用了 `Bun.build`、`Bun.serve`、`Bun.file` 与 bun 的 `spawnSync`,Node 跑不起来。
69
- - **`gh` CLI 已登录**,而且对目标 repo 有读取权。
70
- - 目标 repo 有 git remote 指向 GitHub。
71
- - 没有 runtime 依赖;devDependencies 只有类型与 lint/format 工具。
53
+ CLI 那一侧(产文件消息、错误)只有英文。
72
54
 
73
55
  ## 配置
74
56
 
75
57
  全部有默认值,一个都不设也跑得起来。默认值长在 `scripts/issue-map.ts` 的 `CONFIG`。
76
58
 
77
- | 环境变量 | 默认 | 意思 |
78
- | -------------------------- | --------------------------------- | ------------------------------------------------------------------ |
79
- | `GH_REPO` | 从 cwd 的 git 推断 | 要画别的 repo 时设它(`gh` 自己的变量,fork 与多 remote 也交给它) |
80
- | `ISSUE_MAP_PARENT_HEADING` | `Parent` | 子票在正文指向母票的段落标题。GitHub 原生 sub-issue 有值时优先 |
81
- | `ISSUE_MAP_LABELS_UNREADY` | `needs-triage,needs-info` | 挂了就是还没评估完,不能交给谁做 |
82
- | `ISSUE_MAP_LABELS_READY` | `ready-for-agent,ready-for-human` | 挂了才算评估完、可以动工 |
83
- | `ISSUE_MAP_LABELS_ACTIVE` | `in-progress` | 挂了代表有人在做,不必有 assignee |
84
- | `ISSUE_MAP_LABELS_HUMAN` | `ready-for-human` | 这些要人做,下一步不写实作指令 |
85
- | `ISSUE_MAP_CMD_IMPLEMENT` | `/implement` | 可以动工时图上叫人跑的指令 |
86
- | `ISSUE_MAP_CMD_TRIAGE` | `/triage` | 还要评估时图上叫人跑的指令 |
87
- | `ISSUE_MAP_PORT` | `4747` | server 的端口 |
88
- | `ISSUE_MAP_OPEN` | 开 | 设 `0` 就不自动开浏览器(`bun --watch` 的开发模式默认关掉) |
89
-
90
- 两个要特别想过的:
91
-
92
- - **标签词汇**:目标 repo 没在用这套标签就要换成它自己的名字。程序会侦测——快照里完全没出现 ready/unready 任何一个标签时,就不拿 triage 当闸门,否则每张票都会变成「待评估」。
93
- - **指令名**:`/implement`、`/triage` Claude Code 的 skill。目标 repo 没有的话一定要换掉,不然图上会叫人跑不存在的东西。
59
+ | 环境变量 | 默认 | 意思 |
60
+ | -------------------------- | --------------------------------- | ------------------------------------------------------ |
61
+ | `GH_REPO` | 从 cwd 的 git 推断 | 要画别的 repo 时设它(`gh` 自己的变量,fork 也交给它) |
62
+ | `ISSUE_MAP_PARENT_HEADING` | `Parent` | 子票在正文指向母票的段落标题 |
63
+ | `ISSUE_MAP_LABELS_UNREADY` | `needs-triage,needs-info` | 还没评估完,不能交给谁做 |
64
+ | `ISSUE_MAP_LABELS_READY` | `ready-for-agent,ready-for-human` | 评估完、可以动工 |
65
+ | `ISSUE_MAP_LABELS_ACTIVE` | `in-progress` | 有人在做,不必有 assignee |
66
+ | `ISSUE_MAP_LABELS_HUMAN` | `ready-for-human` | 要人做,下一步不写实作指令 |
67
+ | `ISSUE_MAP_CMD_IMPLEMENT` | `/implement` | 可以动工时图上叫人跑的指令 |
68
+ | `ISSUE_MAP_CMD_TRIAGE` | `/triage` | 还要评估时图上叫人跑的指令 |
69
+ | `ISSUE_MAP_PORT` | OS 指派的空端口 | server 的端口 |
70
+ | `ISSUE_MAP_OPEN` | 开 | 设 `0` 就不自动开浏览器 |
71
+
72
+ 三个要特别想过的:
73
+
74
+ - **标签词汇。** ready/unready 的默认值是 mattpocock/skills 五个[标准 triage 标签](https://github.com/mattpocock/skills/blob/main/skills/engineering/setup-matt-pocock-skills/triage-labels.md)
75
+ 里的四个。目标 repo 没在用这套就换成它自己的名字。快照里完全没出现这些标签时,就不拿 triage
76
+ 闸门,否则每张票都会变成「待评估」。(`in-progress` 是这个工具自己加的,那套 skill 没有「有人在
77
+ 做」这个标签。)
78
+ - **指令名。** `/implement`、`/triage` 就是那边的 [`implement`](https://github.com/mattpocock/skills/tree/main/skills/engineering/implement) 与
79
+ [`triage`](https://github.com/mattpocock/skills/tree/main/skills/engineering/triage) skill。要指向目标 repo 真的有的东西,不然图上会叫人跑不存在的。
80
+ - **已完成的兄弟票要靠原生 sub-issue。** 地图只跟 GitHub 要 open 票还牵着的 closed 票,而子票是从
81
+ 原生的 sub-issue 关系拿的。用 `## Parent` 正文惯例的 repo 看不到一组里**已完成**的子票,那一组的
82
+ 进度会比实际少。把子票在票页的 Sub-issues 关联上去一次就会回来;正文惯例可以留着,原生的本来
83
+ 就优先。
94
84
 
95
85
  ## 常见失败
96
86
 
97
- - `gh api graphql 失敗:…` — `gh` 没登录,或 cwd 不在目标 repo 的 git 树里。
98
- - `open issue 超過 100 張,這支要改成分頁抓` — GraphQL 的 `first` 上限就是 100。要支持更多票得在 `issue-map.ts` 的 `query()` 加分页;这是要改程序,不是配置。
99
-
100
- (CLI 消息是繁体中文,所以这里照它实际印出来的样子引用。)
87
+ `gh api graphql failed: …` — `gh` 没登录,或 cwd 不在目标 repo 的 git 树里。
101
88
 
102
89
  ## 文件
103
90
 
104
- | 文件 | 职责 |
105
- | ---------------------------- | ---------------------------------------------------------------------- |
106
- | `scripts/issue-map.ts` | 抓快照、算每张票的状态与下一步、产出 HTML。移植配置在里面的 `CONFIG` |
107
- | `scripts/issue-map-model.ts` | 纯数据模型:分组、关键路径。前后端共用 |
108
- | `scripts/issue-map-i18n.ts` | 四种语言的文案与查表。页面上每一句话的唯一来源 |
109
- | `scripts/issue-map-page.ts` | 浏览器端代码,构建时被打包进 HTML |
110
- | `scripts/issue-map.html` | 模板。两个占位区块(`issue-map-data`、`issue-map-code`)会被填入 |
111
- | `scripts/issue-map-serve.ts` | 本机 server,每个请求重抓一次 |
112
- | `scripts/mutate.ts` | 变异测试:改坏一行看测试会不会红。守门测试的反向验证用它,不要手改文件 |
91
+ | 文件 | 职责 |
92
+ | ---------------------------- | -------------------------------------------------------- |
93
+ | `scripts/issue-map.ts` | 抓快照、算状态与下一步、产出 HTML。配置在里面的 `CONFIG` |
94
+ | `scripts/issue-map-model.ts` | 纯数据模型:分组、关键路径、排版。前后端共用 |
95
+ | `scripts/issue-map-i18n.ts` | 四种语言的文案与查表 |
96
+ | `scripts/issue-map-page.ts` | 浏览器端代码,构建时被打包进 HTML |
97
+ | `scripts/issue-map.html` | 模板。两个占位区块会被填入 |
98
+ | `scripts/issue-map-serve.ts` | 本机 server,每个请求重抓一次 |
99
+ | `scripts/mutate.ts` | 变异测试:改坏一行看测试会不会红 |
113
100
 
114
101
  ## 在这个 repo 里开发
115
102
 
116
103
  ```bash
117
104
  bun install
118
- bun run issue-map:serve # --watch,改代码会自动重启;刻意不自动开浏览器(每存一次档就会多一个标签页)
105
+ bun run issue-map:serve # --watch;不自动开标签页(每存一次档就会多一个)
119
106
  bun run issue-map # 只产文件到 dist/issue-map.html
120
107
  bun run check # lint + format:check + typecheck
121
- bun test # 纯模型那一层(分组、关键路径、排版)
108
+ bun test # 纯模型那一层
122
109
  ```
123
110
 
124
111
  这个 repo 自己还没有 issue,`GH_REPO=<owner>/<repo>` 指到有票的 repo 才画得出东西。
125
112
 
126
- `tests/` 只守会让地图说谎或不能看的事,外观(颜色、形状、间距)刻意不验。新增守门测试要走反向验证——把它宣称要挡的缺陷放回产品代码,确认它会红:
113
+ **测试。** `tests/` 只守会让地图说谎或不能看的事,外观(颜色、形状、间距)刻意不验。新增守门测试
114
+ 要走反向验证——把它宣称要挡的缺陷放回产品代码,确认它会红:
127
115
 
128
116
  ```bash
129
117
  bun run mutate scripts/issue-map-model.ts tests/issue-map-layout.test.ts
130
118
  ```
131
119
 
132
- ## 两个设计上的决定,改之前先知道
120
+ **要动 `scripts/issue-map-i18n.ts`。** `EN` 是原稿,也是键的定义处;三份翻译的类型由它推导,少一个
121
+ 键或少一个 `{n}` 代入名,`bun run typecheck` 就会红。英文要分单复数的键写成 `{ one, other }`,中日
122
+ 文写一句字符串就好。模型那一侧不算句子——`nextStep` 是 `{ kind: 'waitChildren', count: 2 }` 这种结构
123
+ 化的值,话在这里才组出来。
124
+
125
+ ## 两个设计上的决定
133
126
 
134
- - **这一页不能改状态。** 没有按钮会回写 GitHub。刻意的:状态只有一个事实来源,多一个入口就会不一致。
135
- - **票名不进地图。** 节点只挂票号,名字在下方清单。试过在正文加短名段落,那是票名的第二个事实来源,改标题不会改它;机械缩短标题读不通。
127
+ - **这一页不能改状态。** 没有按钮会回写 GitHub。状态只有一个事实来源,多一个入口就会不一致。
128
+ - **不另设短名字段。** 站点标的是标题的开头几个字。在票里手动维护一个短名会变成票名的第二个
129
+ 事实来源,改标题不会跟着改。完整标题在下方清单。
package/README.zh-TW.md CHANGED
@@ -6,128 +6,124 @@
6
6
 
7
7
  把 GitHub Issues 的阻擋關係畫成一頁開發地圖:**哪幾張票現在可以動、哪幾張在等誰、關鍵路徑是哪一條。**
8
8
 
9
- 狀態的權威永遠是 GitHub Issues。這一頁只是快照,頁面上不能改狀態——所以不會長出第二個事實來源。
10
-
11
- ## 為什麼有這個專案
12
-
13
- 起點是 [mattpocock/skills](https://github.com/mattpocock/skills)。團隊照它那套「把工作流寫成
14
- skill、讓 agent 照著跑」開始做事之後,開票變得很便宜:想到一件事就開一張票,交給 skill 去接。
15
- 票因此長得很快——那是流程在運作的證據,不是問題。
16
-
17
- 問題在下一步。Agent 一輪吃一張票,所以每一輪真正要決定的是**派哪一張**,而這個答案不在任何
18
- 單一張票裡,它在票與票之間:誰擋著誰、哪一組子票還差幾張、最長的那條鏈有多長。GitHub Issues
19
- 一次只讓你讀一張票,要湊出那張圖就得一張一張點開來,而且每天都要重湊一次。
20
-
21
- 這一頁就是那張圖。
22
-
23
- ## 用法
24
-
25
- 在**要看的那個 repo** 裡跑:
26
-
27
9
  ```bash
28
10
  bunx issue-map@latest
29
11
  ```
30
12
 
31
- `@latest` npm 上最新的一版;要釘住特定版本就寫 `bunx issue-map@0.2.0`。
32
-
33
- 起在 `http://localhost:4747` **並直接開瀏覽器**。每次重新整理都重抓 GitHub,看到的一定是現在的狀態。repo 是 `gh` 從 cwd 的 git 推斷的,不必填。
13
+ 在要看的那個 repo 裡跑。會直接開瀏覽器分頁,每次重新整理都重抓 GitHub。狀態的權威永遠是 GitHub
14
+ Issues——這一頁只是快照,不能改狀態。
34
15
 
35
- 不要自動開分頁就設 `ISSUE_MAP_OPEN=0`。
16
+ ## 為什麼有這個專案
36
17
 
37
- 只要一份靜態 HTML 的話(`-p` 是用來選另一個 bin 的,少了它會變成起 server):
18
+ Agent 一輪吃一張票,所以每一輪真正要決定的是**派哪一張**。這個答案不在任何單一張票裡,它在票與
19
+ 票之間:誰擋著誰、哪一組子票還差幾張、最長的那條鏈有多長。GitHub 一次只讓你讀一張票。這一頁就是
20
+ 那張圖。
38
21
 
39
- ```bash
40
- bunx -p issue-map@latest issue-map-build # 寫到 dist/issue-map.html
41
- bunx -p issue-map@latest issue-map-build out.html
42
- ```
22
+ 它是繞著 [mattpocock/skills](https://github.com/mattpocock/skills) 那套做法建的——把工作流寫成
23
+ skill、讓 agent 照著跑——預設的標籤與指令也是從那裡來的。
43
24
 
44
- 快照就是快照——狀態會過期,要看現在的狀態就用上面的 server。
25
+ ## 用法
45
26
 
46
- ## 語言
27
+ | 指令 | 得到什麼 |
28
+ | --------------------------------------------------- | -------------------------------- |
29
+ | `bunx issue-map@latest` | server 起在空 port,自動開瀏覽器 |
30
+ | `bunx -p issue-map@latest issue-map-build` | 靜態檔到 `dist/issue-map.html` |
31
+ | `bunx -p issue-map@latest issue-map-build out.html` | 靜態檔到你指定的路徑 |
47
32
 
48
- 頁面右上角切換,**預設英文**,另外支援繁體中文、簡體中文、日文。選了哪一種記在瀏覽器
49
- (localStorage `issue-map:locale`),跟 repo 無關——語言是看的人的偏好,不是某個專案的設定。
50
- 刻意不看 `navigator.language`:預設就是英文,猜錯了反而每次進來都要改回去。
33
+ - repo 是 `gh` 從 cwd 的 git 推斷的,不必填。
34
+ - `ISSUE_MAP_PORT` 固定 port。固定就是嚴格的:被佔住時直接失敗,不會偷偷換一個。
35
+ - `ISSUE_MAP_OPEN=0` 不自動開分頁。
36
+ - 靜態檔會過期,要看現在的狀態就用 server。
37
+ - 在跑不了 script 的地方(嚴格 CSP、某些預覽窗),檔案會退回一份純文字的票清單,而不是一片空白。
38
+ - `@latest` 取 npm 上最新的一版;要釘住就寫 `bunx issue-map@0.2.0`。
51
39
 
52
- 文案全部在 `scripts/issue-map-i18n.ts`,那是頁面上每一句話的唯一來源:
40
+ ## 前置條件
53
41
 
54
- - `EN` 是原稿,也是鍵的定義處。三份翻譯的型別由它推導,少翻一個鍵 `bun run typecheck` 就會紅。
55
- - 句子裡的代入名(`{n}`、`{issues}`)也是型別的一部分,少傳一個編不過——不然缺的那個會以
56
- `{n}` 的樣子印在畫面上,而那要真的跑到那一格才看得到。
57
- - 英文要分單複數的鍵寫成 `{ one, other }`,中日文寫一句字串就好(`Intl.PluralRules` 對這幾種
58
- 語言只有 `other`)。
42
+ - **Bun**——這幾支用了 `Bun.build`、`Bun.serve`、`Bun.file`,Node 跑不起來。
43
+ - **`gh` CLI 已登入**,而且對目標 repo 有讀取權。
44
+ - 目標 repo 有 git remote 指向 GitHub。
59
45
 
60
- 模型與抓取那一側**不再算好句子**:`nextStep` `{ kind: 'waitChildren', count: 2 }` 這種結構化
61
- 的值,分組名字也一樣,話在 i18n 那一層才組出來。快照裡存中文句子的話,換一次語言就得重抓一次
62
- GitHub。
46
+ 沒有 runtime 依賴。
63
47
 
64
- CLI 那一側(產檔訊息、錯誤)刻意留中文:那是給開發者看的,不是頁面的一部分。
48
+ ## 語言
65
49
 
66
- ## 前置條件
50
+ 頁面右上角切換:英文(預設)、繁體中文、簡體中文、日文。選了哪一種記在瀏覽器,跟 repo 無關——語言
51
+ 是看的人的偏好,不是某個專案的設定。
67
52
 
68
- - **Bun**。這幾支用了 `Bun.build`、`Bun.serve`、`Bun.file` 與 bun 的 `spawnSync`,Node 跑不起來。
69
- - **`gh` CLI 已登入**,而且對目標 repo 有讀取權。
70
- - 目標 repo 有 git remote 指向 GitHub。
71
- - 沒有 runtime 依賴;devDependencies 只有型別與 lint/format 工具。
53
+ CLI 那一側(產檔訊息、錯誤)只有英文。
72
54
 
73
55
  ## 設定
74
56
 
75
57
  全部有預設值,一個都不設也跑得起來。預設值長在 `scripts/issue-map.ts` 的 `CONFIG`。
76
58
 
77
- | 環境變數 | 預設 | 意思 |
78
- | -------------------------- | --------------------------------- | ------------------------------------------------------------------ |
79
- | `GH_REPO` | 從 cwd 的 git 推斷 | 要畫別的 repo 時設它(`gh` 自己的變數,fork 與多 remote 也交給它) |
80
- | `ISSUE_MAP_PARENT_HEADING` | `Parent` | 子票在內文指向母票的段落標題。GitHub 原生 sub-issue 有值時優先 |
81
- | `ISSUE_MAP_LABELS_UNREADY` | `needs-triage,needs-info` | 掛了就是還沒評估完,不能交給誰做 |
82
- | `ISSUE_MAP_LABELS_READY` | `ready-for-agent,ready-for-human` | 掛了才算評估完、可以動工 |
83
- | `ISSUE_MAP_LABELS_ACTIVE` | `in-progress` | 掛了代表有人在做,不必有 assignee |
84
- | `ISSUE_MAP_LABELS_HUMAN` | `ready-for-human` | 這些要人做,下一步不寫實作指令 |
85
- | `ISSUE_MAP_CMD_IMPLEMENT` | `/implement` | 可以動工時圖上叫人跑的指令 |
86
- | `ISSUE_MAP_CMD_TRIAGE` | `/triage` | 還要評估時圖上叫人跑的指令 |
87
- | `ISSUE_MAP_PORT` | `4747` | server 的 port |
88
- | `ISSUE_MAP_OPEN` | 開 | 設 `0` 就不自動開瀏覽器(`bun --watch` 的開發模式預設關掉) |
89
-
90
- 兩個要特別想過的:
91
-
92
- - **標籤字彙**:目標 repo 沒在用這套標籤就要換成它自己的名字。程式會偵測——快照裡完全沒出現 ready/unready 任何一個標籤時,就不拿 triage 當閘門,否則每張票都會變成「待評估」。
93
- - **指令名**:`/implement`、`/triage` Claude Code 的 skill。目標 repo 沒有的話一定要換掉,不然圖上會叫人跑不存在的東西。
59
+ | 環境變數 | 預設 | 意思 |
60
+ | -------------------------- | --------------------------------- | ------------------------------------------------------ |
61
+ | `GH_REPO` | 從 cwd 的 git 推斷 | 要畫別的 repo 時設它(`gh` 自己的變數,fork 也交給它) |
62
+ | `ISSUE_MAP_PARENT_HEADING` | `Parent` | 子票在內文指向母票的段落標題 |
63
+ | `ISSUE_MAP_LABELS_UNREADY` | `needs-triage,needs-info` | 還沒評估完,不能交給誰做 |
64
+ | `ISSUE_MAP_LABELS_READY` | `ready-for-agent,ready-for-human` | 評估完、可以動工 |
65
+ | `ISSUE_MAP_LABELS_ACTIVE` | `in-progress` | 有人在做,不必有 assignee |
66
+ | `ISSUE_MAP_LABELS_HUMAN` | `ready-for-human` | 要人做,下一步不寫實作指令 |
67
+ | `ISSUE_MAP_CMD_IMPLEMENT` | `/implement` | 可以動工時圖上叫人跑的指令 |
68
+ | `ISSUE_MAP_CMD_TRIAGE` | `/triage` | 還要評估時圖上叫人跑的指令 |
69
+ | `ISSUE_MAP_PORT` | OS 指派的空 port | server 的 port |
70
+ | `ISSUE_MAP_OPEN` | 開 | 設 `0` 就不自動開瀏覽器 |
71
+
72
+ 三個要特別想過的:
73
+
74
+ - **標籤字彙。** ready/unready 的預設值是 mattpocock/skills 五個[標準 triage 標籤](https://github.com/mattpocock/skills/blob/main/skills/engineering/setup-matt-pocock-skills/triage-labels.md)
75
+ 裡的四個。目標 repo 沒在用這套就換成它自己的名字。快照裡完全沒出現這些標籤時,就不拿 triage
76
+ 閘門,否則每張票都會變成「待評估」。(`in-progress` 是這個工具自己加的,那套 skill 沒有「有人在
77
+ 做」這個標籤。)
78
+ - **指令名。** `/implement`、`/triage` 就是那邊的 [`implement`](https://github.com/mattpocock/skills/tree/main/skills/engineering/implement) 與
79
+ [`triage`](https://github.com/mattpocock/skills/tree/main/skills/engineering/triage) skill。要指向目標 repo 真的有的東西,不然圖上會叫人跑不存在的。
80
+ - **已完成的兄弟票要靠原生 sub-issue。** 地圖只跟 GitHub 要 open 票還牽著的 closed 票,而子票是從
81
+ 原生的 sub-issue 關係拿的。用 `## Parent` 內文慣例的 repo 看不到一組裡**已完成**的子票,那一組的
82
+ 進度會比實際少。把子票在票頁的 Sub-issues 關聯上去一次就會回來;內文慣例可以留著,原生的本來
83
+ 就優先。
94
84
 
95
85
  ## 常見失敗
96
86
 
97
- - `gh api graphql 失敗:…` — `gh` 沒登入,或 cwd 不在目標 repo 的 git 樹裡。
98
- - `open issue 超過 100 張,這支要改成分頁抓` — GraphQL 的 `first` 上限就是 100。要支援更多票得在 `issue-map.ts` 的 `query()` 加分頁;這是要改程式,不是設定。
87
+ `gh api graphql failed: …` — `gh` 沒登入,或 cwd 不在目標 repo 的 git 樹裡。
99
88
 
100
89
  ## 檔案
101
90
 
102
- | 檔案 | 責任 |
103
- | ---------------------------- | ---------------------------------------------------------------------- |
104
- | `scripts/issue-map.ts` | 抓快照、算每張票的狀態與下一步、產出 HTML。移植設定在裡面的 `CONFIG` |
105
- | `scripts/issue-map-model.ts` | 純資料模型:分組、關鍵路徑。前後端共用 |
106
- | `scripts/issue-map-i18n.ts` | 四種語言的文案與查表。頁面上每一句話的唯一來源 |
107
- | `scripts/issue-map-page.ts` | 瀏覽器端程式碼,建置時被打包進 HTML |
108
- | `scripts/issue-map.html` | 樣板。兩個佔位區塊(`issue-map-data`、`issue-map-code`)會被填入 |
109
- | `scripts/issue-map-serve.ts` | 本機 server,每個請求重抓一次 |
110
- | `scripts/mutate.ts` | 突變測試:改壞一行看測試會不會紅。守門測試的反向驗證用它,不要手改檔案 |
91
+ | 檔案 | 責任 |
92
+ | ---------------------------- | -------------------------------------------------------- |
93
+ | `scripts/issue-map.ts` | 抓快照、算狀態與下一步、產出 HTML。設定在裡面的 `CONFIG` |
94
+ | `scripts/issue-map-model.ts` | 純資料模型:分組、關鍵路徑、排版。前後端共用 |
95
+ | `scripts/issue-map-i18n.ts` | 四種語言的文案與查表 |
96
+ | `scripts/issue-map-page.ts` | 瀏覽器端程式碼,建置時被打包進 HTML |
97
+ | `scripts/issue-map.html` | 樣板。兩個佔位區塊會被填入 |
98
+ | `scripts/issue-map-serve.ts` | 本機 server,每個請求重抓一次 |
99
+ | `scripts/mutate.ts` | 突變測試:改壞一行看測試會不會紅 |
111
100
 
112
101
  ## 在這個 repo 裡開發
113
102
 
114
103
  ```bash
115
104
  bun install
116
- bun run issue-map:serve # --watch,改程式碼會自動重啟;刻意不自動開瀏覽器(每存一次檔就會多一個分頁)
105
+ bun run issue-map:serve # --watch;不自動開分頁(每存一次檔就會多一個)
117
106
  bun run issue-map # 只產檔到 dist/issue-map.html
118
107
  bun run check # lint + format:check + typecheck
119
- bun test # 純模型那一層(分組、關鍵路徑、排版)
108
+ bun test # 純模型那一層
120
109
  ```
121
110
 
122
111
  這個 repo 自己還沒有 issue,`GH_REPO=<owner>/<repo>` 指到有票的 repo 才畫得出東西。
123
112
 
124
- `tests/` 只守會讓地圖說謊或不能看的事,外觀(顏色、形狀、間距)刻意不驗。新增守門測試要走反向驗證——把它宣稱要擋的缺陷放回產品碼,確認它會紅:
113
+ **測試。** `tests/` 只守會讓地圖說謊或不能看的事,外觀(顏色、形狀、間距)刻意不驗。新增守門測試
114
+ 要走反向驗證——把它宣稱要擋的缺陷放回產品碼,確認它會紅:
125
115
 
126
116
  ```bash
127
117
  bun run mutate scripts/issue-map-model.ts tests/issue-map-layout.test.ts
128
118
  ```
129
119
 
130
- ## 兩個設計上的決定,改之前先知道
120
+ **要動 `scripts/issue-map-i18n.ts`。** `EN` 是原稿,也是鍵的定義處;三份翻譯的型別由它推導,少一個
121
+ 鍵或少一個 `{n}` 代入名,`bun run typecheck` 就會紅。英文要分單複數的鍵寫成 `{ one, other }`,中日
122
+ 文寫一句字串就好。模型那一側不算句子——`nextStep` 是 `{ kind: 'waitChildren', count: 2 }` 這種結構
123
+ 化的值,話在這裡才組出來。
124
+
125
+ ## 兩個設計上的決定
131
126
 
132
- - **這一頁不能改狀態。** 沒有按鈕會回寫 GitHub。刻意的:狀態只有一個事實來源,多一個入口就會不一致。
133
- - **票名不進地圖。** 節點只掛票號,名字在下方清單。試過在內文加短名段落,那是票名的第二個事實來源,改標題不會改它;機械縮短標題讀不通。
127
+ - **這一頁不能改狀態。** 沒有按鈕會回寫 GitHub。狀態只有一個事實來源,多一個入口就會不一致。
128
+ - **不另設短名欄位。** 站點標的是標題的開頭幾個字。在票裡手動維護一個短名會變成票名的第二個
129
+ 事實來源,改標題不會跟著改。完整標題在下方清單。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "issue-map",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Draws the blocking relationships between your GitHub Issues as a one-page dev map: which issues can be picked up now, which are waiting on what, and where the critical path runs.",
5
5
  "keywords": [
6
6
  "bun",
@@ -26,6 +26,7 @@
26
26
  "scripts/issue-map-i18n.ts",
27
27
  "scripts/issue-map-model.ts",
28
28
  "scripts/issue-map-page.ts",
29
+ "scripts/issue-map-view.ts",
29
30
  "scripts/issue-map-serve.ts",
30
31
  "scripts/issue-map.html"
31
32
  ],
@@ -42,10 +43,10 @@
42
43
  "check": "bun run lint && bun run format:check && bun run typecheck"
43
44
  },
44
45
  "devDependencies": {
45
- "@types/bun": "latest",
46
- "oxfmt": "^0.62.0",
46
+ "@types/bun": "^1.4.2",
47
+ "oxfmt": "^0.67.0",
47
48
  "oxlint": "^1.76.0",
48
- "typescript": "^5"
49
+ "typescript": "^7.0.2"
49
50
  },
50
51
  "engines": {
51
52
  "bun": ">=1.0.0"
@@ -2,15 +2,15 @@
2
2
  * 頁面文案。**沒有 Bun、沒有 DOM**——它只是字典與一個查表函式,所以抓資料那一側與畫面那一側
3
3
  * 都能 import。
4
4
  *
5
- * 這裡是頁面上所有給人看的字的唯一來源。畫面那一側不寫死任何一句話,模型那一側也不再算好
6
- * 中文句子送過來——`nextStep` 與分組名字都是結構化的值,句子在這裡才組出來。少了這一層,同一
7
- * 句話會同時長在模型、樣板與畫面三處,換語言只會換到其中一處。
5
+ * 這裡是頁面上所有給人看的字的唯一來源。畫面那一側不寫死任何一句話,模型那一側送的是結構化
6
+ * 的值(`nextStep`、分組名字),句子在這裡才組出來——否則同一句話會同時長在模型、樣板與畫面
7
+ * 三處,換語言只會換到其中一處。
8
8
  *
9
- * `en` 是原稿,也是鍵的定義處:`Messages` 由它推導,少翻一個鍵就編不過。翻譯缺鍵不會 fallback
9
+ * `en` 是原稿,也是鍵的定義處:`Messages` 由它推導,少翻一個鍵就編不過。翻譯缺鍵不 fallback
10
10
  * 成英文——那會安靜地留下半英半中的畫面。
11
11
  *
12
12
  * 字典的值會進 `innerHTML`(`lede.*` 刻意帶 `<strong>`),所以**翻譯裡不要放意料外的標記**;
13
- * 代入的值由呼叫端負責逃脫,跟原本用模板字串時一樣。
13
+ * 代入的值由呼叫端負責逃脫。
14
14
  */
15
15
 
16
16
  /** 支援的語言。第一個是預設。 */
@@ -100,6 +100,8 @@ const EN = {
100
100
  'group.linkedSub': 'linked by prerequisites, but under no parent issue',
101
101
  'group.spec': '#{n} parent spec',
102
102
  'group.progress': 'sub-issues {done} / {total} done · parent closes only when all do',
103
+ 'group.undrawn':
104
+ 'Map not drawn — nothing here blocks anything. The issues are in the list below.',
103
105
 
104
106
  'status.ready': 'ready',
105
107
  'status.active': 'in progress',
@@ -199,6 +201,7 @@ const ZH_TW: Messages = {
199
201
  'group.linkedSub': '有前置關係,但不屬於任何母票',
200
202
  'group.spec': '#{n} 母票規格',
201
203
  'group.progress': '子票 {done} / {total} 已完成 · 全關後才關 parent',
204
+ 'group.undrawn': '沒有畫圖——這一組裡沒有任何阻擋關係。票在下方清單。',
202
205
 
203
206
  'status.ready': '可接手',
204
207
  'status.active': '進行中',
@@ -288,6 +291,7 @@ const ZH_CN: Messages = {
288
291
  'group.linkedSub': '有前置关系,但不属于任何母票',
289
292
  'group.spec': '#{n} 母票规格',
290
293
  'group.progress': '子票 {done} / {total} 已完成 · 全关后才关 parent',
294
+ 'group.undrawn': '没有画图——这一组里没有任何阻挡关系。票在下方清单。',
291
295
 
292
296
  'status.ready': '可接手',
293
297
  'status.active': '进行中',
@@ -381,6 +385,8 @@ const JA: Messages = {
381
385
  'group.linkedSub': '依存関係はあるが、親チケットには属さない',
382
386
  'group.spec': '#{n} 親チケットの仕様',
383
387
  'group.progress': 'サブチケット {done} / {total} 完了 · すべて閉じてから親を閉じる',
388
+ 'group.undrawn':
389
+ '図は描いていません——このグループにはブロック関係がありません。チケットは下の一覧にあります。',
384
390
 
385
391
  'status.ready': '着手可',
386
392
  'status.active': '対応中',
@@ -3,8 +3,7 @@
3
3
  * 那一側(`issue-map-page.ts`)都 import 它,所以它不能碰任何一邊的專屬 API。
4
4
  *
5
5
  * 這裡住的是「同一份定義只有一份」的東西:狀態的五個值、一張票的形狀、分群規則、關鍵路徑,
6
- * 以及線路圖的排版。畫面那一側曾經把狀態的五個值再抄一次,兩邊沒有東西保證同步;現在型別
7
- * 是共用的,抄錯編不過。
6
+ * 以及線路圖的排版。兩側共用同一份型別,任何一邊自己抄一份都編不過。
8
7
  */
9
8
 
10
9
  /** 一張票現在的處境。同時是頁面的顏色與篩選分頁。 */
@@ -75,7 +74,6 @@ export type Snapshot = {
75
74
  readonly repo: string
76
75
  /** 這一次實際生效的標籤字彙。圖例照它寫,不然改了設定圖例就會說謊。 */
77
76
  readonly labels: { readonly ready: readonly string[]; readonly unready: readonly string[] }
78
- /** 一張圖一群。 */
79
77
  readonly groups: readonly Group[]
80
78
  /** 最長的一條依序未完成鏈,也就是最少要幾輪。 */
81
79
  readonly criticalPath: number
@@ -185,6 +183,13 @@ export type Layout = {
185
183
  readonly height: number
186
184
  }
187
185
 
186
+ /** 月台一列至少幾站。票很少時不要排成細細一條。 */
187
+ const ISLAND_MIN_PER_ROW = 4
188
+ /**
189
+ * 月台一列最多幾站。再寬下去橫向要捲很遠,而月台上的左右位置本來就不帶意義——寧可多幾列。
190
+ */
191
+ const ISLAND_MAX_PER_ROW = 12
192
+
188
193
  /**
189
194
  * 把一組票排成線路圖。
190
195
  *
@@ -235,7 +240,9 @@ export function layoutOf(members: readonly MapIssue[]): Layout {
235
240
  }
236
241
 
237
242
  const depth = wired.length ? Math.max(...wired.map((m) => levelOf(m.number))) : 1
238
- const perRow = Math.max(depth, 4)
243
+ // 月台排成接近正方形,不然沒有阻擋關係的 repo 會把整批票疊成一條幾千 px 高的直條。
244
+ const squarish = Math.ceil(Math.sqrt(island.length))
245
+ const perRow = Math.max(depth, Math.min(squarish, ISLAND_MAX_PER_ROW), ISLAND_MIN_PER_ROW)
239
246
  const xy = new Map<number, Point>()
240
247
  tracks.forEach((chain, index) => {
241
248
  for (const n of chain) {