# TSL Codegen Toolkit

本工具把一个或多个 tsf function、独立 class、完整 unit 转换为统一
`declarations` json/yaml 录入文件，再生成 TSL API skill 使用的 markdown 文档，
并根据 markdown 重建统一 API 索引 `function_index.tsv`。

## 目录结构

```text
tools/tsl-codegen/
  ├─ README.md                         使用说明
  ├─ STANDARD.md                       API 文档与录入格式标准
  ├─ examples/
  │   ├─ example.yaml                  yaml 录入例子
  │   └─ example.json                  json 录入例子
  ├─ scripts/
  │   ├─ convert_tsf.py                 tsf → json/yaml
  │   ├─ generate.py                    yaml/json → markdown
  │   ├─ lint.py                        markdown 格式校验
  │   ├─ api_markdown.py                lint/index 共用标题模型
  │   ├─ build_index.py                 重建 13 列 function_index.tsv
  │   └─ build_dictionary.py            HTML 数据字典 → markdown/TSV
  └─ tests/                             工具测试
```

## 使用顺序

### 1. 进入仓库根目录

先切换到包含 `tools/` 和 `skills/` 的仓库根目录。后续命令都从该目录运行：

```bash
cd /path/to/playbook
```

json 使用 Python 标准库，不需要额外安装解析包。yaml 需要安装 `pyyaml`：

```bash
python -m pip install pyyaml
```

markdown 生成器会强制使用仓库锁定版本的 Prettier。首次使用前安装 Node.js，并在
仓库根目录安装依赖：

```bash
npm install
```

### 2. 阅读标准

先阅读 [`STANDARD.md`](STANDARD.md)。其中定义：

- function、class、unit 同级 H2 顶级声明和混合页面的固定层级
- tsf function、独立 class 和完整 unit 文档块格式
- yaml/json 统一 `declarations` 判别联合与成员字段
- 13 列统一 API 索引和完全限定名称
- 参数表、返回类型和示例代码规则

录入文件和生成的 markdown 都必须符合该标准

### 3. 准备自己的 yaml 或 json

#### 从 tsf 转换

转换器接受任意混合输入：function、独立 class 和完整 unit TSF 可以在同一条命令中
出现。每个文件贡献一个对外 declaration，生成的 `declarations` 严格保持命令行
输入顺序。页面级 `module` 和 `path` 通过参数统一提供，`--format` 明确选择
json 或 yaml：

```bash
python tools/tsl-codegen/scripts/convert_tsf.py \
  src/OpenXmlAttribute.tsf \
  src/ParseOpenXml.tsf \
  src/OpenXmlRuntime.tsf \
  --format json \
  --module "OfficeXml / OpenXml" \
  --path officexml/openxml/elements \
  --output tmp/openxml-elements.json
```

生成 yaml 时，把 `--format json` 改为 `--format yaml`，并把输出文件后缀改为
`.yaml`。混合输入规则不变。

查看中文帮助：

```bash
python tools/tsl-codegen/scripts/convert_tsf.py --help
```

转换规则：

- function TSF 只转换第一个独立顶层 `function`
- 独立 class 只公开第一个与文件名一致的 class；后续内部 class 和 private 成员忽略
- class 开头默认 public；public/protected 进入草稿；类方法只接受 `class function`
- 类方法没有 `///` 描述时，使用最终声明或修饰符分号后的同一行 `//` 注释作为描述；
  正式 `///` 描述优先，类外实现行和函数体内注释不读取
- property 类型可选；源码未声明类型时草稿中的 `type` 为空字符串，访问方式仍然必填
- 完整 unit 只收录 interface 的 function/var/const/class；implementation 全部忽略
- function、class、unit 可以任意混合；重复 function signature、重复 class/unit
  名称会报错，不同 signature 的 function 重载和跨 kind 同名允许
- 公开 API 中的 `procedure` 当前不支持
- 输入支持 utf-8（含 BOM）和 gb18030；输出统一为 utf-8
- 参数类型和返回类型都是可选的 TSL 注解；转换器从函数签名和 `///` 文档块中尽量
  解析，无法取得时保留空字段，生成待手工完善的录入稿
- `@param: name {类型} 参数说明` 可以为无类型签名补充参数类型；花括号内的全部内容
  都属于类型；`@returns:` 可以补充返回类型；签名和 `///` 同时提供类型时必须一致
- 描述、参数类型或说明等编辑性缺口不会阻止转换；类型冲突、文档结构或声明冲突会按
  “文件:行号”报错；全部输入成功后才写输出文件
- json/yaml 输出补齐各 kind 的固定维护字段：function/method 固定包含
  `desc`、`params`、`returns`，property 固定包含 `desc`、`type`、`params`、`access`，
  field/variable 固定包含 `desc`、`type`，constant 固定包含 `desc`、`type`、`value`；
  参数固定包含 `name`、`type`、`desc`，class/unit 固定包含 `desc`、`members`；缺失值
  使用空字符串或空数组，条件字段只在源码确实存在时输出
- 生成器把 method `returns`、property/constant `type` 的空字符串按可选字段未填写处理；
  顶级/unit function 返回类型和 field/variable 类型等必填内容仍须补全
- 输出文件不得与任一输入 tsf 相同

#### 手工准备

从以下例子选择一种格式：

- [`examples/example.yaml`](examples/example.yaml)
- [`examples/example.json`](examples/example.json)

两份完整例子深度等价，同时包含顶级 function、完整 class、class function、
typed/untyped property、字段、常量、完整 unit 和 interface class 成员

例子中的 API 是格式示例，不是真实 TSL API。复制例子到自己的工作目录，再修改
`module`、`path` 和非空有序 `declarations`。录入根只允许这三个字段；旧
`functions`、`class`、`unit` 根不兼容，生成器会直接拒绝。混合页面的最小
完整外形：

```json
{
  "module": "OfficeXml / OpenXml",
  "path": "officexml/openxml/elements",
  "declarations": [
    {
      "kind": "class",
      "name": "OpenXmlAttribute",
      "desc": "表示 OpenXml 属性",
      "members": []
    },
    {
      "kind": "function",
      "name": "ParseOpenXml",
      "signature": "ParseOpenXml(xml)",
      "desc": "解析 OpenXml 文本",
      "params": [
        {"name": "xml", "type": "string", "desc": "OpenXml 文本"}
      ],
      "returns": "OpenXmlElement"
    }
  ]
}
```

生成的 markdown 使用纯 API 标题和独立声明行。class function 的参数类型进入参数表，
返回类型写在返回行：

```markdown
# OfficeXml / OpenXml

## `OpenXmlAttribute`

声明：class

表示 OpenXml 属性

### `CreateVirtual(position, row_index, _story)`

声明：class function

创建虚段落

可见性：`public`

| 参数        | 类型      | 说明     |
| ----------- | --------- | -------- |
| `position`  | integer   | 段落位置 |
| `row_index` | integer   | 行索引   |
| `_story`    | StoryNode | 故事节点 |

返回：ParagraphSegment
```

录入文件可放在自己的临时目录，例如：

```text
tmp/my-api.yaml
tmp/my-api.json
```

一个录入文件对应一个 markdown 叶子页。录入文件不放入 skill；是否长期保留由
维护者自行决定

#### Class 与 Framework Profile

生成 class 页面并更新 `function_index.tsv` 后，class 已可通过自动成员视图查询，不要求
额外的 Framework Profile。`class_lookup.py` 返回 `profile_status=not_profiled` 表示普通
class，属于正常且完整的结果；不要为它创建空 profile。

本工具不会从 class 名称或成员列表推断生命周期、配置、回调和执行阶段。只有另有可核验
协议证据的框架类才需要 curated Framework Profile。

### 4. 选择 Skill 中的目标位置

TSL API skill 的相关目录如下：

```text
skills/tsl-api-reference/
  ├─ SKILL.md
  ├─ data/
  │   └─ function_index.tsv
  ├─ references/
  │   └─ codegen/
  │       ├─ builtin/                    playbook 维护，页面直接平铺
  │       │   └─ <page>.md
  │       ├─ dotnet/                     playbook 维护
  │       │   └─ <module-dir>/<page>.md
  │       └─ project/                    用户项目文档的默认 scope
  │           └─ <module-dir>/<page>.md
  └─ scripts/
      └─ lookup.py
```

用户录入的 markdown 目标路径固定为：

```text
skills/tsl-api-reference/references/codegen/<scope>/<module-dir>/<page>.md
```

- `<scope>`：用户文档默认使用 `project`，也可以自定义单级目录名。`builtin` 和
  `dotnet` 由 playbook 项目维护，不应用于存放用户自己的 API 文档
- `<module-dir>`：功能分类目录，例如 `base`、`runtime`、`document`
- `<page>.md`：相关 API 的叶子文档，例如 `array.md`、`elements.md`

`builtin` 是维护侧的特例：页面直接位于 `codegen/builtin/<page>.md`，索引中的
`module` 等于文件名（不含 `.md`）。

录入文件的 `module` 是 markdown 一级标题，不是目录名。例如：

```text
module: 我的项目 / 数组
path: base/array
目标文件: project/base/array.md
```

查找项目中的现有页面：

```bash
rg --files skills/tsl-api-reference/references/codegen/project
```

生成后可以直接打开目标 markdown 手动阅读。例如：

```text
skills/tsl-api-reference/references/codegen/project/base/array.md
```

### 5. 生成 markdown

#### 新建叶子页

目标文件不存在时，可以直接生成到 skill。例如：

```bash
python tools/tsl-codegen/scripts/generate.py --file tmp/my-api.yaml
```

json 使用相同命令：

```bash
python tools/tsl-codegen/scripts/generate.py --file tmp/my-api.json
```

需要批量生成时，将录入文件放在同一目录并使用 `--dir`：

```bash
python tools/tsl-codegen/scripts/generate.py --dir tmp/api-recordings
```

目录模式只处理目录中的直属文件，不进入子目录。默认处理 `.json`、`.yaml` 和
`.yml`；`--format json` 只处理 `.json`，`--format yaml` 只处理 `.yaml` 和
`.yml`。所有录入文件会先完成解析、校验和格式化；预检发现任何错误或输出目标冲突
时，生成器会列出对应输入文件和具体原因，并且不写入任何 markdown。

生成器读取录入文件中的 `path`，默认写入
`skills/tsl-api-reference/references/codegen/project/<path>.md`。不指定
`--scope` 时，scope 就是 `project`。该路径默认相对于当前工作目录

从其他目录运行生成器时，使用 `--root` 指定包含 `skills/` 的项目根目录。例如在
`tools/tsl-codegen` 目录中运行：

```bash
python scripts/generate.py --file ../../tmp/my-api.json --root ../..
```

此时仍会写入仓库根目录下的
`skills/tsl-api-reference/references/codegen/project/<path>.md`，不会在
`tools/tsl-codegen` 下创建新的 `skills/` 目录。`--root` 接受绝对路径；相对路径按
运行命令时的当前工作目录解析

写入前会自动使用仓库的 `.prettierrc.json` 格式化 markdown，使新页面与现有
builtin 页面保持一致。未安装 Prettier 或格式化失败时，生成器会停止且不写目标文件

需要使用自定义 scope 时，通过 `--scope` 指定单级目录名：

```bash
python tools/tsl-codegen/scripts/generate.py --file tmp/my-api.json --scope my-project
```

#### 修改现有叶子页

生成器会整体覆盖 `path` 对应的页面。只有录入文件包含该页面的全部 API 时才运行
生成器。只修改现有页面中的少量条目时，应按照 `STANDARD.md` 直接编辑 markdown

### 6. 手动检查并校验 markdown

以下命令以新建页面
`skills/tsl-api-reference/references/codegen/project/base/my_api.md` 为例

先打开文件，检查页面标题、API 名称、成员层级、参数、返回类型和示例

#### 6.1 格式说明

通过 `generate.py` 生成的页面已经完成 Prettier 格式化，不需要再次处理。直接手工
编辑 markdown 后，可以单独格式化目标文件：

```bash
npx --no-install prettier --write skills/tsl-api-reference/references/codegen/project/base/my_api.md
```

#### 6.2 校验

校验目标文件：

```bash
python tools/tsl-codegen/scripts/lint.py --file skills/tsl-api-reference/references/codegen/project/base/my_api.md
```

校验整个项目目录：

```bash
python tools/tsl-codegen/scripts/lint.py --dir skills/tsl-api-reference/references/codegen/project
```

使用 `--strict` 时，警告也会导致校验失败

校验器将以下问题视为错误：

- 缺少描述
- 顶层/unit function 缺少返回类型
- 有参数但没有参数表
- 无参数但存在参数表
- 参数表不是固定三列，或参数名称/顺序与签名不一致
- 参数表数据行缺少参数名、类型或说明
- class member 缺少 visibility
- property 缺少访问方式，field/variable 缺少类型，constant 缺少值
- class/unit 成员标题层级、声明形式或子标题错误，或出现 private API

以下问题默认作为警告：

- 可选参数说明未以 `可选。` 开头
- tags 行为空

### 7. 更新并验证统一 API 索引

markdown 确认无误后，重建 TSV：

索引固定为 13 列：保留原 8 列并追加 kind、binding、visibility、owner、
qualified_name。关键词检索会同时匹配原有字段和这些扩展字段。

```bash
python tools/tsl-codegen/scripts/build_index.py --skill-dir skills/tsl-api-reference
```

检查 TSV 是否与 markdown 一致：

```bash
python tools/tsl-codegen/scripts/build_index.py --skill-dir skills/tsl-api-reference --check
```

默认从 `<skill-dir>/references/codegen` 读取 markdown，并写入
`<skill-dir>/data/function_index.tsv`

最后使用简单名称或完全限定名称验证 skill 可以检索到新条目：

```bash
python skills/tsl-api-reference/scripts/lookup.py --name myFunction
python skills/tsl-api-reference/scripts/lookup.py --name DemoUnit.Document.Save
```

查询简单成员名会返回所有 owner 下的同名 API；`Unit.Class.Member` 等完全限定名称
用于缩小范围。重载共享完全限定名称，查询会返回全部重载。关键词候选显示
`class function`、`static field` 等实际声明术语。

最终提交：

- 新增或修改的 markdown 叶子页
- `skills/tsl-api-reference/data/function_index.tsv`

TSL API skill 只需要 markdown 和 TSV。录入用的 yaml/json 可以由维护者在自己的
版本库中管理。
