Community程式設計與開發github.com

marcioaltoe/archive-spec

將完成的規格封存 — 驗證每個任務已完成、QA 通過且索引參考自我包含,然後在解析的封存根目錄下寫入 <slug>.md 並移除 Spec 資料夾,該資料夾會保留在記錄的 source_revision 的 Git 中。在 Roundfix Spec Run 結束且 QA 通過後自動執行,或當使用者要求封存規格時執行。

archive-spec 是什麼?

這項技能「Archive Spec」旨在自動化封存已完成的軟體規格 (Spec) 的流程。它確保規格中的所有任務都已標記為完成、品質保證 (QA) 已通過,且所有參考資料都自我包含,然後將規格的摘要記錄為 `<slug>.md` 檔案,並刪除原始的規格資料夾。整個操作保持 Git 歷史記錄完整,原始規格資料夾會留在其最後已知的修訂版本中。此技能支援多種規格的根目錄配置,包括預設的 `docs/specs` 以及其他外部位址。它會自動處理「Archive Relink」,將非核心檔案提升到 `docs/references/`,並更新引用,以確保在規格移除後所有連結依然有效。此外,它也處理進階案例,例如聲明式 QA 拒絕、部分 QA 驗證、環境封鎖的查閱,以及由使用者授權的明確 QA 覆寫。此技能也提供「取消規格」(Cancelling a Spec) 功能,這是一個受控流程,用於將未交付的規格歸檔,並將所有採用的待辦事項 (Backlog Entries) 和發現 (Findings) 恢復到其原始位置。

相容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/marcioaltoe/roundfix/tree/HEAD/skills/archive-spec

Installed? Explore more 程式設計與開發 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

Archive Spec (封存規格)

透過在已解析的封存根目錄下寫入 <slug>.md,並移除 <resolved-spec-root>/<slug>/ 來封存已完成的規格;該資料夾會保留在 Git 中記錄的 source_revision。來源是已配置的 Spec Root;目的地是其已解析的封存根目錄 — 對於內建的 docs/specs 根目錄為 docs/history/specs/,對於外部或非預設根目錄則為 <spec-root>/_archived/,其位置與 roundfix archive 的目標一致。正常的封存意味著已實作、已驗證且自我包含 — 每個任務都已完成,當 Task Graph 設定了 QA 關卡時 QA 已通過,且每個索引參考都歸屬於該規格。QA 封存覆寫則記錄明確的例外情況,而不聲稱已驗證。在這兩種處置之後,執行一次 ls <spec-root>/ 即可區分進行中的工作與歷史記錄,而封存記錄作為已建置內容及其原因的記錄,仍可透過 grep 搜尋。

觸發條件是規格完成,而非發布:在 Roundfix Spec Run 結束、QA 關卡通過後自動執行此動作,或在使用者要求時執行。合併和發布是分開的、由使用者驅動的步驟 — 封存提交僅隨分支移動,並在功能本身的 PR 內提交。

Archive Relink (封存重新連結)

在寫入封存記錄之前,封存會執行 Archive Relink。它會自動將即時文件 (Live Document) 引用的每個非核心檔案,從擁有者規格提升到 docs/references/<basename>,然後將每個即時文件的引用重寫為指向提升後的副本;對規格資料夾或核心工件的引用則指向封存記錄。當存在任何重寫的檔案時,記錄會在 relinked 下列出它們。來源保留一個擁有者,因此次要規格不需要副本 (ADR-0260、ADR-0083)。

規格資料夾會透過刪除而退役。在複製的結果和每個提升的副本中,已移除資料夾內的目標會命名一個提升的副本,或成為一個 Git 定位器 `<path>@<commit>`;它永遠不會指向記錄本身。該提交包含路徑,並且可以從預設分支或標籤到達。當不存在這樣的提交時,定位器會命名該路徑和記錄的 pull request。其他目標則保留其儲存庫目標 (ADR-0278)。

當提升目標已存在、兩個提升共用一個基礎名稱、或要重寫的即時文件在 HEAD 上有未提交的變更時,封存會拒絕寫入。非即時文件中的引用,或重寫後仍存在的引用,會產生警告,指出檔案、行號和目標;封存會保持這些檔案不變。

Squash merges (壓縮合併)

壓縮合併僅在 pull request 的頭部引用中保留 source_revision;記錄會在 pull_request 中命名該 pull request。

Declared QA decline (聲明式 QA 拒絕)

聲明 qa: declined 且包含非空 qa_reason 的 Task Graph,會在每個任務都完成後封存一次,無需 QA 報告也無需覆寫,其封存記錄的處置為 no-qa。對於此類規格,會拒絕 --qa-override。所有其他封存前提條件仍然適用。

QA settlement (QA 結算)

相同的結果會結算被撰寫的 qa 任務,並決定封存留下的內容:

結果結算方式封存方式
pass將 QA 任務結算為 completed,並在報告沒有不被允許的封鎖行時使規格符合封存資格。封存記錄,其中包含 QA 報告和裁決;規格、其 QA 報告和證據會保留在 Git 中記錄的 source_revision。
符合條件的聲明 partial在沒有行失敗、被跳過或因發現而受阻、每個因聲明而受阻的行都有一個匹配的 ## Unreachable Acceptance (無法達到的驗收) 聲明、且每個因環境而受阻的行都是 PR 前的 Pull Request 行(記錄為 blocked (environment: no open Pull Request) 並在其來源中命名該 Pull Request 行),或是執行沙盒無法觸及的外部證據行(記錄為 blocked (environment: network denied: <host>) 並在其來源中命名該外部證據行)時,將 QA 任務結算為 completed。這兩個行都不需要 Unreachable Acceptance 聲明,且若唯一的未滿足行是這類行,則 partial 符合資格。封存記錄,其中包含 QA 報告和裁決,並將聲明的 satisfied-by 記錄攜帶為 unproven;規格、其 QA 報告和證據會保留在 Git 中記錄的 source_revision。
environment-blocked將行保留為受阻;當等效證據滿足環境政策時,報告仍可結算為 pass。本身不會封存任何內容;符合條件的報告可以封存規格。
failed將 QA 任務保留為未解決,除非應用了授權的覆寫,否則拒絕封存。不會封存任何內容。
missing將 QA 任務保留為未解決,除非應用了授權的覆寫,否則拒絕封存。不會封存任何內容。
override不會改變 QA 任務狀態或報告裁決;將封存結算為在 QA 失敗或缺失的情況下明確授權。封存記錄,包含 qa_override、qa_override_approval、qa_override_reason、qa_override_qa_outcome、在 QA 任務未完成時的 qa_override_qa_task_status 和 qa_override_revision;QA 任務和報告在 Git 中記錄的 source_revision 保持不變。

Rows the Spec's authorization keeps offline (規格授權保留的離線行)

上述符合條件的聲明 partial 也接受一個外部證據行,該行因為規格自身的授權禁止查找而未被關卡查閱,記錄為 blocked (environment: <subject> prohibited by Spec authorization) 並在其來源中命名該外部證據行。subject 名稱說明了授權禁止的內容,使用其原始措辭:network 代表網路查閱,或 another repository;長度為 1 到 80 個字元,且不含括號。與網路拒絕的行一樣,它永遠不會決定符合條件的 partial,也不需要 Unreachable Acceptance 聲明,一個行最多計入一種豁免類型,而結算、roundfix qa-report accept 和 roundfix archive 透過相同的政策來讀取它 (ADR-0276、ADR-0335)。

Cancelling a Spec (取消規格)

不會交付的規格使用 roundfix cancel <slug> --reason <text> 來取消,絕不手動刪除。該命令會寫入一個處置為 cancelled 的封存記錄,將每個已採用的待辦事項 (Backlog Entry) 以 status: open 返回到 docs/backlog/,將每個已採用的發現 (Finding) 以 status: pending 返回到 docs/findings/,然後移除規格資料夾,而其內容會保留在 Git 的 source_revision 中。被取消的記錄永遠不會滿足前提條件,永遠不會取代另一個規格,並且會被 Delivery Retry 拒絕。

Ignored files and a failed removal (被忽略的檔案與失敗的刪除)

當 Git 忽略規格資料夾下的任何檔案時,封存命令和取消命令會拒絕執行。每個命令都會列出每個被忽略的檔案,並且不會寫入任何內容。操作員必須移除每個檔案,或使用 git add -f 提交它,然後重試。

如果規格資料夾的最終移除失敗,兩個命令都會保留所有寫入,以 1 退出,並列印完成移除的命令:chmod -R u+w '<folder>' && rm -rf '<folder>'。

Preconditions — verify, don't trust (前提條件 — 驗證,不信任)

在觸碰任何事物之前,使用最新的命令證據檢查所有三項:

  1. 必需的任務已完成。 對於正常封存,讀取 _tasks.md 中列出的每個 task_NN.md;每個 status 必須是 completed。對於 QA 封存覆寫,每個非 QA 任務必須是 completed;QA 任務可以保持 pending 或 failed。如果 QA 任務也是 completed,則最新的報告必須仍然不符合資格,否則覆寫是不必要的且會被拒絕。

    命令: 執行 grep -n '^status:' <each-task-file-listed-in-_tasks.md> 並保留其輸出。任何不是 status: completed 的值都會阻止正常的封存。在覆寫期間,只有另一個狀態的非 QA 任務會阻止封存;命名該任務檔案。

  2. QA 已通過。 qa/ 中最新的報告必須通過儲存庫的 QA 驗證器。不要使用行 grep 來替代結構化驗證。在 Roundfix 中,使用與 Archive Command 相同的 internal/spec.QAVerdict 合約:透過 qa-report-YYYY-MM-DD[-NN].md 檔案名稱合約選擇最新的報告,解析其 YAML 前端資料,要求支援的 verdict,在存在時要求 blocked-row 欄位必須是非負整數,並在 rows_blocked_finding 非零時拒絕 verdict: pass。保留驗證器的報告路徑和結果作為證據。缺失的 qa/ 目錄、格式錯誤的最新報告或未通過的裁決都會阻止正常封存。僅在透過 roundfix archive <slug> --qa-override --approval <source> --reason <text> 執行明確的 QA 封存覆寫時才繼續。切勿透過編輯封存記錄來記錄覆寫。符合條件的最新報告不會使失敗或待處理的 QA 任務完成;僅在報告符合條件且每個任務都完成時,覆寫才會被拒絕。

  3. 規格是自我包含的。 當 docs/specs/<slug>/references/_index.md 存在或是符號連結時,應用此前提條件;符號連結(包括中斷的連結)是無效的索引狀態,而非舊版規格。只有當該路徑不存在也不是符號連結的規格,才早於此合約,且無需改造歷史工件即可通過。對於索引規格,每個索引的 path 必須相對於 _index.md 存在,每個從未更新的 source 路徑必須不存在,且規格內部的 Markdown 連結目標不得指向 docs/_inbox/ 或 docs/findings/。

    命令: 首先執行此連結目標檢查;其語法特意匹配內聯或參考風格的 Markdown 連結,而不是僅命名任一棵樹的散文:

    spec_dir=docs/specs/<slug>
    link_hits=$(grep --include='*.md' -RInE '(\([^)]*(docs/)?(_inbox|findings)/[^)]*\)|^[[:space:]]*\[[^]]+\]:[[:space:]]*<?[^[:space:]>]*(docs/)?(_inbox|findings)/)' "$spec_dir")
    link_status=$?
    if test "$link_status" -eq 0; then
      printf '%s\n' "$link_hits"
      echo "self-containment failed: rewrite each listed link at adoption step 8"
      exit 1
    fi
    test "$link_status" -eq 1 || exit "$link_status"
    

    然後解析並驗證每個資料行。索引屬於目前的規格:owner 必須等於其四位數字前綴,type 必須是 inbox、finding 或 backlog,且每個 source 和 path 必須只出現一次。path 必須是相對於 _index.md 的一個基礎名稱;拒絕絕對路徑、.、..、路徑分隔符號和符號連結,而不是允許遍歷或指向 references/ 外部的連結。從儲存庫根目錄執行以下命令,並將規範化的行以及任何診斷資訊保留為證據:

    slug=<slug>
    index="docs/specs/$slug/references/_index.md"
    expected_owner=${slug%%-*}
    parsed_index=$(mktemp) || exit 1
    trap 'rm -f "$parsed_index"' EXIT HUP INT TERM
    
    if test -L "$(dirname "$index")"; then
      printf 'self-containment failed: references/ must not be a symbolic link\n' >&2
      exit 1
    fi
    
    if test -L "$index" || test ! -f "$index"; then
      printf 'self-containment failed: references/_index.md must be a regular, non-symbolic-link file\n' >&2
      exit 1
    fi
    
    awk -F '|' -v expected_owner="$expected_owner" '
    function trim(value) {
      gsub(/^[[:space:]]+|[[:space:]]+$/, "", value)
      return value
    }
    function reject(message) {
      print "self-containment failed: " message > "/dev/stderr"
      invalid = 1
    }
    function separator(value) {
      value = trim(value)
      return value ~ /^:?-{3,}:?$/
    }
    /^[[:space:]]*$/ || /^[[:space:]]*#/ { next }
    !header {
      if (NF != 7 || trim($2) != "source" || trim($3) != "type" ||
          trim($4) != "owner" || trim($5) != "adopted date" ||
          trim($6) != "path") {
        reject("_index.md must use the fixed source | type | owner | adopted date | path header")
      } else {
        header = 1
      }
      next
    }
    !divider {
      if (NF != 7 || !separator($2) || !separator($3) || !separator($4) ||
          !separator($5) || !separator($6)) {
        reject("_index.md has an invalid table separator")
      } else {
        divider = 1
      }
      next
    }
    {
      source = trim($2)
      type = trim($3)
      owner = trim($4)
      adopted = trim($5)
      path = trim($6)
      if (NF != 7 || source == "" || type == "" || owner == "" ||
          adopted == "" || path == "") {
        reject("invalid index row at line " NR ": " $0)
        next
      }
      if (type != "inbox" && type != "finding" && type != "backlog") {
        reject("type must be `inbox`, `finding`, or `backlog` at line " NR ": " type)
      }
      if (owner != expected_owner) {
        reject("owner must be " expected_owner " at line " NR ": " owner)
      }
      if (seen_source[source]++) {
        reject("duplicate source at line " NR ": " source)
      }
      if (seen_path[path]++) {
        reject("duplicate path at line " NR ": " path)
      }
      if (adopted !~ /^[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]$/) {
        reject("adopted date must be YYYY-MM-DD at line " NR ": " adopted)
      }
      if ((type == "inbox" && source !~ /^docs\/_inbox\/[^\/]+\.md$/) ||
          (type == "finding" && source !~ /^docs\/findings\/[^\/]+\.md$/) ||
          (type == "backlog" && source !~ /^docs\/backlog\/[^\/]+\.md$/)) {
        reject("source does not match type at line " NR ": " source)
      }
      print source "|" type "|" owner "|" adopted "|" path
    }
    END {
      if (!header || !divider) {
        reject("_index.md is missing its fixed table header")
      }
      if (invalid) {
        exit 1
      }
    }
    ' "$index" > "$parsed_index" || exit $?
    
    while IFS='|' read -r source type owner adopted path; do
      case "$path" in
        ""|.|..|/*|*/*|*\\*)
          printf 'self-containment failed: path must be one basename relative to `_index.md`: %s\n' "$path" >&2
          exit 1
          ;;
      esac
      source_basename=${source##*/}
      if test "$path" != "$source_basename"
    

…

[截斷以進行翻譯]

Individual skills in this repo

This repo contains 4 individual skills — each has its own dedicated page.

marcioaltoe/roundfix

Use Roundfix to plan releases with the read-only Release Plan Command, clean CodeRabbit pull request feedback, diagnose runtime readiness with the Doctor Command, execute a Spec's Task Graph with the Implement Command, monitor Runs through the Supervisor Run Event Stream, reclaim Run storage with the GC Command, archive completed Specs, run its `--follow-up`, `--status` and `--unattended` Skill Modes, and, inside daemon-assigned Batch runs, follow the bounded Review Issue or Task resolution contract.

marcioaltoe/write-prd

Write a PRD as a durable local artifact at docs/specs/<slug>/_prd.md — research first, clarify with one multiple-choice question at a time, record decisions as ADRs, then write directly without draft-approval rounds.

marcioaltoe/write-tasks

Decompose a spec's PRD/TechSpec into a dependency-ordered task graph — vertical-slice task files plus a machine-parseable _tasks.md DAG manifest under docs/specs/<slug>/, including the authored QA gate decision, decided autonomously from the Spec and escalated only when authority, architecture, or blast radius is at stake.

marcioaltoe/write-techspec

Write the technical spec at docs/specs/<slug>/_techspec.md — explore the architecture, settle technical decisions one question at a time, record them as ADRs, and produce a build order that write-tasks can decompose. Entry point for feature work with an approved PRD, and also for refactors and bug fixes, where it mints the spec folder with a minimal _prd.md instead of requiring a product interview.

相關技能