mcpField
A
archive-spec

archive-spec

github.com/marcioaltoeAgent 工作流073 次安装今天更新
代码仓库

关于 archive-spec

archive-spec 是一项用于归档已完成或取消的规格文档的 Agent 技能,适用于遵循 Roundfix 规格与 QA 流程的项目。它会检查归档前提、重写引用,并保留归档记录及来源修订信息。

主要功能

  1. 01归档已完成的规格
  2. 02支持经授权的 QA 覆盖归档
  3. 03归档前验证任务与 QA 状态
  4. 04整理引用并提升关联文件
  5. 05取消规格并恢复关联条目

适用场景

  1. 01QA 通过后归档已实现的规格
  2. 02按授权例外记录未通过 QA 的归档
  3. 03将引用文件移出即将删除的规格目录
  4. 04取消规格并将条目恢复至待办目录

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/marcioaltoe/skills --skill archive-spec

安装后在 Claude Code 中输入/archive-spec确认已加载。

详细介绍

归档规范

通过在解析后的归档根目录下写入 <slug>.md 并移除 <resolved-spec-root>/<slug>/ 来归档已完成的规范;该目录会在 Git 中保留于记录的 source_revision。源目录是已配置的 Spec Root;目标目录是其解析后的归档根目录:内置的 docs/specs 根目录对应 docs/history/specs/,外部或非默认根目录对应 <spec-root>/_archived/,与 roundfix archive 的目标目录一致。正常归档意味着 已实现、已验证且内容自包含——所有任务均已完成、QA 已通过,且所有已索引引用均由该 Spec 所有。QA 归档例外用于记录明确的例外情况,但不声称已完成验证。无论采用哪种处置方式,之后执行一次 ls <spec-root>/,即可区分进行中的工作与历史记录;归档内容可供搜索,作为已构建内容及其原因的记录。

触发归档的条件是规范完成,而非发布:在 QA 门禁通过后,于 implement-spec 循环结束时自动执行,或在用户提出要求时执行。合并和发布是由用户单独决定的步骤——归档提交只需随分支一起流转,并随功能自身的 PR 发布。

归档重链接

写入归档记录之前,归档过程会执行归档重链接。对于每个由 Live Document 引用、且由所属 Spec 提供的非核心文件,归档过程会自动将其提升至 docs/references/<basename>,然后将每个 Live Document 中的引用改为指向提升后的副本;对 Spec 文件夹或核心产物的引用则指向归档记录。如果存在重写的文件,记录会在 relinked 下列出这些文件。源文件只保留一个所属方,因此次要 Spec 无需副本(ADR-0260、ADR-0083)。

Spec 文件夹通过删除而退役。在复制结果和每个提升后的副本中,原本指向已删除文件夹内的目标会改为指向提升后的副本,或变为 Git 定位符 `<path>@<commit>`;绝不会指向记录本身。该提交保留此路径,并可从默认分支或标签访问。如果不存在这样的提交,定位符会指明该路径以及记录对应的拉取请求。其他目标仍保留其在代码库中的目标位置(ADR-0278)。

如果提升目标已存在、两个待提升文件的基本文件名相同,或待重写的 Live Document 存在未在 HEAD 提交的更改,归档过程会拒绝写入。非 Live Document 文件中的引用,或重写后仍保留的引用,会作为警告列出文件、行号和目标;归档过程不会修改这些文件。

Squash 合并

Squash 合并只会在拉取请求的 head ref 中保留 source_revision;记录会在 pull_request 中注明该拉取请求。

QA 结算

同一结果会结算已编写的 qa Task,并决定归档时保留的内容:

结果结算归档
pass将 QA Task 结算为 completed;如果报告没有不允许的阻塞行,则使 Spec 符合归档条件。Archive Record,其中注明 QA Report 和判定结果;Spec、其 QA 报告和证据保留在 Git 的记录 source_revision 中。
符合条件的声明式 partial满足以下条件时,将 QA Task 结算为 completed:没有行失败、跳过或因发现问题而阻塞;每个声明为阻塞的行都由匹配的 ## Unreachable Acceptance 声明覆盖;每个环境阻塞的行都是 PR 前的 Pull Request 行,记录为 blocked (environment: no open Pull Request),且其溯源中注明 Pull Request 行;或者是 Run 沙盒无法访问的外部证据行,记录为 blocked (environment: network denied: <host>),且其溯源中注明外部证据行。这两类行都不需要 Unreachable Acceptance 声明;如果未满足的行只有这两类行,则该 partial 符合条件。Archive Record,其中注明 QA Report 和判定结果,并将各声明的 satisfied-by 记录为 unproven;Spec、其 QA 报告和证据保留在 Git 的记录 source_revision 中。
environment-blocked保持该行阻塞;如果等效证据满足环境策略,报告仍可结算为 pass。仅此项不会归档任何内容;符合条件的报告仍可归档 Spec。
failedQA Task 保持未解决状态;除非适用已授权的覆盖,否则拒绝归档。无。
missingQA Task 保持未解决状态;除非适用已授权的覆盖,否则拒绝归档。无。
override不更改 QA Task 状态或报告判定结果;即使 QA 失败或缺失,也会在明确授权的情况下结算归档。Archive Record,其中包含 qa_override、qa_override_approval、qa_override_reason、qa_override_qa_outcome、当 QA Task 未完成时的 qa_override_qa_task_status,以及 qa_override_revision;QA Task 和 Reports 在 Git 的记录 source_revision 中保持不变。
Spec 授权要求保持离线的行

上述符合条件的声明式 partial 也接受一种外部证据行:由于 Spec 自身的授权禁止查询,门禁未进行查询;该行记录为 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)。

取消 Spec

不再交付的 Spec 应使用 roundfix cancel <slug> --reason <text> 取消,绝不能手动删除。该命令会写入 disposition 为 cancelled 的 Archive Record,将每个已采用的 Backlog Entry 以 status: open 退回 docs/backlog/,并将每个已采用的 Finding 以 status: pending 退回 docs/findings/,随后移除 Spec 文件夹;其字节仍保留在 Git 的 source_revision 中。已取消的记录永远不满足任何前置条件,不会取代其他 Spec,且 Delivery Retry 会拒绝该记录。

被忽略的文件和移除失败

只要 Git 忽略了 Spec 文件夹下的任何文件,归档命令和取消命令就会拒绝执行。每条命令都会列出所有被忽略的文件,且不会写入任何内容。操作人员必须移除每个文件,或使用 git add -f 将其提交,然后重试。

如果最终无法移除 Spec 文件夹,两条命令都会保留所有写入内容、以 1 退出,并打印完成移除操作的命令: chmod -R u+w '<folder>' && rm -rf '<folder>'。

前置条件 — 验证,不要轻信

动手前,使用新鲜的命令证据检查以下三项:

  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 中,使用与归档命令相同的 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. Spec 内容完整自足。 当 docs/specs/<slug>/references/_index.md 存在或为符号链接时,应用此项前置条件;符号链接(包括失效链接)属于无效索引状态,不应视为旧版 Spec。只有该路径既不存在也不是符号链接的 Spec 才早于此契约,不经补充历史制品即可通过。对于已建立索引的 Spec,每个索引中的 path 都必须相对于 _index.md 存在;每个从未更新的 source 路径都必须不存在;Spec 内的 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"
    

    然后解析并验证每一条数据行。索引必须属于当前 Spec: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"; then
        printf 'self-containment failed: path must equal source basename for %s: %s != %s\n' "$source" "$path" "$source_basename" >&2
        exit 1
      fi
      current="$(dirname "$index")/$path"
      if test -L "$current" || test ! -f "$current"; then
        printf 'self-containment failed: invalid or missing path %s; finish adoption step 7\n' "$path" >&2
        exit 1
      fi
      if test -e "$source" || test -L "$source"; then
        printf 'self-containment failed: source still exists at %s; finish adoption step 7\n' "$source" >&2
        exit 1
      fi
    done < "$parsed_index"
    cat "$parsed_index"
    

仅通过 roundfix archive <slug> --qa-override --approval <source> --reason <text> 执行的 QA 归档覆盖,可以覆盖未满足的常规 QA 前置条件:QA 任务未完成、最新报告不符合资格,或 QA 证据缺失或无法读取。仅当所有任务均已完成且最新报告符合资格时,命令才会被拒绝,因为该规格可以正常归档。它绝不会覆盖自包含性:维护者可以覆盖验证要求,但自包含性是制品本身的属性,必须通过完成采纳来修复。

已合并的 PR 或发布标签不是前置条件,归档也绝不会等待它们。

如果任何检查失败,请停止并报告有问题的任务、报告、来源或链接,以及修复自包含性问题所需的采纳步骤——该规格会保持活跃状态。

若要在 QA 失败、缺失或不符合其他资格条件时归档,请在归档命令中明确提供批准信息和原因:

roundfix archive <slug> --qa-override --approval <source> --reason <text>

此覆盖仍要求所有非 QA 任务均为 completed。无论最新报告的判定结果如何,它都接受失败或待处理的 QA 任务;仅当所有任务均已完成且最新报告符合资格时才会拒绝,因为同一规格可以正常归档。它会记录批准来源、原因、观察到的 QA 结果和归档修订版本,但不会更改 QA 任务或报告。QA 任务未完成时,它还会记录 qa_override_qa_task_status。无法读取的报告,其记录的结果会写明相对于规格文件夹的路径,而不会写入计算机上的绝对路径。

步骤

对于 QA 归档覆盖,只能通过 roundfix archive <slug> --qa-override --approval <source> --reason <text> 执行归档。对于常规归档,请运行 roundfix archive <slug>;它会验证前置条件、写入归档记录并移除规格文件夹。切勿手动编辑归档记录;拒绝条件、溯源信息和归档生命周期均由该命令管理。交付队列会写入提交主题 docs: archive <slug>(Conventional Commits);如果这会超过 100 个字符,则写入 docs: archive the <spec number> Spec(对于不含规格编号的 slug,则写入 docs: archive the Spec)。除非收到请求,否则不要推送。

报告新路径以及任何移交内容(任务 ## Result 部分中的未完成后续事项应放入新规格,而不是归档)。如果工作尚未合并,建议通过 github-pr-workflow 创建 PR;是否创建 PR 由用户决定。

归档记录

归档会在解析后的归档根目录下写入 <slug>.md。记录会列出 QA 报告及其判定结果、处理方式,以及 source_revision,即被移除的规格文件夹仍可恢复时所对应的 Git 修订版本。归档前,请使用 roundfix archive <slug> --plan 查看将要归档的内容和归档建议中的候选文件。使用可重复执行的 --promote <path> 来提升持久知识;提升操作会在归档变更中将该文件复制到 docs/references/。

取消归档

罕见、明确、可逆:从 Git 中的记录 source_revision 恢复规格文件夹,并删除记录。重新开启通常意味着新的工作——优先创建引用已归档规格的新规格。

反模式

  • 在 QA 失败或缺失时悄悄归档——必须由用户明确授权覆盖,并记录在案。
  • 因 PR 尚未合并或发布标签尚未创建而阻止归档——完成情况(任务 + QA)才是门槛;发布是单独的、由用户决定的步骤。
  • 将已完成的规格留在活跃集合中,“等 PR 合并后再处理”——活跃文件夹只用于正在进行的工作。
  • 编辑已归档的规格——它是记录;新要求应放入链接回原规格的新规格中。
  • 删除而不是归档——这里就是回答“我们之前不是已经试过了吗?”的地方。

评论

支持 Markdown