✨ feat(tsl_syntax): add concept map to lookup
新增 --map 模式,扫描 24 页「本篇职责」段实时生成 TSL 概念地图, 帮助从零写代码时把自然语言需求映射到该查的 TSL 概念(property、 unit、operator 重载等),随后仍用 --query 取精确语法。地图从 references 实时生成,零漂移;--check 增加护栏,职责段被清空即报错。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -21,6 +21,16 @@ python <this-skill-dir>/scripts/lookup.py --query "数组下标" --mode explain
|
||||
|
||||
以 lookup 返回的章节作为本次任务的事实来源;需要补充时改进查询词并再次检索,不得绕过 lookup 手工路由。需要复取输出中列出的某个章节时,用 `--section <Section ID>` 单独取回。
|
||||
|
||||
## 从零起手先看概念地图
|
||||
|
||||
面对自然语言需求、还不确定该往哪个 TSL 概念上想时(尤其从零编写、周围无参考代码),先运行:
|
||||
|
||||
```bash
|
||||
python <this-skill-dir>/scripts/lookup.py --map
|
||||
```
|
||||
|
||||
它列出全部专题的职责摘要,用于把需求映射到 TSL 特有概念(如 `property`、`unit`、`operator` 重载、原生矩阵运算、命名参数、`with` 环境等,这些在别的语言里未必存在或写法不同)。地图只负责提示该查什么概念,不含语法细节;选定概念后仍用 `--query` 取精确语法。不要把地图当成可照写来源。
|
||||
|
||||
## 事实边界
|
||||
|
||||
本 Skill 只拥有 TSL/TSF 的语言语法、文件模型、表达式、控制流、对象、运行时语言结构和 TS-SQL 外形。其他事实交给对应所有者:
|
||||
|
||||
@@ -29,6 +29,7 @@ ALLOWED_IDENTITIES = {
|
||||
}
|
||||
ROUTER_PHRASES = ("路由中心", "选择一个主专题", "候选页继续判断")
|
||||
EXCLUDED_REFERENCE_FILES = {"index.md"}
|
||||
DUTY_HEADING = "本篇职责"
|
||||
SUSPICIOUS_FENCE_RE = re.compile(r"^(?:\s+`{3}|`{4,})")
|
||||
WRITE_PRELUDE_ANCHORS = (
|
||||
("02_core_model.md", "文件模型核心规则"),
|
||||
@@ -351,6 +352,20 @@ def validate_references(
|
||||
f"write 模式前置章节缺失:{page_name} 的「{heading}」",
|
||||
)
|
||||
)
|
||||
# 概念地图逐页从「本篇职责」段生成;有该段的页必须产出非空摘要,
|
||||
# 否则某页职责段被清空/写坏时地图会静默缺页。
|
||||
mapped_pages = {page_name for page_name, _, _ in build_concept_map(references_dir)}
|
||||
for section in sections:
|
||||
if section.heading_path != (DUTY_HEADING,):
|
||||
continue
|
||||
if section.page.name not in mapped_pages:
|
||||
problems.append(
|
||||
ValidationProblem(
|
||||
section.page,
|
||||
1,
|
||||
f"概念地图摘要为空:{section.page.name} 的「{DUTY_HEADING}」",
|
||||
)
|
||||
)
|
||||
return problems
|
||||
|
||||
|
||||
@@ -583,6 +598,69 @@ def render_result(result: QueryResult) -> str:
|
||||
return "\n".join(lines).rstrip() + "\n"
|
||||
|
||||
|
||||
def _duty_summary(body: str) -> str:
|
||||
lines = body.splitlines()
|
||||
paragraph: list[str] = []
|
||||
for line in lines[1:]:
|
||||
stripped = line.strip()
|
||||
if not stripped:
|
||||
if paragraph:
|
||||
break
|
||||
continue
|
||||
paragraph.append(stripped)
|
||||
summary = " ".join(paragraph)
|
||||
# 职责段以冒号引出列表时(如 09),首段本身空洞,把随后的
|
||||
# 列表项折叠进摘要才能保留实际覆盖面。
|
||||
if summary.endswith((":", ":")):
|
||||
items: list[str] = []
|
||||
seen_paragraph = False
|
||||
for line in lines[1:]:
|
||||
stripped = line.strip()
|
||||
if not stripped:
|
||||
continue
|
||||
if not seen_paragraph:
|
||||
if stripped == summary or stripped in summary:
|
||||
seen_paragraph = True
|
||||
continue
|
||||
if stripped.startswith(("-", "*", "·")):
|
||||
items.append(stripped.lstrip("-*· ").strip())
|
||||
elif items:
|
||||
break
|
||||
if items:
|
||||
summary = summary + ";".join(items) + "。"
|
||||
return summary
|
||||
|
||||
|
||||
def build_concept_map(references_dir: Path = DEFAULT_REFERENCES_DIR) -> list[tuple[str, str, str]]:
|
||||
entries: list[tuple[str, str, str]] = []
|
||||
seen_pages: set[str] = set()
|
||||
for section in load_sections(references_dir):
|
||||
page_name = section.page.name
|
||||
if page_name in seen_pages:
|
||||
continue
|
||||
if section.heading_path != (DUTY_HEADING,):
|
||||
continue
|
||||
summary = _duty_summary(section.body)
|
||||
if not summary:
|
||||
continue
|
||||
seen_pages.add(page_name)
|
||||
entries.append((page_name, section.page_title, summary))
|
||||
entries.sort(key=lambda item: item[0])
|
||||
return entries
|
||||
|
||||
|
||||
def render_concept_map(entries: list[tuple[str, str, str]]) -> str:
|
||||
lines = [
|
||||
"# TSL 概念地图",
|
||||
"",
|
||||
"把自然语言需求映射到该查哪个 TSL 概念,随后仍用 `--query` 取精确语法。",
|
||||
"本清单不含语法细节,也不替代 lookup;用命中专题里的关键语法词组成查询。",
|
||||
]
|
||||
for page_name, page_title, summary in entries:
|
||||
lines.extend(["", f"## {page_title}", "", summary])
|
||||
return "\n".join(lines).rstrip() + "\n"
|
||||
|
||||
|
||||
def _configure_utf8() -> None:
|
||||
for stream in (sys.stdout, sys.stderr):
|
||||
reconfigure = getattr(stream, "reconfigure", None)
|
||||
@@ -595,6 +673,7 @@ def _parser() -> argparse.ArgumentParser:
|
||||
action = parser.add_mutually_exclusive_group(required=True)
|
||||
action.add_argument("--query")
|
||||
action.add_argument("--section")
|
||||
action.add_argument("--map", dest="show_map", action="store_true")
|
||||
action.add_argument("--check", action="store_true")
|
||||
parser.add_argument("--mode", choices=("write", "diagnose", "explain"))
|
||||
parser.add_argument("--limit", type=int, default=5)
|
||||
@@ -625,6 +704,9 @@ def main(argv: list[str] | None = None) -> int:
|
||||
parser.error("--mode is required with --query")
|
||||
if args.mode is not None and args.query is None:
|
||||
parser.error("--mode only applies to --query")
|
||||
if args.show_map:
|
||||
print(render_concept_map(build_concept_map(args.references_dir)), end="")
|
||||
return 0
|
||||
if args.check:
|
||||
problems = validate_references(args.references_dir)
|
||||
for problem in problems:
|
||||
|
||||
Reference in New Issue
Block a user