當你用試算表管理 WBS(工作分解結構圖)時,常會遇到這些情況:
這篇文章整理的是:把 WBS 寫成 每行一個任務的扁平 Markdown 表格,並同時從功能軸與工程軸彙總工時的方法。內容包含以 AI 編輯為前提的欄位設計,以及為了不盲信 AI 的彙總結果而準備的驗算腳本。
※本文是個人的運用備忘錄。內容以 2026-08-05 當時確認的資訊為準。Markdown 表格的行為是根據 GitHub Flavored Markdown(GFM)規範撰寫,文中的行為也已在參照實作 cmark-gfm 上實際確認。不過,包含 Qiita 在內的各服務 Markdown 渲染器,行為不一定完全一致。

本文只聚焦以下三點:
另一方面,以下內容不會涵蓋:

先講結論。適合用 AI 編輯的 WBS,具備以下四點:
功能 工程 這些欄位表示簡單說,就是把 WBS 表格當成 「彙總的輸入資料」,而不是 「給人類閱讀的最終成品」。

不是試算表不好,而是當你以 AI 與 Git 為前提時,「文字」本身就變得很有價值。
.xlsx 是由 ZIP 壓縮的 XML 集合(Office Open XML),所以在 Git 裡會被當成二進位檔。實際上,只改動 xlsx 內的一個地方後提交並看差異,會像這樣:
$ git diff --stat
wbs.xlsx | Bin 299 -> 299 bytes
1 file changed, 0 insertions(+), 0 deletions(-)
$ git diff
diff --git a/wbs.xlsx b/wbs.xlsx
index 433c46f..6bf598d 100644
Binary files a/wbs.xlsx and b/wbs.xlsx differ
看不出來「是哪個任務的估算多了幾天」。如果是 Markdown 表格,只有變動的那一行會出現在 diff 裡。
-| T-007 | 承認流程 | 實作 | 承認/退回 API 的實作 | 鈴木 | 5 | 5 | doing |
+| T-007 | 承認流程 | 實作 | 承認/退回 API 的實作 | 鈴木 | 8 | 5 | doing |
Markdown 表格是純文字,可以直接把檔案讀給 AI。像「把 T-007 的估算改成 8 人日,理由寫在備註」這種局部修改,因為可以明確定位列,所以指示也更容易。
WBS 更新可以做成 Pull Request。工時增減會以 diff 的形式留下來,因此可以用留言記錄「為什麼這個估算增加了」。
當然,也有失去的東西:
其中 自動彙總可以由後面會提到的腳本替代。如果排序與篩選真的已經成為必要功能,則應該果斷移到專用工具。

本文在彙總 WBS 時,將「軸」定義為以下意思:
稱呼意義本文中的欄位功能軸要做什麼(功能/成果物單位)功能工程軸處於哪個階段的工作(需求定義、設計、實作…)工程這兩個軸不分開管理成兩張表,而是作為欄位放進同一份任務表。如此一來,就能從同一份主表產生功能別與工程別兩種彙總。
另外,腳本能確認的是 已登錄任務的彙總是否一致。至於是否少了必要任務,則由人來審查確認。

常見的錯誤是,把功能軸 WBS 與工程軸 WBS 分別放在不同工作表。結果只更新其中一邊,總和就不一致了。
因此,做法改成這樣:
如果用試算表來類比,就像樞紐分析表。只要主表只有一份,更新點也就只有一處。
wbs.md(主表:每行一個任務)
│
├─▶ 功能軸彙總(各功能的估算/實績)
├─▶ 工程軸彙總(各工程的估算/實績)
└─▶ 功能 × 工程的交叉彙總

以讓 AI 編輯為前提時,欄位設計有一些有用的規則:
T-001 這種連號,即使重新排序也不要變更值。這樣就能對 AI 下達像「把 T-007 的估算改成 8 人日」這樣的指令功能 工程 的值一致,就能彙總估算(人日),儲存格放 3。如果寫成 3人日,彙總時就無法直接當數字處理- — 這樣從外觀上就能察覺欄位是否錯位備註 保持簡短| 時,請用 \| 跳脫 — 直接寫會多出一欄todo / doing / done 之類,AI 與人類都不會表記混亂依照這些規則撰寫的範本如下(請存成 wbs.md)。
| ID | 功能 | 工程 | 任務 | 負責人 | 估算(人日) | 實績(人日) | 狀態 |
| --- | --- | --- | --- | --- | ---: | ---: | --- |
| T-001 | 申請表單 | 需求定義 | 梳理輸入項目與驗證條件 | 佐藤 | 2 | 2 | done |
| T-002 | 申請表單 | 設計 | 建立畫面流程圖與 API 請求規格 | 佐藤 | 3 | 3 | done |
| T-003 | 申請表單 | 實作 | 實作表單畫面 | 田中 | 5 | 6 | doing |
| T-004 | 申請表單 | 測試 | 輸入值的邊界值測試 | 田中 | 2 | 0 | todo |
| T-005 | 核准流程 | 需求定義 | 整理核准路線的條件 | 佐藤 | 3 | 3 | done |
| T-006 | 核准流程 | 設計 | 狀態轉移與資料表設計 | 鈴木 | 4 | 4 | done |
| T-007 | 核准流程 | 實作 | 實作核准/退回 API | 鈴木 | 8 | 5 | doing |
| T-008 | 核准流程 | 測試 | 核准路線的全面測試 | 鈴木 | 4 | 0 | todo |
| T-009 | 清單/搜尋 | 設計 | 整理搜尋條件與排序規格 | 田中 | 2 | 2 | done |
| T-010 | 清單/搜尋 | 實作 | 實作清單畫面與分頁 | 田中 | 5 | 0 | todo |
| T-011 | 清單/搜尋 | 測試 | 大量資料的顯示確認 | 田中 | 2 | 0 | todo |
| T-012 | 通知 | 實作 | 寄送核准請求信件的處理 | 鈴木 | 3 | 0 | todo |
| T-013 | 共通基礎 | 設計 | 設計認證/權限模型 | 佐藤 | 3 | 3 | done |
| T-014 | 共通基礎 | 實作 | CI 設定與自動執行測試 | 田中 | 2 | 2 | done |
| T-015 | 共通基礎 | 上線 | 撰寫正式環境部署手冊 | 佐藤 | 2 | 0 | todo |
把數值欄位分隔列寫成 ---:,就會靠右對齊,數字排列更整齊、也更容易閱讀。

以 功能 欄位分組,彙總估算與實績。執行後述腳本時,會輸出下表:
| 功能 | 估算(人日) | 實績(人日) | 任務數 |
| --- | ---: | ---: | ---: |
| 申請表單 | 12 | 11 | 4 |
| 核准流程 | 19 | 12 | 4 |
| 清單/搜尋 | 9 | 2 | 3 |
| 通知 | 3 | 0 | 1 |
| 共通基礎 | 7 | 5 | 3 |
| **合計** | **50** | **30** | 15 |
從功能軸來看,可以討論出這些事情:
「砍掉一個功能」這種判斷,若沒有以功能軸彙整工時,是很難做的。

把同一張表依 工程 欄位分組後,結果如下:
| 工程 | 估算(人日) | 實績(人日) | 任務數 |
| --- | ---: | ---: | ---: |
| 需求定義 | 5 | 5 | 2 |
| 設計 | 12 | 12 | 4 |
| 實作 | 23 | 13 | 5 |
| 測試 | 8 | 0 | 3 |
| 上線 | 2 | 0 | 1 |
| **合計** | **50** | **30** | 15 |
工程軸是用來思考排程與人力配置的。
此外,也可以做二軸交叉彙總。空白(-)表示「那個任務根本沒立出來」,因此 很有助於找缺漏。
| 功能 | 需求定義 | 設計 | 實作 | 測試 | 上線 | 合計 |
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
| 申請表單 | 2 | 3 | 5 | 2 | - | 12 |
| 核准流程 | 3 | 4 | 8 | 4 | - | 19 |
| 清單/搜尋 | - | 2 | 5 | 2 | - | 9 |
| 通知 | - | - | 3 | - | - | 3 |
| 共通基礎 | - | 3 | 2 | - | 2 | 7 |
| **合計** | 5 | 12 | 23 | 8 | 2 | **50** |
這個例子中,通知缺少設計與測試任務,而清單/搜尋缺少需求定義。這可以拿來判斷,究竟是刻意省略,還是單純漏了。

這裡是重點。當你請 AI「把這張表彙總」時,它很快就會回傳看起來合理的表格;但如果直接拿來用,會踩到一些坑。
GFM 規範中,如果某一行的儲存格數量比標題行多,超出的部分會被忽略。少的話則補空格。以 cmark-gfm 參照實作確認後如下。
輸入:
| ID | 功能 | 估算 |
| --- | --- | --- |
| T-001 | 申請表單 | 2 | 追加的備註 |
| T-002 | 核准流程 |
輸出(節錄):
<tr><td>T-001</td><td>申請表單</td><td>2</td></tr>
<tr><td>T-002</td><td>核准流程</td><td></td></tr>
追加的備註 不會報錯,而是直接消失。即使 AI 只在某一行多加一欄,在預覽裡也不容易察覺。
如果標題行與分隔行的儲存格數量不一致,GFM 不會把它認定為表格。AI 新增了一欄卻忘記修分隔行時,整個表格就會變成普通段落。
| ID | 功能 | 估算 |
| --- | --- |
| T-001 | 申請表單 | 2 |
轉成 HTML 後會變成 <p>| ID | 功能 | 估算 | ...</p>。這是「表格莫名崩掉」的典型例子。
| 會拆欄如果任務名稱包含像 A|B 這樣的 pipe,欄位就會在那裡被切開。用 \| 跳脫後,就能把 | 當成儲存格內容保留下來。
語言模型在多位數加法或大量列彙總時,可能會出錯。而且即使錯了,它也會用很有自信的語氣回答。彙總結果一定要由機械式重新計算後比對。
請求 AI 幫忙整理或排序時,有時會掉列或改變順序。一旦讓它改了主表,就一定要用 git diff 檢查行數與差異。

理解了這些坑之後,讓 AI 幫忙的重點就應該從 工時計算 轉向 表格結構審查。數字由我們自己掌握,請它看的是粒度與表記是否一致。
以下是可直接貼上使用的提示詞範例:
請幫我審查以下 WBS 表格。
目的:
- 希望能夠以功能軸與工程軸彙總工時
- 想減少作業粒度的落差
- 之後要轉成 CSV,再在 Excel 內做樞紐分析
請確認以下觀點:
1. 功能名稱的粒度是否一致
2. 工程名稱是否有表記不一致的情況(例如:單元測試 / 單體測試 / UT)
3. 任務是否為每行一個工作
4. 估算是否有特別大的列(哪些列應該拆分)
5. 狀態是否有 `todo` / `doing` / `done` 以外的值
6. 數值欄位中是否混入單位或符號
7. 儲存格內是否有未跳脫的 `|`
請輸出以下內容:
- 指摘事項(附上列 ID)
- 修正版的 Markdown 表格(不要減少列數,ID 不可變)
- 被修改的列清單
請不要修改工時數值本身。
最後兩行很重要。明確寫出 ID 不可變、列數不可減少,差異審查才成立。不讓 AI 改工時數值,並不是不信任它的估算,而是為了讓 AI 修改了什麼、自己修改了什麼,能夠在 git diff 中清楚區分。
也可以一起請它做彙總,但那些數字一定要用下一節的腳本再核對一次。

前面提到的第 1 到第 4 個坑,可以只靠 Python 標準函式庫的腳本機械式處理掉。將以下內容存成 wbs_rollup.py,並用 python3 wbs_rollup.py wbs.md 執行。
#!/usr/bin/env python3
"""讀取以 Markdown 表格撰寫的 WBS,並以功能軸與工程軸彙總工時。"""
import re
import sys
from collections import defaultdict
KEY_FEATURE = "功能"
KEY_PHASE = "工程"
KEY_ESTIMATE = "估算(人日)"
KEY_ACTUAL = "實績(人日)"
def split_row(line):
"""將一行 Markdown 表格拆成儲存格清單(不把 \\| 當分隔符)。"""
line = line.strip()
if line.startswith("|"):
line = line[1:]
if line.endswith("|"):
line = line[:-1]
cells = re.split(r"(?<!\\)\|", line)
return [c.strip().replace("\\|", "|") for c in cells]
def is_delimiter_row(cells):
return bool(cells) and all(re.fullmatch(r":?-{1,}:?", c) for c in cells)
def parse_first_table(text):
"""回傳本文中第一個 Markdown 表格的(標題、列資料、警告)。"""
lines = text.splitlines()
header, rows, warnings = None, [], []
for i, line in enumerate(lines):
if not line.lstrip().startswith("|"):
if header is not None:
break
continue
cells = split_row(line)
if header is None:
header = cells
continue
if is_delimiter_row(cells):
if len(cells) != len(header):
raise SystemExit(
f"第 {i + 1} 行:標題行({len(header)} 欄)與分隔行({len(cells)} 欄)"
"欄位數不同。此表將不會被 Markdown 正確渲染"
)
continue
if len(cells) != len(header):
warnings.append(
f"第 {i + 1} 行:欄位數為 {len(cells)}(標題是 {len(header)}):"
f"{cells[0] if cells else ''}"
)
cells = (cells + [""] * len(header))[: len(header)]
rows.append(dict(zip(header, cells)))
if header is None:
raise SystemExit("找不到 Markdown 表格")
return header, rows, warnings
def to_number(value, row_id, column, warnings):
value = value.strip()
if value in ("", "-", "—"):
return 0.0
try:
return float(value)
except ValueError:
warnings.append(f"{row_id}: {column} 不是數字:{value!r}")
return 0.0
def render_table(headers, rows, align_right=()):
delim = ["---:" if h in align_right else "---" for h in headers]
out = ["| " + " | ".join(headers) + " |", "| " + " | ".join(delim) + " |"]
for row in rows:
out.append("| " + " | ".join(str(c) for c in row) + " |")
return "\n".join(out)
def fmt(value):
return str(int(value)) if float(value).is_integer() else f"{value:g}"
def main(path):
with open(path, encoding="utf-8") as f:
header, rows, warnings = parse_first_table(f.read())
for key in (KEY_FEATURE, KEY_PHASE, KEY_ESTIMATE, KEY_ACTUAL):
if key not in header:
raise SystemExit(f"找不到欄位 {key!r}。標題:{header}")
by_feature = defaultdict(lambda: [0.0, 0.0, 0])
by_phase = defaultdict(lambda: [0.0, 0.0, 0])
cross = defaultdict(float)
features, phases = [], []
total_estimate = total_actual = 0.0
for row in rows:
row_id = row.get("ID", "?")
feature, phase = row[KEY_FEATURE], row[KEY_PHASE]
estimate = to_number(row[KEY_ESTIMATE], row_id, KEY_ESTIMATE, warnings)
actual = to_number(row[KEY_ACTUAL], row_id, KEY_ACTUAL, warnings)
if feature not in features:
features.append(feature)
if phase not in phases:
phases.append(phase)
for bucket, key in ((by_feature, feature), (by_phase, phase)):
bucket[key][0] += estimate
bucket[key][1] += actual
bucket[key][2] += 1
cross[(feature, phase)] += estimate
total_estimate += estimate
total_actual += actual
print(f"## 彙總結果({len(rows)} 個任務)\n")
print("### 功能軸\n")
body = [[f, fmt(by_feature[f][0]), fmt(by_feature[f][1]), by_feature[f][2]]
for f in features]
body.append(["**合計**", f"**{fmt(total_estimate)}**",
f"**{fmt(total_actual)}**", len(rows)])
print(render_table(["功能", "估算(人日)", "實績(人日)", "任務數"], body,
align_right=("估算(人日)", "實績(人日)", "任務數")))
print("\n### 工程軸\n")
body = [[p, fmt(by_phase[p][0]), fmt(by_phase[p][1]), by_phase[p][2]]
for p in phases]
body.append(["**合計**", f"**{fmt(total_estimate)}**",
f"**{fmt(total_actual)}**", len(rows)])
print(render_table(["工程", "估算(人日)", "實績(人日)", "任務數"], body,
align_right=("估算(人日)", "實績(人日)", "任務數")))
print("\n### 功能 × 工程(估算人日)\n")
body = []
for f in features:
row = [f] + [fmt(cross[(f, p)]) if cross[(f, p)] else "-" for p in phases]
row.append(fmt(by_feature[f][0]))
body.append(row)
body.append(["**合計**"] + [fmt(by_phase[p][0]) for p in phases]
+ [f"**{fmt(total_estimate)}**"])
print(render_table(["功能"] + phases + ["合計"], body,
align_right=tuple(phases) + ("合計",)))
if warnings:
print("\n### 警告\n")
for w in warnings:
print(f"- {w}")
sys.exit(1)
if __name__ == "__main__":
if len(sys.argv) != 2:
raise SystemExit("用法:python3 wbs_rollup.py <WBS Markdown 檔>")
main(sys.argv[1])
這個腳本的重點不只是彙總,而是 偵測異常並以 exit code 1 失敗。例如把 T-012 那一行改成「估算寫成 3人日,並且多加一欄」後執行,會看到如下結果:
$ python3 wbs_rollup.py wbs.md
(…彙總結果…)
### 警告
- 第 14 行:欄位數為 9(標題是 8):T-012
- T-012: 估算(人日) 不是數字:'3人日'
$ echo $?
1
因為會透過 exit code 失敗,所以可以放進 CI。AI 修改 WBS 的 Pull Request 中,能自動攔下欄位崩壞與非數字儲存格。
# .github/workflows/wbs.yml 的一部分
- name: Validate WBS
run: python3 wbs_rollup.py wbs.md

把前面內容落到實務上,流程會變成這樣:
wbs.md(主表)放進 repositorywbs.mdpython3 wbs_rollup.py wbs.md,並將彙總結果輸出成 wbs_summary.mdgit diff 檢查變動行(行數是否符合預期、是否有不該消失的列)wbs_rollup.py,偵測表格崩壞如果把彙總結果也保留下來,彙總本身的差異也能追蹤。
$ python3 wbs_rollup.py wbs.md > wbs_summary.md
$ git diff --stat
wbs.md | 2 +-
wbs_summary.md | 12 ++++++------
2 files changed, 7 insertions(+), 7 deletions(-)
「如果把 T-007 的估算從 5 改成 8,承認流程合計就從 16 變 19,整體從 47 變 50」這種因果關係,也會直接保留在差異裡。

最終報告常常還是要交給 Excel。在某些情況下,直接貼上 Markdown 表格也能匯入,但欄位一多就容易崩,所以透過 CSV 會更穩定。
和 wbs_rollup.py 一樣,這也可以只用標準函式庫完成。
#!/usr/bin/env python3
"""將 wbs.md 的表格轉成 CSV。用法:python3 wbs_to_csv.py wbs.md wbs.csv"""
import csv
import re
import sys
# 暫時保留跳脫過的 \|,使用不會出現在表格中的字元
SENTINEL = "\x00"
def split_row(line: str) -> list[str]:
line = line.strip().replace(r"\|", SENTINEL)
if line.startswith("|"):
line = line[1:]
if line.endswith("|"):
line = line[:-1]
return [c.strip().replace(SENTINEL, "|") for c in line.split("|")]
def parse(text: str) -> tuple[list[str], list[list[str]]]:
lines = [ln for ln in text.splitlines() if ln.strip().startswith("|")]
if len(lines) < 2:
sys.exit("找不到表格")
header = split_row(lines[0])
if not all(re.fullmatch(r":?-{1,}:?", c) for c in split_row(lines[1])):
sys.exit("第 2 行不是分隔行")
rows = []
for lineno, ln in enumerate(lines[2:], start=3):
cells = split_row(ln)
if len(cells) != len(header):
# 這裡要直接失敗,先檢查欄位數不一致
sys.exit(f"第 {lineno} 行:欄位數不是 {len(header)},而是 {len(cells)}")
rows.append(cells)
return header, rows
def main() -> None:
src, dest = sys.argv[1], sys.argv[2]
with open(src, encoding="utf-8") as f:
header, rows = parse(f.read())
# 給 Excel 用,所以用 utf-8-sig(含 BOM)。newline="" 是 csv 模組的慣例
with open(dest, "w", encoding="utf-8-sig", newline="") as f:
writer = csv.writer(f)
writer.writerow(header)
writer.writerows(rows)
print(f"已將 {len(rows)} 行寫入 {dest}")
if __name__ == "__main__":
main()
這個腳本刻意強調以下三點:
zip(header, cells) 這種簡單寫法,會在短的一側直接截斷,因此若不先檢查欄位數,很容易漏掉資料缺失。所以要在用 zip 組合前先驗證欄位數,只要有一行不符就停止。\| 跳脫:GFM 可以用 \| 在儲存格內寫 pipe。若單純 split("|"),欄位就會錯位。utf-8-sig 輸出:若在 Excel 以不含 BOM 的 UTF-8 開啟,在日文或中文環境下有時會被誤判成 Shift_JIS,導致亂碼。utf-8-sig 會在檔案開頭加上 EF BB BF 三個位元組,讓 Excel 直接把它當 UTF-8。反過來,如果這個 CSV 要再被程式處理,BOM 反而是多餘的,那就維持 utf-8 即可。newline="" 是因為 csv 模組會自行以 r\n 輸出換行;不加的話,依環境不同可能會每隔一行多出空白行。

匯入 CSV 後,就可以用樞紐分析表切換軸。這就像把 wbs_rollup.py 的同樣彙總,用 GUI 方式再做一次。
想看什麼列欄值功能別工時功能(不放)估算(人日) 的合計工程別工時工程(不放)估算(人日) 的合計功能 × 工程功能``工程``估算(人日) 的合計負責人別工時負責人(不放)估算(人日) 的合計估算與實績對照功能(不放)估算(人日) 與 實績(人日) 的合計特別是把 功能 放在列、工程 放在欄的交叉彙總,可以一眼看出「只有這個功能的測試工時偏少」「設計工時太集中」這類偏差。
這裡也建議至少確認一次:Excel 的合計是否與 wbs_rollup.py 的合計一致。若不一致,通常是數值欄位混入單位或空白,導致 Excel 把它當文字處理。
整體流程會是這樣:
1. 用 Markdown 寫 wbs.md(主表)
2. 請 AI 做結構審查(不要讓它改工時數值)
3. 用 python3 wbs_rollup.py wbs.md 驗算
4. 用 python3 wbs_to_csv.py wbs.md wbs.csv 轉成 CSV
5. 匯入 Excel,使用樞紐分析表做彙總與圖表化
正本永遠是 wbs.md。如果在 Excel 裡直接修改,就會失去差異追蹤,所以要把 Excel 當成展示用檢視 來看待。

它不是萬能的。以下情況就應該老實考慮其他方法:
狀況會發生的事代替方案任務超過數百行表格太長,人與 AI 都難以讀完;AI 的上下文也會膨脹按功能分檔,或直接移到專用專案管理工具要管理依賴關係與關鍵路徑表格無法完整表現前後關係搭配甘特圖(例如 Mermaid 的 gantt)或專用工具多人同時更新衝突變多決定更新負責人與頻率,並固定行順序以降低衝突需要每天登記實績工時純靠 Markdown 手動輸入無法長期維持從工時管理系統匯入實績,反映到 實績(人日) 欄位想細緻管理進度狀態 欄位的粒度不夠把任務再拆小一點### 不要分得太細,也不要混得太雜
先不論適不適合,光是粒度就可能失敗。
「每行一個任務」很重要,但 如果拆得太細,更新表格本身就會變成工作。若細到數十分鐘一項,每次改估算都得動到幾十行,最後反而沒人維護。大致上,只有在符合以下任一條件時才拆:
功能 或 工程)改變了反過來說,不要拆到連決策都用不到的粒度。
另一件事是,不要把估算用途與進度管理用途硬塞進同一張表。這張表的主要目的在估算,所以只保留 狀態 與 實績(人日) 到某個程度即可。如果再加上開始日、結束日、議題、優先度、進度率等欄位,欄位會迅速膨脹,AI 和人都會更難讀,git diff 的雜訊也會增加。想更細追進度時,通常在那個階段改用別的工具比較健康。
若只是想輔助顯示依賴關係,也可以加上 Mermaid 的 gantt。Mermaid 會在任務名稱後用冒號分隔中繼資料,並以 after <taskID> 指定前置任務。
gantt
title Expense Reimbursement App Renovation
dateFormat YYYY-MM-DD
section 申請表單
需求定義 :done, t001, 2026-08-03, 2d
設計 :done, t002, after t001, 3d
實作 :active, t003, after t002, 5d
section 核准流程
需求定義 :done, t005, 2026-08-03, 3d
設計 :done, t006, after t005, 4d
實作 :active, t007, after t006, 8d
不過,這很容易變成甘特圖與 WBS 的雙重管理。工時彙總仍應以 wbs.md 為正本,而甘特圖則視為「展示用檢視」會比較穩妥。

為了讓 WBS 更適合 AI 編輯,我做了以下事情:
功能 工程 欄位而非階層來承載軸ID、不要減少列數、不要改數值utf-8-sig 輸出git diff 能以行為單位追蹤工時增減WBS 要不要交給 AI 的分水嶺,與其說是模型有多聰明,不如說是 「格式是否能察覺已經壞掉」。先把表格形式整理好,就更能放心把編輯工作交給 AI。
test/spec.txt 裡有表格規範與範例)\| 跳脫、外側 pipe 可省略等)csv(newline="" 的慣例、DictWriter 的 restval / extrasaction)codecs(utf-8-sig 的說明)原文出處:https://qiita.com/Tadataka_Takahashi/items/6be2bf42e6b9751accc8