✨ 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>` 单独取回。
|
以 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 外形。其他事实交给对应所有者:
|
本 Skill 只拥有 TSL/TSF 的语言语法、文件模型、表达式、控制流、对象、运行时语言结构和 TS-SQL 外形。其他事实交给对应所有者:
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ ALLOWED_IDENTITIES = {
|
|||||||
}
|
}
|
||||||
ROUTER_PHRASES = ("路由中心", "选择一个主专题", "候选页继续判断")
|
ROUTER_PHRASES = ("路由中心", "选择一个主专题", "候选页继续判断")
|
||||||
EXCLUDED_REFERENCE_FILES = {"index.md"}
|
EXCLUDED_REFERENCE_FILES = {"index.md"}
|
||||||
|
DUTY_HEADING = "本篇职责"
|
||||||
SUSPICIOUS_FENCE_RE = re.compile(r"^(?:\s+`{3}|`{4,})")
|
SUSPICIOUS_FENCE_RE = re.compile(r"^(?:\s+`{3}|`{4,})")
|
||||||
WRITE_PRELUDE_ANCHORS = (
|
WRITE_PRELUDE_ANCHORS = (
|
||||||
("02_core_model.md", "文件模型核心规则"),
|
("02_core_model.md", "文件模型核心规则"),
|
||||||
@@ -351,6 +352,20 @@ def validate_references(
|
|||||||
f"write 模式前置章节缺失:{page_name} 的「{heading}」",
|
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
|
return problems
|
||||||
|
|
||||||
|
|
||||||
@@ -583,6 +598,69 @@ def render_result(result: QueryResult) -> str:
|
|||||||
return "\n".join(lines).rstrip() + "\n"
|
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:
|
def _configure_utf8() -> None:
|
||||||
for stream in (sys.stdout, sys.stderr):
|
for stream in (sys.stdout, sys.stderr):
|
||||||
reconfigure = getattr(stream, "reconfigure", None)
|
reconfigure = getattr(stream, "reconfigure", None)
|
||||||
@@ -595,6 +673,7 @@ def _parser() -> argparse.ArgumentParser:
|
|||||||
action = parser.add_mutually_exclusive_group(required=True)
|
action = parser.add_mutually_exclusive_group(required=True)
|
||||||
action.add_argument("--query")
|
action.add_argument("--query")
|
||||||
action.add_argument("--section")
|
action.add_argument("--section")
|
||||||
|
action.add_argument("--map", dest="show_map", action="store_true")
|
||||||
action.add_argument("--check", action="store_true")
|
action.add_argument("--check", action="store_true")
|
||||||
parser.add_argument("--mode", choices=("write", "diagnose", "explain"))
|
parser.add_argument("--mode", choices=("write", "diagnose", "explain"))
|
||||||
parser.add_argument("--limit", type=int, default=5)
|
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")
|
parser.error("--mode is required with --query")
|
||||||
if args.mode is not None and args.query is None:
|
if args.mode is not None and args.query is None:
|
||||||
parser.error("--mode only applies to --query")
|
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:
|
if args.check:
|
||||||
problems = validate_references(args.references_dir)
|
problems = validate_references(args.references_dir)
|
||||||
for problem in problems:
|
for problem in problems:
|
||||||
|
|||||||
Reference in New Issue
Block a user