@loviusc/sa-spec 0.1.0 → 0.2.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/package.json CHANGED
@@ -1,7 +1,11 @@
1
1
  {
2
2
  "name": "@loviusc/sa-spec",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Personal system analysis documentation workspace CLI",
5
+ "publishConfig": {
6
+ "access": "public",
7
+ "registry": "https://registry.npmjs.org/"
8
+ },
5
9
  "bin": {
6
10
  "sa-spec": "bin/sa-spec.js"
7
11
  },
package/readme.md CHANGED
@@ -2,14 +2,14 @@
2
2
 
3
3
  `sa-spec` 是一個輕量的系統分析文件工作台,用固定流程把需求想法整理成可確認、可歸檔、可檢查的文件。
4
4
 
5
- AI 工作流只使用四個指令:
5
+ AI 工作流只使用四個指令,並支援簡寫別名(兩者等價):
6
6
 
7
- ```text
8
- spec:init
9
- spec:propose
10
- spec:analyze
11
- spec:commit
12
- ```
7
+ | 標準指令 | 簡寫 | 說明 |
8
+ |---|---|---|
9
+ | `spec:init` | `sci` | 初始化專案與規則 |
10
+ | `spec:propose` | `scp` | 提出需求並整理摘要 |
11
+ | `spec:analyze` | `sca` | 衝突檢查與規格起草 |
12
+ | `spec:commit` | `scc` | 確認歸檔並建立 Git commit |
13
13
 
14
14
  每個需求歸檔後固定包含:
15
15
 
@@ -17,7 +17,8 @@ spec:commit
17
17
  project/
18
18
  REQ-001-login/
19
19
  srs.md
20
- uml.puml
20
+ uml/
21
+ main.puml
21
22
  openapi.yaml
22
23
  db-schema.md
23
24
  handoff.md
@@ -236,10 +237,13 @@ sa-spec init --full
236
237
  |---|---|
237
238
  | `sa-spec init` | 建立最小文件骨架。 |
238
239
  | `sa-spec init --full` | 建立文件骨架與完整規則文件。 |
239
- | `sa-spec new --id REQ-001 --name login` | 建立需求資料夾與空白文件模板,包含 SRS、UML、OpenAPI、DB Schema、handoff 與 history。 |
240
+ | `sa-spec new [--id REQ-001] --name login [--branch]` | 建立需求資料夾與空白文件模板(未帶 `--id` 時啟用多人草稿模式,可選 `--branch` 自動建立並切換至 `feat/<name>` 分支)。 |
241
+ | `sa-spec finalize --name login` | 將草稿需求定號歸檔為最新序號(自動計算最新號碼、重命名目錄、批次置換 `{{REQ_ID}}` 並重建索引)。 |
242
+ | `sa-spec reindex` | 掃描 `project/REQ-*` 目錄,依序號重新生成乾淨無衝突的 `project/index.md`。 |
240
243
  | `sa-spec check` | 檢查基本文件是否存在。 |
241
244
  | `sa-spec status` | 顯示系統規格與需求索引狀態。 |
242
245
  | `sa-spec doctor` | 檢查必要檔案是否存在。 |
246
+ | `sa-spec upgrade [--dry-run]` | 將既有專案升級為最新版本:同步最新 AGENTS 與規則文件,並將既有需求中的 `uml.puml` 自動遷移為 `uml/main.puml`。 |
243
247
 
244
248
  `sa-spec new` 是快速建空白模板的輔助工具。正式需求流程仍建議使用 `spec:init`、`spec:propose`、`spec:analyze`、`spec:commit`。
245
249
 
@@ -259,7 +263,7 @@ sa-spec init --full
259
263
  | AI 對話 | Claude Code 類型的 VS Code AI 套件或 CLI 整合 | 擇一 | 用 Claude 讀取工作區規則並協作產出需求文件。 |
260
264
  | AI 對話 | Gemini 類型的 VS Code AI 套件或 CLI 整合 | 擇一 | 用 Gemini 讀取工作區規則並協作產出需求文件。 |
261
265
  | Markdown | Markdown 預覽或 Markdown All in One 類型套件 | 選用 | 預覽 `srs.md`、`history.md`、`system-spec.md`。 |
262
- | UML | PlantUML 類型套件 | 選用 | 預覽 `uml.puml`。 |
266
+ | UML | PlantUML 類型套件 | 選用 | 預覽需求內的 `uml/*.puml`。 |
263
267
  | YAML | YAML 類型套件 | 選用 | 編輯與檢查 `openapi.yaml`。 |
264
268
  | Git | GitLens 類型套件 | 選用 | 查看需求文件修改歷程。 |
265
269
 
@@ -280,10 +284,10 @@ Markdown、PlantUML、YAML、GitLens 這類套件可以提升閱讀與檢查體
280
284
  | CLI | `sa-spec init --full` | 終端機或 VS Code Terminal | 建立文件骨架與規則檔。 |
281
285
  | CLI | `sa-spec check` | 終端機或 VS Code Terminal | 檢查必要文件是否存在。 |
282
286
  | CLI | `sa-spec status` | 終端機或 VS Code Terminal | 查看系統規格與需求索引狀態。 |
283
- | AI 工作流程 | `spec:init` | VS Code AI 對話側邊欄 | 載入規則並確認工作區狀態。 |
284
- | AI 工作流程 | `spec:propose` | VS Code AI 對話側邊欄 | 整理使用者提出的需求。 |
285
- | AI 工作流程 | `spec:analyze` | VS Code AI 對話側邊欄 | 檢查衝突並產出待確認草稿。 |
286
- | AI 工作流程 | `spec:commit` | VS Code AI 對話側邊欄 | 歸檔已確認版本並建立本機 Git commit。 |
287
+ | AI 工作流程 | `spec:init` / `sci` | VS Code AI 對話側邊欄 | 載入規則並確認工作區狀態。 |
288
+ | AI 工作流程 | `spec:propose` / `scp` | VS Code AI 對話側邊欄 | 整理使用者提出的需求。 |
289
+ | AI 工作流程 | `spec:analyze` / `sca` | VS Code AI 對話側邊欄 | 檢查衝突並產出待確認草稿。 |
290
+ | AI 工作流程 | `spec:commit` / `scc` | VS Code AI 對話側邊欄 | 歸檔已確認版本並建立本機 Git commit。 |
287
291
 
288
292
  建議操作順序:
289
293
 
@@ -297,32 +301,32 @@ code .
297
301
  接著在 VS Code 的 AI 對話側邊欄輸入:
298
302
 
299
303
  ```text
300
- spec:init
301
- spec:propose
302
- spec:analyze
303
- spec:commit
304
+ spec:init # 或簡寫 sci
305
+ spec:propose # 或簡寫 scp
306
+ spec:analyze # 或簡寫 sca
307
+ spec:commit # 或簡寫 scc
304
308
  ```
305
309
 
306
310
  如果你有安裝 prompts 或 slash commands,可以用對應的快捷指令;沒有安裝也可以直接手動輸入上面的文字指令。
307
311
 
308
312
  ## AI 工作流程
309
313
 
310
- | 指令 | 用途 |
311
- |---|---|
312
- | `spec:init` | 初始化並載入 `AGENTS.md`、`skills/system-analysis/SKILL.md` 與必要規則。 |
313
- | `spec:propose` | 使用者提出需求後,整理需求摘要、目標、初步流程與待確認事項。 |
314
- | `spec:analyze` | 檢查是否與既有需求衝突;無衝突才產出 SRS、UML、OpenAPI、DB Schema 草稿。 |
315
- | `spec:commit` | 使用者確認無衝突版本後,將版本歸檔到 `project/`,並建立本機 Git commit。 |
314
+ | 指令 | 簡寫 | 用途 |
315
+ |---|---|---|
316
+ | `spec:init` | `sci` | 初始化並載入 `AGENTS.md`、`skills/system-analysis/SKILL.md` 與必要規則。 |
317
+ | `spec:propose` | `scp` | 使用者提出需求後,整理需求摘要、目標、初步流程與待確認事項。 |
318
+ | `spec:analyze` | `sca` | 檢查是否與既有需求衝突;無衝突才產出 SRS、UML、OpenAPI、DB Schema 草稿。 |
319
+ | `spec:commit` | `scc` | 使用者確認無衝突版本後,將版本歸檔到 `project/`,並建立本機 Git commit。 |
316
320
 
317
321
  狀態機:
318
322
 
319
323
  ```text
320
- init -> propose -> analyze
324
+ init (sci) -> propose (scp) -> analyze (sca)
321
325
 
322
- analyze 無衝突 -> 產出待確認版本 -> commit
323
- analyze 有衝突 -> 釐清問題 -> analyze
326
+ analyze 無衝突 -> 產出待確認版本 -> commit (scc)
327
+ analyze 有衝突 -> 釐清問題 -> analyze (sca)
324
328
 
325
- commit -> 歸檔 -> 本機 Git commit
329
+ commit (scc) -> 歸檔 -> 本機 Git commit
326
330
  ```
327
331
 
328
332
  `spec:analyze` 若發現衝突,只會列出衝突與待釐清問題,不會修改任何需求文件,也不會產出最終文件。釐清後應再次執行 `spec:analyze`,重新檢查並產出草稿。
@@ -334,7 +338,7 @@ commit -> 歸檔 -> 本機 Git commit
334
338
  ```text
335
339
  請先閱讀 project/REQ-001-pomodoro-timer/handoff.md。
336
340
  以 handoff.md 作為本次開發入口與限制摘要。
337
- 如需完整需求細節,再查閱同資料夾內的 srs.md、uml.puml、openapi.yaml、db-schema.md 與 history.md。
341
+ 如需完整需求細節,再查閱同資料夾內的 srs.md、uml/、openapi.yaml、db-schema.md 與 history.md。
338
342
  不得自行新增 handoff.md 未確認的角色、權限、流程、狀態、API 或 DB 欄位。
339
343
  若發現 handoff.md 與既有程式或規格衝突,請停止開發並回報。
340
344
  ```
@@ -347,6 +351,64 @@ docs(REQ-001): 番茄時鐘 - 新增專注與休息計時需求
347
351
 
348
352
  只有在使用者明確要求推送 remote,或專案設定啟用 auto-push 時,才會執行 `git push`。如果 push 失敗,只回報認證或網路問題,不會回滾已完成的文件歸檔與本機 commit。
349
353
 
354
+ ## 多人協作解決方案(Git-Flow 與延遲綁定 ID)
355
+
356
+ 在單人開發時,循序流水號(`REQ-001`、`REQ-002`)簡單直覺;但在多人平行協作時,容易引發兩大痛點:
357
+ 1. **序號競爭撞車**:Alice 與 Bob 同時在各自的分支開發,雙方本機都取了 `REQ-003`,合併回主幹時發生目錄重名衝突。
358
+ 2. **中心化檔案衝突**:每次歸檔都要修改 `project/index.md`,導致頻繁的 Git merge conflict。
359
+
360
+ `sa-spec v0.2.0` 引進 **「Feature Branch 隔離 + 延遲綁定序號(Late-Binding ID)」** 完整解決方案:
361
+
362
+ ```mermaid
363
+ flowchart TD
364
+ A[開新需求: sa-spec new --name cart --branch] --> B[建立並切換至 feat/cart 分支<br/>以 project/cart/ 建立草稿目錄]
365
+ B --> C[AI 分析與起草: sca<br/>內部使用 {{REQ_ID}} 佔位<br/>自動同步 master 規格防護]
366
+ C --> D{使用者確認通過<br/>執行 scc 或 finalize}
367
+ D --> E[查詢 master 最新序號 N]
368
+ E --> F[重命名為 project/REQ-N+1-cart/<br/>批次置換 {{REQ_ID}} 為正式號碼]
369
+ F --> G[sa-spec reindex: 動態重構 index.md]
370
+ G --> H[合併回 master: 零衝突、序號永遠連續!]
371
+ ```
372
+
373
+ ### 多人協作四步流程
374
+
375
+ #### 1. 提出需求(草稿與分支隔離)
376
+ 開需求時**不搶序號**,直接以語意名稱建立分支與草稿目錄:
377
+
378
+ ```bash
379
+ # 自動建立並切換至 feat/cart-checkout 分支,建立 project/cart-checkout/ 草稿目錄
380
+ sa-spec new --name cart-checkout --branch
381
+ ```
382
+ 在 VS Code 側邊欄輸入:
383
+ ```text
384
+ scp 購物車結帳需求
385
+ ```
386
+ * **特點**:目錄名稱為 `project/cart-checkout/`,內部所有檔案皆使用 `{{REQ_ID}}` 佔位符。多人同時提出需求互不干擾、零衝突。即使需求中途放棄,直接刪除分支即可,絕不留下幽靈跳號。
387
+
388
+ #### 2. 分析與衝突檢查(規格基線同步)
389
+ 在分支上執行 `spec:analyze`(或 `sca`)前,工具會確認已取得 `master`(或 `origin/master`)的最新規格,確保是以全專案最新基準進行影響評估,避免因分支落後而漏檢權限、API 或資料表衝突。
390
+
391
+ #### 3. 確認歸檔與延遲定號(Late-Binding)
392
+ 當需求規格確認無誤,準備歸檔時:
393
+
394
+ ```bash
395
+ # 自動向 master 查詢最新可用序號(例如 REQ-006)
396
+ sa-spec finalize --name cart-checkout
397
+ ```
398
+ * **自動化執行**:
399
+ 1. 取得最新序號(例如目前最大是 `REQ-005`,自動取得 `REQ-006`)。
400
+ 2. 使用 `git mv` 將目錄重命名為 `project/REQ-006-cart-checkout`。
401
+ 3. 自動掃描並將目錄內所有檔案的 `{{REQ_ID}}` 批次置換為 `REQ-006`。
402
+ 4. 自動執行 `reindex` 重建需求總表。
403
+
404
+ #### 4. 消除索引合併衝突(`sa-spec reindex`)
405
+ `project/index.md` 改為由目錄動態生成的「衍生清單」,不再依賴脆弱的手動 append:
406
+
407
+ ```bash
408
+ sa-spec reindex
409
+ ```
410
+ * 任何時候若有 PR 合併產生 index 衝突,只要執行 `sa-spec reindex`,即可依序號重新排序生成乾淨的總表,無需人工手動解 conflict。
411
+
350
412
  ## 完整範例:番茄時鐘
351
413
 
352
414
  以下示範從空資料夾開始,建立一個「番茄時鐘」需求。
@@ -520,7 +582,8 @@ project/
520
582
  system-spec.md
521
583
  REQ-001-pomodoro-timer/
522
584
  srs.md
523
- uml.puml
585
+ uml/
586
+ state.puml
524
587
  openapi.yaml
525
588
  db-schema.md
526
589
  handoff.md
@@ -591,3 +654,39 @@ git push origin master
591
654
  - `project/system-spec.md` 只能在 `spec:commit` 依使用者確認內容更新。
592
655
  - `spec:commit` 會自動建立本機 Git commit;remote push 需使用者明確要求或啟用 auto-push。
593
656
  - 一次最多問 3 個待確認問題,優先詢問會影響範圍、資料、權限、API、DB 或驗收的問題。
657
+
658
+ ## 更新紀錄
659
+
660
+ ### v0.2.0
661
+ - **多人協作 Git-Flow 解決方案(延遲綁定序號與零衝突索引)**:
662
+ - **解決痛點**:多人平行開發時,傳統流水號(`REQ-001`)容易在各自分支撞車,且中心化的 `project/index.md` 容易造成 Git merge conflict。
663
+ - **延遲綁定序號(Late-Binding ID)**:開需求時不搶號,在 feature branch 上以語意名稱建立草稿需求(如 `project/cart`),內部使用 `{{REQ_ID}}` 佔位;只有在 `spec:commit` 最終歸檔時,才向 master/遠端確認最新可用序號並定號(如 `REQ-003`),自動批次置換佔位符,徹底避免幽靈跳號與序號競爭。
664
+ - **動態重建需求索引(`sa-spec reindex`)**:將 `project/index.md` 改為根據 `project/REQ-*` 目錄動態掃描並依序號排序生成,消除手動 append 造成的 Git 合併衝突。
665
+ - **CLI 指令升級**:
666
+ - `sa-spec new [--id REQ-001] --name login [--branch]`:支援草稿模式與自動切換 feature 分支。
667
+ - `sa-spec finalize --name login`:自動定號、重命名目錄、置換佔位符並重建索引。
668
+ - `sa-spec reindex`:一鍵掃描並重新排序生成乾淨的 `project/index.md`。
669
+ - **分析前防護**:`spec:analyze` 執行前確保取得 master/遠端最新規格,避免分支規格落後導致漏檢衝突。
670
+
671
+ ### v0.1.3
672
+ - **支援指令簡寫別名(同時保留完整指令)**:
673
+ - `sci` = `spec:init`
674
+ - `scp` = `spec:propose`
675
+ - `sca` = `spec:analyze`
676
+ - `scc` = `spec:commit`
677
+ - 兩者完全等價,使用者可依輸入習慣自由選擇長指令或 3 字母縮寫。
678
+
679
+ ### v0.1.2
680
+ - **新增 `sa-spec upgrade` 版本升級指令**:
681
+ - **工作區規則同步**:自動檢查並更新專案中的 `AGENTS.md`、`skills/system-analysis/SKILL.md` 以及 `rules/` 目錄下的規則文件至最新版本(自動保留既有的 memory context)。
682
+ - **既有需求自動遷移**:掃描 `project/REQ-*` 目錄,將舊版 `uml.puml` 自動重構移動為 `uml/main.puml`,並自動修復 `handoff.md` 中的來源引用路徑。
683
+ - **提供 `--dry-run` 模擬模式**:可加上 `--dry-run` 參數先行預覽哪些檔案會被更新或遷移,而不實際寫入檔案。
684
+
685
+ ### v0.1.1
686
+ - **PlantUML 檔案架構重構(解決多圖渲染與預覽開不起來問題)**:
687
+ - **目錄化結構**:每個需求目錄下由原本單一檔案 `uml.puml` 改為 `uml/` 資料夾(預設提供 `uml/main.puml` 骨架)。
688
+ - **單檔單圖原則**:嚴格規範每個 `.puml` 檔案僅包含單一組 `@startuml` 與 `@enduml`,禁止在單一檔案內混放多張圖。
689
+ - **支援多面向拆分**:需求若涉及多個面向(如業務流程、循序互動、狀態轉換),必須拆分為獨立 `.puml` 檔案(例如 `uml/main.puml`、`uml/sequence.puml`、`uml/state.puml`),確保 PlantUML 預覽外掛能順暢開啟與渲染。
690
+ - **CLI 自動化**:`sa-spec new` 與 `sa-spec template req` 自動建立 `uml/` 資料夾並產出預設的 `uml/main.puml`。
691
+ - **全專案規範同步**:同步更新 `AGENTS.md`、`system-analysis` skill、`rules/uml-style.md`、`rules/file-style.md` 與 `handoff.md`。
692
+
package/src/cli.js CHANGED
@@ -17,9 +17,18 @@ function main() {
17
17
  case "new":
18
18
  createReqTemplate(args.slice(1));
19
19
  return;
20
+ case "finalize":
21
+ finalizeReq(args.slice(1));
22
+ return;
23
+ case "reindex":
24
+ reindexProject();
25
+ return;
20
26
  case "check":
21
27
  runCheck();
22
28
  return;
29
+ case "upgrade":
30
+ upgradeProject(args.slice(1));
31
+ return;
23
32
  case "doctor":
24
33
  runDoctor();
25
34
  return;
@@ -29,6 +38,11 @@ function main() {
29
38
  case "template":
30
39
  runTemplate(args.slice(1));
31
40
  return;
41
+ case "version":
42
+ case "--version":
43
+ case "-v":
44
+ showVersion();
45
+ return;
32
46
  case "help":
33
47
  case "--help":
34
48
  case "-h":
@@ -212,18 +226,54 @@ function createReqTemplate(args, label = "new") {
212
226
  const id = getOption(args, "--id");
213
227
  const name = getOption(args, "--name");
214
228
 
215
- if (!id || !name) {
216
- fail("Usage: sa-spec template req --id REQ-001 --name login");
229
+ if (!name) {
230
+ fail("Usage: sa-spec new [--id REQ-001] --name login [--branch]");
217
231
  return;
218
232
  }
219
233
 
220
234
  const slug = slugify(name);
235
+
236
+ // 草稿模式(Git-Flow 多人協作):未指定 id 時建立草稿目錄,以 {{REQ_ID}} 佔位
237
+ if (!id) {
238
+ if (args.includes("--branch")) {
239
+ const branchName = `feat/${slug}`;
240
+ const branchRes = spawnSync("git", ["checkout", "-b", branchName], { cwd, encoding: "utf8" });
241
+ if (branchRes.status === 0) {
242
+ console.log(`已建立並切換至分支: ${branchName}`);
243
+ } else {
244
+ console.log(`提示: 分支切換略過 (${(branchRes.stderr || "").trim() || "可能是已有同名分支或未在 git 目錄"})`);
245
+ }
246
+ }
247
+
248
+ const reqDir = path.join("project", slug);
249
+ const umlDir = path.join(reqDir, "uml");
250
+ const replacements = { REQ_ID: "{{REQ_ID}}", REQ_NAME: name };
251
+ const results = [
252
+ ensureDir(reqDir),
253
+ ensureDir(umlDir),
254
+ ensureFileFromTemplate(path.join("project", "req", "srs.md"), path.join(reqDir, "srs.md"), replacements),
255
+ ensureFileFromTemplate(path.join("project", "req", "uml", "main.puml"), path.join(umlDir, "main.puml"), replacements),
256
+ ensureFileFromTemplate(path.join("project", "req", "openapi.yaml"), path.join(reqDir, "openapi.yaml"), replacements),
257
+ ensureFileFromTemplate(path.join("project", "req", "db-schema.md"), path.join(reqDir, "db-schema.md"), replacements),
258
+ ensureFileFromTemplate(path.join("project", "req", "handoff.md"), path.join(reqDir, "handoff.md"), replacements),
259
+ ensureFileFromTemplate(path.join("project", "req", "history.md"), path.join(reqDir, "history.md"), replacements)
260
+ ];
261
+
262
+ printResultList(label, results);
263
+ console.log(`\n已建立草稿需求:${reqDir}(內部使用 {{REQ_ID}} 佔位符)`);
264
+ console.log(`分析確認後,可使用 sa-spec finalize --name ${slug} 定號歸檔。`);
265
+ return;
266
+ }
267
+
268
+ // 傳統模式:指定序號
221
269
  const reqDir = path.join("project", `${id}-${slug}`);
270
+ const umlDir = path.join(reqDir, "uml");
222
271
  const replacements = { REQ_ID: id, REQ_NAME: name };
223
272
  const results = [
224
273
  ensureDir(reqDir),
274
+ ensureDir(umlDir),
225
275
  ensureFileFromTemplate(path.join("project", "req", "srs.md"), path.join(reqDir, "srs.md"), replacements),
226
- ensureFileFromTemplate(path.join("project", "req", "uml.puml"), path.join(reqDir, "uml.puml"), replacements),
276
+ ensureFileFromTemplate(path.join("project", "req", "uml", "main.puml"), path.join(umlDir, "main.puml"), replacements),
227
277
  ensureFileFromTemplate(path.join("project", "req", "openapi.yaml"), path.join(reqDir, "openapi.yaml"), replacements),
228
278
  ensureFileFromTemplate(path.join("project", "req", "db-schema.md"), path.join(reqDir, "db-schema.md"), replacements),
229
279
  ensureFileFromTemplate(path.join("project", "req", "handoff.md"), path.join(reqDir, "handoff.md"), replacements),
@@ -234,6 +284,166 @@ function createReqTemplate(args, label = "new") {
234
284
  printResultList(label, results);
235
285
  }
236
286
 
287
+ function finalizeReq(args) {
288
+ const name = getOption(args, "--name") || getOption(args, "--draft") || args[0];
289
+ if (!name) {
290
+ fail("Usage: sa-spec finalize --name <name-or-slug>");
291
+ return;
292
+ }
293
+
294
+ const slug = slugify(name);
295
+ const projectDir = path.join(cwd, "project");
296
+ let draftRelDir = path.join("project", slug);
297
+ let draftAbsDir = path.join(cwd, draftRelDir);
298
+
299
+ if (!fs.existsSync(draftAbsDir)) {
300
+ const altRelDir = path.join("project", `draft-${slug}`);
301
+ if (fs.existsSync(path.join(cwd, altRelDir))) {
302
+ draftRelDir = altRelDir;
303
+ draftAbsDir = path.join(cwd, altRelDir);
304
+ } else {
305
+ fail(`找不到草稿需求目錄: ${draftRelDir}`);
306
+ return;
307
+ }
308
+ }
309
+
310
+ // 掃描既有最大 REQ 編號
311
+ let maxIdNum = 0;
312
+ if (fs.existsSync(projectDir)) {
313
+ const entries = fs.readdirSync(projectDir, { withFileTypes: true });
314
+ for (const entry of entries) {
315
+ if (entry.isDirectory() && entry.name.startsWith("REQ-")) {
316
+ const m = entry.name.match(/^REQ-(\d+)/i);
317
+ if (m) {
318
+ const num = parseInt(m[1], 10);
319
+ if (num > maxIdNum) {
320
+ maxIdNum = num;
321
+ }
322
+ }
323
+ }
324
+ }
325
+ }
326
+
327
+ const nextIdNum = maxIdNum + 1;
328
+ const nextId = `REQ-${String(nextIdNum).padStart(3, "0")}`;
329
+ const finalDirName = `${nextId}-${slug}`;
330
+ const finalRelDir = path.join("project", finalDirName);
331
+ const finalAbsDir = path.join(cwd, finalRelDir);
332
+
333
+ if (fs.existsSync(finalAbsDir)) {
334
+ fail(`目標目錄 ${finalRelDir} 已存在,請確認序號或目錄名稱。`);
335
+ return;
336
+ }
337
+
338
+ // 重命名目錄 (優先嘗試 git mv)
339
+ const gitMv = spawnSync("git", ["mv", draftRelDir, finalRelDir], { cwd, encoding: "utf8" });
340
+ if (gitMv.status === 0) {
341
+ console.log(`[Git] 已將目錄重命名為: ${finalRelDir}`);
342
+ } else {
343
+ fs.renameSync(draftAbsDir, finalAbsDir);
344
+ console.log(`已將目錄重命名為: ${finalRelDir}`);
345
+ }
346
+
347
+ // 替換目錄內檔案的 {{REQ_ID}} 佔位符
348
+ const files = listFilesRecursive(finalAbsDir);
349
+ for (const filePath of files) {
350
+ try {
351
+ const content = fs.readFileSync(filePath, "utf8");
352
+ if (content.includes("{{REQ_ID}}")) {
353
+ const updated = content.split("{{REQ_ID}}").join(nextId);
354
+ fs.writeFileSync(filePath, updated, "utf8");
355
+ }
356
+ } catch (_) {
357
+ // 略過無法以文字讀取之檔案
358
+ }
359
+ }
360
+
361
+ // 自動重新整理 index.md
362
+ reindexProject();
363
+
364
+ console.log(`\n 需求已成功定號歸檔為: ${nextId} (${finalDirName})`);
365
+ console.log(`建議接下來執行:`);
366
+ console.log(` git add project/`);
367
+ console.log(` git commit -m "docs(${nextId}): ${name} - 規格確認歸檔"`);
368
+ }
369
+
370
+ function reindexProject() {
371
+ const projectDir = path.join(cwd, "project");
372
+ const indexPath = path.join(projectDir, "index.md");
373
+ if (!fs.existsSync(projectDir)) {
374
+ fail("project/ directory not found.");
375
+ return;
376
+ }
377
+
378
+ // 讀取現有索引
379
+ const existingMap = new Map();
380
+ if (fs.existsSync(indexPath)) {
381
+ const existingRows = parseIndexRows(fs.readFileSync(indexPath, "utf8"));
382
+ for (const row of existingRows) {
383
+ existingMap.set(row.id, row);
384
+ }
385
+ }
386
+
387
+ const entries = fs.readdirSync(projectDir, { withFileTypes: true });
388
+ const reqDirs = entries.filter((e) => e.isDirectory() && e.name.startsWith("REQ-"));
389
+
390
+ const rows = [];
391
+ for (const entry of reqDirs) {
392
+ const dirName = entry.name;
393
+ const match = dirName.match(/^(REQ-\d+)(?:-(.*))?$/i);
394
+ if (!match) continue;
395
+ const id = match[1].toUpperCase();
396
+ const fallbackTitle = match[2] || dirName;
397
+
398
+ const reqDirPath = path.join(projectDir, dirName);
399
+ const srsPath = path.join(reqDirPath, "srs.md");
400
+
401
+ let title = fallbackTitle;
402
+ let description = "待補充";
403
+ let status = "待確認";
404
+ let createdAt = formatLocalDateTime(new Date());
405
+ let updatedAt = createdAt;
406
+
407
+ if (existingMap.has(id)) {
408
+ const prev = existingMap.get(id);
409
+ title = prev.title || title;
410
+ description = prev.description || description;
411
+ status = prev.status || status;
412
+ createdAt = prev.createdAt || createdAt;
413
+ updatedAt = prev.updatedAt || updatedAt;
414
+ }
415
+
416
+ if (fs.existsSync(srsPath)) {
417
+ const srsContent = fs.readFileSync(srsPath, "utf8");
418
+ const titleMatch = srsContent.match(/^# 需求規格[::]\s*(?:REQ-\d+\s*)?(.*)$/m);
419
+ if (titleMatch && titleMatch[1].trim()) {
420
+ title = titleMatch[1].trim();
421
+ }
422
+ const statusMatch = srsContent.match(/^-\s*狀態[::]\s*(.+)$/m);
423
+ if (statusMatch && statusMatch[1].trim()) {
424
+ status = statusMatch[1].trim();
425
+ }
426
+ }
427
+
428
+ const numMatch = id.match(/\d+/);
429
+ const num = numMatch ? parseInt(numMatch[0], 10) : 0;
430
+
431
+ rows.push({ num, id, title, description, status, createdAt, updatedAt });
432
+ }
433
+
434
+ // 依數字循序排序
435
+ rows.sort((a, b) => a.num - b.num);
436
+
437
+ const header = `# 需求索引\n\n| 編號 | 標題 | 敘述 | 狀態 | 建立時間 | 更新時間 |\n|---|---|---|---|---|---|\n`;
438
+ const tableRows = rows
439
+ .map((r) => `| ${r.id} | ${r.title} | ${r.description} | ${r.status} | ${r.createdAt} | ${r.updatedAt} |`)
440
+ .join("\n");
441
+ const nextContent = rows.length > 0 ? `${header}${tableRows}\n` : `${header}\n`;
442
+
443
+ fs.writeFileSync(indexPath, nextContent, "utf8");
444
+ console.log(`sa-spec reindex: 已重新索引 ${rows.length} 個需求至 project/index.md`);
445
+ }
446
+
237
447
  function ensureIndexRow(id, name) {
238
448
  const indexPath = path.join(cwd, "project", "index.md");
239
449
  if (!fs.existsSync(indexPath)) {
@@ -319,7 +529,10 @@ function parseIndexRows(content) {
319
529
  .map((parts) => ({
320
530
  id: parts[1],
321
531
  title: parts[2],
322
- status: parts[4]
532
+ description: parts[3],
533
+ status: parts[4],
534
+ createdAt: parts[5],
535
+ updatedAt: parts[6]
323
536
  }));
324
537
  }
325
538
 
@@ -336,10 +549,133 @@ function slugify(value) {
336
549
  .replace(/^-+|-+$/g, "");
337
550
  }
338
551
 
552
+ function upgradeProject(args = []) {
553
+ const dryRun = args.includes("--dry-run");
554
+ const results = [];
555
+
556
+ results.push(ensureDir("skills"));
557
+ results.push(ensureDir(path.join("skills", "system-analysis")));
558
+
559
+ results.push(updateFileFromTemplate("AGENTS.md", "AGENTS.md", dryRun));
560
+ results.push(
561
+ updateFileFromTemplate(
562
+ path.join("skills", "system-analysis", "SKILL.md"),
563
+ path.join("skills", "system-analysis", "SKILL.md"),
564
+ dryRun
565
+ )
566
+ );
567
+
568
+ const rulesTargetDir = path.join(cwd, "rules");
569
+ if (fs.existsSync(rulesTargetDir)) {
570
+ const templateRulesDir = path.join(templatesDir, "rules");
571
+ for (const file of listFilesRecursive(templateRulesDir)) {
572
+ const relativePath = path.relative(templatesDir, file);
573
+ results.push(updateFileFromTemplate(relativePath, relativePath, dryRun));
574
+ }
575
+ }
576
+
577
+ const projectDir = path.join(cwd, "project");
578
+ if (fs.existsSync(projectDir)) {
579
+ const entries = fs.readdirSync(projectDir, { withFileTypes: true });
580
+ for (const entry of entries) {
581
+ if (entry.isDirectory() && entry.name.startsWith("REQ-")) {
582
+ const reqRelDir = path.join("project", entry.name);
583
+ const oldUmlRelPath = path.join(reqRelDir, "uml.puml");
584
+ const oldUmlAbsPath = path.join(cwd, oldUmlRelPath);
585
+ const newUmlRelDir = path.join(reqRelDir, "uml");
586
+ const newUmlAbsDir = path.join(cwd, newUmlRelDir);
587
+ const newMainUmlRelPath = path.join(newUmlRelDir, "main.puml");
588
+ const newMainUmlAbsPath = path.join(cwd, newMainUmlRelPath);
589
+
590
+ if (fs.existsSync(oldUmlAbsPath)) {
591
+ if (!fs.existsSync(newMainUmlAbsPath)) {
592
+ if (!dryRun) {
593
+ if (!fs.existsSync(newUmlAbsDir)) {
594
+ fs.mkdirSync(newUmlAbsDir, { recursive: true });
595
+ }
596
+ fs.renameSync(oldUmlAbsPath, newMainUmlAbsPath);
597
+ }
598
+ results.push({
599
+ action: "migrate",
600
+ target: `${oldUmlRelPath} -> ${newMainUmlRelPath}`
601
+ });
602
+ } else {
603
+ results.push({
604
+ action: "warn",
605
+ target: oldUmlRelPath,
606
+ message: `目標 ${newMainUmlRelPath} 已存在,請確認是否可手動移除舊檔。`
607
+ });
608
+ }
609
+ }
610
+
611
+ const handoffRelPath = path.join(reqRelDir, "handoff.md");
612
+ const handoffAbsPath = path.join(cwd, handoffRelPath);
613
+ if (fs.existsSync(handoffAbsPath)) {
614
+ const content = fs.readFileSync(handoffAbsPath, "utf8");
615
+ if (content.includes("uml.puml")) {
616
+ if (!dryRun) {
617
+ fs.writeFileSync(handoffAbsPath, content.replace(/uml\.puml/g, "uml/"), "utf8");
618
+ }
619
+ results.push({
620
+ action: "update",
621
+ target: handoffRelPath,
622
+ message: "更新來源參照 uml.puml -> uml/"
623
+ });
624
+ }
625
+ }
626
+ }
627
+ }
628
+ }
629
+
630
+ printResultList(dryRun ? "upgrade (dry-run)" : "upgrade", results);
631
+ }
632
+
633
+ function updateFileFromTemplate(templateRelativePath, targetRelativePath, dryRun = false) {
634
+ const targetAbsolutePath = path.join(cwd, targetRelativePath);
635
+ const templateAbsolutePath = path.join(templatesDir, templateRelativePath);
636
+ if (!fs.existsSync(templateAbsolutePath)) {
637
+ return { action: "skip", target: targetRelativePath };
638
+ }
639
+
640
+ let templateContent = readTemplate(templateRelativePath);
641
+ if (!fs.existsSync(targetAbsolutePath)) {
642
+ if (!dryRun) {
643
+ writeFileIfMissing(targetAbsolutePath, templateContent);
644
+ }
645
+ return { action: "create", target: targetRelativePath };
646
+ }
647
+
648
+ const currentContent = fs.readFileSync(targetAbsolutePath, "utf8");
649
+ if (currentContent.includes("<claude-mem-context>")) {
650
+ const memMatch = currentContent.match(/(<claude-mem-context>[\s\S]*?<\/claude-mem-context>)/);
651
+ if (memMatch && !templateContent.includes("<claude-mem-context>")) {
652
+ templateContent = `${templateContent.trimEnd()}\n\n\n${memMatch[1]}\n`;
653
+ }
654
+ }
655
+
656
+ if (currentContent.trim() === templateContent.trim()) {
657
+ return { action: "skip", target: targetRelativePath };
658
+ }
659
+
660
+ if (!dryRun) {
661
+ fs.writeFileSync(targetAbsolutePath, templateContent, "utf8");
662
+ }
663
+ return { action: "update", target: targetRelativePath };
664
+ }
665
+
339
666
  function printResultList(label, results) {
340
667
  console.log(`sa-spec ${label}`);
341
668
  for (const result of results) {
342
- const action = result.action === "create" ? "CREATED" : result.action === "update" ? "UPDATED" : result.action === "warn" ? "WARNING" : "SKIPPED";
669
+ const action =
670
+ result.action === "create"
671
+ ? "CREATED"
672
+ : result.action === "update"
673
+ ? "UPDATED"
674
+ : result.action === "migrate"
675
+ ? "MIGRATED"
676
+ : result.action === "warn"
677
+ ? "WARNING"
678
+ : "SKIPPED";
343
679
  console.log(`${action} ${result.target}${result.message ? ` - ${result.message}` : ""}`);
344
680
  }
345
681
  }
@@ -349,11 +685,20 @@ function showHelp() {
349
685
 
350
686
  Usage:
351
687
  sa-spec init [--full]
352
- sa-spec new --id REQ-001 --name login
688
+ sa-spec new [--id REQ-001] --name login [--branch]
689
+ sa-spec finalize --name login
690
+ sa-spec reindex
353
691
  sa-spec check
692
+ sa-spec upgrade [--dry-run]
693
+ sa-spec --version
354
694
  `);
355
695
  }
356
696
 
697
+ function showVersion() {
698
+ const pkg = JSON.parse(fs.readFileSync(path.join(rootDir, "package.json"), "utf8"));
699
+ console.log(pkg.version);
700
+ }
701
+
357
702
  function fail(message) {
358
703
  console.error(message);
359
704
  process.exitCode = 1;
@@ -7,7 +7,7 @@
7
7
  每個需求歸檔時固定包含:
8
8
 
9
9
  - `srs.md`:需求規格。
10
- - `uml.puml`:UML 圖。
10
+ - `uml/`:UML 圖目錄(依面向拆分為獨立 `.puml` 檔,如 `main.puml`、`sequence.puml` 等,禁止單檔混放多圖)。
11
11
  - `openapi.yaml`:OpenAPI 草稿。
12
12
  - `db-schema.md`:DB Schema 草稿。
13
13
  - `handoff.md`:給 OpenSpec 或其他 AI 開發工具的開發交接摘要。
@@ -15,14 +15,14 @@
15
15
 
16
16
  # 指令流程
17
17
 
18
- 只使用四個指令:
18
+ 使用四個指令,並支援簡寫別名(兩者等價):
19
19
 
20
- | 指令 | 用途 | 規則 |
21
- |---|---|---|
22
- | `spec:init` | 初始化 | 載入並遵守 `AGENTS.md`、`skills/system-analysis/SKILL.md` 與必要規則,確認 `project/` 結構是否存在。 |
23
- | `spec:propose` | 提出需求 | 使用者提出需求後,先整理需求摘要、目標、初步流程與待確認事項,不歸檔、不修改既有需求。 |
24
- | `spec:analyze` | 分析與起草 | 先檢查新需求是否與 `project/` 內所有既有需求衝突;無衝突才產出 SRS、UML、OpenAPI、DB Schema 草稿;有衝突則只列出衝突與待釐清問題,待使用者釐清後再次執行 `spec:analyze`。 |
25
- | `spec:commit` | 確認歸檔 | 只把使用者已確認的版本歸檔到該需求資料夾,更新 `project/index.md`、`project/system-spec.md` 與 `history.md`,並建立本機 Git commit。 |
20
+ | 指令 | 簡寫 | 用途 | 規則 |
21
+ |---|---|---|---|
22
+ | `spec:init` | `sci` | 初始化 | 載入並遵守 `AGENTS.md`、`skills/system-analysis/SKILL.md` 與必要規則,確認 `project/` 結構是否存在。 |
23
+ | `spec:propose` | `scp` | 提出需求 | 使用者提出需求後,先整理需求摘要、目標、初步流程與待確認事項,不歸檔、不修改既有需求。 |
24
+ | `spec:analyze` | `sca` | 分析與起草 | 先檢查新需求是否與 `project/` 內所有既有需求衝突;無衝突才產出 SRS、UML、OpenAPI、DB Schema 草稿;有衝突則只列出衝突與待釐清問題,待使用者釐清後再次執行 `spec:analyze`(或 `sca`)。 |
25
+ | `spec:commit` | `scc` | 確認歸檔 | 只把使用者已確認的版本歸檔到該需求資料夾,更新 `project/index.md`、`project/system-spec.md` 與 `history.md`,並建立本機 Git commit。 |
26
26
 
27
27
  # 重要限制
28
28
 
@@ -39,8 +39,9 @@
39
39
  - 交給 OpenSpec 或其他 AI 開發工具時,必須指定它先閱讀 `handoff.md`,並以 `handoff.md` 作為開發入口與限制摘要;其他規格文件作為詳細來源。
40
40
  - `spec:commit` 完成文件歸檔後,必須自動建立本機 Git commit;commit message 以需求編號與需求名稱為主,並加上簡短需求說明,格式為 `docs(REQ-001): 需求名稱 - 簡短需求說明`。
41
41
  - `spec:commit` 只有在使用者明確要求推送 remote,或專案設定啟用 auto-push 時,才可執行 `git push`;push 失敗時只回報認證或網路問題,不得回滾已完成的文件歸檔與本機 commit。
42
+ - 多人協作與 Git-Flow 模式(延遲綁定序號):支援多人平行開發。提出需求時可於 feature branch(如 `feat/<name>`)建立草稿目錄,內部使用 `{{REQ_ID}}` 佔位;執行 `spec:analyze` 前應先確保取得 master 最新規格以防漏檢衝突;執行 `spec:commit` 歸檔時向 master 確認最新可用序號並定號,自動將目錄定名為正式 `REQ-xxx`、替換內部佔位符、重新生成 `project/index.md`,避免多人合併時的序號競爭與索引衝突。
42
43
  - 文件未經使用者確認,不得標示為 `已審核`。
43
- - `uml.puml` 不限單一 UML 圖型;`spec:analyze` 必須依需求選擇最能說明流程、互動、狀態或資料關係的 UML 圖,例如活動圖、循序圖、使用案例圖、狀態圖或類別圖,不可為了固定格式硬產生活動圖。需求複雜時,可在同一個 `uml.puml` 內放多張 PlantUML 圖並清楚分段。
44
+ - `uml/` 內的 UML 圖不限單一 UML 圖型;`spec:analyze` 必須依需求選擇最能說明流程、互動、狀態或資料關係的 UML 圖,例如活動圖、循序圖、使用案例圖、狀態圖或類別圖,不可為了固定格式硬產生活動圖。**嚴禁將多張圖混在同一個檔案中**,必須在需求資料夾下的 `uml/` 目錄拆分成獨立的 `.puml` 檔案(例如 `uml/main.puml`、`uml/sequence.puml`、`uml/state.puml`),且每個檔案只能包含一組 `@startuml` 與 `@enduml`,以避免 PlantUML 渲染或預覽失敗。
44
45
  - 一次最多問 3 個待確認問題,優先詢問會影響範圍、資料、權限、API、DB 或驗收的問題。
45
46
 
46
47
  # 衝突檢查重點
@@ -67,12 +68,12 @@
67
68
  # 狀態機
68
69
 
69
70
  ```text
70
- init -> propose -> analyze
71
+ init (sci) -> propose (scp) -> analyze (sca)
71
72
 
72
- analyze 無衝突 -> 產出待確認版本 -> commit
73
- analyze 有衝突 -> 釐清問題 -> analyze
73
+ analyze 無衝突 -> 產出待確認版本 -> commit (scc)
74
+ analyze 有衝突 -> 釐清問題 -> analyze (sca)
74
75
 
75
- commit -> 歸檔 -> 本機 Git commit
76
+ commit (scc) -> 歸檔 -> 本機 Git commit
76
77
  ```
77
78
 
78
79
  # 歸檔結構
@@ -82,7 +83,8 @@ project/
82
83
  index.md
83
84
  REQ-001-name/
84
85
  srs.md
85
- uml.puml
86
+ uml/
87
+ main.puml
86
88
  openapi.yaml
87
89
  db-schema.md
88
90
  handoff.md
@@ -1,7 +1,7 @@
1
1
  # {{REQ_ID}} {{REQ_NAME}} Handoff
2
2
 
3
3
  - 狀態:待確認
4
- - 來源文件:`srs.md`、`uml.puml`、`openapi.yaml`、`db-schema.md`
4
+ - 來源文件:`srs.md`、`uml/`、`openapi.yaml`、`db-schema.md`
5
5
  - 目標讀者:OpenSpec、Codex、Claude、Gemini 或其他 AI 開發工具
6
6
 
7
7
  ## 要開發什麼
@@ -8,7 +8,7 @@ User -> System: 發起需求
8
8
  System --> User: 回傳結果
9
9
 
10
10
  note right of System
11
- 待於 spec:exec 後補充流程細節。
11
+ 待於 spec:analyze 後補充流程細節。
12
12
  end note
13
13
 
14
14
  @enduml
@@ -2,14 +2,14 @@
2
2
 
3
3
  `sa-spec` 是一個輕量的系統分析文件工作台,用固定流程把需求想法整理成可確認、可歸檔、可檢查的文件。
4
4
 
5
- AI 工作流只使用四個指令:
5
+ AI 工作流只使用四個指令,並支援簡寫別名(兩者等價):
6
6
 
7
- ```text
8
- spec:init
9
- spec:propose
10
- spec:analyze
11
- spec:commit
12
- ```
7
+ | 標準指令 | 簡寫 | 說明 |
8
+ |---|---|---|
9
+ | `spec:init` | `sci` | 初始化專案與規則 |
10
+ | `spec:propose` | `scp` | 提出需求並整理摘要 |
11
+ | `spec:analyze` | `sca` | 衝突檢查與規格起草 |
12
+ | `spec:commit` | `scc` | 確認歸檔並建立 Git commit |
13
13
 
14
14
  每個需求歸檔後固定包含:
15
15
 
@@ -17,7 +17,8 @@ spec:commit
17
17
  project/
18
18
  REQ-001-login/
19
19
  srs.md
20
- uml.puml
20
+ uml/
21
+ main.puml
21
22
  openapi.yaml
22
23
  db-schema.md
23
24
  handoff.md
@@ -236,10 +237,13 @@ sa-spec init --full
236
237
  |---|---|
237
238
  | `sa-spec init` | 建立最小文件骨架。 |
238
239
  | `sa-spec init --full` | 建立文件骨架與完整規則文件。 |
239
- | `sa-spec new --id REQ-001 --name login` | 建立需求資料夾與空白文件模板,包含 SRS、UML、OpenAPI、DB Schema、handoff 與 history。 |
240
+ | `sa-spec new [--id REQ-001] --name login [--branch]` | 建立需求資料夾與空白文件模板(未帶 `--id` 時啟用多人草稿模式,可選 `--branch` 自動建立並切換至 `feat/<name>` 分支)。 |
241
+ | `sa-spec finalize --name login` | 將草稿需求定號歸檔為最新序號(自動計算最新號碼、重命名目錄、批次置換 `{{REQ_ID}}` 並重建索引)。 |
242
+ | `sa-spec reindex` | 掃描 `project/REQ-*` 目錄,依序號重新生成乾淨無衝突的 `project/index.md`。 |
240
243
  | `sa-spec check` | 檢查基本文件是否存在。 |
241
244
  | `sa-spec status` | 顯示系統規格與需求索引狀態。 |
242
245
  | `sa-spec doctor` | 檢查必要檔案是否存在。 |
246
+ | `sa-spec upgrade [--dry-run]` | 將既有專案升級為最新版本:同步最新 AGENTS 與規則文件,並將既有需求中的 `uml.puml` 自動遷移為 `uml/main.puml`。 |
243
247
 
244
248
  `sa-spec new` 是快速建空白模板的輔助工具。正式需求流程仍建議使用 `spec:init`、`spec:propose`、`spec:analyze`、`spec:commit`。
245
249
 
@@ -259,7 +263,7 @@ sa-spec init --full
259
263
  | AI 對話 | Claude Code 類型的 VS Code AI 套件或 CLI 整合 | 擇一 | 用 Claude 讀取工作區規則並協作產出需求文件。 |
260
264
  | AI 對話 | Gemini 類型的 VS Code AI 套件或 CLI 整合 | 擇一 | 用 Gemini 讀取工作區規則並協作產出需求文件。 |
261
265
  | Markdown | Markdown 預覽或 Markdown All in One 類型套件 | 選用 | 預覽 `srs.md`、`history.md`、`system-spec.md`。 |
262
- | UML | PlantUML 類型套件 | 選用 | 預覽 `uml.puml`。 |
266
+ | UML | PlantUML 類型套件 | 選用 | 預覽需求內的 `uml/*.puml`。 |
263
267
  | YAML | YAML 類型套件 | 選用 | 編輯與檢查 `openapi.yaml`。 |
264
268
  | Git | GitLens 類型套件 | 選用 | 查看需求文件修改歷程。 |
265
269
 
@@ -280,10 +284,10 @@ Markdown、PlantUML、YAML、GitLens 這類套件可以提升閱讀與檢查體
280
284
  | CLI | `sa-spec init --full` | 終端機或 VS Code Terminal | 建立文件骨架與規則檔。 |
281
285
  | CLI | `sa-spec check` | 終端機或 VS Code Terminal | 檢查必要文件是否存在。 |
282
286
  | CLI | `sa-spec status` | 終端機或 VS Code Terminal | 查看系統規格與需求索引狀態。 |
283
- | AI 工作流程 | `spec:init` | VS Code AI 對話側邊欄 | 載入規則並確認工作區狀態。 |
284
- | AI 工作流程 | `spec:propose` | VS Code AI 對話側邊欄 | 整理使用者提出的需求。 |
285
- | AI 工作流程 | `spec:analyze` | VS Code AI 對話側邊欄 | 檢查衝突並產出待確認草稿。 |
286
- | AI 工作流程 | `spec:commit` | VS Code AI 對話側邊欄 | 歸檔已確認版本並建立本機 Git commit。 |
287
+ | AI 工作流程 | `spec:init` / `sci` | VS Code AI 對話側邊欄 | 載入規則並確認工作區狀態。 |
288
+ | AI 工作流程 | `spec:propose` / `scp` | VS Code AI 對話側邊欄 | 整理使用者提出的需求。 |
289
+ | AI 工作流程 | `spec:analyze` / `sca` | VS Code AI 對話側邊欄 | 檢查衝突並產出待確認草稿。 |
290
+ | AI 工作流程 | `spec:commit` / `scc` | VS Code AI 對話側邊欄 | 歸檔已確認版本並建立本機 Git commit。 |
287
291
 
288
292
  建議操作順序:
289
293
 
@@ -297,32 +301,32 @@ code .
297
301
  接著在 VS Code 的 AI 對話側邊欄輸入:
298
302
 
299
303
  ```text
300
- spec:init
301
- spec:propose
302
- spec:analyze
303
- spec:commit
304
+ spec:init # 或簡寫 sci
305
+ spec:propose # 或簡寫 scp
306
+ spec:analyze # 或簡寫 sca
307
+ spec:commit # 或簡寫 scc
304
308
  ```
305
309
 
306
310
  如果你有安裝 prompts 或 slash commands,可以用對應的快捷指令;沒有安裝也可以直接手動輸入上面的文字指令。
307
311
 
308
312
  ## AI 工作流程
309
313
 
310
- | 指令 | 用途 |
311
- |---|---|
312
- | `spec:init` | 初始化並載入 `AGENTS.md`、`skills/system-analysis/SKILL.md` 與必要規則。 |
313
- | `spec:propose` | 使用者提出需求後,整理需求摘要、目標、初步流程與待確認事項。 |
314
- | `spec:analyze` | 檢查是否與既有需求衝突;無衝突才產出 SRS、UML、OpenAPI、DB Schema 草稿。 |
315
- | `spec:commit` | 使用者確認無衝突版本後,將版本歸檔到 `project/`,並建立本機 Git commit。 |
314
+ | 指令 | 簡寫 | 用途 |
315
+ |---|---|---|
316
+ | `spec:init` | `sci` | 初始化並載入 `AGENTS.md`、`skills/system-analysis/SKILL.md` 與必要規則。 |
317
+ | `spec:propose` | `scp` | 使用者提出需求後,整理需求摘要、目標、初步流程與待確認事項。 |
318
+ | `spec:analyze` | `sca` | 檢查是否與既有需求衝突;無衝突才產出 SRS、UML、OpenAPI、DB Schema 草稿。 |
319
+ | `spec:commit` | `scc` | 使用者確認無衝突版本後,將版本歸檔到 `project/`,並建立本機 Git commit。 |
316
320
 
317
321
  狀態機:
318
322
 
319
323
  ```text
320
- init -> propose -> analyze
324
+ init (sci) -> propose (scp) -> analyze (sca)
321
325
 
322
- analyze 無衝突 -> 產出待確認版本 -> commit
323
- analyze 有衝突 -> 釐清問題 -> analyze
326
+ analyze 無衝突 -> 產出待確認版本 -> commit (scc)
327
+ analyze 有衝突 -> 釐清問題 -> analyze (sca)
324
328
 
325
- commit -> 歸檔 -> 本機 Git commit
329
+ commit (scc) -> 歸檔 -> 本機 Git commit
326
330
  ```
327
331
 
328
332
  `spec:analyze` 若發現衝突,只會列出衝突與待釐清問題,不會修改任何需求文件,也不會產出最終文件。釐清後應再次執行 `spec:analyze`,重新檢查並產出草稿。
@@ -334,7 +338,7 @@ commit -> 歸檔 -> 本機 Git commit
334
338
  ```text
335
339
  請先閱讀 project/REQ-001-pomodoro-timer/handoff.md。
336
340
  以 handoff.md 作為本次開發入口與限制摘要。
337
- 如需完整需求細節,再查閱同資料夾內的 srs.md、uml.puml、openapi.yaml、db-schema.md 與 history.md。
341
+ 如需完整需求細節,再查閱同資料夾內的 srs.md、uml/、openapi.yaml、db-schema.md 與 history.md。
338
342
  不得自行新增 handoff.md 未確認的角色、權限、流程、狀態、API 或 DB 欄位。
339
343
  若發現 handoff.md 與既有程式或規格衝突,請停止開發並回報。
340
344
  ```
@@ -347,6 +351,64 @@ docs(REQ-001): 番茄時鐘 - 新增專注與休息計時需求
347
351
 
348
352
  只有在使用者明確要求推送 remote,或專案設定啟用 auto-push 時,才會執行 `git push`。如果 push 失敗,只回報認證或網路問題,不會回滾已完成的文件歸檔與本機 commit。
349
353
 
354
+ ## 多人協作解決方案(Git-Flow 與延遲綁定 ID)
355
+
356
+ 在單人開發時,循序流水號(`REQ-001`、`REQ-002`)簡單直覺;但在多人平行協作時,容易引發兩大痛點:
357
+ 1. **序號競爭撞車**:Alice 與 Bob 同時在各自的分支開發,雙方本機都取了 `REQ-003`,合併回主幹時發生目錄重名衝突。
358
+ 2. **中心化檔案衝突**:每次歸檔都要修改 `project/index.md`,導致頻繁的 Git merge conflict。
359
+
360
+ `sa-spec v0.2.0` 引進 **「Feature Branch 隔離 + 延遲綁定序號(Late-Binding ID)」** 完整解決方案:
361
+
362
+ ```mermaid
363
+ flowchart TD
364
+ A[開新需求: sa-spec new --name cart --branch] --> B[建立並切換至 feat/cart 分支<br/>以 project/cart/ 建立草稿目錄]
365
+ B --> C[AI 分析與起草: sca<br/>內部使用 {{REQ_ID}} 佔位<br/>自動同步 master 規格防護]
366
+ C --> D{使用者確認通過<br/>執行 scc 或 finalize}
367
+ D --> E[查詢 master 最新序號 N]
368
+ E --> F[重命名為 project/REQ-N+1-cart/<br/>批次置換 {{REQ_ID}} 為正式號碼]
369
+ F --> G[sa-spec reindex: 動態重構 index.md]
370
+ G --> H[合併回 master: 零衝突、序號永遠連續!]
371
+ ```
372
+
373
+ ### 多人協作四步流程
374
+
375
+ #### 1. 提出需求(草稿與分支隔離)
376
+ 開需求時**不搶序號**,直接以語意名稱建立分支與草稿目錄:
377
+
378
+ ```bash
379
+ # 自動建立並切換至 feat/cart-checkout 分支,建立 project/cart-checkout/ 草稿目錄
380
+ sa-spec new --name cart-checkout --branch
381
+ ```
382
+ 在 VS Code 側邊欄輸入:
383
+ ```text
384
+ scp 購物車結帳需求
385
+ ```
386
+ * **特點**:目錄名稱為 `project/cart-checkout/`,內部所有檔案皆使用 `{{REQ_ID}}` 佔位符。多人同時提出需求互不干擾、零衝突。即使需求中途放棄,直接刪除分支即可,絕不留下幽靈跳號。
387
+
388
+ #### 2. 分析與衝突檢查(規格基線同步)
389
+ 在分支上執行 `spec:analyze`(或 `sca`)前,工具會確認已取得 `master`(或 `origin/master`)的最新規格,確保是以全專案最新基準進行影響評估,避免因分支落後而漏檢權限、API 或資料表衝突。
390
+
391
+ #### 3. 確認歸檔與延遲定號(Late-Binding)
392
+ 當需求規格確認無誤,準備歸檔時:
393
+
394
+ ```bash
395
+ # 自動向 master 查詢最新可用序號(例如 REQ-006)
396
+ sa-spec finalize --name cart-checkout
397
+ ```
398
+ * **自動化執行**:
399
+ 1. 取得最新序號(例如目前最大是 `REQ-005`,自動取得 `REQ-006`)。
400
+ 2. 使用 `git mv` 將目錄重命名為 `project/REQ-006-cart-checkout`。
401
+ 3. 自動掃描並將目錄內所有檔案的 `{{REQ_ID}}` 批次置換為 `REQ-006`。
402
+ 4. 自動執行 `reindex` 重建需求總表。
403
+
404
+ #### 4. 消除索引合併衝突(`sa-spec reindex`)
405
+ `project/index.md` 改為由目錄動態生成的「衍生清單」,不再依賴脆弱的手動 append:
406
+
407
+ ```bash
408
+ sa-spec reindex
409
+ ```
410
+ * 任何時候若有 PR 合併產生 index 衝突,只要執行 `sa-spec reindex`,即可依序號重新排序生成乾淨的總表,無需人工手動解 conflict。
411
+
350
412
  ## 完整範例:番茄時鐘
351
413
 
352
414
  以下示範從空資料夾開始,建立一個「番茄時鐘」需求。
@@ -520,7 +582,8 @@ project/
520
582
  system-spec.md
521
583
  REQ-001-pomodoro-timer/
522
584
  srs.md
523
- uml.puml
585
+ uml/
586
+ state.puml
524
587
  openapi.yaml
525
588
  db-schema.md
526
589
  handoff.md
@@ -591,3 +654,39 @@ git push origin master
591
654
  - `project/system-spec.md` 只能在 `spec:commit` 依使用者確認內容更新。
592
655
  - `spec:commit` 會自動建立本機 Git commit;remote push 需使用者明確要求或啟用 auto-push。
593
656
  - 一次最多問 3 個待確認問題,優先詢問會影響範圍、資料、權限、API、DB 或驗收的問題。
657
+
658
+ ## 更新紀錄
659
+
660
+ ### v0.2.0
661
+ - **多人協作 Git-Flow 解決方案(延遲綁定序號與零衝突索引)**:
662
+ - **解決痛點**:多人平行開發時,傳統流水號(`REQ-001`)容易在各自分支撞車,且中心化的 `project/index.md` 容易造成 Git merge conflict。
663
+ - **延遲綁定序號(Late-Binding ID)**:開需求時不搶號,在 feature branch 上以語意名稱建立草稿需求(如 `project/cart`),內部使用 `{{REQ_ID}}` 佔位;只有在 `spec:commit` 最終歸檔時,才向 master/遠端確認最新可用序號並定號(如 `REQ-003`),自動批次置換佔位符,徹底避免幽靈跳號與序號競爭。
664
+ - **動態重建需求索引(`sa-spec reindex`)**:將 `project/index.md` 改為根據 `project/REQ-*` 目錄動態掃描並依序號排序生成,消除手動 append 造成的 Git 合併衝突。
665
+ - **CLI 指令升級**:
666
+ - `sa-spec new [--id REQ-001] --name login [--branch]`:支援草稿模式與自動切換 feature 分支。
667
+ - `sa-spec finalize --name login`:自動定號、重命名目錄、置換佔位符並重建索引。
668
+ - `sa-spec reindex`:一鍵掃描並重新排序生成乾淨的 `project/index.md`。
669
+ - **分析前防護**:`spec:analyze` 執行前確保取得 master/遠端最新規格,避免分支規格落後導致漏檢衝突。
670
+
671
+ ### v0.1.3
672
+ - **支援指令簡寫別名(同時保留完整指令)**:
673
+ - `sci` = `spec:init`
674
+ - `scp` = `spec:propose`
675
+ - `sca` = `spec:analyze`
676
+ - `scc` = `spec:commit`
677
+ - 兩者完全等價,使用者可依輸入習慣自由選擇長指令或 3 字母縮寫。
678
+
679
+ ### v0.1.2
680
+ - **新增 `sa-spec upgrade` 版本升級指令**:
681
+ - **工作區規則同步**:自動檢查並更新專案中的 `AGENTS.md`、`skills/system-analysis/SKILL.md` 以及 `rules/` 目錄下的規則文件至最新版本(自動保留既有的 memory context)。
682
+ - **既有需求自動遷移**:掃描 `project/REQ-*` 目錄,將舊版 `uml.puml` 自動重構移動為 `uml/main.puml`,並自動修復 `handoff.md` 中的來源引用路徑。
683
+ - **提供 `--dry-run` 模擬模式**:可加上 `--dry-run` 參數先行預覽哪些檔案會被更新或遷移,而不實際寫入檔案。
684
+
685
+ ### v0.1.1
686
+ - **PlantUML 檔案架構重構(解決多圖渲染與預覽開不起來問題)**:
687
+ - **目錄化結構**:每個需求目錄下由原本單一檔案 `uml.puml` 改為 `uml/` 資料夾(預設提供 `uml/main.puml` 骨架)。
688
+ - **單檔單圖原則**:嚴格規範每個 `.puml` 檔案僅包含單一組 `@startuml` 與 `@enduml`,禁止在單一檔案內混放多張圖。
689
+ - **支援多面向拆分**:需求若涉及多個面向(如業務流程、循序互動、狀態轉換),必須拆分為獨立 `.puml` 檔案(例如 `uml/main.puml`、`uml/sequence.puml`、`uml/state.puml`),確保 PlantUML 預覽外掛能順暢開啟與渲染。
690
+ - **CLI 自動化**:`sa-spec new` 與 `sa-spec template req` 自動建立 `uml/` 資料夾並產出預設的 `uml/main.puml`。
691
+ - **全專案規範同步**:同步更新 `AGENTS.md`、`system-analysis` skill、`rules/uml-style.md`、`rules/file-style.md` 與 `handoff.md`。
692
+
@@ -20,12 +20,11 @@
20
20
  - 每個需求資料夾至少包含:
21
21
  - `srs.md`:需求規格。
22
22
  - `history.md`:需求歷程。
23
- - `uml.puml`:UML 圖。
23
+ - `uml/`:UML 圖目錄,依面向拆分為獨立 `.puml` 檔(如 `main.puml`、`sequence.puml` 等,單檔單圖,禁止單檔混放多圖)。
24
24
  - `openapi.yaml`:OpenAPI 草稿。
25
25
  - `db-schema.md`:DB Schema 草稿。
26
26
  - `handoff.md`:給 OpenSpec 或其他 AI 開發工具的開發交接摘要。
27
27
  - 若需求涉及畫面,使用 `wireframe.md`。
28
- - 若需求需要 UML,放入 `uml/` 資料夾。
29
28
  - 若需求需要 API 或資料模型,使用 `api.md` 或獨立資料模型文件。
30
29
  - 若需求需要 DB Table 設計,使用 `db-table.md`。
31
30
 
@@ -37,13 +36,12 @@ project/
37
36
  srs.md
38
37
  wireframe.md
39
38
  history.md
40
- uml.puml
39
+ uml/
40
+ main.puml
41
+ sequence.puml
41
42
  openapi.yaml
42
43
  db-schema.md
43
44
  handoff.md
44
- uml/
45
- use-case.puml
46
- sequence.puml
47
45
  api.md
48
46
  db-table.md
49
47
  ```
@@ -2,7 +2,9 @@
2
2
 
3
3
  ## 格式
4
4
  - UML 圖一律使用 PlantUML。
5
- - PlantUML 檔案副檔名使用 `.puml`。
5
+ - PlantUML 檔案副檔名使用 `.puml`,放置於需求的 `uml/` 資料夾中(例如 `uml/main.puml`、`uml/sequence.puml`)。
6
+ - **嚴禁將多張圖混在同一個檔案中**:每個 `.puml` 檔案僅能包含單一組 `@startuml` 與 `@enduml`,避免 PlantUML 預覽或渲染工具解析失敗或無法開啟。
7
+ - 若需求涉及多個面向(例如業務流程、循序互動、狀態轉換),必須依圖型或面向拆分成獨立的 `.puml` 檔案。
6
8
  - 圖表內容以繁體中文描述,並採用台灣常用語。
7
9
 
8
10
  ## 限制
@@ -10,6 +12,7 @@
10
12
  - 不明確的部分必須標示為「待確認」,並回到人工審核。
11
13
  - UML 必須能追溯到對應需求編號。
12
14
  - UML 內容不得超出已確認或已標示假設的需求範圍。
15
+ - 單張圖應保持精簡清晰,避免過度龐大或單一圖節點過多導致渲染失敗。
13
16
 
14
17
  ## 圖表類型
15
18
  - Use Case Diagram:用於描述使用者、外部系統與系統功能邊界。
@@ -8,7 +8,7 @@ description: 用 init、propose、analyze、commit 四段流程整理需求,
8
8
  ## 核心交付物
9
9
 
10
10
  - `srs.md`:需求規格。
11
- - `uml.puml`:UML 圖。
11
+ - `uml/`:UML 圖目錄(依面向拆分為獨立 `.puml` 檔,如 `main.puml`、`sequence.puml` 等,禁止單檔混放多圖)。
12
12
  - `openapi.yaml`:OpenAPI 草稿。
13
13
  - `db-schema.md`:DB Schema 草稿。
14
14
  - `handoff.md`:給 OpenSpec 或其他 AI 開發工具的開發交接摘要。
@@ -16,12 +16,14 @@ description: 用 init、propose、analyze、commit 四段流程整理需求,
16
16
 
17
17
  ## 指令流程
18
18
 
19
- | 指令 | 用途 | 執行規則 |
20
- |---|---|---|
21
- | `spec:init` | 初始化 | 載入並遵守 `AGENTS.md`、本 skill 與必要規則,確認 `project/` 結構是否存在。 |
22
- | `spec:propose` | 提出需求 | 整理使用者需求為摘要、目標、初步流程與待確認事項;不歸檔、不修改既有需求。 |
23
- | `spec:analyze` | 分析與起草 | 先檢查新需求是否與 `project/` 內所有既有需求衝突;無衝突才產出 SRS、UML、OpenAPI、DB Schema 草稿;有衝突則只列出衝突與待釐清問題,待使用者釐清後再次執行 `spec:analyze`。 |
24
- | `spec:commit` | 確認歸檔 | 只把使用者已確認的版本歸檔到該需求資料夾,更新 `project/index.md`、`project/system-spec.md` 與 `history.md`,並建立本機 Git commit。 |
19
+ 使用四個核心指令,並支援簡寫別名(兩者等價):
20
+
21
+ | 指令 | 簡寫 | 用途 | 執行規則 |
22
+ |---|---|---|---|
23
+ | `spec:init` | `sci` | 初始化 | 載入並遵守 `AGENTS.md`、本 skill 與必要規則,確認 `project/` 結構是否存在。 |
24
+ | `spec:propose` | `scp` | 提出需求 | 整理使用者需求為摘要、目標、初步流程與待確認事項;不歸檔、不修改既有需求。 |
25
+ | `spec:analyze` | `sca` | 分析與起草 | 先檢查新需求是否與 `project/` 內所有既有需求衝突;無衝突才產出 SRS、UML、OpenAPI、DB Schema 草稿;有衝突則只列出衝突與待釐清問題,待使用者釐清後再次執行 `spec:analyze`(或 `sca`)。 |
26
+ | `spec:commit` | `scc` | 確認歸檔 | 只把使用者已確認的版本歸檔到該需求資料夾,更新 `project/index.md`、`project/system-spec.md` 與 `history.md`,並建立本機 Git commit。 |
25
27
 
26
28
  ## 硬性限制
27
29
 
@@ -41,8 +43,9 @@ description: 用 init、propose、analyze、commit 四段流程整理需求,
41
43
  - 交給 OpenSpec 或其他 AI 開發工具時,必須指定它先閱讀 `handoff.md`,並以 `handoff.md` 作為開發入口與限制摘要;其他規格文件作為詳細來源。
42
44
  - `spec:commit` 完成文件歸檔後,必須自動建立本機 Git commit;commit message 以需求編號與需求名稱為主,並加上簡短需求說明,格式為 `docs(REQ-001): 需求名稱 - 簡短需求說明`。
43
45
  - `spec:commit` 只有在使用者明確要求推送 remote,或專案設定啟用 auto-push 時,才可執行 `git push`;push 失敗時只回報認證或網路問題,不得回滾已完成的文件歸檔與本機 commit。
46
+ - 多人協作與 Git-Flow 模式(延遲綁定序號):支援多人平行開發。提出需求時可於 feature branch(如 `feat/<name>`)建立草稿目錄,內部使用 `{{REQ_ID}}` 佔位;執行 `spec:analyze` 前應先確保取得 master 最新規格以防漏檢衝突;執行 `spec:commit` 歸檔時向 master 確認最新可用序號並定號,自動將目錄定名為正式 `REQ-xxx`、替換內部佔位符、重新生成 `project/index.md`,避免多人合併時的序號競爭與索引衝突。
44
47
  - 文件未經使用者確認,不得標示為 `已審核`。
45
- - `uml.puml` 不限單一 UML 圖型;`spec:analyze` 必須依需求選擇最能說明流程、互動、狀態或資料關係的 UML 圖,例如活動圖、循序圖、使用案例圖、狀態圖或類別圖,不可為了固定格式硬產生活動圖。需求複雜時,可在同一個 `uml.puml` 內放多張 PlantUML 圖並清楚分段。
48
+ - `uml/` 內的 UML 圖不限單一 UML 圖型;`spec:analyze` 必須依需求選擇最能說明流程、互動、狀態或資料關係的 UML 圖,例如活動圖、循序圖、使用案例圖、狀態圖或類別圖,不可為了固定格式硬產生活動圖。**嚴禁將多張圖混在同一個檔案中**,必須在需求資料夾下的 `uml/` 目錄拆分成獨立的 `.puml` 檔案(例如 `uml/main.puml`、`uml/sequence.puml`、`uml/state.puml`),且每個檔案只能包含一組 `@startuml` 與 `@enduml`,以避免 PlantUML 渲染或預覽失敗。
46
49
  - 一次最多問 3 個待確認問題,優先詢問會影響範圍、資料、權限、API、DB 或驗收的問題。
47
50
 
48
51
  ## analyze 衝突檢查
@@ -55,7 +58,7 @@ description: 用 init、propose、analyze、commit 四段流程整理需求,
55
58
  - 所有 `project/REQ-*/openapi.yaml`
56
59
  - 所有 `project/REQ-*/db-schema.md`
57
60
  - 所有 `project/REQ-*/handoff.md`
58
- - 必要時讀取 `project/REQ-*/uml.puml`
61
+ - 必要時讀取 `project/REQ-*/uml/*.puml`
59
62
 
60
63
  至少檢查:
61
64
 
@@ -81,7 +84,7 @@ description: 用 init、propose、analyze、commit 四段流程整理需求,
81
84
  無衝突時,輸出一個待確認版本,包含:
82
85
 
83
86
  - SRS 草稿。
84
- - UML PlantUML 草稿。
87
+ - UML PlantUML 草稿(放置於 `uml/` 資料夾中,每張圖獨立檔案)。
85
88
  - OpenAPI YAML 草稿。
86
89
  - DB Schema 草稿。
87
90
  - Handoff 交接摘要草稿。
@@ -97,7 +100,8 @@ description: 用 init、propose、analyze、commit 四段流程整理需求,
97
100
  project/
98
101
  REQ-001-name/
99
102
  srs.md
100
- uml.puml
103
+ uml/
104
+ main.puml
101
105
  openapi.yaml
102
106
  db-schema.md
103
107
  handoff.md
@@ -119,7 +123,7 @@ project/
119
123
 
120
124
  若需求不需要 API 或 DB,必須明確標示「無 API 需求」或「無 DB 需求」。不得在 `handoff.md` 中新增未經使用者確認的角色、權限、資料、流程、狀態、API 或 DB 欄位。
121
125
 
122
- 交給 OpenSpec 或其他 AI 開發工具時,應要求它先閱讀 `handoff.md`,並以 `handoff.md` 作為本次開發入口與限制摘要。若需要完整細節,再查閱同需求資料夾內的 `srs.md`、`uml.puml`、`openapi.yaml`、`db-schema.md` 與 `history.md`。
126
+ 交給 OpenSpec 或其他 AI 開發工具時,應要求它先閱讀 `handoff.md`,並以 `handoff.md` 作為本次開發入口與限制摘要。若需要完整細節,再查閱同需求資料夾內的 `srs.md`、`uml/`、`openapi.yaml`、`db-schema.md` 與 `history.md`。
123
127
 
124
128
  歸檔完成後,必須只 stage 本次歸檔相關檔案,並建立本機 Git commit。commit message 格式:
125
129
 
@@ -132,10 +136,10 @@ docs(REQ-001): 需求名稱 - 簡短需求說明
132
136
  ## 狀態機
133
137
 
134
138
  ```text
135
- init -> propose -> analyze
139
+ init (sci) -> propose (scp) -> analyze (sca)
136
140
 
137
- analyze 無衝突 -> 產出待確認版本 -> commit
138
- analyze 有衝突 -> 釐清問題 -> analyze
141
+ analyze 無衝突 -> 產出待確認版本 -> commit (scc)
142
+ analyze 有衝突 -> 釐清問題 -> analyze (sca)
139
143
 
140
- commit -> 歸檔 -> 本機 Git commit
144
+ commit (scc) -> 歸檔 -> 本機 Git commit
141
145
  ```