ops-wiki-agent-kit 0.1.0 → 0.1.2

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 (49) hide show
  1. package/.github/agents/source-code-to-spec-documenter.agent.md +1 -0
  2. package/.github/agents/source-code-to-system-ops-overview.agent.md +1 -0
  3. package/.github/prompts/00-discover-target-baseline.prompt.md +37 -0
  4. package/.github/prompts/01-generate-target-foundation-spec.prompt.md +13 -9
  5. package/.github/prompts/02-generate-target-architecture-spec.prompt.md +11 -7
  6. package/.github/prompts/03-generate-target-ops-spec.prompt.md +11 -7
  7. package/.github/prompts/generate-system-ops-overview.prompt.md +3 -2
  8. package/.github/skills/source-code-to-ops-spec-guidelines/SKILL.md +3 -25
  9. package/.github/skills/source-code-to-ops-spec-guidelines/references/01-system-overview-and-business-scenarios-guideline.md +0 -8
  10. package/.github/skills/source-code-to-ops-spec-guidelines/references/02-core-architecture-flow-data-logic-guideline.md +230 -171
  11. package/.github/skills/source-code-to-ops-spec-guidelines/references/03-error-ops-scenario-coverage-guideline.md +20 -2
  12. package/.github/skills/source-code-to-ops-spec-guidelines/references/supporting-output-format-diagram-and-example-reference.md +145 -143
  13. package/.github/skills/source-code-to-spec-documenter/SKILL.md +35 -6
  14. package/.github/skills/source-code-to-spec-documenter/references/generation-handoff-contract.md +43 -132
  15. package/.github/skills/source-code-to-spec-documenter/references/generation-workflow.md +100 -48
  16. package/.github/skills/source-code-to-spec-documenter/references/handoff-initial-template.json +76 -0
  17. package/.github/skills/source-code-to-spec-documenter/references/source-tracing-rules.md +61 -15
  18. package/.github/skills/source-code-to-spec-documenter/references/target-queue-contract.md +24 -7
  19. package/.github/skills/source-code-to-spec-documenter/scripts/handoff_update.py +310 -0
  20. package/.github/skills/source-code-to-spec-documenter/scripts/spec_queue.py +31 -2
  21. package/.github/skills/source-code-to-spec-tools/SKILL.md +11 -0
  22. package/.github/skills/source-code-to-spec-tools/references/repository-artifact-contract.md +61 -51
  23. package/.github/skills/source-code-to-spec-tools/references/target-queue-schema-contract.md +13 -1
  24. package/.github/skills/source-code-to-spec-tools/references/terminology-contract.md +2 -2
  25. package/.github/skills/source-code-to-spec-tools/scripts/catalog_query.py +43 -2
  26. package/.github/skills/source-code-to-spec-tools/scripts/queue_contract.py +3 -0
  27. package/.github/skills/source-code-to-spec-tools/scripts/target_query.py +3 -0
  28. package/.github/skills/source-code-to-system-ops-overview/SKILL.md +2 -1
  29. package/.github/skills/source-code-to-system-ops-overview/references/system-operations-overview-guideline.md +36 -5
  30. package/package.json +1 -1
  31. package/.github/agents/docs-target-catalog.agent.md +0 -52
  32. package/.github/agents/docs-target-queue-from-catalog.agent.md +0 -34
  33. package/.github/agents/source-code-to-spec-reviewer.agent.md +0 -51
  34. package/.github/prompts/00-generate-target-all-spec.prompt.md +0 -35
  35. package/.github/prompts/04-review-target-spec.prompt.md +0 -24
  36. package/.github/prompts/docs-target-catalog.prompt.md +0 -32
  37. package/.github/prompts/docs-target-queue-from-catalog.prompt.md +0 -28
  38. package/.github/skills/database-query/SKILL.md +0 -140
  39. package/.github/skills/database-query/references/client-commands.md +0 -189
  40. package/.github/skills/database-query/references/query-safety.md +0 -109
  41. package/.github/skills/database-query/scripts/find_db_config.py +0 -273
  42. package/.github/skills/docs-target-catalog/SKILL.md +0 -194
  43. package/.github/skills/docs-target-catalog/references/docs-target-queue-conversion.md +0 -164
  44. package/.github/skills/docs-target-catalog/references/entrypoint-source-patterns.md +0 -83
  45. package/.github/skills/docs-target-catalog/references/output-templates.md +0 -168
  46. package/.github/skills/docs-target-queue-from-catalog/SKILL.md +0 -255
  47. package/.github/skills/docs-target-queue-from-catalog/references/docs-target-queue-contract.md +0 -125
  48. package/.github/skills/docs-target-queue-from-catalog/references/metadata-acquisition-patterns.md +0 -149
  49. package/.github/skills/docs-target-queue-from-catalog/scripts/write_documentation_target_queue.py +0 -527
@@ -14,6 +14,41 @@
14
14
 
15
15
  只有在上述 CLI 查不到、解析失敗或檔案格式超出支援時,才補手動 `rg` 或 parser-specific inspection,並在 handoff 中記錄 CLI limitation。不要呼叫其他 skill 私有 `scripts/` 目錄。
16
16
 
17
+ ## Baseline And Rate-Limited Tracing
18
+
19
+ `baseline` scope 的目標是建立可復用 discovery ledger,不是產生 target-facing SPEC,也不是證明所有 behavior 都已查完。它必須使用 CLI-first、bounded tracing,降低單次 agent run 的 `read/search` 密度,同時保留後續 scope 必須追蹤的線索。
20
+
21
+ `baseline` scope 必須完成:
22
+
23
+ 1. 使用 `target_query.py preflight` 讀取 selected row、coverage review 與 related row candidates。
24
+ 2. 使用 `catalog_query.py handoff` / `catalog_query.py search` 讀取 catalog-level source acquisition 與 known gaps。
25
+ 3. 使用 canonical `source_lookup.py` 解析 selected `entrypoint` 的直接 evidence。
26
+ 4. 建立 `reader_facing_name_map`,包含 selected row、candidate related rows、visible subfunctions 與 catalog handoff rows。
27
+ 5. 登記 visible subfunctions、direct links、form actions、popup、AJAX、include、downstream candidate、shared data dependency 與 unmapped behavior。
28
+ 6. 將未完成的 deeper tracing 寫入 `not_checked`、`coverage_gaps` 或 `unresolved_items`。
29
+
30
+ `baseline` scope 不得:
31
+
32
+ - 建立或更新任何 target-facing `docs/**/*.md` 文件。
33
+ - 因 bounded tracing 沒有展開 downstream job、DAO、SQL、error / ops behavior,就寫成 `N/A`、已確認不存在或已確認不適用。
34
+ - 用連續未界定的 regex search 取代 canonical CLI lookup。
35
+ - 丟棄 entrypoint 可見的子功能、連結、popup、AJAX、include 或 downstream candidate。
36
+
37
+ ### Baseline Tool Budget And Stop Conditions
38
+
39
+ `baseline` scope 必須優先降低單次 agent run 的工具請求數。除非使用者明確要求 deep tracing,本 scope 的查詢與讀檔採下列節流規則:
40
+
41
+ - 重用同一輪已取得的 CLI output,不得為了重新確認而重跑相同 `target_query.py preflight`、`catalog_query.py handoff/search`、`source_lookup.py` 或相同 `rg` 查詢。
42
+ - 不在 generation run 期間執行 CLI `--help`、錯誤案例、smoke test、語法測試或維護用驗證;工具契約錯誤應在該工具修正回合處理,baseline run 只記錄 `cli_limitation` / `not_checked` 後停止對該方向的重試。
43
+ - Source file read 以 selected `entrypoint` 與其直接可見 include / form action / AJAX / popup / handler 為上限;同一檔案同一輪只讀一次。若需要更多檔案才能追完 downstream、DAO、SQL、job 或 error / ops path,先把候選寫入 `relation_candidates`、`coverage_gaps` 或 `not_checked`,交給 `01` / `02` / `03`。
44
+ - 達到 bounded scope、遇到 rate limit 風險、或 evidence 已足以建立 search priority 時,不繼續追求完整性;寫出 `partial` 或 `baseline_ready` handoff,並在 `not_checked` 明列後續應追的檔案、keyword 或 behavior。
45
+
46
+ 對 `generation_scope=foundation` 且 `doc_profile=standard`,若已存在 usable baseline,foundation tracing 可採 bounded update:只追 selected `entrypoint`、直接 include / handler、直接資料資源、直接子功能入口與明顯 handoff。更深的 downstream job lifecycle、DAO/SQL mapping、status transition、error handling、retry/rerun 與 ops behavior,應保留為 `not_checked` 或 follow-up gaps,交給 `architecture` / `ops` scope 繼續驗證。
47
+
48
+ `foundation` bounded tracing 降低的是單次工具呼叫密度,不降低 evidence standard。文件中所有已確認敘述仍必須可回查 source evidence;未確認的項目必須明確標示「無法由目前來源確認」、`TBD` 或 `Unresolved`,並寫入 handoff。
49
+
50
+ `architecture` 與 `ops` scope 必須把前序 handoff 的 `not_checked`、`coverage_gaps`、`relation_candidates` 與 `unmapped_behaviors_found` 視為 mandatory follow-up input。不得因 baseline 或 foundation 沒有展開某個 path,就推定該 behavior 不存在。
51
+
17
52
  ## Canonical Source Lookup Command
18
53
 
19
54
  `source_root` 是本次 selected row 要做 source tracing 的 source artifact 根目錄;若 queue、handoff、使用者指示或 workflow context 沒有提供更窄範圍,預設為 target source repo root。即使 `source_root` 等於 repo root,也必須在 `source_lookup.py` 命令中顯式傳入 `--root`,不要依賴目前 shell `cwd`。
@@ -37,26 +72,37 @@ python -B .github/skills/source-code-to-spec-tools/scripts/source_lookup.py --ro
37
72
  }
38
73
  ```
39
74
 
40
- | Field | Required | Rule |
41
- | ------------ | -------: | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
42
- | `path` | Y | Repo-relative file path、可重讀 metadata path,或穩定 source identifier。對 repo file 必須使用實際相對路徑,不要只寫 filename。 |
43
- | `line` | Y | Repo file evidence 必須填 1-based line number。只有 DB metadata、runtime export、external contract 或 generated doc 無可用行號時才可填 `null`。 |
44
- | `symbol` | Y | 最小可搜尋 technical handle,例如 `ClassName.methodName()`、`GET /api/orders`、`TABLE_NAME`、`config.key`、`JobName`、`ProcedureName`。 |
45
- | `kind` | Y | 使用穩定英文值:`entrypoint`、`route`、`handler`、`class`、`method`、`SQL`、`table`、`config`、`job`、`procedure`、`routine`、`file`、`test`、`runtime_metadata`、`generated_doc`、`external_contract`、`other`。 |
75
+ | Field | Required | Rule |
76
+ | -------- | -------: | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
77
+ | `path` | Y | Repo-relative file path、可重讀 metadata path,或穩定 source identifier。對 repo file 必須使用實際相對路徑,不要只寫 filename。 |
78
+ | `line` | Y | Repo file evidence 必須填 1-based line number。只有 DB metadata、runtime export、external contract 或 generated doc 無可用行號時才可填 `null`。 |
79
+ | `symbol` | Y | 最小可搜尋 technical handle,例如 `ClassName.methodName()`、`GET /api/orders`、`TABLE_NAME`、`config.key`、`JobName`、`ProcedureName`。 |
80
+ | `kind` | Y | 使用穩定英文值:`entrypoint`、`route`、`handler`、`class`、`method`、`SQL`、`table`、`config`、`job`、`procedure`、`routine`、`file`、`test`、`runtime_metadata`、`generated_doc`、`external_contract`、`other`。 |
46
81
 
47
82
  `source_lookup.py` 的 `matches[].evidence_ref` 是可複用的初始 EvidenceRef;後續人工 tracing 可調整 `kind` 與 `symbol`,但不可移除必要欄位。若同一結論需要多個 evidence,請用多個 `EvidenceRef`,不要把多個 path/line 塞進同一欄字串。
48
83
 
49
84
  ## Knowledge Map Preflight
50
85
 
51
- 1. 使用 `target_query.py preflight` 讀取 `docs/docs-target-queue.md` 的 selected row,確認 `id`、`source_type`、`group`、`name`、`entrypoint`、`document_path`、`keyword`、status、`last_handoff` 與 `notes`。
86
+ 1. 使用 `target_query.py preflight` 讀取 `docs/docs-target-queue.md` 的 selected row,確認 `id`、`source_type`、`group`、`name`、`entrypoint`、`document_path`、`keyword`、`baseline_status`、doc status、`last_handoff` 與 `notes`。
52
87
  2. 讀取 queue 的 `Source Acquisition Summary`,確認 selected row 來自 DB export、catalog direct rows、repo-owned route/config、hardcoded source、external metadata 或 unresolved source。
53
88
  3. 讀取 queue 的 `Coverage Review`,先排除 known helper、folder-only row、unmapped servlet、missing metadata 或 excluded target,避免把 gap 當成新 target。
54
89
  4. 使用 `catalog_query.py` 讀取 `docs/docs-target-catalog.md` 的 `Authoritative Target Source Handoff`、direct target rows、hardcoded target rows 與 catalog-level `Coverage Review`,確認 final row authority 與 acquisition action。
55
- 5. 使用 `target_query.py related` 查找 candidate related rows:同 `group`、同 `entrypoint`、同 handler/class、同 route/path、同 scheduler/job、同 table/resource、同 `keyword` catalog evidence 指向同一 implementation artifact 的 rows。這些只是候選關係,不是 target boundary 結論;必須依 `Functional Boundary And Handoff Edges` 分類後,才可寫入 handoff `related_rows`。
56
- 6. 若 selected row 有 `last_handoff`、既有 docs、partial output 或 review failure,先讀取並復用已確認的 evidence、unresolved items 與仍有效的說明;但舊 handoff 的 `id`、path segment 或 related row key 不得作為 reader-facing label 復用。
90
+ 5. 優先使用 `target_query.py preflight` 回傳的 `related_rows` 作為 candidate related rows;只有 preflight 缺少 related candidates、需要不同 `--limit` / `--include-self`,或需要重新查證 related rows 時,才另外呼叫 `target_query.py related`。這些只是候選關係,不是 target boundary 結論;必須依 `Functional Boundary And Handoff Edges` 分類後,才可寫入 handoff `related_rows`。
91
+ 6. 若 selected row 有 `last_handoff`、既有 docs、partial output 或 review failure,先讀取並復用已確認的 evidence、unresolved items 與仍有效的說明;`last_handoff` 必須指向 `.github/artifacts/source-code-to-spec/<target_id>/handoff.json` durable handoff。舊 handoff 的 `id`、path segment 或 related row key 也不得作為 reader-facing label 復用。
57
92
  7. 建立 `reader_facing_name_map`,至少包含 selected row,以及本次可能寫入 final SPEC 或 handoff summary 的 candidate related rows、confirmed `handoff_edge` rows、既有 handoff related rows 與 catalog handoff rows。每筆必須保留 machine-readable `id`,但 final SPEC 顯示用 `display_label` 必須優先取 row `name` / 功能名稱,其次取 source-backed job / API / handler / batch name;若無法確認可讀 label,`display_label=Unresolved`。若既有 handoff 沒有 `reader_facing_name_map` 或某筆 map 只剩 raw `id`,必須用 current queue / catalog / source evidence 重建 map,並把無法解析的 label 寫成 `Unresolved`。
58
93
  8. 將 preflight 結果整理為 `source map`、`known gaps`、`related rows`、`reader_facing_name_map`、`coverage hints`、`trace hypotheses`、`search_priority`,再開始 source tracing。
59
94
 
95
+ ### Visible Subfunction Preservation
96
+
97
+ Entry point 可見的 link、form action、button handler、AJAX/fetch/XHR、popup、tab、wizard step、include、iframe、menu node、report/export/import、upload/download 或 downstream activation candidate 都不得因目前 scope 不展開而消失。
98
+
99
+ 分類規則:
100
+
101
+ - 若子功能沒有獨立 activation evidence,且是 selected target 的 private helper 或同一 handler 內 sub-action,應在目前 target 的 source tracing 中保留為 current target behavior。
102
+ - 若子功能已有 queue row、獨立 menu/API/job/scheduler/command/route 或其他 activation evidence,不能寫進目前 target 的完整內部規格;必須登記為 `relation_candidates`、confirmed `related_rows`,或最小 cross-reference / boundary note。
103
+ - 若 source evidence 可見,但 queue / catalog 沒有對應 row,必須寫入 `code_verification.unmapped_behaviors_found`,並視 impact 寫入 `coverage_gaps` 或 `unresolved_items`。
104
+ - 若只是 shared table/resource、lookup、report query、audit/log 或 incidental reference,不建立 `handoff_edge`;必要時寫入 `relation_candidates` 並說明 unresolved reason。
105
+
60
106
  ## Existing Output Reconciliation
61
107
 
62
108
  既有 target 文件、handoff 與 reviewer note 是前次 generation context,不是目前 SPEC truth。當使用者指定 `id`,或 queue row 的 `document_path` / `last_handoff` 指向既有輸出時,必須在寫新內容前做 reconciliation:
@@ -116,7 +162,7 @@ Code verification 的目標是產生 full detail source-backed SPEC,而不是
116
162
  - 驗證 preflight 中的 handler、target name、entrypoint、data resource、coverage hints 與 known gaps 是否仍符合 source。
117
163
  - 如果 queue/catalog 與 code 不一致,以 code evidence 為準,但必須記錄差異。
118
164
  - 不可因 preflight 沒提到某個行為,就跳過 entrypoint 可達的 code path。
119
- - `generation_scope` 只限制本次 selected output file 與本次 run 擁有的正式章節,不限制 source tracing 範圍,也不降低本 scope source-backed detail 的深度。若同一 business lifecycle 可能需要 downstream job、callback、staging table、status transition 或 external integration 才能解釋目前 scope 的交付點,必須檢查足夠 evidence 並依 `Functional Boundary And Handoff Edges` 分類;不可因本次只產生 foundation / architecture / ops 單檔就省略,也不可把獨立 downstream target 的內部流程展開到目前文件。
165
+ - `generation_scope` 只限制本次 selected output file 與本次 run 擁有的正式章節,不降低本 scope 內已確認結論的 evidence standard。`baseline` 與 bounded `foundation` 可將深層 source tracing 轉成 `not_checked` / follow-up ledger,但不得把未查項目寫成不存在或不適用。若同一 business lifecycle 可能需要 downstream job、callback、staging table、status transition 或 external integration 才能解釋目前 scope 的交付點,必須檢查足夠 evidence 或明確登記為 follow-up,並依 `Functional Boundary And Handoff Edges` 分類;不可因本次只產生 foundation / architecture / ops 單檔就省略,也不可把獨立 downstream target 的內部流程展開到目前文件。
120
166
  - `doc_profile=full` 時,必須保留可讀取的完整 SQL excerpt;動態 SQL 必須轉為 source-backed pseudo SQL,並列出條件來源、參數、join、排序、狀態過濾與限制。DAO / Repository / Mapper method 必須 mapping 到 SQL、table、status column、DB object、stored routine、file resource 或 external resource。
121
167
 
122
168
  每次 generation / update 都必須整理 `Evidence Checked` / `Not Checked`:
@@ -160,11 +206,11 @@ A 文件不應展開 B-owned 內部規格:
160
206
 
161
207
  將 candidate relation 分為以下三類:
162
208
 
163
- | relation | Definition | Current target doc behavior | Handoff behavior |
164
- | --- | --- | --- | --- |
165
- | `handoff_edge` | A 建立 work item,B 以明確 source evidence 接手並推進同一 work item。 | 完整寫 A 的交付資料、狀態、correlation key、SQL / DAO mapping 與 source evidence;B 只寫 activation pointer、交接摘要與 cross-reference。 | 可寫入 `related_rows`。 |
166
- | `shared_data_dependency` | A / B 使用同一 table/resource,但沒有明確接手或狀態推進關係。 | 通常不寫;必要時只列資料影響。 | 不寫入 `related_rows`;必要時寫入 `coverage_gaps` / `unresolved_items`。 |
167
- | `incidental_reference` | lookup、report、audit/log、history、join、permission check 或其他偶然引用。 | 不寫。 | 不寫入 `related_rows`。 |
209
+ | relation | Definition | Current target doc behavior | Handoff behavior |
210
+ | ------------------------ | --------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
211
+ | `handoff_edge` | A 建立 work item,B 以明確 source evidence 接手並推進同一 work item。 | 完整寫 A 的交付資料、狀態、correlation key、SQL / DAO mapping 與 source evidence;B 只寫 activation pointer、交接摘要與 cross-reference。 | 可寫入 `related_rows`。 |
212
+ | `shared_data_dependency` | A / B 使用同一 table/resource,但沒有明確接手或狀態推進關係。 | 通常不寫;必要時只列資料影響。 | 不寫入 `related_rows`;必要時寫入 `coverage_gaps` / `unresolved_items`。 |
213
+ | `incidental_reference` | lookup、report、audit/log、history、join、permission check 或其他偶然引用。 | 不寫。 | 不寫入 `related_rows`。 |
168
214
 
169
215
  只有同時符合下列條件,才可判定為 `handoff_edge`:
170
216
 
@@ -9,12 +9,13 @@
9
9
  1. 明確指定 `id` 時,一律以該 `id` 為準。
10
10
  2. 明確指定 `id` 時,不檢查 `document_completed_flag(Y/N)` 或任何 doc status;即使該列不是 `pending` 或不是 `N`,也必須處理該 selected row。
11
11
  3. 未提供 `id` 時,依 `docs/docs-target-queue.md` main target table 順序由上到下挑選第一筆符合本次 scope 的 row。
12
- 4. 若 `scope=all`,挑選第一筆 `document_completed_flag(Y/N)=N` 的 row
13
- 5. 若 `scope=foundation`,挑選第一筆 `foundation_doc_status=pending` 的 row。
14
- 6. 若 `scope=architecture`,挑選第一筆 `architecture_doc_status=pending` 的 row。
15
- 7. 若 `scope=ops`,挑選第一筆 `ops_doc_status=pending` 的 row。
16
- 8. No-id auto selection 不自動挑選 `failed`、`partial`、`blocked`、`in_progress`、`generated`、`n/a` 或其他非 `pending` status;若要重跑這些 row,必須明確指定 `id`。
17
- 9. `review_status` 不參與 generation no-id auto selection;review workflow 有自己的 selection / status 規則。
12
+ 4. 若 `scope=baseline`,挑選第一筆 `baseline_status=pending` 的 row;`last_handoff` 只用於 artifact drift / durable handoff consistency 檢查,不作 no-id auto selection 主條件。
13
+ 5. 若 `scope=all`,挑選第一筆 `document_completed_flag(Y/N)=N` 的 row。
14
+ 6. 若 `scope=foundation`,挑選第一筆 `foundation_doc_status=pending` 的 row。
15
+ 7. 若 `scope=architecture`,挑選第一筆 `architecture_doc_status=pending` 的 row。
16
+ 8. `scope=ops`,挑選第一筆 `ops_doc_status=pending` row
17
+ 9. No-id auto selection 不自動挑選 `failed`、`partial`、`blocked`、`in_progress`、`generated`、`n/a`、`baseline_ready` 或其他非 `pending` status;若要重跑這些 row,必須明確指定 `id`。
18
+ 10. `review_status` 不參與 generation no-id auto selection;review workflow 有自己的 selection / status 規則。
18
19
 
19
20
  明確指定 `id` 只鎖定 selected row,不代表直接覆蓋文件,也不得只因 target docs 或 `last_handoff` 已存在就跳過。Selected output file 的 create-or-update action 由 `generation-workflow.md` 的 Output Action Contract 擁有;既有內容比對與 material difference 判斷由 `source-tracing-rules.md` 擁有。
20
21
 
@@ -26,6 +27,7 @@
26
27
 
27
28
  | scope | target document | status field |
28
29
  | --- | --- | --- |
30
+ | `baseline` | 不產生 target-facing SPEC;只建立 shared handoff | `baseline_status` |
29
31
  | `all` | 三份文件全部處理 | `foundation_doc_status`、`architecture_doc_status`、`ops_doc_status` |
30
32
  | `foundation` | `{name}-01-document-foundation-and-business.md` | `foundation_doc_status` |
31
33
  | `architecture` | `{name}-02-core-architecture-flow-data-logic.md` | `architecture_doc_status` |
@@ -39,6 +41,7 @@ Generation:
39
41
 
40
42
  | generation entrypoint / scope | status fields owned by this run |
41
43
  | --- | --- |
44
+ | `00-discover-target-baseline.prompt.md` / `baseline` | `baseline_status`;完成時同步更新 `last_handoff` |
42
45
  | `00-generate-target-all-spec.prompt.md` / `all` | `foundation_doc_status`、`architecture_doc_status`、`ops_doc_status` |
43
46
  | `01-generate-target-foundation-spec.prompt.md` / `foundation` | `foundation_doc_status` |
44
47
  | `02-generate-target-architecture-spec.prompt.md` / `architecture` | `architecture_doc_status` |
@@ -48,6 +51,7 @@ Status update rules:
48
51
 
49
52
  | event | status update |
50
53
  | --- | --- |
54
+ | `baseline` scope 建立或刷新 handoff | 將 `baseline_status` 設為 `baseline_ready`、`partial`、`blocked` 或 `failed`,並在建立 handoff 時將 `last_handoff` 更新為 durable path `.github/artifacts/source-code-to-spec/<target_id>/handoff.json`;不得更新 target-facing doc status、`review_status` 或 `document_completed_flag(Y/N)` |
51
55
  | 選入本次 scope 產生流程 | 本次 scope owned status field 設為 `in_progress`;`all` scope 同時設定三個 doc status |
52
56
  | 本次 selected output file 已產生、更新或經 reconciliation 判定 existing file 可作為目前 scope 的有效輸出 | 對應 doc status 設為 `generated` |
53
57
  | 本次 scope 不適用 | 對應 doc status 設為 `n/a` |
@@ -58,7 +62,7 @@ Status update rules:
58
62
 
59
63
  Completion flag update:
60
64
 
61
- - 每次 generation run 結束後,都必須檢查 selected row 的 `document_path` 下三份 target SPEC 文件是否存在。
65
+ - 每次非 baseline generation run 結束後,都必須檢查 selected row 的 `document_path` 下三份 target SPEC 文件是否存在。
62
66
  - 若 `{name}-01-document-foundation-and-business.md`、`{name}-02-core-architecture-flow-data-logic.md`、`{name}-03-error-ops-scenario-coverage.md` 三份文件都存在,設定 `document_completed_flag(Y/N)=Y`。
63
67
  - 若三份文件尚未齊全,設定或保持 `document_completed_flag(Y/N)=N`。
64
68
  - `document_completed_flag(Y/N)` 不等待 `review_status=passed`,也不代表 review 已通過。
@@ -76,3 +80,16 @@ Review:
76
80
  | 明確豁免審查 | `review_status=waived` |
77
81
 
78
82
  `document_completed_flag(Y/N)` 表示三份 target SPEC 文件是否已存在,不再由 `review_status` gate。Review workflow 只更新 `review_status` 與 review findings;除非 review 發現三份文件實際缺漏或路徑錯誤,否則不應只因 review 未通過而把 completion flag 改回 `N`。
83
+
84
+ ## Queue Sync Gate
85
+
86
+ Generation workflow 在 handoff update 成功後,必須透過 `$source-code-to-spec-documenter` Queue Helper 的 `spec_queue.py update` 同步 selected row,並立刻用 `spec_queue.py select` 重新讀取同一列驗證結果。這個 gate 是 workflow completion 的一部分;若 update 或 reselect 失敗,不得宣告 `docs/docs-target-queue.md` 已更新。
87
+
88
+ Queue sync 必須遵守:
89
+
90
+ - `last_handoff` 一律更新為 `.github/artifacts/source-code-to-spec/<target_id>/handoff.json`。
91
+ - `baseline` scope 只更新 `baseline_status` 與 `last_handoff`。
92
+ - `foundation`、`architecture`、`ops` scope 只更新本次 scope 擁有的 doc status,並依實際三份文件存在性更新 `document_completed_flag(Y/N)`。
93
+ - `all` scope 同步更新三個 doc status,並依實際三份文件存在性更新 `document_completed_flag(Y/N)`。
94
+ - 非 baseline generation 若本次文件可審查,`review_status` 設為 `pending`;若 target boundary 或必要 source 受阻,設為 `blocked`。
95
+ - Final response 使用 reselect 後的 queue row,不使用記憶中的預期狀態。
@@ -0,0 +1,310 @@
1
+ #!/usr/bin/env python3
2
+ import sys
3
+
4
+ sys.dont_write_bytecode = True
5
+
6
+ import argparse
7
+ import json
8
+ import os
9
+ import tempfile
10
+ from datetime import datetime, timezone
11
+ from pathlib import Path
12
+
13
+
14
+ SUPPORTED_SCOPES = {"baseline", "all", "foundation", "architecture", "ops"}
15
+ SUPPORTED_STATUSES = {"baseline_ready", "generated", "partial", "blocked", "failed"}
16
+
17
+ ROOT_FIELDS = [
18
+ "handoff_type",
19
+ "target_id",
20
+ "source_type",
21
+ "group",
22
+ "name",
23
+ "entrypoint",
24
+ "document_root",
25
+ "generation_scope",
26
+ "doc_profile",
27
+ "selected_coverage_views",
28
+ "generation_entrypoint",
29
+ "review_entrypoint",
30
+ "queue_path",
31
+ "handoff_path",
32
+ "baseline",
33
+ "scope_deltas",
34
+ "current_run",
35
+ "map_drift",
36
+ "artifact_drift",
37
+ "status",
38
+ ]
39
+
40
+ DELTA_FIELDS = [
41
+ "scope",
42
+ "status",
43
+ "generated_files",
44
+ "file_actions",
45
+ "change_justification",
46
+ "source_evidence",
47
+ "not_checked",
48
+ "coverage_gaps",
49
+ "unresolved_items",
50
+ ]
51
+
52
+ REQUIRED_ROOT_FIELDS = [
53
+ "target_id",
54
+ "entrypoint",
55
+ "document_root",
56
+ "generation_scope",
57
+ "doc_profile",
58
+ "queue_path",
59
+ "handoff_path",
60
+ "baseline",
61
+ "current_run",
62
+ "map_drift",
63
+ "artifact_drift",
64
+ "status",
65
+ ]
66
+
67
+
68
+ def canonical_handoff_path(target_id):
69
+ return f".github/artifacts/source-code-to-spec/{target_id}/handoff.json"
70
+
71
+
72
+ def normalize_path_text(value):
73
+ return str(value or "").strip().replace("\\", "/")
74
+
75
+
76
+ def read_json(path):
77
+ return json.loads(Path(path).read_text(encoding="utf-8-sig"))
78
+
79
+
80
+ def write_json_atomic(path, data):
81
+ path = Path(path)
82
+ path.parent.mkdir(parents=True, exist_ok=True)
83
+ temp_name = None
84
+ try:
85
+ with tempfile.NamedTemporaryFile(
86
+ "w",
87
+ encoding="utf-8",
88
+ newline="\n",
89
+ dir=str(path.parent),
90
+ prefix=f".{path.name}.",
91
+ suffix=".tmp",
92
+ delete=False,
93
+ ) as handle:
94
+ temp_name = handle.name
95
+ json.dump(data, handle, ensure_ascii=False, indent=2)
96
+ handle.write("\n")
97
+ os.replace(temp_name, path)
98
+ except Exception:
99
+ if temp_name:
100
+ try:
101
+ os.unlink(temp_name)
102
+ except OSError:
103
+ pass
104
+ raise
105
+
106
+
107
+ def require_object(value, label):
108
+ if not isinstance(value, dict):
109
+ raise SystemExit(f"{label} must be a JSON object")
110
+ return value
111
+
112
+
113
+ def require_scope(scope):
114
+ normalized = str(scope or "").strip().lower()
115
+ if normalized not in SUPPORTED_SCOPES:
116
+ raise SystemExit(
117
+ f"Unsupported scope: {scope}. Supported scopes: {', '.join(sorted(SUPPORTED_SCOPES))}"
118
+ )
119
+ return normalized
120
+
121
+
122
+ def validate_handoff_path(path_text, target_id):
123
+ expected = canonical_handoff_path(target_id)
124
+ actual = normalize_path_text(path_text)
125
+ if actual != expected:
126
+ raise SystemExit(
127
+ f"Invalid handoff path. Expected {expected}; got {path_text}"
128
+ )
129
+ return expected
130
+
131
+
132
+ def validate_patch(patch, target_id, scope):
133
+ patch_target = patch.get("target_id")
134
+ if patch_target and str(patch_target) != target_id:
135
+ raise SystemExit(f"Patch target_id {patch_target} does not match --target-id {target_id}")
136
+
137
+ current_run = require_object(patch.get("current_run"), "patch.current_run")
138
+ patch_scope = patch.get("generation_scope") or current_run.get("scope")
139
+ if patch_scope and require_scope(patch_scope) != scope:
140
+ raise SystemExit(
141
+ f"Patch scope {patch_scope} does not match --scope {scope}. "
142
+ "Use a patch JSON generated for the same invocation scope; do not reuse another scope's patch."
143
+ )
144
+
145
+ status = patch.get("status") or current_run.get("status")
146
+ if status and str(status).strip() not in SUPPORTED_STATUSES:
147
+ raise SystemExit(
148
+ f"Unsupported status: {status}. Supported statuses: {', '.join(sorted(SUPPORTED_STATUSES))}"
149
+ )
150
+
151
+ current_run_scope = current_run.get("scope")
152
+ if current_run_scope and require_scope(current_run_scope) != scope:
153
+ raise SystemExit(
154
+ f"current_run.scope {current_run_scope} does not match --scope {scope}. "
155
+ "Use a patch JSON generated for the same invocation scope; do not reuse another scope's patch."
156
+ )
157
+
158
+
159
+ def compact_delta(current_run, scope, timestamp):
160
+ delta = {}
161
+ for field in DELTA_FIELDS:
162
+ if field == "scope":
163
+ delta[field] = scope
164
+ elif field == "status":
165
+ delta[field] = current_run.get("status", "partial")
166
+ else:
167
+ delta[field] = current_run.get(field, [])
168
+ delta["updated_at"] = timestamp
169
+ return delta
170
+
171
+
172
+ def replace_scope_delta(scope_deltas, delta):
173
+ updated = []
174
+ replaced = False
175
+ for existing in scope_deltas:
176
+ if not isinstance(existing, dict):
177
+ continue
178
+ if str(existing.get("scope", "")).strip().lower() == delta["scope"]:
179
+ updated.append(delta)
180
+ replaced = True
181
+ else:
182
+ updated.append(existing)
183
+ if not replaced:
184
+ updated.append(delta)
185
+ return updated
186
+
187
+
188
+ def validate_merged_handoff(handoff):
189
+ missing = [
190
+ field
191
+ for field in REQUIRED_ROOT_FIELDS
192
+ if field not in handoff or handoff[field] in (None, "")
193
+ ]
194
+ if missing:
195
+ raise SystemExit(f"Merged handoff is missing required fields: {', '.join(missing)}")
196
+ require_object(handoff["baseline"], "handoff.baseline")
197
+ require_object(handoff["current_run"], "handoff.current_run")
198
+ require_object(handoff["map_drift"], "handoff.map_drift")
199
+ require_object(handoff["artifact_drift"], "handoff.artifact_drift")
200
+
201
+
202
+ def merge_handoff(existing, patch, target_id, scope, handoff_path):
203
+ current_run = dict(require_object(patch["current_run"], "patch.current_run"))
204
+ current_run["scope"] = scope
205
+ status = patch.get("status") or current_run.get("status") or "partial"
206
+
207
+ if status not in SUPPORTED_STATUSES:
208
+ raise SystemExit(f"Unsupported status: {status}")
209
+
210
+ if existing:
211
+ handoff = {field: existing.get(field) for field in ROOT_FIELDS if field in existing}
212
+ else:
213
+ handoff = {}
214
+
215
+ for key, value in patch.items():
216
+ if key in {"scope_deltas", "current_run"}:
217
+ continue
218
+ if key in ROOT_FIELDS:
219
+ handoff[key] = value
220
+
221
+ handoff["handoff_type"] = handoff.get(
222
+ "handoff_type", "source-code-to-spec-document-generation"
223
+ )
224
+ handoff["target_id"] = target_id
225
+ handoff["generation_scope"] = scope
226
+ handoff["handoff_path"] = handoff_path
227
+ handoff["current_run"] = current_run
228
+ handoff["status"] = status
229
+ handoff.setdefault("queue_path", "docs/docs-target-queue.md")
230
+ handoff.setdefault("map_drift", {"status": "none", "items": [], "map_corrections": []})
231
+ handoff.setdefault("artifact_drift", {"status": "none", "items": []})
232
+
233
+ if "baseline" in patch:
234
+ handoff["baseline"] = patch["baseline"]
235
+ elif "baseline" not in handoff or handoff["baseline"] is None:
236
+ raise SystemExit("Existing handoff has no baseline; patch.baseline is required")
237
+
238
+ scope_deltas = handoff.get("scope_deltas")
239
+ if not isinstance(scope_deltas, list):
240
+ scope_deltas = []
241
+
242
+ if scope != "baseline":
243
+ timestamp = patch.get("updated_at") or datetime.now(timezone.utc).isoformat()
244
+ scope_deltas = replace_scope_delta(scope_deltas, compact_delta(current_run, scope, timestamp))
245
+
246
+ handoff["scope_deltas"] = scope_deltas
247
+ validate_merged_handoff(handoff)
248
+ return {field: handoff[field] for field in ROOT_FIELDS if field in handoff}
249
+
250
+
251
+ def command_update(args):
252
+ scope = require_scope(args.scope)
253
+ handoff_path = validate_handoff_path(args.handoff, args.target_id)
254
+ patch = require_object(read_json(args.patch), "patch")
255
+ validate_patch(patch, args.target_id, scope)
256
+
257
+ path = Path(handoff_path)
258
+ existing = read_json(path) if path.exists() else None
259
+ if existing:
260
+ existing_target = existing.get("target_id")
261
+ if existing_target and str(existing_target) != args.target_id:
262
+ raise SystemExit(
263
+ f"Existing handoff target_id {existing_target} does not match --target-id {args.target_id}"
264
+ )
265
+
266
+ updated = merge_handoff(existing, patch, args.target_id, scope, handoff_path)
267
+ write_json_atomic(path, updated)
268
+ print(
269
+ json.dumps(
270
+ {
271
+ "handoff_path": handoff_path,
272
+ "target_id": args.target_id,
273
+ "scope": scope,
274
+ "status": updated.get("status"),
275
+ "scope_deltas": len(updated.get("scope_deltas", [])),
276
+ "queue_update_required": True,
277
+ "last_handoff": handoff_path,
278
+ },
279
+ ensure_ascii=False,
280
+ indent=2,
281
+ )
282
+ )
283
+
284
+
285
+ def build_parser():
286
+ parser = argparse.ArgumentParser(
287
+ description="Compactly merge source-code-to-spec handoff patch JSON into durable handoff.json."
288
+ )
289
+ subparsers = parser.add_subparsers(dest="command", required=True)
290
+
291
+ update = subparsers.add_parser("update", help="Create or compact-update one durable handoff.")
292
+ update.add_argument("--handoff", required=True)
293
+ update.add_argument("--target-id", required=True)
294
+ update.add_argument("--scope", required=True)
295
+ update.add_argument("--patch", required=True)
296
+ update.set_defaults(func=command_update)
297
+
298
+ return parser
299
+
300
+
301
+ def main():
302
+ args = build_parser().parse_args()
303
+ args.func(args)
304
+
305
+
306
+ if __name__ == "__main__":
307
+ try:
308
+ main()
309
+ except BrokenPipeError:
310
+ sys.exit(1)
@@ -16,6 +16,7 @@ else:
16
16
  raise SystemExit("Cannot locate source-code-to-spec-tools/scripts/queue_contract.py")
17
17
 
18
18
  from queue_contract import (
19
+ BASELINE_STATUSES,
19
20
  DOC_STATUSES,
20
21
  MAIN_COLUMNS,
21
22
  REVIEW_STATUSES,
@@ -25,7 +26,7 @@ from queue_contract import (
25
26
  )
26
27
 
27
28
 
28
- SUPPORTED_SCOPE_TEXT = "all, foundation, architecture, ops"
29
+ SUPPORTED_SCOPE_TEXT = "baseline, all, foundation, architecture, ops"
29
30
 
30
31
 
31
32
  def split_markdown_row(line):
@@ -59,6 +60,9 @@ def normalize_row(row):
59
60
  normalized["doc_profile"] = normalize_doc_profile(
60
61
  normalized.get("doc_profile", ""), normalized.get("source_type", "")
61
62
  )
63
+ normalized["baseline_status"] = normalize_status(
64
+ normalized["baseline_status"], BASELINE_STATUSES, "pending"
65
+ )
62
66
  for field in ("foundation_doc_status", "architecture_doc_status", "ops_doc_status"):
63
67
  normalized[field] = normalize_status(normalized[field], DOC_STATUSES, "pending")
64
68
  normalized["review_status"] = normalize_status(
@@ -115,6 +119,8 @@ def row_for_id(rows, target_id):
115
119
 
116
120
  def scope_fields(scope):
117
121
  scope = str(scope or "").strip().lower()
122
+ if scope == "baseline":
123
+ return ["baseline_status"]
118
124
  if scope == "foundation":
119
125
  return ["foundation_doc_status"]
120
126
  if scope == "architecture":
@@ -126,9 +132,22 @@ def scope_fields(scope):
126
132
  raise SystemExit(f"Unsupported scope: {scope}. Supported scopes: {SUPPORTED_SCOPE_TEXT}")
127
133
 
128
134
 
135
+ def normalize_last_handoff_path(value, target_id):
136
+ text = str(value or "").strip()
137
+ expected = f".github/artifacts/source-code-to-spec/{target_id}/handoff.json"
138
+ if text != expected:
139
+ raise SystemExit(
140
+ "Invalid last_handoff path. Expected "
141
+ f"{expected}; source-code-to-spec handoff is durable-only."
142
+ )
143
+ return text
144
+
145
+
129
146
  def is_selectable(row, scope):
130
147
  normalized_scope = str(scope or "").strip().lower()
131
148
  scope_fields(normalized_scope)
149
+ if normalized_scope == "baseline":
150
+ return row.get("baseline_status") == "pending"
132
151
  if normalized_scope == "all":
133
152
  return normalize_flag(row.get("document_completed_flag(Y/N)")) == "N"
134
153
  for field in scope_fields(normalized_scope):
@@ -142,6 +161,11 @@ def select_row(rows, args):
142
161
  target_id = getattr(args, "id", None)
143
162
  if target_id:
144
163
  return row_for_id(rows, target_id)
164
+ if str(args.scope or "").strip().lower() == "baseline":
165
+ for row in rows:
166
+ if is_selectable(row, args.scope):
167
+ return row
168
+ raise SystemExit(f"No selectable target row found for scope={args.scope}")
145
169
  for row in rows:
146
170
  if is_selectable(row, args.scope):
147
171
  return row
@@ -157,6 +181,10 @@ def command_select(args):
157
181
  def command_update(args):
158
182
  path, lines, start, end, rows = read_queue(args.queue)
159
183
  row = row_for_id(rows, args.id)
184
+ if args.baseline is not None:
185
+ row["baseline_status"] = normalize_status(
186
+ args.baseline, BASELINE_STATUSES, row["baseline_status"]
187
+ )
160
188
  for argument_name, field_name in (
161
189
  ("foundation", "foundation_doc_status"),
162
190
  ("architecture", "architecture_doc_status"),
@@ -172,7 +200,7 @@ def command_update(args):
172
200
  if args.completed is not None:
173
201
  row["document_completed_flag(Y/N)"] = normalize_flag(args.completed)
174
202
  if args.last_handoff is not None:
175
- row["last_handoff"] = args.last_handoff
203
+ row["last_handoff"] = normalize_last_handoff_path(args.last_handoff, args.id)
176
204
  if args.notes is not None:
177
205
  row["notes"] = args.notes
178
206
  write_queue(path, lines, start, end, rows)
@@ -197,6 +225,7 @@ def build_parser():
197
225
  update = subparsers.add_parser("update", help="Update queue status fields for one row.")
198
226
  update.add_argument("--queue", required=True)
199
227
  update.add_argument("--id", required=True)
228
+ update.add_argument("--baseline")
200
229
  update.add_argument("--foundation")
201
230
  update.add_argument("--architecture")
202
231
  update.add_argument("--ops")
@@ -43,6 +43,15 @@ Queue 欄位順序、supporting table 欄位、status enum、`doc_profile` 規
43
43
 
44
44
  從 repo root 執行以下 scripts,預設輸出 JSON,方便後續 handoff 或 agent parsing。執行本 skill 的 Python CLI 時使用 `python -B`,避免 smoke / generation run 產生 `__pycache__` 或 `*.pyc` runtime cache。
45
45
 
46
+ 本 skill 只提供 lookup / schema backbone CLI:`target_query.py`、`catalog_query.py`、`source_lookup.py` 與 `queue_contract.py`。`spec_queue.py` 與 `handoff_update.py` 不在本 skill;queue row selection / status update 與 durable handoff merge 必須使用 `.github/skills/source-code-to-spec-documenter/scripts/` 下的 workflow helper:
47
+
48
+ ```bash
49
+ python -B .github/skills/source-code-to-spec-documenter/scripts/spec_queue.py ...
50
+ python -B .github/skills/source-code-to-spec-documenter/scripts/handoff_update.py ...
51
+ ```
52
+
53
+ 不要嘗試執行 `.github/skills/source-code-to-spec-tools/scripts/spec_queue.py` 或 `.github/skills/source-code-to-spec-tools/scripts/handoff_update.py`;這些路徑不是 canonical script location。
54
+
46
55
  ### target_query.py
47
56
 
48
57
  用途:查詢 `docs/docs-target-queue.md` 的 target row、related rows、preflight context 與 supporting tables。
@@ -78,6 +87,8 @@ python -B .github/skills/source-code-to-spec-tools/scripts/catalog_query.py sear
78
87
  - 需要把 catalog table rows 轉給 queue normalization workflow。
79
88
  - 需要查 catalog-level coverage gap,不想手動掃 Markdown table。
80
89
 
90
+ `catalog_query.py handoff` 查的是 `docs/docs-target-catalog.md` 中的 handoff rows,不查 `docs/docs-target-queue.md` 的 target row id。若要查 `F1`、`J2` 這類 queue row id,使用 `target_query.py preflight --queue docs/docs-target-queue.md --id <target_id>`。
91
+
81
92
  ### source_lookup.py
82
93
 
83
94
  用途:對 source root 做 bounded handle lookup,回傳 `path`、`line`、`handles`、matched text、可選 context,以及可直接放入 handoff 的 `EvidenceRef`。