Files
gplan-2/tools/anchor_util.py
T

92 lines
4.2 KiB
Python
Raw 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
# -*- coding: utf-8 -*-
r"""anchor_util.py — 内容判据锚(节锚)核验共用件:pipeline_gate(卡片 C5)与 capability_audit(判据出处注记道)共用的核验逻辑单点。
锚形态(2026-11-09 判据锚内容化改版批立,用户裁行号形态废止):
`<文档相对路径>「节名」<行关键词>`——节名与行关键词在 design/ 现行文档(或 game/sim/ 测量面实现件)
里定位判据句,文档增删行使行号漂移与此形态无关,只有判据句内容真被改动才会使锚失配。
核验口径:
.md 目标——节名首段(「·」前,沿 2026-11-07 架构收窄批惯例)须命中文档某个标题行(行首 # 记号),
行关键词须落在该标题行的节块内(标题行起至下一个标题级别不高于它的标题行止);
.gd 目标(测量面,无标题结构)——节名首段与行关键词均须在文件内子串命中。
本件只做纯文本核验、不读盘:目标文件文本由调用方读入传入(编码口径归调用方)。
机检局限如实声明:判据句被改写而行关键词仍为其子串时核验不红(语义漂移),由复核门实读兜。
"""
import re
# 内容锚整体形态:路径+「节名」+行关键词(关键词止于空白与常见分隔符;反引号/+/§/冒号
# 不入词——卡内锚常套反引号包裹、杂尾修饰会污染关键词)
CONTENT_ANCHOR_RE = re.compile(
r"([^\s`'\"()(),,、;;::|]+\.md)「([^「」]+)」([^\s「」|,,、;;::()()+§`]+)")
# 行号锚(废止形态,2026-11-09 用户裁)——判据锚位置出现即红
LINE_ANCHOR_RE = re.compile(r"([^\s`'\"()(),,、;;::|]+\.md)\s*:\s*(\d+)")
def section_block(text, head):
"""节名首段 head 命中的标题行的节块文本;无命中标题行返回 None。
节块=该标题行起至下一个标题级别不高于它的标题行止(不含该行);
标题行=行首 # 记号的 ATX 标题(本仓库 design/ 文档惯例);围栏代码块(```)内的
# 行不是标题,探测时跳过。节名命中两档:先找标题文本与 head 全等(去除首尾空白)的行,
全等无命中再退子串包含——同名节(如「验收标准」嵌「一屏样张的验收标准」)取精确者。"""
lines = text.splitlines()
def headings():
fence = False
for i, ln in enumerate(lines):
s = ln.lstrip()
if s.startswith("```"):
fence = not fence
continue
if fence:
continue
m = re.match(r"^(#{1,6})\s*(.+?)\s*$", s)
if m:
yield i, len(m.group(1)), m.group(2).strip()
start = -1
level = 0
for i, lvl, txt in headings():
if txt == head.strip():
start = i
level = lvl
break
if start < 0:
for i, lvl, txt in headings():
if head in txt:
start = i
level = lvl
break
if start < 0:
return None
fence = False
for j in range(start + 1, len(lines)):
s = lines[j].lstrip()
if s.startswith("```"):
fence = not fence
continue
if fence:
continue
m = re.match(r"^(#{1,6})\s", s)
if m and len(m.group(1)) <= level:
return "\n".join(lines[start:j])
return "\n".join(lines[start:])
def verify(target_path, target_text, sec, kw):
"""核验一个内容锚。返回 None=通过;字符串=红因白话。
target_path 仅用于按扩展名分派核验口径(.gd=测量面文件内子串命中,其余=标题+节块口径)。"""
if target_path.replace("\\", "/").endswith(".gd"):
if sec.split("·")[0] not in target_text:
return "节名首段不在文件内"
if kw not in target_text:
return "行关键词不在文件内"
return None
block = section_block(target_text, sec.split("·")[0])
if block is None:
return "节名首段未命中任何标题行"
if kw not in block:
return "行关键词不在该节块内"
return None