diff --git a/scripts/build_tsl_playbook.py b/scripts/build_tsl_playbook.py index 16bb8f4e..5f175e94 100644 --- a/scripts/build_tsl_playbook.py +++ b/scripts/build_tsl_playbook.py @@ -10,7 +10,7 @@ DEFAULT_OUTPUT = Path("tmp") / "tsl-playbook" def copy_tree(src: Path, dst: Path) -> None: - shutil.copytree(src, dst) + shutil.copytree(src, dst, ignore=shutil.ignore_patterns("__pycache__", "*.pyc")) def build_agents_text(ruleset_path: Path) -> str: diff --git a/scripts/playbook.py b/scripts/playbook.py index a4f6f884..0eee41ea 100644 --- a/scripts/playbook.py +++ b/scripts/playbook.py @@ -3,7 +3,7 @@ import re import sys from datetime import datetime, timezone from pathlib import Path -from shutil import copy2, copytree, rmtree, which +from shutil import copy2, copytree, ignore_patterns, rmtree, which import subprocess import importlib.util from typing import Optional @@ -543,8 +543,16 @@ def install_snapshot(config: dict, context: dict) -> int: if gitattributes_src.is_file(): copy2(gitattributes_src, dest_prefix / ".gitattributes") - copytree(PLAYBOOK_ROOT / "scripts", dest_prefix / "scripts") - copytree(PLAYBOOK_ROOT / "skills", dest_prefix / "skills") + copytree( + PLAYBOOK_ROOT / "scripts", + dest_prefix / "scripts", + ignore=ignore_patterns("__pycache__", "*.pyc"), + ) + copytree( + PLAYBOOK_ROOT / "skills", + dest_prefix / "skills", + ignore=ignore_patterns("__pycache__", "*.pyc"), + ) copy2(PLAYBOOK_ROOT / "SKILLS.md", dest_prefix / "SKILLS.md") common_docs = PLAYBOOK_ROOT / "docs/common" @@ -1420,7 +1428,7 @@ def install_skills_action(config: dict, context: dict) -> int: backup = skills_dst_root / f"{name}.bak.{timestamp}" dst.rename(backup) log(f"Backed up existing skill: {name} -> {backup.name}") - copytree(src, dst) + copytree(src, dst, ignore=ignore_patterns("__pycache__", "*.pyc")) rewrite_skill_docs_links(dst, resolve_docs_prefix(context)) tag = " [thirdparty]" if origin == "thirdparty" else "" log(f"Installed: {name}{tag}") diff --git a/skills/tsl-syntax-reference/SKILL.md b/skills/tsl-syntax-reference/SKILL.md index 1c416a55..29050cbb 100644 --- a/skills/tsl-syntax-reference/SKILL.md +++ b/skills/tsl-syntax-reference/SKILL.md @@ -1,15 +1,15 @@ --- name: tsl-syntax-reference -description: Use when Codex 需要编写、修改、审查或解释 TSL/TSF 代码,或排查语法错误并核对语言与运行时规则。 +description: 当需要编写、修改、审查或解释 TSL/TSF 代码,或排查语法错误并核对语言与运行时规则时使用。 --- # TSL Syntax Reference ## 唯一检索入口 -本 Skill 只通过随附的 `scripts/lookup.py` 检索权威 TSL/TSF 语法事实,不手工选择或顺序通读 `references/` 页面。根据任务意图选择模式,并用用户的原始术语、错误文本和关键语法词组成查询: +本 Skill 只通过随附的 `scripts/lookup.py` 检索权威 TSL/TSF 语法事实,不手工选择或顺序通读 `references/` 页面。`` 指本 `SKILL.md` 所在目录。根据任务意图选择模式,并用用户的原始术语、错误文本和关键语法词组成查询: -```powershell +```bash python /scripts/lookup.py --query "命名参数 默认参数" --mode write python /scripts/lookup.py --query "invalid statement 声明区" --mode diagnose python /scripts/lookup.py --query "数组下标" --mode explain @@ -19,7 +19,7 @@ python /scripts/lookup.py --query "数组下标" --mode explain - `diagnose`:定位语法错误或错误写法。 - `explain`:解释语言规则或代码含义。 -以 lookup 返回的章节作为本次任务的事实来源;需要补充时改进查询词并再次检索,不得绕过 lookup 手工路由。 +以 lookup 返回的章节作为本次任务的事实来源;需要补充时改进查询词并再次检索,不得绕过 lookup 手工路由。需要复取输出中列出的某个章节时,用 `--section
` 单独取回。 ## 事实边界 @@ -53,6 +53,6 @@ lookup 无匹配或返回非零状态、结果没有支持当前结论、代码 维护本 Skill、参考页或 lookup 实现后必须运行: -```powershell +```bash python /scripts/lookup.py --check ``` diff --git a/skills/tsl-syntax-reference/references/01_quickstart.md b/skills/tsl-syntax-reference/references/01_quickstart.md index a08787d5..700a4d8d 100644 --- a/skills/tsl-syntax-reference/references/01_quickstart.md +++ b/skills/tsl-syntax-reference/references/01_quickstart.md @@ -1,10 +1,5 @@ # TSL 快速落代码规则 -文档类型:语法主线 -是否可直接用于生成代码:是 -是否含可直接照写示例:是 -是否含不可照写反例:是 - 本篇只回答两个落代码前问题:用户已给出 `.tsl` / `.tsf` 后缀时,智能体如何按后缀组织代码;以及生成代码前必须先核对哪些语言硬规则。 ## 本篇职责 @@ -44,7 +39,7 @@ - “顶层函数骨架”“顶层函数定义体”:只指 `function` 骨架;不要因为任务没有返回值就自动改成 `procedure`。 - `class function` / “类方法”:同一件事;前者是代码关键字写法,后者是中文描述。 -## 起手骨架选择规则 +## 任务到文件模型的选择规则 | 任务 | 起手骨架 | | ------------------------------------ | ------------------------------ | diff --git a/skills/tsl-syntax-reference/references/02_core_model.md b/skills/tsl-syntax-reference/references/02_core_model.md index 2202b448..63e5c353 100644 --- a/skills/tsl-syntax-reference/references/02_core_model.md +++ b/skills/tsl-syntax-reference/references/02_core_model.md @@ -1,10 +1,5 @@ # TSL 文件模型规则 -文档类型:语法主线 -是否可直接用于生成代码:是 -是否含可直接照写示例:是 -是否含不可照写反例:是 - 本篇说明 TSL 的文件模型判断规则:智能体如何区分 `.tsl` 可执行脚本与 `.tsf` 可复用声明文件,如何识别脚本语句区和声明区,以及为什么很多错误其实是“文件模型选错了”。 ## 本篇职责 diff --git a/skills/tsl-syntax-reference/references/03_values_and_literals.md b/skills/tsl-syntax-reference/references/03_values_and_literals.md index 3736d712..2f4756ee 100644 --- a/skills/tsl-syntax-reference/references/03_values_and_literals.md +++ b/skills/tsl-syntax-reference/references/03_values_and_literals.md @@ -1,10 +1,5 @@ # TSL 值与字面量 -文档类型:语法主线 -是否可直接用于生成代码:是 -是否含可直接照写示例:是 -是否含不可照写反例:是 - 这一篇整理基本类型、字面量、数组、字符串、字符串编码边界与基础值模型,避免把值规则分散在函数或金融示例里。 ## 本篇职责 @@ -470,8 +465,7 @@ writeLn(result); 说明: -- `%d` 是整数占位符 -- `%s` 是字符串占位符 +- 示例里的 `%d` / `%s` 分别对应整数、字符串占位;只为读懂本例 - `format` 的完整占位符规范使用 `tsl-api-reference` skill 按名查询。 ### `formatdatetime` 日期格式化 @@ -494,9 +488,7 @@ writeLn(result); 说明: -- `yyyy` 表示四位年份 -- `mm` 表示两位月份 -- `dd` 表示两位日期 +- 示例里的 `yyyy-mm-dd` 对应四位年、两位月、两位日;只为读懂本例 - `formatdatetime` 的完整格式说明使用 `tsl-api-reference` skill 按名查询。 ## 默认生成模板 diff --git a/skills/tsl-syntax-reference/references/04_variables_and_constants.md b/skills/tsl-syntax-reference/references/04_variables_and_constants.md index 580a7c73..87e78d3e 100644 --- a/skills/tsl-syntax-reference/references/04_variables_and_constants.md +++ b/skills/tsl-syntax-reference/references/04_variables_and_constants.md @@ -1,10 +1,5 @@ # TSL 变量与常量 -文档类型:语法主线 -是否可直接用于生成代码:是 -是否含可直接照写示例:是 -是否含不可照写反例:是 - 这一篇收拢变量与常量初始化规则。 ## 本篇职责 diff --git a/skills/tsl-syntax-reference/references/05_functions_and_calls.md b/skills/tsl-syntax-reference/references/05_functions_and_calls.md index eed820aa..3c365616 100644 --- a/skills/tsl-syntax-reference/references/05_functions_and_calls.md +++ b/skills/tsl-syntax-reference/references/05_functions_and_calls.md @@ -1,10 +1,5 @@ # TSL 函数与调用 -文档类型:语法主线 -是否可直接用于生成代码:是 -是否含可直接照写示例:是 -是否含不可照写反例:是 - 这一篇只负责 `function` / `procedure` 的定义、调用、参数传递和值返回。跨 `unit` 声明边界、外部系统交互和业务函数库只在本页保留最小边界。 ## 本篇职责 @@ -27,13 +22,13 @@ ## 核心规则 - 最稳妥的函数骨架仍然是 `function Name(...); begin ... end;`。 -- 用户提示词里的”函数”默认对应 `function`,不要自动改写成 `procedure`。 +- 用户提示词里的“函数”默认对应 `function`,不要自动改写成 `procedure`。 - `procedure Name(...); begin ... end;` 只在用户明确要求 `procedure` / 过程时生成;不要因为没有返回值就自动改用 `procedure`。 - 在 `.tsl` 文件模型层,脚本语句后可以接函数声明;语句区在前顺序执行,声明区在后提供函数/过程定义。见 [02_core_model.md](02_core_model.md)。 - 在 `.tsf` 文件模型层,顶层 `function` / `procedure` 是模块/函数扩展声明;部署到解释器 `funcext` 后可被脚本直接调用。 - 函数头后默认保留分号;不要为了简写主动省略。 - 函数体内部的普通语句照常用分号结尾;顶层函数/过程声明的 `end` 后必须加分号(写成 `end;`)。 -- 函数体内的控制流块(`if`/`while`/`for` 等)的 `end` 不加分号;控制流分号规则见 [07_control_flow.md](07_control_flow.md)。 +- 函数体内的控制流块(`if`/`while`/`for` 等)的 `end` 后加不加分号都合法;唯一例外是 `if` 的 `then` 块 `end` 与 `else` 之间不能加分号。控制流分号规则见 [07_control_flow.md](07_control_flow.md)。 - 一个函数定义体里可以同时出现主函数和子函数。 - 函数支持参数类型注解和返回值类型注解。 - 不带类型注解时,多个参数用逗号分隔。 @@ -154,7 +149,7 @@ end; 2 ``` -### 签名增强 +### 签名增强:参数类型与返回值类型注解 带参数类型和返回值类型: @@ -356,7 +351,7 @@ end; 99 ``` -### 调用增强 +### 调用增强:命名参数与混用规则 命名参数调用: @@ -977,13 +972,12 @@ writeLn(intToStr(value: 200)); 代码块身份:反例 / 不可照写 ```text +writeLn(Pack(a: 1, 2, c: 3)); + function Pack(a, b, c); begin return a * 100 + b * 10 + c; end; -begin - writeLn(Pack(a: 1, 2, c: 3)); -end. ``` 不要在同一次调用里“先进入命名参数模式,再退回位置参数”。这类写法会直接编译失败,错误信息包含 `paramname: not found`。 diff --git a/skills/tsl-syntax-reference/references/06_expressions_and_operators.md b/skills/tsl-syntax-reference/references/06_expressions_and_operators.md index 600668cf..923cec50 100644 --- a/skills/tsl-syntax-reference/references/06_expressions_and_operators.md +++ b/skills/tsl-syntax-reference/references/06_expressions_and_operators.md @@ -1,10 +1,5 @@ # TSL 表达式与运算符 -文档类型:语法主线 -是否可直接用于生成代码:是 -是否含可直接照写示例:是 -是否含不可照写反例:是 - 这一篇集中放语言级表达式与运算符。智能体写代码时,只使用本页和对应专题页已经明确的表达式事实,不要把其他语言或外部资料里的运算符习惯直接搬进 TSL。 ## 本篇职责 diff --git a/skills/tsl-syntax-reference/references/07_control_flow.md b/skills/tsl-syntax-reference/references/07_control_flow.md index 6aab72e9..f55b6972 100644 --- a/skills/tsl-syntax-reference/references/07_control_flow.md +++ b/skills/tsl-syntax-reference/references/07_control_flow.md @@ -1,10 +1,5 @@ # TSL 控制流 -文档类型:语法主线 -是否可直接用于生成代码:是 -是否含可直接照写示例:是 -是否含不可照写反例:是 - 这一篇只收录流程控制与异常控制,不讨论金融语义。 ## 本篇职责 @@ -468,7 +463,6 @@ end - 在 `else` 前面误加分号。 - 生成没有分号的裸分支赋值,例如 `then value := 1 else ...`;带 `else` 时用块式分支。 - 以为 `try ... finally` 会吞掉异常。 -- 把 `case` 写成赋值右侧表达式。 - 在还没搞清表达式规则前,先把复杂业务函数塞进条件里。 - 把控制流问题和函数文件模型问题混在一起排查。 diff --git a/skills/tsl-syntax-reference/references/08_objects_and_classes.md b/skills/tsl-syntax-reference/references/08_objects_and_classes.md index 61daea07..6f4422dd 100644 --- a/skills/tsl-syntax-reference/references/08_objects_and_classes.md +++ b/skills/tsl-syntax-reference/references/08_objects_and_classes.md @@ -1,17 +1,12 @@ # TSL 对象与类 -文档类型:语法主线 -是否可直接用于生成代码:仅部分 -是否含可直接照写示例:是 -是否含不可照写反例:是 - 这一篇收拢面向对象语法,只保留语言层规则。 ## 本篇职责 回答“`type Name = class`、字段、`static`、方法、`property`、析构、类类型、继承和对象创建在 TSL 里怎样写”。 -本页后半段有少量依赖 `unit` 的双文件示例;如果你还没建立 `unit` / `uses` 的多文件心智模型,先看 [09_units_and_scope.md](09_units_and_scope.md)。 +本页后半段有少量依赖 `unit` 的双文件示例;`unit` / `uses` 的多文件模型事实见 [09_units_and_scope.md](09_units_and_scope.md)。 ## 智能体对象/类判断流程 @@ -43,7 +38,7 @@ - 基础 `property` 形态:`property Name read fieldOrMethod write fieldOrMethod`。 - `property Name: Type ...` 这种类型注解写法可用。 - 参数化 `property` 可用;本页只写基础模式。 -- 参数化 `property` 的 accessor 方法记录两种常见模式:读方法接同参数个数,写方法接“参数个数 + 赋值值”。 +- 参数化 `property` 的 accessor 方法记录两种常见模式:读方法可接同参数个数(如 `getItem(i)`),也可以写成无参形态(如 `getDateV()`);写方法接“参数个数 + 赋值值”。 - 索引型 `property` 可用;调用时用圆括号 `obj.Prop(index)`。 - 固定 `index` property 可用;它可以把某个固定索引直接映射成普通属性读写。 - `class(Name)` 和 `findClass("Name")` 都可以拿到类类型。 @@ -57,7 +52,7 @@ - 创建对象有两种方式:`new ClassName()` 最常用,`createObject(...)` 作为次选。 - 普通本地类实例化默认生成 `new ClassName()`;`createObject("ClassName")`、`createObject(ClassType)` 只在字符串类名、类类型变量或跨 `unit` 路径场景生成。 - 如果类里定义了 `function create(...)`,`new`、`createObject("ClassName", ...)` 和 `createObject(ClassType, ...)` 都可以透传构造参数,也都支持默认参数和命名参数。 -- 析构写法是无参 `function destroy();`;把对象引用设为 `nil` 时会触发它。 +- 析构写法是无参 `function destroy();`;对象的最后一个引用被清空(如设为 `nil`)时会触发它。存在别名引用时,只清空其中一个引用不会触发。 - 工厂式 `self(0)` / `self(1)` 可用;不要生成 `self()` 这种无参工厂式写法。 - 跨 `unit` 类路径创建和继承属于多文件边界;相关事实见 [09_units_and_scope.md](09_units_and_scope.md) 和 [19_namespace_libpath_and_unit_runtime.md](19_namespace_libpath_and_unit_runtime.md),不要从普通单文件 `new` / `createObject` 规则直接泛化。 - `{$ifdef parentClassInUnit}` 为真,可用于探测“继承和构造单元中的类”能力是否可用。 @@ -750,8 +745,8 @@ end; 输出说明: -- `read getItem` 这种”读方法接同参数个数”的写法可以通过 -- `write setItem` 这种”写方法接参数个数 + 赋值值”的写法可以通过 +- `read getItem` 这种“读方法接同参数个数”的写法可以通过 +- `write setItem` 这种“写方法接参数个数 + 赋值值”的写法可以通过 - 上述例子中的 `obj.Item(2)` 输出 `x` ### 对象创建与类类型 @@ -904,7 +899,7 @@ end; - `findClass("MathBox").Add(...)` 可以调用类方法 - 上述例子依次输出 `7`、`11` -### 类方法、重载、继承与析构 +### 重载、继承与析构 `overload` 方法: diff --git a/skills/tsl-syntax-reference/references/09_units_and_scope.md b/skills/tsl-syntax-reference/references/09_units_and_scope.md index e22394f3..d21e3cbd 100644 --- a/skills/tsl-syntax-reference/references/09_units_and_scope.md +++ b/skills/tsl-syntax-reference/references/09_units_and_scope.md @@ -1,10 +1,5 @@ # TSL unit 与作用域 -文档类型:语法主线 -是否可直接用于生成代码:仅部分 -是否含可直接照写示例:是 -是否含不可照写反例:是 - 这一篇只讲“怎么写和怎么调用 `unit`”。`namespace`、查找路径、`unit` 生命周期、`tsl.conf` 与运行时入口统一放到 [19_namespace_libpath_and_unit_runtime.md](19_namespace_libpath_and_unit_runtime.md)。 ## 本篇职责 @@ -43,9 +38,9 @@ - 只写在 `implementation` 里的函数,对 `unit` 外部不可见。 - 出现重名接口时,未限定调用按 `uses` 从后往前解析;最后一个 `uses` 的同名接口优先。 - 要显式指定某个 `unit` 的接口,可以用 `UnitA.Ping()`、`unit(UnitA).Ping()` 或 `call("UnitA.Ping")`。 -- `unit` 生命周期、接口状态、命名空间和查找路径细节,转去看 [19_namespace_libpath_and_unit_runtime.md](19_namespace_libpath_and_unit_runtime.md)。 +- `unit` 生命周期、接口状态、命名空间和查找路径细节见 [19_namespace_libpath_and_unit_runtime.md](19_namespace_libpath_and_unit_runtime.md)。 -## 可直接照写示例 +## 示例与行为 ### 最小 `unit` @@ -495,8 +490,6 @@ end; end. ``` -后面的多文件片段只在需要 `uses`、可见性或重名解析时再回来看。 - ## 禁止项 - 把依赖外部 `unit` 的 `uses` 例子误当成单文件最小示例。 diff --git a/skills/tsl-syntax-reference/references/10_runtime_context_and_with.md b/skills/tsl-syntax-reference/references/10_runtime_context_and_with.md index 9aff3302..60a2153e 100644 --- a/skills/tsl-syntax-reference/references/10_runtime_context_and_with.md +++ b/skills/tsl-syntax-reference/references/10_runtime_context_and_with.md @@ -1,10 +1,5 @@ # TSL 运行时上下文、服务与全局缓存 -文档类型:语法主线 -是否可直接用于生成代码:仅部分 -是否含可直接照写示例:是 -是否含不可照写反例:是 - 这一篇只处理运行时环境参数、块环境 `with` 语句、`with` 后缀、`#` 网格调用、`timeout` 后缀、`dupvalue(...)` 和全局缓存,不处理任何金融业务语义。 ## 本篇职责 @@ -48,6 +43,8 @@ ## 可直接照写示例 +### `setSysParam` / `getSysParam` / `sysParams[...]` 基础读写 + 直接设置和读取系统参数: 代码块身份:可直接照写示例 @@ -163,7 +160,9 @@ writeLn(getSysParam("b")); - 块后输出 `1`、`9` - 说明 `with **` 不继承未传入的外层系统参数,并且块结束后恢复外层系统参数 -沿用同一个 `TestDo.tsf`,看 `with array(...)` 的覆盖边界: +### 后缀 `with array(...)`:网格调用的临时覆盖 + +`#Func(...) with array(...)` 在一次网格调用里临时覆盖系统参数,调用结束后恢复外层原值。下面用一个 `TestDo.tsf` 演示覆盖边界: 代码块身份:配置片段 / 概念骨架 代码块说明:多文件结构骨架;依赖函数文件查找路径,不是可直接复制的单文件最小示例。 @@ -193,7 +192,7 @@ writeLn(getSysParam("b")); - 调用结束后,外层 `getSysParam("b")` 输出 `8` - 说明 `with array(...)` 是“只在该次调用里临时覆盖,再恢复外部原值” -沿用上一个 `TestDo.tsf`,只把 `main.tsl` 改成下面这样: +沿用上面的 `TestDo.tsf`,只把 `main.tsl` 改成下面这样(外层未预设参数): 代码块身份:配置片段 / 概念骨架 代码块说明:多文件结构骨架;依赖函数文件查找路径,不是可直接复制的单文件最小示例。 @@ -231,7 +230,7 @@ end; 结果说明: - `#AddOne(5)` 可以执行 -- `dupvalue(r)` 返回最终结果 `6` +- `dupvalue(#AddOne(5))` 返回最终结果 `6` 代码块身份:输出片段 @@ -303,9 +302,7 @@ writeLn(cached_data[0], ',', cached_data[1], ',', cached_data[2]); 说明: - TSL 标识符大小写无关,`setGlobalCache` 和 `SetGlobalCache` 等价 -- 推荐使用小写 `setGlobalCache`、`getGlobalCache` 以保持一致性 -- `setGlobalCache(key, value)` 写入缓存,可选第三参数指定过期时间(秒) -- `getGlobalCache(key, out_var)` 读取缓存到输出变量 +- 示例只展示 `setGlobalCache(key, value)` / `getGlobalCache(key, out_var)` 的最小调用形态;完整参数规格(含过期时间等可选参数)用 `tsl-api-reference` skill 按名查询 ### `checkGlobalCacheExpired` @@ -324,7 +321,7 @@ writeLn(checkGlobalCacheExpired(cache_ref)); - 刚取出的缓存引用上,`checkGlobalCacheExpired(cache_ref)` 返回 `0` - 同名缓存被重新设置后,旧引用上的 `checkGlobalCacheExpired(cache_ref)` 返回 `1` -### 写入后会实例化 +### 缓存值本地写入后会实例化 代码块身份:可直接照写示例 @@ -398,7 +395,7 @@ begin end; ``` -需要缓存时,从 `setGlobalCache` / `getGlobalCache` 那一节选择模板。 +需要缓存时,默认模板是 `setGlobalCache(key, value)` 写入、`getGlobalCache(key, out_var)` 读取。 ## 禁止项 diff --git a/skills/tsl-syntax-reference/references/11_pitfalls.md b/skills/tsl-syntax-reference/references/11_pitfalls.md index d38c817c..f76aebc8 100644 --- a/skills/tsl-syntax-reference/references/11_pitfalls.md +++ b/skills/tsl-syntax-reference/references/11_pitfalls.md @@ -1,10 +1,5 @@ # TSL 高频误写 -文档类型:反例索引页 -是否可直接用于生成代码:否 -是否含可直接照写示例:否 -是否含不可照写反例:是 - 这一篇不讲新知识,只做反例索引。 ## 本篇职责 diff --git a/skills/tsl-syntax-reference/references/12_matrix_and_collections.md b/skills/tsl-syntax-reference/references/12_matrix_and_collections.md index 9b22c9dd..a00a4de4 100644 --- a/skills/tsl-syntax-reference/references/12_matrix_and_collections.md +++ b/skills/tsl-syntax-reference/references/12_matrix_and_collections.md @@ -1,10 +1,5 @@ # TSL 数组、键表、矩阵样数据与集合运算 -文档类型:语法主线 -是否可直接用于生成代码:是 -是否含可直接照写示例:是 -是否含不可照写反例:否 - 这一篇收拢 `array(...)` 的扩展用法:顺序数组、字符串键表、嵌套数组、本页明确的矩阵样比较,以及 `in` / `sqlin` / `union2` / `intersect` / `minus` / `outersect` 这类去重型集合关系。 ## 本篇职责 @@ -29,7 +24,7 @@ - `in` / `not in` 处理的是元素存在关系,以及左侧为数组时的子集关系。 - `sqlin` / `not sqlin` 处理的是行存在关系;左侧要当成一整行去匹配右侧结果集。 - `union2`、`intersect`、`minus`、`outersect` 都按“行”运算,而不是按单元格逐个运算。 -- 集合运算结果会折叠重复行;如果需求是保留重复记录,改看 [13_resultset_and_filters.md](13_resultset_and_filters.md)。 +- 集合运算结果会折叠重复行;保留重复记录的过滤规则见 [13_resultset_and_filters.md](13_resultset_and_filters.md)。 - 当数据本身就是一维数组时,按行集合运算和按元素集合运算是一致的。 - 左侧是数组时,先判定需求语义:子集关系用 `in`,整行存在关系用 `sqlin`。 @@ -137,6 +132,9 @@ writeLn(1 not in array(0, 2)); 0 1 1 +1 +0 +1 ``` `sqlin` 按整行判断左侧是否存在于右侧结果集中: @@ -198,7 +196,7 @@ outersect_rows := left_rows outersect right_rows; - 集合运算先把数据当成“行集合”来看,再做包含、并交差。 - 过滤运算先保留“原结果集里的每一条命中记录”;因此重复行会保留下来。 - 需要“集合关系”时留在本页。 -- 需要“从原表里筛出哪些行”时看 [13_resultset_and_filters.md](13_resultset_and_filters.md)。 +- “从原表里筛出哪些行”的过滤规则见 [13_resultset_and_filters.md](13_resultset_and_filters.md)。 ## 默认生成模板 @@ -211,7 +209,7 @@ matched := 1 in array(1, 2, 3); row_matched := array(1, 2) sqlin array((1, 2), (3, 4)); ``` -### 点前缀比较算符产生逻辑数组 +## 点前缀比较算符产生逻辑数组 点前缀比较(`.=`、`.<>`、`.>`、`.>=`、`.<`、`.<=`)对数组/矩阵逐元素比较,返回真假值数组: @@ -274,7 +272,7 @@ writeLn("子集 (1,0):", subset[1][0]); - `a[indexes]` 按下标提取对应行,等价于 `select * from a where [0] > 0.5 end` - `mfind` 的完整用法见 [22_matrix_deep_dive.md](22_matrix_deep_dive.md) -### 非完全矩阵与缺位当 0 处理 +## 非完全矩阵缺位当 0 处理与标量广播 基础算符作用于非完全矩阵(行长度不一致或字符串键不对齐的数组)时,对应位置不存在或为 `nil` 时**默认当 0 处理**: @@ -341,15 +339,15 @@ writeLn("(1,1):", result[1][1]); ## 禁止项 -- 不要把字符串下标按数组的 `0` 起始规则来写;字符串索引事实见 [03_values_and_literals.md](03_values_and_literals.md)。 +- 不要把字符串下标按数组的 `0` 起始规则来写;字符串下标从 `1` 开始,详见 [03_values_and_literals.md](03_values_and_literals.md)。 - 不要把矩阵链式比较 `::...` 和标量链式比较混写成同一种语法。 - 不要把 `in` 和 `sqlin` 当成同一个概念。 - 不要期待 `union2` 保留重复行。 -- 不要用集合运算去做”保留原始重复记录”的过滤任务。 -- 不要把二维结果集默认当成”按元素逐个比较”的集合运算。 -- 左侧数组要表达”这些值是否都属于右侧集合”时,用 `in`。 -- 左侧数组要表达”这一整行是否存在于右侧结果集”时,用 `sqlin`。 -- `minus` 表达集合差集;如果任务要求保留左侧原始重复次数,改走 [13_resultset_and_filters.md](13_resultset_and_filters.md) 的过滤规则。 +- 不要用集合运算去做“保留原始重复记录”的过滤任务。 +- 不要把二维结果集默认当成“按元素逐个比较”的集合运算。 +- 左侧数组要表达“这些值是否都属于右侧集合”时,用 `in`。 +- 左侧数组要表达“这一整行是否存在于右侧结果集”时,用 `sqlin`。 +- `minus` 表达集合差集;保留左侧原始重复次数的过滤规则见 [13_resultset_and_filters.md](13_resultset_and_filters.md)。 - 不要在本页发明结果集过滤、TS-SQL 查询、写回语法或更大矩阵函数族。 - 不要把普通 `array(...)` 自动升级成 `FMArray`;`FMArray` 专属事实见 [23_fmarray.md](23_fmarray.md)。 - 不要把点前缀比较 `.>` 和矩阵链式比较 `::>` 混用;`.>` 返回逻辑数组,`::>` 是链式比较。 diff --git a/skills/tsl-syntax-reference/references/13_resultset_and_filters.md b/skills/tsl-syntax-reference/references/13_resultset_and_filters.md index 56261d47..6f8ee1cf 100644 --- a/skills/tsl-syntax-reference/references/13_resultset_and_filters.md +++ b/skills/tsl-syntax-reference/references/13_resultset_and_filters.md @@ -1,10 +1,5 @@ # TSL 结果集与过滤 -文档类型:语法主线 -是否可直接用于生成代码:是 -是否含可直接照写示例:是 -是否含不可照写反例:是 - 这一篇只讲结果集过滤语法:怎样按某一列过滤、怎样按整行过滤、怎样只返回行下标,以及怎样按字段列表过滤。它不负责 TS-SQL 查询语法,也不把去重型集合运算和业务选股逻辑混进来。 ## 本篇职责 @@ -168,8 +163,8 @@ sub_result := rows[keep_index, array("A", "C")]; - 过滤运算按“原结果集中的每一行是否命中”来决定保留或排除。 - 集合运算按“行集合”来做并、交、差;结果默认会折叠重复行。 -- 需要保留重复记录时,优先看这一篇。 -- 需要去重后的并集、交集、差集时,优先看 [12_matrix_and_collections.md](12_matrix_and_collections.md)。 +- 保留重复记录的过滤规则由本页负责。 +- 去重后的并集、交集、差集事实见 [12_matrix_and_collections.md](12_matrix_and_collections.md)。 ## 默认生成模板 diff --git a/skills/tsl-syntax-reference/references/14_ts_sql.md b/skills/tsl-syntax-reference/references/14_ts_sql.md index b57e31a4..02f31661 100644 --- a/skills/tsl-syntax-reference/references/14_ts_sql.md +++ b/skills/tsl-syntax-reference/references/14_ts_sql.md @@ -1,15 +1,10 @@ # TSL TS-SQL -文档类型:语法主线 -是否可直接用于生成代码:是 -是否含可直接照写示例:是 -是否含不可照写反例:是 - 这一篇是 TS-SQL 的唯一语法入口:内存数组查询、返回形态、字段访问、`where` / `group by` / `order by`、一维数组查询、多表 `join`(含 `left join`)、`insert` / `update` / `delete` 写回、`thisGroup`、`thisRowIndex`、`refMaxOf` / `refMinOf` 都在这里收拢。 ## 本篇职责 -回答”写 TS-SQL 查询和写回时,怎样从最小 `select ... from ... end` 骨架开始,逐步处理筛选、分组、排序、多表联接(含 LEFT JOIN)、组内子查询、极值引用,以及如何用 `insert`/`update`/`delete` 修改内存数组”。 +回答“写 TS-SQL 查询和写回时,怎样从最小 `select ... from ... end` 骨架开始,逐步处理筛选、分组、排序、多表联接(含 LEFT JOIN)、组内子查询、极值引用,以及如何用 `insert`/`update`/`delete` 修改内存数组”。 ## 智能体 TS-SQL 判断流程 @@ -27,7 +22,7 @@ - TS-SQL 是 TSL 自带的类 SQL 查询语法,不是金融业务函数库。 - 基础查询文档骨架是:以 `select` / `sselect` / `vselect` / `mselect` 开始,以 `end` 收尾。 - `from` 后面可以直接跟内存数组结果集。 -- 在内存二维结果集上,文档字段访问写法是 `["字段名"]`。 +- 在内存二维结果集上,文档字段访问写法是 `["字段名"]`;列没有名字(如直接来自数组)时用位置下标 `[0]`、`[1]` 访问。 - 在一维数组上做 TS-SQL 时,优先使用 `thisRow` 和 `thisRowIndex`。 - `select` 返回二维结果,`sselect` 返回一维结果,`vselect` 返回单值,`mselect` 返回 `Matrix`。 - `where`、`group by`、`order by` 可以直接接在 `from` 后面继续使用;`order by` 支持 `asc`/`desc` 与多列逗号分隔。 @@ -314,7 +309,7 @@ writeLn("(1,1):", result[1]["vb"]); - `on` 子句指定联接条件,用 `[1].["k"] = [2].["k"]` 匹配键 - 其他联接类型:`right join`(保留右表)、`full join`(保留双方)、`cross join`(笛卡尔积) -### JOIN 家族其余形态 +### `right join` / `full join` / `cross join` 与逗号联接 `right join` / `full join` 的不匹配行同样用 `nil` 填充: @@ -439,7 +434,7 @@ writeLn("(1,0):", a[1]["id"]); - `insert into 数组变量` 直接修改原数组 - `insertfields([字段列表])` 指定要插入的字段 -- `values(...)` 提供对应值,可以写多组 `values`、`values` 实现批量插入 +- `values(...)` 提供对应值,可以连续写多组 `values(...)` 实现批量插入 批量插入可以直接跟一个同结构数组,`insertfields` 也支持一次给多字段赋值: @@ -573,11 +568,13 @@ writeLn(mrows(a)); 代码块身份:可直接照写示例 ```tsl -a := array(("cls": "A", "en": 90), ("cls": "A", "en": 80), ("cls": "B", "en": 85)); +a := array(("cls": "A", "en": 90), ("cls": "A", "en": 80), ("cls": "A", "en": 90), ("cls": "B", "en": 85)); distinct_rows := select distinct ["cls"] from a end; distinct_sum := vselect sumof(distinct ["en"]) from a end; +plain_sum := vselect sumof(["en"]) from a end; writeLn(mrows(distinct_rows)); writeLn(distinct_sum); +writeLn(plain_sum); ``` 代码块身份:输出片段 @@ -585,12 +582,13 @@ writeLn(distinct_sum); ```text 2 255 +345 ``` 说明: - `select distinct [字段]` 折叠重复行 -- `sumof(distinct [字段])` 只对不同值求和:`90 + 80 + 85 = 255` +- `sumof(distinct [字段])` 只对不同值求和:`90 + 80 + 85 = 255`;不加 `distinct` 时重复的 `90` 计两次,得 `345` ### `as` 别名、`as nil` 与字段区间 @@ -805,10 +803,7 @@ integer ### `aggof` 自定义聚集扩展 -> 代码块身份:仅服务端可执行示例 -> 本地 `TSL.exe` 会报 `AggOf Init Error`;下例在服务端(pyTSL)验证通过。 - -`aggof('名称', 表达式)` 调用一个自定义回调函数做聚集: +`aggof('名称', 表达式)` 调用一个自定义回调函数做聚集。本地 `TSL.exe` 会报 `AggOf Init Error`;下例在服务端(pyTSL)验证通过: 代码块身份:仅服务端可执行示例 @@ -855,6 +850,7 @@ TS-SQL 的最短默认骨架如下: 代码块身份:可直接照写示例 ```tsl +source_rows := array((1, 10), (2, 20)); query_result := select * from source_rows end; ``` @@ -894,7 +890,7 @@ query_result := select A from source_rows end; query_result := select [0] from values end; ``` -这种对一维数组直接用 `[0]` 的写法虽然返回长度为 `3` 的结果,但取到的值是 `nil`,不能当成可靠入口。对一维数组应改用 `thisRow` 和 `thisRowIndex`。 +这种对一维数组直接用 `[0]` 的写法虽然能返回与源数组等长的结果,但取到的值是 `nil`,不能当成可靠入口。对一维数组应改用 `thisRow` 和 `thisRowIndex`。 代码块身份:反例 / 不可照写 diff --git a/skills/tsl-syntax-reference/references/15_debug_and_profiler.md b/skills/tsl-syntax-reference/references/15_debug_and_profiler.md index d82bbec3..520129ee 100644 --- a/skills/tsl-syntax-reference/references/15_debug_and_profiler.md +++ b/skills/tsl-syntax-reference/references/15_debug_and_profiler.md @@ -1,10 +1,5 @@ # TSL 调试与性能分析器 -文档类型:语法主线 -是否可直接用于生成代码:仅部分 -是否含可直接照写示例:是 -是否含不可照写反例:是 - 这一篇收拢本页明确的调试、计时、性能分析器和调用栈相关入口。 ## 本篇职责 @@ -116,7 +111,7 @@ end; 结果说明: - 只输出 `before` -- 说明 `debugReturn bb;` 不只是结束 `Inner(...)`,而是直接让整段脚本提前返回 +- 说明 `debugReturn value;` 不只是结束 `Inner(...)`,而是直接让整段脚本提前返回 - 因此 `Inner(3)` 后面的 `writeLn("after")` 不会执行 ### `debugRunEnv` 与 `debugRunEnvDo` @@ -215,8 +210,8 @@ writeLn(line_number); 结果说明: -- 输出 `3` -- 说明 `__line__` 直接返回所在代码行号 +- 输出 `1` +- 说明 `__line__` 直接返回它所在的代码行号(这里 `__line__` 在脚本第 1 行) `__stack_frame`: @@ -242,12 +237,12 @@ end; ```text array( -(11,"__main__"), -(8,"Outer")) +(1,"__main__"), +(10,"Outer")) ``` - 说明 `__stack_frame` 返回的是调用栈帧数组 -- 在这个最小例子里,可以直接看到调用位置行号和调用者函数名 +- 在这个最小例子里,可以直接看到调用位置行号和调用者函数名:`__main__` 在第 1 行调用 `Outer()`,`Outer` 在第 10 行调用 `Inner()` ## 禁止项 diff --git a/skills/tsl-syntax-reference/references/16_lexical_structure_and_compile_options.md b/skills/tsl-syntax-reference/references/16_lexical_structure_and_compile_options.md index f799f08e..dcc1c039 100644 --- a/skills/tsl-syntax-reference/references/16_lexical_structure_and_compile_options.md +++ b/skills/tsl-syntax-reference/references/16_lexical_structure_and_compile_options.md @@ -1,10 +1,5 @@ # TSL 词法结构与编译选项 -文档类型:语法深水专题 -是否可直接用于生成代码:是 -是否含可直接照写示例:是 -是否含不可照写反例:是 - 这一篇吸收语法手册里“词法层”和“编译期开关”相关内容:标识符、注释、条件编译和依赖编译选项。 ## 本篇职责 diff --git a/skills/tsl-syntax-reference/references/17_types_and_conversions.md b/skills/tsl-syntax-reference/references/17_types_and_conversions.md index d130b8ad..d584a856 100644 --- a/skills/tsl-syntax-reference/references/17_types_and_conversions.md +++ b/skills/tsl-syntax-reference/references/17_types_and_conversions.md @@ -1,10 +1,5 @@ # TSL 类型与转换 -文档类型:语法深水专题 -是否可直接用于生成代码:是 -是否含可直接照写示例:是 -是否含不可照写反例:是 - 这一篇吸收语言基础里“数据类型、初始值、弱类型行为、类型转换、复数类型”这一层。 ## 本篇职责 @@ -256,6 +251,7 @@ writeLn(z2); 1 4 3 +5-2j ``` 共轭、模与等值比较: @@ -269,7 +265,7 @@ writeLn(abs(z)); writeLn(real(c)); writeLn(imag(c)); if 3.15 = 3.15 + 0j then - writeLn(1); + writeLn(1) else writeLn(0); ``` diff --git a/skills/tsl-syntax-reference/references/18_external_calls_and_threads.md b/skills/tsl-syntax-reference/references/18_external_calls_and_threads.md index 11a2597c..b9187ef4 100644 --- a/skills/tsl-syntax-reference/references/18_external_calls_and_threads.md +++ b/skills/tsl-syntax-reference/references/18_external_calls_and_threads.md @@ -1,10 +1,5 @@ # TSL 外部调用与线程 -文档类型:语法深水专题 -是否可直接用于生成代码:仅部分 -是否含可直接照写示例:是 -是否含不可照写反例:是 - 这一篇吸收函数专题里和外部系统交互有关的部分:`external`、动态库调用、原生函数指针包装、C 回调和线程调用。 ## 本篇职责 @@ -59,6 +54,7 @@ function Tick64Alias(): int64; stdcall; external "kernel32.dll" name "GetTickCou Linux / POSIX 环境的同类最小骨架: 代码块身份:可直接照写示例 +代码块说明:仅类 Unix 环境可执行(依赖 `libc.so.6`);Windows 下不可照抄,本块只演示 `.so` 库名写法。 ```tsl writeLn(getpid() > 0); @@ -171,7 +167,7 @@ end; 结果说明: - 输出 `1` -- 说明类常量字符串可以用于 `external kernel_dll` 这种 DLL 名位置 +- 说明类常量字符串(如 `kKernelDll`)可以用于 `external kKernelDll` 这种 DLL 名位置 不作为可写事实边界: @@ -201,8 +197,8 @@ end; 结果说明: -- `fp <> nil` 输出 `1` -- `##f(3, 4)` 输出 `7` +- `func_ptr <> nil` 输出 `1` +- `##wrapped_func(3, 4)` 输出 `7` - 说明 `makeInstance(...)` 生成的函数指针可以再通过 `function(...); external fp;` 包装回 TSL 侧调用 ### 线程模式最小正例 diff --git a/skills/tsl-syntax-reference/references/19_namespace_libpath_and_unit_runtime.md b/skills/tsl-syntax-reference/references/19_namespace_libpath_and_unit_runtime.md index 69fb83d4..bc4b97ec 100644 --- a/skills/tsl-syntax-reference/references/19_namespace_libpath_and_unit_runtime.md +++ b/skills/tsl-syntax-reference/references/19_namespace_libpath_and_unit_runtime.md @@ -1,10 +1,5 @@ # TSL 命名空间、Libpath 与 unit 运行时 -文档类型:语法深水专题 -是否可直接用于生成代码:仅部分 -是否含可直接照写示例:是 -是否含不可照写反例:是 - 这一篇收拢 `unit` 的运行时行为,以及 `.tsf` / `namespace` / `libpath` 的查找规则。`unit` 的基础写法、`uses` 位置和重名解析,统一放在 [09_units_and_scope.md](09_units_and_scope.md)。 ## 本篇职责 diff --git a/skills/tsl-syntax-reference/references/20_object_runtime_and_introspection.md b/skills/tsl-syntax-reference/references/20_object_runtime_and_introspection.md index cddf2db4..b420bd79 100644 --- a/skills/tsl-syntax-reference/references/20_object_runtime_and_introspection.md +++ b/skills/tsl-syntax-reference/references/20_object_runtime_and_introspection.md @@ -1,10 +1,5 @@ # TSL 对象运行时与反射 -文档类型:语法深水专题 -是否可直接用于生成代码:仅部分 -是否含可直接照写示例:是 -是否含不可照写反例:是 - 这一篇收拢对象模型里不适合继续堆在基础类主线里的运行时内容:类信息、函数句柄、对象状态、引用计数、运行时对象枚举、弱引用与自动弱引用。普通类声明、继承、构造和普通对象创建仍以 [08_objects_and_classes.md](08_objects_and_classes.md) 为主线;本页只在用户明确需要运行时反射、内省、对象生命周期或弱引用能力时进入。 ## 本篇职责 @@ -40,7 +35,7 @@ - `functionInfo` 至少可以读出 `functionname`、`returntype`、`classname` 这几个字段。 - `tslObjects(1)` 会按类名分组返回对象信息,并且分组项里的 `"obj"` 字段可以重新拿到可调用对象。 - 对象赋值会延长对象存活;只有最后一个引用清空后才会触发 `destroy()`。 -- `self` 在本页只用于需要本实例对象引用的运行时函数,例如 `objectstate(self)`;普通成员访问仍按 [08_objects_and_classes.md](08_objects_and_classes.md) 的规则,不加 `self` 前缀。 +- `self` 表示本实例对象引用;本页在需要该引用的场景使用它,如传给运行时函数(`objectstate(self)`)或保存自身引用(`sca := self;`)。普通成员访问仍按 [08_objects_and_classes.md](08_objects_and_classes.md) 的规则,不加 `self` 前缀。 - 弱引用能力的条件编译宏是 `weakptr`;自动弱引用相关宏是 `autoWeak`。 - `weakRef(obj)` 可以创建弱引用;对象仍存活时,`weakref_get(w)` 可以拿回强引用。 - `checkWeakRef(w)` 在对象仍存活时返回 `1`,对象已释放后返回 `-1`。 @@ -355,15 +350,15 @@ end; 代码块身份:可直接照写示例 ```tsl +objA := new TestClass01(100); +objB := new TestClass01(101); + objsInfo := tslObjects(1); writeLn(length(objsInfo["TestClass01"])); newObjA := objsInfo["TestClass01"][0, "obj"]; writeLn(newObjA is class(TestClass01)); writeLn(newObjA.add(1, 2)); -objA := new TestClass01(100); -objB := new TestClass01(101); - type TestClass01 = class public value; @@ -575,6 +570,8 @@ end; 上面这种给段落切换关键字**加分号**(`weakRef;` / `autoRef;`)的写法会编译失败,错误信息包含 `invalid class definition`。关键在分号:段落切换关键字要像 `public` / `private` 那样**不带分号**独占一行,带分号后被当成独立语句才报错。正确的段落式写法见下一段。 +### 段落式 `weakRef` / `autoRef` 的正确写法 + 段落式 `weakRef` / `autoRef`(不带分号)像 `public` / `private` 一样切换后续成员的弱引用属性: 代码块身份:可直接照写示例 diff --git a/skills/tsl-syntax-reference/references/21_builtin_runtime_objects.md b/skills/tsl-syntax-reference/references/21_builtin_runtime_objects.md index 7dac3c20..ff6bf30d 100644 --- a/skills/tsl-syntax-reference/references/21_builtin_runtime_objects.md +++ b/skills/tsl-syntax-reference/references/21_builtin_runtime_objects.md @@ -1,10 +1,5 @@ # TSL 内置运行时对象 -文档类型:语法深水专题 -是否可直接用于生成代码:仅部分 -是否含可直接照写示例:是 -是否含不可照写反例:是 - 这一篇只收运行时内置对象的文档明确最小模型。这里讲的是语言运行时对象,不是金融业务 API。 ## 本篇职责 @@ -76,7 +71,7 @@ writeLn(list["B"]); - 依次输出 `2`、`bbb` - 说明 `THashedStringList` 可按 `Name=Value` 的名称键读取值 -### `TStream` 家族的最小可靠入口:`TMemoryStream` +### `TStream` 家族的最小可靠入口:`TMemoryStream` 与 `TFileStream` 代码块身份:可直接照写示例 diff --git a/skills/tsl-syntax-reference/references/22_matrix_deep_dive.md b/skills/tsl-syntax-reference/references/22_matrix_deep_dive.md index 8bba4759..f82da843 100644 --- a/skills/tsl-syntax-reference/references/22_matrix_deep_dive.md +++ b/skills/tsl-syntax-reference/references/22_matrix_deep_dive.md @@ -1,20 +1,15 @@ # TSL 矩阵深水专题 -文档类型:语法深水专题 -是否可直接用于生成代码:是 -是否含可直接照写示例:是 -是否含不可照写反例:是 - 这一篇只讲矩阵专用语法主干:矩阵初始化、数列构造、矩阵逆/广义逆、矩阵乘除乘方、矩阵转置、矩阵拼接、矩阵尺寸与索引、矩阵遍历、子矩阵和 `mfind` 查找。它和 [12_matrix_and_collections.md](12_matrix_and_collections.md) 的分工是:`12` 讲普通数组与集合关系,这一篇讲矩阵专用构造、运算、遍历、子矩阵和矩阵查找接口。 ## 本篇职责 -回答”怎样直接构造全零矩阵、全一矩阵、随机矩阵、单位矩阵、空矩阵和数列数组,怎样写矩阵逆/广义逆,怎样进行矩阵乘除乘方,怎样转置矩阵,怎样拼接矩阵,怎样拿到矩阵的行数、列数、行索引和列索引,怎样遍历矩阵、取/改子矩阵,以及怎样用 `mfind` 找到或替换符合条件的单元格”。 +回答“怎样直接构造全零矩阵、全一矩阵、随机矩阵、单位矩阵、空矩阵和数列数组,怎样写矩阵逆/广义逆,怎样进行矩阵乘除乘方,怎样转置矩阵,怎样拼接矩阵,怎样拿到矩阵的行数、列数、行索引和列索引,怎样遍历矩阵、取/改子矩阵,以及怎样用 `mfind` 找到或替换符合条件的单元格”。 ## 智能体矩阵深水判断流程 1. 先判断要写矩阵初始化、数列构造、矩阵逆/广义逆、矩阵尺寸与索引读取、矩阵遍历、子矩阵,还是 `mfind` 查找/替换。 -2. 基础数组和矩阵样比较先回看 `12_matrix_and_collections.md`。 +2. 基础数组与矩阵样比较的事实在 [12_matrix_and_collections.md](12_matrix_and_collections.md)。 3. `mrows` / `mcols` / `msize` 等函数只照文档返回形态写。 4. 需要逐单元执行语句块时用 `matrix::begin ... end`;需要把表达式结果写回每个单元时用 `matrix ::= expression`。 5. 需要遍历到嵌套数组最深层时用 `matrix:.begin ... end` 或 `matrix:.= expression`。 @@ -25,8 +20,8 @@ ## 核心规则 -- 矩阵初始化函数的参数规格使用 `tsl-api-reference` skill 按名查询;本页只保留矩阵行为示例和返回形态边界。 -- `zeros(...)`、`ones(...)`、`rand(...)`、`nils(...)`、`eye(...)` 都可以直接用于矩阵初始化。 +- 矩阵初始化和基础函数的具体参数规格使用 `tsl-api-reference` skill 按名查询;本页只保留矩阵行为示例、返回形态边界和跨函数通用的广播 / `ErrDefine` / `ErrReplace` 机制。 +- `zeros(...)`、`ones(...)`、`rand(...)`、`nils(...)`、`eye(...)` 都可以直接用于矩阵初始化;全 `nil` 单元的“空矩阵”用 `nils(...)` 构造。 - `zeros(3)`、`ones(3)`、`nils(2)` 这类单参数写法可以直接生成一维结果。 - `zeros(2, 3)`、`rand(2, 3)` 这类双参数写法可以直接生成二维矩阵。 - `zeros(2, array("A", "B"))` 这种写法可以直接生成带列名的二维结果。 @@ -525,7 +520,7 @@ B 0 ``` -### 矩阵乘法、除法、乘方:`:*`、`:/`、`:^` +### 矩阵乘法、除法、左除、乘方:`:*`、`:/`、`:\`、`:^` `:*` 是矩阵乘法(区别于逐元素乘 `*`): @@ -603,7 +598,7 @@ writeLn(result[1][0]); 说明: -- `:\` 左除用于解线性方程组 `A * X = B`,等价于 `X = A^(-1) * B` +- `:\` 左除用于解线性方程组 AX = B(数学记法),等价于先求逆再矩阵乘:`!a :* b` - **右侧 `b` 必须是列向量**(用 `array((4), (8))` 而非 `array(4, 8)`) - `a :\ b` 返回 `array((2.0), (2.0))`,即 `X` 的列向量 - 当 A 行数 > 列数时返回最小二乘解,行数 < 列数时返回一个可行解 @@ -699,10 +694,34 @@ array(2.0,NAN,3.0) 说明: -- `sqrt(data, 1, -999)` 中,第二参数 `ErrDefine=1` 表示允许 NIL 值不允许错误值 -- `ErrReplace=-999` 是错误位置的替换值(本例中 `-1` 的平方根为错误,但 `ErrDefine=1` 保留为 NAN) -- `ErrDefine` 取值:`0`=不允许错误和 NIL、`1`=允许 NIL 不允许错误、`2`=错误值保留为原值 -- `ErrReplace` 在 `ErrDefine=0` 或 `1` 时生效,用于替换错误/NIL 位置 +- `ErrDefine` 取值:`0`=不允许错误和 NIL(两者都替换为 `ErrReplace`)、`1`=允许 NIL 不允许错误(NIL 保留,错误替换为 `ErrReplace`)、`2`=错误和 NIL 都保留原值(`ErrReplace` 不生效) +- 「错误」指输入无效(如 NIL、类型不符);数学定义域产生的 `NAN` 结果(如 `sqrt(-1)`)不属于错误位置,任何 `ErrDefine` 档位下都不会被 `ErrReplace` 替换,本例输出保留 `NAN` 即因如此 +- 不带 `ErrDefine` / `ErrReplace` 时,无效输入会直接运行报错 + +无效输入在不同 `ErrDefine` 档位下的替换差异: + +代码块身份:可直接照写示例 + +```tsl +data := array(4, nil, "AAA", 9); +writeLn(tostn(sqrt(data, 0, -999))); +writeLn(tostn(sqrt(data, 1, -999))); +writeLn(tostn(sqrt(data, 2, -999))); +``` + +代码块身份:输出片段 + +```text +array(2.0,-999,-999,3.0) +array(2.0,NIL,-999,3.0) +array(2.0,NIL,"AAA",3.0) +``` + +说明: + +- `ErrDefine=0` 把 NIL 和错误值 `"AAA"` 都替换成 `-999` +- `ErrDefine=1` 保留 NIL、只替换错误值 +- `ErrDefine=2` 全部保留原值 另一个例子: @@ -871,7 +890,7 @@ writeLn("(1,3):", result[1][3]); (1,3): 8 ``` -`:|` 对非完全矩阵补 `nil`,`|` 不补: +普通 `array` 的非完全矩阵:`:|` 缺位补 `nil`,`|` 直接顺接不补位: 代码块身份:可直接照写示例 @@ -887,16 +906,17 @@ writeLn("bar 结果 (1,2):", bar_result[1][2]); 代码块身份:输出片段 ```text -colon 结果列数: 6 -colon (1,2): nil -bar 结果 (1,2): 1 +colon 结果列数:6 +colon (1,2): +bar 结果 (1,2):2 ``` 说明: - `union` / `&=` 用于行方向拼接 - `|` / `:|` 用于列方向拼接 -- 非完全矩阵(行长度不一致)用 `:|` 会在缺位补 `nil`,而 `|` 不补 +- 普通 `array` 的非完全矩阵(行长度不一致):`:|` 按原列位置对齐、缺位补 `nil`;`|` 把两行元素直接顺接(本例第 1 行变成 `(2,3,2,3)`,所以 `[1][2]` 是 `2`) +- 上述差异只适用于普通 `array`;`FMArray` 上 `|` 与 `:|` 行为一致且行数不齐时补 `0`,见 [23_fmarray.md](23_fmarray.md) - 对应的复合赋值形式:`&=`(行并)、`|=`(列并)、`:|=`(列并补 nil) ## 默认生成模板 @@ -923,7 +943,7 @@ matrix_value := zeros(2, 3); - 用 `::` 期待遍历到任意深度;深度遍历使用 `:.`。 - 子矩阵赋值时用形状不匹配的矩阵硬塞。 - 把 `union` 的复合赋值写成 `union=`;正确写法是 `&=`。 -- 一维数组直接 `union` 期待得到二维结果;需要先双转置 ` `b `` 变成行向量。 +- 一维数组直接 `union` 期待得到二维结果;需要先对它连用两次转置前缀(双转置)变成行向量。 - 在基础函数异常参数时用分号分隔;正确写法用逗号:`sqrt(data, 1, -999)`。 代码块身份:反例 / 不可照写 diff --git a/skills/tsl-syntax-reference/references/23_fmarray.md b/skills/tsl-syntax-reference/references/23_fmarray.md index 5267eb55..5e67bb46 100644 --- a/skills/tsl-syntax-reference/references/23_fmarray.md +++ b/skills/tsl-syntax-reference/references/23_fmarray.md @@ -1,10 +1,5 @@ # TSL FMArray -文档类型:语法深水专题 -是否可直接用于生成代码:是 -是否含可直接照写示例:是 -是否含不可照写反例:是 - 这一篇只讲 `FMArray` 文档主干能力:怎样构造 `FMArray`、怎样判断类型、怎样和 `Array` 互转、怎样读取尺寸、做基础运算、做多维转置与维度交换、做矩阵连接、参与 `select/mselect`,以及 `insert/delete/update` 语法边界。 ## 本篇职责 @@ -33,7 +28,7 @@ - `union2` 支持 `FMArray` / `Array` 混合运算;结果类型跟随左值。 - 对超过二维的 `FMArray`,反引号转置会把全部维度倒置;`mswap` 可只交换指定维度。 - `union` 可做按行连接且不去重,但列结构必须兼容。 -- `|` 和 `:|` 都可做矩阵并右方(按列连接);行数不一致时会用 `0` 补齐,并且两者表现一致。 +- `|` 和 `:|` 都可做矩阵并右方(按列连接);在 `FMArray` 上行数不一致时会用 `0` 补齐,并且两者表现一致(普通 `array` 上两者行为不同:`:|` 缺位补 `nil`、`|` 直接顺接,见 [22_matrix_deep_dive.md](22_matrix_deep_dive.md))。 - 对 `FMArray` 做普通 `select` 不会保留 `FMArray` 类型;`mselect` 也不会返回 `dataType=27`。 - `insert into a array(...)`、`delete from a where ...;`、`update a set ... where ... end;` 都能作用于 `FMArray`,但三者的收尾形式并不完全相同。 @@ -76,6 +71,11 @@ writeLn(f2[0,0], ',', f2[0,1], ',', f2[1,0], ',', f2[1,1]); 0 1 1 +3 +1,2,3 +2 +2 +1,2,3,4 ``` ### `mInit`、`mInitDiag`、`mRand` diff --git a/skills/tsl-syntax-reference/references/24_object_overloads_and_iteration.md b/skills/tsl-syntax-reference/references/24_object_overloads_and_iteration.md index d59f7ce7..1d3b851e 100644 --- a/skills/tsl-syntax-reference/references/24_object_overloads_and_iteration.md +++ b/skills/tsl-syntax-reference/references/24_object_overloads_and_iteration.md @@ -1,10 +1,5 @@ # TSL 对象重载与迭代 -文档类型:语法深水专题 -是否可直接用于生成代码:仅部分 -是否含可直接照写示例:是 -是否含不可照写反例:否 - 这一篇只讲对象直接参与语言级操作的文档能力:基础算符重载、`[]` 重载、`for in` 重载,以及 `mrows` / `mcols` / `msize` 这类矩阵关键字重载。 ## 本篇职责 @@ -90,7 +85,7 @@ end; - 依次输出 `20`、`0`、`1`、`1` - 说明 `obj + value` 可以通过成员 `operator +` 接管 - 说明带 `is_left` 的比较算符可以同时处理 `obj < value` 和 `value < obj` -- 私有成员用尾随下划线的 `real_part_` / `imaginary_part_`,对外用 `PascalCase` property 暴露 +- 本例私有成员是 `real_part_` / `imaginary_part_`,对外通过 property 暴露(命名风格本身不属于本页事实) 代码块身份:输出片段 @@ -137,18 +132,32 @@ end; ### `[]` 重载:`operator[0]` / `operator[1]` -沿用上一段的 `bb` 类与测试主体,只把读取签名从 `function operator[](index);` 改成 `function operator[0](index, s1);`: +读取签名也可以写成 `function operator[0](index, s1);`(与上一段 `operator[]` 等价的另一组写法): -代码块身份:配置片段 / 概念骨架 +代码块身份:可直接照写示例 ```tsl +t := array(1, 2, 3, 4, 5); +b := new IndexableBox(t); +writeLn(b[2]); +b[3] := 999; +writeLn(b.data[3]); + type IndexableBox = class public - // 其余字段、create()、operator[1] 和测试主体同上一段 + data; + function create(v); + begin + data := v; + end; function operator[0](index, s1); begin return data[index]; end; + function operator[1](index, v); + begin + data[index] := v; + end; end; ``` @@ -207,7 +216,7 @@ end; - 说明 `operator for(flag)` 可以重载 `for in` - 也说明同一个对象可以按返回值形态同时支持“单变量遍历”和“索引 + 值遍历” -### `mrows` / `mcols` / `msize` 重载 +### `mrows` / `mcols` / `msize` 重载(带参形态,支持下标列表) 代码块身份:可直接照写示例 @@ -263,7 +272,7 @@ end; - 说明 `mrows(obj)`、`mcols(obj)`、`msize(obj)` 都可由对象重载接管 - 说明对象方法式调用 `obj.mcols(1)` 也可继续取得列下标列表 -### `++` 与 `+=` +### `++` / `+=` / `--` / `-=` 自增自减重载 代码块身份:可直接照写示例 @@ -306,8 +315,8 @@ end; 结果说明: - 依次输出 `11`、`11`、`12`、`17` -- 说明前置 `++` 会直接修改对象状态 -- 说明这个最小样例里,后置 `counter++` 返回的是递增前快照 +- 前置 `++counter` 走 `v` 非 `0` 分支,直接原地修改对象状态 +- 后置 `counter++` 走 `v = 0` 分支:分支构造并返回递增后的新对象,运行时用它更新 `counter` 本身,而后置表达式的取值是递增前的原对象(所以 `c.data` 是 `11`、`counter.data` 是 `12`) - 说明 `operator += (v)` 可以接管 `counter += 5` `--` 与 `-=` 与之对称: @@ -349,7 +358,7 @@ end; - 输出 `7` - 说明 `operator -= (v)` 可以接管 `counter -= 3` -- `operator--(v)` 与 `operator++(v)` 结构对称:`v = 0` 分支返回递减前快照,否则原地递减 +- `operator--(v)` 与 `operator++(v)` 结构对称:`v = 0`(后置)分支构造并返回递减后的新对象供运行时更新变量本身,后置表达式的取值是递减前的原对象;`v` 非 `0`(前置)时原地递减 ### 二进制函数重载:`operator funcName` @@ -435,7 +444,8 @@ end; 结果说明: - 输出 `array(1,314)`:`ret` 为 `1`(转换成功),出参 `msg` 为 `314` -- 说明类内需要调用被重载的同名全局函数时,用 `::` 前缀指定全局版本,否则会递归回自己 +- 重载的分派按实参类型决定:本类对象走重载版本,非对象实参(如 `value` 是字符串)走全局版本 +- 因此当被重载的实参已经不是本类对象(如上面 `DateToStr` 两例把对象拆成基础类型再调用)时,不加 `::` 也不会递归;只有当传给同名函数的实参仍是本类对象时才会递归回自己,这时用 `::` 前缀强制指定全局版本 - 重载函数支持通过参数传出返回值(`msg` 作为出参被赋值) ### `::` / `:.` 遍历重载与 `mcell` / `mrow` / `mcol` / `mIndexCount` / `mIndex` @@ -512,7 +522,7 @@ end; - `:.`(深度遍历)重载方式与 `::` 相同,把内部 `data::begin ... end` 换成 `data:.begin ... end` 即可 - 遍历体里用到的 `mcell` / `mrow` / `mcol` / `mIndexCount` / `mIndex` 必须各自重载,否则报 `override function not found` -### 关键字函数重载:`msize` / `mrows` / `mcols` +### `mrows` / `mcols` / `msize` 重载(无参形态,只取数量且免 `::`) `msize` / `mrows` / `mcols` 这类关键字函数也能重载,形态同二进制函数重载 `[class] function operator KeyWord(...)`,但**关键字重载不需要 `::` 指定全局**: diff --git a/skills/tsl-syntax-reference/scripts/lookup.py b/skills/tsl-syntax-reference/scripts/lookup.py index 45e75f0c..1b2e72fa 100644 --- a/skills/tsl-syntax-reference/scripts/lookup.py +++ b/skills/tsl-syntax-reference/scripts/lookup.py @@ -29,6 +29,11 @@ ALLOWED_IDENTITIES = { } ROUTER_PHRASES = ("路由中心", "选择一个主专题", "候选页继续判断") EXCLUDED_REFERENCE_FILES = {"index.md"} +SUSPICIOUS_FENCE_RE = re.compile(r"^(?:\s+`{3}|`{4,})") +WRITE_PRELUDE_ANCHORS = ( + ("02_core_model.md", "文件模型核心规则"), + ("01_quickstart.md", "语言核心事实速查"), +) HEADING_TOKEN_SCORE = 12 HEADING_EXACT_SCORE = 20 @@ -249,16 +254,24 @@ def _identity_problems(page: Path, lines: list[str]) -> list[ValidationProblem]: problems.append(ValidationProblem(page, index, f"未知身份:{identity}")) in_fence = False for index, line in enumerate(lines): - if not FENCE_RE.match(line): + if FENCE_RE.match(line): + if in_fence: + in_fence = False + else: + if _associated_identity(lines, index) is None: + problems.append( + ValidationProblem( + page, index + 1, "每个代码围栏必须关联恰好一个代码块身份" + ) + ) + in_fence = True continue - if in_fence: - in_fence = False - continue - if _associated_identity(lines, index) is None: + if not in_fence and SUSPICIOUS_FENCE_RE.match(line): problems.append( - ValidationProblem(page, index + 1, "每个代码围栏必须关联恰好一个代码块身份") + ValidationProblem( + page, index + 1, "不支持的代码围栏形态(缩进围栏或四个及以上反引号)" + ) ) - in_fence = True if in_fence: problems.append(ValidationProblem(page, len(lines), "代码围栏未闭合")) return problems @@ -268,8 +281,17 @@ def _local_link_problems( page: Path, text: str, references_dir: Path ) -> list[ValidationProblem]: problems: list[ValidationProblem] = [] - without_fences = re.sub(r"```.*?```", "", text, flags=re.DOTALL) - searchable_markdown = re.sub(r"`[^`\n]*`", "", without_fences) + # Blank out fenced/inline code without changing line offsets, so reported + # line numbers line up with the original file. + without_fences = re.sub( + r"```.*?```", + lambda match: re.sub(r"[^\n]", " ", match.group(0)), + text, + flags=re.DOTALL, + ) + searchable_markdown = re.sub( + r"`[^`\n]*`", lambda match: " " * len(match.group(0)), without_fences + ) for match in MARKDOWN_LINK_RE.finditer(searchable_markdown): target = match.group(1).strip().split(maxsplit=1)[0].strip("<>") if target.startswith(("#", "http://", "https://", "mailto:")): @@ -313,6 +335,22 @@ def validate_references( if section.id in ids: problems.append(ValidationProblem(section.page, 1, f"重复 section ID:{section.id}")) ids[section.id] = section + for page_name, heading in WRITE_PRELUDE_ANCHORS: + page_sections = [ + section for section in sections if section.page.name == page_name + ] + # Only enforce the anchor when the page is present, so validating a + # synthetic references dir (tests) does not demand the bundled pages. + if page_sections and not any( + heading in section.heading_path for section in page_sections + ): + problems.append( + ValidationProblem( + references_dir / page_name, + 1, + f"write 模式前置章节缺失:{page_name} 的「{heading}」", + ) + ) return problems @@ -394,12 +432,8 @@ def _score_section(section: Section, query: str, mode: str) -> ScoreBreakdown: def _write_prelude(sections: list[Section]) -> list[Section]: - preferred = [ - ("02_core_model.md", "文件模型核心规则"), - ("01_quickstart.md", "语言核心事实速查"), - ] prelude: list[Section] = [] - for page_name, heading in preferred: + for page_name, heading in WRITE_PRELUDE_ANCHORS: match = next( ( section @@ -413,6 +447,43 @@ def _write_prelude(sections: list[Section]) -> list[Section]: return prelude +STRING_LITERAL_RE = re.compile(r'"[^"\n]*"|\'[^\'\n]*\'') + + +def _bare_tokens(tokens: set[str]) -> set[str]: + # ASCII_TOKEN_RE 的连续字符类会把尾缀符号吞进 token(如编译开关 + # `{$varByRef-}` 产出 `varbyref-`);按剥掉尾部符号的裸形态比较。 + return {token.rstrip(".$:+-") for token in tokens} - {""} + + +def _ascii_anchor(section: Section, ascii_tokens: set[str]) -> bool: + # 只有足够长的标识符按标识符位置命中(标题、行内代码或剥离字符串 + # 字面量后的围栏代码)才豁免中文门控;短 token 与字符串样例数据 + # (如 "XYZ"、"xyz*")不算点名,避免样例值驱动召回。 + anchor_tokens = { + token for token in _bare_tokens(ascii_tokens) if len(token) >= 4 + } + if not anchor_tokens: + return False + heading_tokens = set( + ASCII_TOKEN_RE.findall(normalize("\n".join(section.heading_path))) + ) + code_text = "\n".join( + (*INLINE_CODE_RE.findall(section.body), *FENCED_CODE_RE.findall(section.body)) + ) + code_tokens = set( + ASCII_TOKEN_RE.findall(normalize(STRING_LITERAL_RE.sub(" ", code_text))) + ) + return bool(anchor_tokens & _bare_tokens(heading_tokens | code_tokens)) + + +def _related_sections(a: Section, b: Section) -> bool: + if a.page != b.page or len(a.heading_path) == len(b.heading_path): + return False + shorter, longer = sorted((a.heading_path, b.heading_path), key=len) + return longer[: len(shorter)] == shorter + + def query_sections( query: str, mode: str, @@ -438,7 +509,14 @@ def query_sections( minimum_score = MIXED_QUERY_MIN_SCORE if ascii_tokens and has_chinese else 1 ranked: list[QueryMatch] = [] for section in sections: - if ascii_tokens and has_chinese and not _has_chinese_context(section, query): + # 中文上下文门控只裁剪正文级 ASCII 噪声;标识符在标题或代码里 + # 精确命中的 section 不因中文措辞不同而被丢弃。 + if ( + ascii_tokens + and has_chinese + and not _ascii_anchor(section, ascii_tokens) + and not _has_chinese_context(section, query) + ): continue score = _score_section(section, query, mode) if score.lexical_total < minimum_score: @@ -451,7 +529,17 @@ def query_sections( match.section.id, ) ) - matches = ranked[:limit] + # H2 聚合 section 的正文逐字包含其 H3 子节;父子同时入选时只保留 + # 排名更高的一个,避免同一内容重复返回。 + matches: list[QueryMatch] = [] + for match in ranked: + if any( + _related_sections(match.section, kept.section) for kept in matches + ): + continue + matches.append(match) + if len(matches) == limit: + break prelude = _write_prelude(all_sections) if mode == "write" else [] match_ids = {match.section.id for match in matches} prelude = [section for section in prelude if section.id not in match_ids] @@ -535,6 +623,8 @@ def main(argv: list[str] | None = None) -> int: parser.error("--limit must be between 1 and 10") if args.query is not None and args.mode is None: 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.check: problems = validate_references(args.references_dir) for problem in problems: diff --git a/test/test_build_tsl_playbook.py b/test/test_build_tsl_playbook.py index c5777b29..b2e25eac 100644 --- a/test/test_build_tsl_playbook.py +++ b/test/test_build_tsl_playbook.py @@ -6,13 +6,22 @@ import tempfile import unittest from pathlib import Path +from scripts.build_tsl_playbook import build_agents_text + ROOT = Path(__file__).resolve().parents[1] SCRIPT = ROOT / "scripts" / "build_tsl_playbook.py" SYNC_WORKFLOW = ROOT / ".gitea" / "workflows" / "sync-tsl-playbook.yml" +README = ROOT / "README.md" class BuildTslPlaybookTests(unittest.TestCase): + def test_readme_names_tsl_syntax_skill_as_unique_fact_owner(self): + text = README.read_text(encoding="utf-8") + + self.assertIn("TSL 语法事实唯一由 `tsl-syntax-reference` 管理", text) + self.assertIn("TSL 领域路由与事实边界", text) + def test_builds_minimal_tsl_playbook_tree(self): with tempfile.TemporaryDirectory() as tmp_dir: output = Path(tmp_dir) / "tsl-playbook" @@ -37,6 +46,9 @@ class BuildTslPlaybookTests(unittest.TestCase): self.assertTrue((syntax_skill / "SKILL.md").is_file()) self.assertTrue((syntax_skill / "scripts" / "lookup.py").is_file()) self.assertFalse((syntax_skill / "references" / "index.md").exists()) + self.assertTrue( + (output / "skills" / "tsl-syntax-reference" / "SKILL.md").is_file() + ) self.assertTrue( (output / "skills" / "tsl-api-reference" / "SKILL.md").is_file() ) @@ -61,9 +73,54 @@ class BuildTslPlaybookTests(unittest.TestCase): output_docs = count_files(output / "docs" / "tsl") self.assertEqual(output_docs, source_docs) - source_skill = count_files(ROOT / "skills" / "tsl-api-reference") - output_skill = count_files(output / "skills" / "tsl-api-reference") - self.assertEqual(output_skill, source_skill) + for skill_name in ("tsl-syntax-reference", "tsl-api-reference"): + source_skill = count_files(ROOT / "skills" / skill_name) + output_skill = count_files(output / "skills" / skill_name) + self.assertEqual(output_skill, source_skill) + + def test_missing_tsl_skill_reports_specific_source_path(self): + for skill_name in ("tsl-syntax-reference", "tsl-api-reference"): + with self.subTest(skill=skill_name), tempfile.TemporaryDirectory() as tmp_dir: + repo = Path(tmp_dir) / "repo" + repo.mkdir() + copy_required_sources(repo) + missing_path = repo / "skills" / skill_name + shutil.rmtree(missing_path) + + result = subprocess.run( + [ + sys.executable, + str(repo / "scripts" / "build_tsl_playbook.py"), + "--output", + str(repo / "output"), + ], + capture_output=True, + text=True, + ) + + self.assertNotEqual(result.returncode, 0) + self.assertIn(str(missing_path), result.stderr) + + def test_build_agents_text_only_rewrites_title_and_trailing_newline(self): + marker = ( + "- 需要任何 TSL 语法、文件模型、函数、模块或金融数据事实时," + "第一跳统一进 `docs/tsl/index.md`,由它路由到具体页;" + "不在本文件内猜文件路径,也不全目录搜索。" + ) + with tempfile.TemporaryDirectory() as tmp_dir: + ruleset = Path(tmp_dir) / "index.md" + ruleset.write_text( + f"# TSL 智能体规则\n\n{marker}\n\n", + encoding="utf-8", + newline="\n", + ) + + agents_text = build_agents_text(ruleset) + + self.assertEqual( + agents_text, + f"# TSL Agent Instructions\n\n{marker}\n", + ) def test_sync_workflow_does_not_remove_entire_target_branch(self): text = SYNC_WORKFLOW.read_text(encoding="utf-8") @@ -109,6 +166,7 @@ class BuildTslPlaybookTests(unittest.TestCase): for path in ( "AGENTS.md", "docs/tsl/index.md", + "skills/tsl-syntax-reference/SKILL.md", "skills/tsl-api-reference/SKILL.md", ): git(repo, "cat-file", "-e", f"HEAD:{path}") @@ -125,6 +183,7 @@ class BuildTslPlaybookTests(unittest.TestCase): for path in ( "AGENTS.md", "docs/tsl/index.md", + "skills/tsl-syntax-reference/SKILL.md", "skills/tsl-api-reference/SKILL.md", ): git(repo, "cat-file", "-e", f"HEAD:{path}") @@ -164,20 +223,18 @@ def run_sync(repo: Path) -> None: env = os.environ.copy() env.update( { - "REPO_DIR": str(repo), "TARGET_BRANCH": "tsl-playbook", "COMMIT_AUTHOR_NAME": "test", "COMMIT_AUTHOR_EMAIL": "test@example.invalid", } ) - with tempfile.NamedTemporaryFile( - "w", suffix=".sh", encoding="utf-8", newline="\n", delete=False - ) as script_file: - script_file.write(extract_sync_workflow_script()) - script_path = script_file.name + script_path = repo / ".sync-test.sh" + script_path.write_text( + extract_sync_workflow_script(), encoding="utf-8", newline="\n" + ) try: result = subprocess.run( - ["bash", script_path], + ["bash", script_path.name], cwd=repo, env=env, capture_output=True, @@ -186,7 +243,7 @@ def run_sync(repo: Path) -> None: errors="replace", ) finally: - os.unlink(script_path) + script_path.unlink(missing_ok=True) if result.returncode != 0: raise AssertionError(result.stderr + result.stdout) @@ -223,6 +280,17 @@ def copy_required_sources(repo: Path) -> None: "print('lookup')\n", encoding="utf-8", newline="\n" ) + syntax_skill = repo / "skills" / "tsl-syntax-reference" + (syntax_skill / "references").mkdir(parents=True) + (syntax_skill / "SKILL.md").write_text( + "---\nname: tsl-syntax-reference\n---\n", + encoding="utf-8", + newline="\n", + ) + (syntax_skill / "references" / "index.md").write_text( + "# TSL Syntax Reference\n", encoding="utf-8", newline="\n" + ) + (repo / "rulesets" / "tsl").mkdir(parents=True) (repo / "rulesets" / "tsl" / "index.md").write_text( "# TSL Agent Instructions\n", encoding="utf-8", newline="\n" @@ -230,7 +298,13 @@ def copy_required_sources(repo: Path) -> None: def count_files(path: Path) -> int: - return sum(1 for item in path.rglob("*") if item.is_file()) + return sum( + 1 + for item in path.rglob("*") + if item.is_file() + and "__pycache__" not in item.parts + and item.suffix != ".pyc" + ) def extract_sync_workflow_script() -> str: