Files
host bd55599859 code-to10 v6 A14 batch1: workers F601 duplicates and check_transplant E741 renames
(cherry picked from commit 4411186681584bf55e065c654fe909a3320d4a22)

Reconcile-conflict: test_skill_transplant_prep artifact count — kept HEAD's finalized 0 refs (2026-11-02 user sign-off deleting §4's 6 references, already in target) with incoming's l→line rename; incoming's 6-ref assertion is pre-finalization and must not overwrite.
2026-11-03 02:49:26 +00:00

533 lines
25 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""Check (and draft) the transplant of #244's skill additions into the
v0.3 / v0.4 / v0.5 skill texts.
Background (see TRANSPLANT-PREP-244.md next to this script): #244
(feat-audit-pushback) adds 459 lines to the v0.2 skill text -- an
independent-auditor matrix row, a main section (independent audit +
auto-pushback, rebuild branch, three tightenings, QA full-chain), two
checklist items, and appendix chapter 09-auditor. The v0.3 / v0.4 /
v0.5 texts live on unmerged branches; the transplant itself can only
happen at merge time. This script is the preparation instrument, not
the transplant:
anchors -- verify that a target text still has every anchor
the transplant needs, and that every rule the
transplant must preserve is still present.
apply -- produce a DRAFT transplanted text (stdout or
--out) for review. The draft is partial by
design: paragraphs graded STOP in the prep doc
are NOT applied and are listed on stderr.
Never commit a draft as the transplant.
check-transplanted
-- verify a transplanted text: hunks present,
stopped paragraphs absent, preserved markers
intact, no templates/artifacts/ reference left,
and the target's lines intact in order (i.e. the
transplant did not overwrite target content
except for the documented artifact-reference
replacements).
Grading (see the prep doc for the full table):
* v03: apply everything except the G7 / full-auto sentences (those
回路 do not exist in v0.3 yet) -- sentence-level redactions.
* v04: apply everything except the G7 sentences and the收紧三
pre-rebuild user-confirmation paragraph (it would be a fifth
ask-user case beyond full-auto's exhaustive four).
* v05: apply everything except the whole QA full-chain section and
its checklist item (their cross-client framing contradicts v0.5's
cross-viewport framing), the收紧三 confirmation paragraph, and
renumber the appendix chapter 09-auditor -> 11-auditor (v0.5
already has 09-ops and 10-iteration).
Only the standard library is used. Exit codes: 0 = pass (apply always
exits 0 once the draft is written, even when paragraphs were stopped
-- the stop list on stderr is part of the result); 1 = check failed;
2 = usage error or an anchor/fragment that must exist exactly once
was missing or ambiguous (fail loudly, never silently partial).
"""
import argparse
import sys
from pathlib import Path
# ---------------------------------------------------------------- fragments
# Every fragment below is copied verbatim from #244's SKILL.md
# (feat-audit-pushback @ 39966bd). If a fragment is not found exactly
# once, apply/check fails loudly instead of guessing.
AUDIT_WARNING = (
"移植警告:本节是按主线 v0.2 文本写的,技能\n"
"v0.3 / v0.4 / v0.5 的内容在未合并分支上,合并时必须把本节\n"
"移植进 v0.3 / v0.4 / v0.5 文本,绝不能用本文件覆盖它们\n"
"(覆盖会丢掉它们已有的 G7、G8、全自动等内容)。"
)
AUDIT_WARNING_REPLACEMENT = (
"移植注:本节源自审计加固轨,移植时已按本文件对应章节的回路口径对齐。"
)
QA_INTRO = (
"QA 要求以生产 v0.2 技能为准:\n"
"已有规则语义不改,只补测试机制。移植警告与审计章节相同:\n"
"本节按主线 v0.2 文本写,合并时必须把新增段落移植进\n"
"v0.3 / v0.4 / v0.5 文本,绝不能用本文件覆盖它们。\n"
"改动逐条列在 PR 摘要里(本节为新增,上面章节为新增,\n"
"未改动已有规则的任何一句话——技能正文 diff 全是加法)。"
)
QA_INTRO_REPLACEMENT = "已有规则语义不改,只补测试机制。"
# G7 / full-auto parallel sentence + the stale note that follows it
# (source lines 435-440, in the "与已有回路的关系" paragraph).
G7_FULLAUTO_BLOCK = (
" G7 热修复回路并行(推回是合入前,热修复是发布后应急,\n"
" 推回不能替代热修复、热修复也不能抵推回);全自动 CI 自动\n"
" 修复并行且计数独立(见上限一段)。注意:G7 与全自动 CI 回路\n"
" 在主线 v0.2 文本里尚未出现(它们在未合并的 v0.4 / v0.5 分支里),\n"
" 移植进分支文本时按分支的实际回路定义对齐,不要因主线没有\n"
" 就删掉这段关系说明,也不要把分支内容反向说成主线已有。"
)
# v04 keeps the full-auto half; the G7 half and the stale note go.
G7_HALF = (
" G7 热修复回路并行(推回是合入前,热修复是发布后应急,\n"
" 推回不能替代热修复、热修复也不能抵推回);"
)
G7_NOTE = (
"注意:G7 与全自动 CI 回路\n"
" 在主线 v0.2 文本里尚未出现(它们在未合并的 v0.4 / v0.5 分支里),\n"
" 移植进分支文本时按分支的实际回路定义对齐,不要因主线没有\n"
" 就删掉这段关系说明,也不要把分支内容反向说成主线已有。"
)
PUSHBACK_LIMIT_PAREN = (
"(与全自动 CI 自动修复限\n"
" 3 次同口径,但计数不共用:CI 修复轮次发生在审计前,审计推回\n"
" 轮次发生在审计后,一方不得消耗另一方的预算,代码里\n"
" `ci_auto_fix_rounds` 只作参考输入、不计入推回上限)"
)
PUSHBACK_LIMIT_PAREN_V03 = (
"(推回上限独立于其它修复回路的轮次预算,一方不得消耗另一方的预算)"
)
REBUILD_TAIL = (
",与 G7 热修复回路并行(已发布\n"
" 模块的应急修复走 G7,不走推倒),与全自动 CI 自动修复并行\n"
" 且计数独立(CI 修复轮次不计入模块失败计数、也不计入推倒次数)。"
)
REBUILD_TAIL_V03 = "。"
REBUILD_TAIL_V04 = (
",与全自动 CI 自动修复并行\n"
" 且计数独立(CI 修复轮次不计入模块失败计数、也不计入推倒次数)。"
)
TIGHTEN3_NOTIFY = (
"- 验收失败导致的推倒要先通知用户:因用户验收 fail 累计触发推倒时,\n"
" 推倒前必须先通知用户并确认新方向。通知内容含失败原因、推倒范围、\n"
" 保留资产、新实现第一道门;用户未确认前不得静默重开(代码里\n"
" 是 `rebuild_pending_user_confirmation`,合并继续阻塞、推倒次数\n"
" 不消耗);通知与确认记录写进审计记录;用户确认新方向后,\n"
" 推倒才自动执行,执行后用户仍保留最终 G6 验收权。"
)
# Decision B (see prep doc): templates/artifacts/ references are deleted
# or rewritten to inline structure at transplant time. Each pair must
# occur exactly once in the target; anything else fails loudly.
ARTIFACT_REPLACEMENTS = [
("PRD 按 `templates/artifacts/prd-outline.md` 写完",
"PRD 按产品经理附录章节所列大纲写完"),
("问卷模板在 `templates/artifacts/intake-questions.md`。",
""),
("- 验收清单模板在 `templates/artifacts/acceptance-checklist.md`。\n",
""),
("按 `templates/artifacts/prd-outline.md` 写一份 PRD,至少包含:",
"按下面大纲写一份 PRD,至少包含:"),
(" 协议模板见 `templates/artifacts/` 下的 agent 任务契约模板。",
" (Agent 协议、状态机、错误 taxonomy 的要素见本章第 4、5、6 项。)"),
("按 `templates/artifacts/acceptance-checklist.md` 生成本项目的验收清单,",
"按 PRD 验收标准生成本项目的验收清单,"),
]
# Markers that must survive the transplant, per target (cumulative).
MARKERS = {
"v03": ["派生前校验表", "精简包", "任务间问答默认策略",
"目录检查命令", "单端复核"],
"v04": ["派生前校验表", "精简包", "任务间问答默认策略",
"目录检查命令", "单端复核",
"全自动模式", "full-auto-executor", "FULL_AUTO_ENABLED",
"限 3 次"],
"v05": ["派生前校验表", "精简包", "任务间问答默认策略",
"目录检查命令", "单端复核",
"全自动模式", "full-auto-executor", "FULL_AUTO_ENABLED",
"限 3 次",
"G7 发布后运维与观察期", "G8 迭代与规模化",
"09-ops", "10-iteration", "跨视口"],
}
MAIN_HEADERS = [
"## 独立审计与自动推回",
"### 独立审计角色:不写代码,只独立重跑、抽查证据、单独签字",
"### 自动推回:审计不过关,系统自动推回,不等人工点头",
"## 推倒重做:超过三次后的升级分支(不是并行回路)",
"## 三条收紧:分流、保留资产、审计的审计",
]
QA_HEADER = "## QA 全链路:测试环节跑通各个和所有业务链路"
def fail(msg):
print("FAIL: " + msg, file=sys.stderr)
return 1
def _lines(text):
return text.split("\n")
def _find_exact(lines, predicate, what):
hits = [i for i, line in enumerate(lines) if predicate(line)]
if len(hits) != 1:
raise ValueError("%s: expected exactly 1 hit, found %d"
% (what, len(hits)))
return hits[0]
def extract_source(text):
"""Split #244's text into its four transplant hunks."""
lines = _lines(text)
matrix = [line for line in lines if line.startswith("| 独立审计 |")]
if len(matrix) != 1:
raise ValueError("auditor matrix row: expected 1, found %d"
% len(matrix))
main_start = _find_exact(lines, lambda line: line == MAIN_HEADERS[0],
"main section heading")
honest = _find_exact(lines, lambda line: line.startswith("## 诚实边界"),
"honesty section heading")
if not main_start < honest:
raise ValueError("main section does not precede honesty section")
main = lines[main_start:honest]
while main and main[-1] == "":
main.pop()
check_start = _find_exact(
lines, lambda line: line.startswith("- 审计与推回走了吗?"),
"audit checklist item")
check_end = _find_exact(
lines, lambda line: line.startswith("- QA 全链路走了吗?"),
"QA checklist item")
audit_item = _collect_item(lines, check_start, "悄悄放行?")
qa_item = _collect_item(lines, check_end, "重审重跑失败链路了吗?")
app_start = _find_exact(
lines, lambda line: line.startswith("## 附录角色:") and "auditor" in line,
"appendix auditor chapter")
body = lines[app_start + 1:]
while body and body[0] == "":
body.pop(0)
while body and body[-1] == "":
body.pop()
return {
"matrix_row": matrix[0],
"main": main,
"audit_item": audit_item,
"qa_item": qa_item,
"appendix_body": body,
}
def _collect_item(lines, start, terminator):
item = []
for line in lines[start:]:
item.append(line)
if line.endswith(terminator):
return item
raise ValueError("checklist item starting at %d never ends with %r"
% (start + 1, terminator))
def transform_main(main_lines, target_id):
"""Apply the graded rewrites/redactions; return (lines, stopped)."""
text = "\n".join(main_lines)
stopped = []
# R1/R2: stale self-referential paragraphs, all targets.
for frag, repl, name in [
(AUDIT_WARNING, AUDIT_WARNING_REPLACEMENT, "audit warning"),
(QA_INTRO, QA_INTRO_REPLACEMENT, "QA intro")]:
if target_id == "v05" and name == "QA intro":
continue # whole QA section is stopped for v05, handled below
if text.count(frag) != 1:
raise ValueError("fragment %s: expected 1, found %d"
% (name, text.count(frag)))
text = text.replace(frag, repl)
if target_id == "v03":
for frag, repl, name in [
(G7_FULLAUTO_BLOCK, "", "G7/full-auto block"),
(PUSHBACK_LIMIT_PAREN, PUSHBACK_LIMIT_PAREN_V03,
"pushback limit paren"),
(REBUILD_TAIL, REBUILD_TAIL_V03, "rebuild tail")]:
if text.count(frag) != 1:
raise ValueError("fragment %s: expected 1, found %d"
% (name, text.count(frag)))
text = text.replace(frag, repl)
elif target_id == "v04":
for frag, repl, name in [
(G7_HALF, " ", "G7 half"),
(G7_NOTE, "", "G7 note"),
(REBUILD_TAIL, REBUILD_TAIL_V04, "rebuild tail")]:
if text.count(frag) != 1:
raise ValueError("fragment %s: expected 1, found %d"
% (name, text.count(frag)))
text = text.replace(frag, repl)
if text.count(TIGHTEN3_NOTIFY) != 1:
raise ValueError("tighten3 notify: expected 1, found %d"
% text.count(TIGHTEN3_NOTIFY))
if (TIGHTEN3_NOTIFY + "\n") in text:
text = text.replace(TIGHTEN3_NOTIFY + "\n", "")
else:
text = text.replace(TIGHTEN3_NOTIFY, "")
stopped.append("收紧三:验收失败触发推倒前用户确认段 "
"(v04 全自动四类回头问用户之外,需用户拍板第五类或限定默认模式)")
elif target_id == "v05":
qa_at = text.find(QA_HEADER)
if qa_at < 0:
raise ValueError("QA section heading not found")
text = text[:qa_at].rstrip("\n")
stopped.append("QA 全链路整节 (v05 跨视口口径与跨端 framing 冲突,需视口版重写)")
if text.count(TIGHTEN3_NOTIFY) != 1:
raise ValueError("tighten3 notify: expected 1, found %d"
% text.count(TIGHTEN3_NOTIFY))
if (TIGHTEN3_NOTIFY + "\n") in text:
text = text.replace(TIGHTEN3_NOTIFY + "\n", "")
else:
text = text.replace(TIGHTEN3_NOTIFY, "")
stopped.append("收紧三:验收失败触发推倒前用户确认段 "
"(v05 全自动四类回头问用户之外,需用户拍板第五类或限定默认模式)")
else:
raise ValueError("unknown target-id %r" % target_id)
# Formatting only: a redaction can leave a double blank line where
# the removed sentences sat. Collapse runs of 3+ newlines to one
# blank line; no wording is touched.
import re as _re
text = _re.sub(r"\n{3,}", "\n\n", text)
lines = text.split("\n")
while lines and lines[-1] == "":
lines.pop()
return lines, stopped
def apply_transplant(source_text, target_text, target_id):
"""Return (draft_text, report_lines). Raises ValueError on any miss."""
hunks = extract_source(source_text)
tlines = _lines(target_text)
report = []
# 1. matrix row after the QA row.
qa_row = _find_exact(tlines, lambda line: line.startswith("| QA / 测试 |"),
"target QA matrix row")
if any(line.startswith("## 附录角色:") and "auditor" in line for line in tlines):
raise ValueError("target already contains an auditor appendix chapter")
out = list(tlines)
out.insert(qa_row + 1, hunks["matrix_row"])
report.append("matrix row inserted after target QA row")
# 2. main section before the honesty heading (re-find after insert).
honest = _find_exact(out, lambda line: line.startswith("## 诚实边界"),
"target honesty heading")
main, stopped = transform_main(hunks["main"], target_id)
report.extend("STOPPED (not applied): " + s for s in stopped)
out[honest:honest] = main + [""]
report.append("main section inserted before honesty heading "
"(%d lines, %d stopped paragraphs)"
% (len(main), len(stopped)))
# 3. checklist items before "有一项为否".
check_end = _find_exact(out, lambda line: "有一项为否" in line,
"target checklist end")
items = list(hunks["audit_item"])
if target_id != "v05":
items += hunks["qa_item"]
else:
report.append("STOPPED (not applied): QA checklist item "
"(v05 跨端矩阵/未编译端口径与视口口径冲突)")
out[check_end:check_end] = items
report.append("checklist item(s) inserted: %s"
% ("audit + QA" if target_id != "v05" else "audit only"))
# 4. appendix chapter at EOF.
while out and out[-1] == "":
out.pop()
number = "11" if target_id == "v05" else "09"
heading = ("## 附录角色:%s-auditor(来源 templates/roles/%s-auditor.md)"
% (number, number))
out += ["", "---", "", heading, ""] + hunks["appendix_body"]
report.append("appendix chapter appended as %s-auditor" % number)
# 5. decision B: artifact references.
text = "\n".join(out)
for old, new in ARTIFACT_REPLACEMENTS:
if text.count(old) != 1:
raise ValueError("artifact replacement %r: expected 1, found %d"
% (old[:40], text.count(old)))
text = text.replace(old, new)
report.append("decision B applied: 6 templates/artifacts/ references "
"removed/rewritten")
if not text.endswith("\n"):
text += "\n"
return text, report
def check_anchors(source_text, target_text, target_id):
errors = []
try:
hunks = extract_source(source_text)
except ValueError as exc:
return ["source hunks not extractable: %s" % exc]
tlines = _lines(target_text)
for what, pred in [
("target QA matrix row", lambda line: line.startswith("| QA / 测试 |")),
("target honesty heading", lambda line: line.startswith("## 诚实边界")),
("target checklist end", lambda line: "有一项为否" in line),
("target appendix intro", lambda line: line == "## 附录:角色模板(播种随附版)"),
]:
if sum(1 for line in tlines if pred(line)) != 1:
errors.append("anchor %s: expected 1, found %d"
% (what, sum(1 for line in tlines if pred(line))))
last_chapter = ("10-iteration" if target_id == "v05"
else "08-shared-core")
if not any(line.startswith("## 附录角色:%s" % last_chapter) for line in tlines):
errors.append("target appendix lacks last chapter %s" % last_chapter)
if any("auditor" in line and line.startswith("## 附录角色:") for line in tlines):
errors.append("target already contains an auditor appendix chapter")
for marker in MARKERS[target_id]:
if marker not in target_text:
errors.append("preserved marker missing in target: %s" % marker)
refs = [i + 1 for i, line in enumerate(tlines) if "templates/artifacts" in line]
print("anchors: source hunks OK (matrix + main %d lines + 2 checklist "
"items + appendix %d lines)"
% (len(hunks["main"]), len(hunks["appendix_body"])))
print("anchors: templates/artifacts/ references in target: %d at lines %s "
"(expected: 6 sites, removed at transplant time per decision B)"
% (len(refs), refs))
print("anchors: appendix chapter number for %s: %s"
% (target_id, "11-auditor" if target_id == "v05" else "09-auditor"))
return errors
def check_transplanted(target_text, transplanted_text, target_id):
errors = []
for marker in MARKERS[target_id]:
if marker not in transplanted_text:
errors.append("preserved marker lost: %s" % marker)
rows = [line for line in _lines(transplanted_text)
if line.startswith("| 独立审计 |")]
if len(rows) != 1:
errors.append("auditor matrix row: expected 1, found %d" % len(rows))
for header in MAIN_HEADERS:
if header not in transplanted_text:
errors.append("main header missing: %s" % header)
if target_id == "v05":
if QA_HEADER in transplanted_text:
errors.append("STOPPED QA section present in v05 output")
if "- QA 全链路走了吗?" in transplanted_text:
errors.append("STOPPED QA checklist item present in v05 output")
if "验收失败导致的推倒要先通知用户" in transplanted_text:
errors.append("STOPPED tighten3 notify present in v05 output")
elif target_id == "v04":
if QA_HEADER not in transplanted_text:
errors.append("QA section missing in v04 output")
if "验收失败导致的推倒要先通知用户" in transplanted_text:
errors.append("STOPPED tighten3 notify present in v04 output")
if "G7 热修复回路并行" in transplanted_text:
errors.append("STOPPED G7 sentence present in v04 output")
else: # v03
if QA_HEADER not in transplanted_text:
errors.append("QA section missing in v03 output")
if "G7 热修复回路并行" in transplanted_text:
errors.append("STOPPED G7 sentence present in v03 output")
if "全自动 CI 自动修复" in transplanted_text:
errors.append("STOPPED full-auto sentence present in v03 output")
if "验收失败导致的推倒要先通知用户" not in transplanted_text:
errors.append("tighten3 notify missing in v03 output")
number = "11" if target_id == "v05" else "09"
heading = ("## 附录角色:%s-auditor(来源 templates/roles/%s-auditor.md)"
% (number, number))
if transplanted_text.count(heading) != 1:
errors.append("appendix heading %s: expected 1, found %d"
% (heading, transplanted_text.count(heading)))
if "09-auditor(独立审计,新增)" in transplanted_text:
errors.append("old appendix heading form survived")
for stale in ["移植警告", "以生产 v0.2 技能为准",
"技能正文 diff 全是加法"]:
if stale in transplanted_text:
errors.append("stale transplant warning survived: %s" % stale)
if "templates/artifacts" in transplanted_text:
errors.append("templates/artifacts/ reference survived "
"(decision B not applied)")
# No-overwrite: the target, after decision-B replacements, must be a
# subsequence (in order) of the transplanted text.
transformed = target_text
for old, new in ARTIFACT_REPLACEMENTS:
transformed = transformed.replace(old, new)
t_iter = iter(_lines(transplanted_text))
remaining = _lines(transformed)
pos = 0
for line in t_iter:
if pos < len(remaining) and line == remaining[pos]:
pos += 1
if pos != len(remaining):
errors.append("no-overwrite check failed: target line %d (%r) "
"missing or out of order in transplanted text"
% (pos + 1, remaining[pos][:80] if pos < len(remaining)
else ""))
return errors
def main(argv=None):
parser = argparse.ArgumentParser(
description="Transplant preparation checker for #244 -> v0.3/v0.4/v0.5.")
sub = parser.add_subparsers(dest="cmd", required=True)
for name in ["anchors", "apply", "check-transplanted"]:
p = sub.add_parser(name)
p.add_argument("--source244", required=True)
p.add_argument("--target", required=True)
p.add_argument("--target-id", required=True,
choices=["v03", "v04", "v05"])
if name == "apply":
p.add_argument("--out", default=None)
if name == "check-transplanted":
p.add_argument("--transplanted", required=True)
args = parser.parse_args(argv)
try:
source = Path(args.source244).read_text(encoding="utf-8")
target = Path(args.target).read_text(encoding="utf-8")
except OSError as exc:
print("FAIL: cannot read input: %s" % exc, file=sys.stderr)
return 2
try:
if args.cmd == "anchors":
errors = check_anchors(source, target, args.target_id)
if errors:
for err in errors:
print("FAIL: " + err, file=sys.stderr)
print("anchors FAILED: %d problem(s)" % len(errors),
file=sys.stderr)
return 1
print("anchors OK for %s" % args.target_id)
return 0
if args.cmd == "apply":
draft, report = apply_transplant(source, target, args.target_id)
if args.out:
Path(args.out).write_text(draft, encoding="utf-8")
else:
sys.stdout.write(draft)
for line in report:
print("apply: " + line, file=sys.stderr)
return 0
transplanted = Path(args.transplanted).read_text(encoding="utf-8")
errors = check_transplanted(target, transplanted, args.target_id)
if errors:
for err in errors:
print("FAIL: " + err, file=sys.stderr)
print("check-transplanted FAILED: %d problem(s)" % len(errors),
file=sys.stderr)
return 1
print("check-transplanted OK for %s" % args.target_id)
return 0
except ValueError as exc:
print("FAIL: %s" % exc, file=sys.stderr)
return 2
if __name__ == "__main__":
raise SystemExit(main())