# 排除模式语义

M05 提供纯 MoonBit `parse_pattern` / `pattern_matches`，M06 接入配置和 CLI。匹配器无文件系统依赖，不读取 `.gitignore`。

模式区分大小写，使用 `/` 分段。每次匹配完整相对路径；如果某个真实或隐含目录的完整相对路径被匹配，其后代也被排除。

| 写法 | 含义 | 例子 |
| --- | --- | --- |
| `*` | 当前段内零个或多个 Unicode 字符 | `*.tmp` 匹配根层 `x.tmp` |
| `?` | 当前段内一个 Unicode 字符，不是 UTF-16 码元 | `?.txt` 匹配 `🙂.txt`，不匹配 `ab.txt` |
| 完整段 `**` | 零个或多个路径段 | `a/**/b` 匹配 `a/b` 和 `a/x/y/b` |
| 尾部 `/` | 只匹配目录及其子树 | `cache/` 不匹配名为 `cache` 的根层文件 |
| 普通名称 | 按原始拼写逐字匹配 | `Data` 不匹配 `data` |

`cache` 会匹配根层文件 `cache`，也会匹配目录 `cache` 并排除其后代；`cache/` 只选后者。`**/` 匹配目录及其后代，但不排除根层普通文件，因为扫描根本身不是待匹配条目。`*.tmp` 不直接匹配 `nested/a.tmp`；若需要任意层级，使用 `**/*.tmp`。若目录本身名为 `x.tmp`，其后代会随该目录被排除。

不提供取反、字符类、花括号展开或转义。模式出现以下情况时，`parse_pattern` 返回 `PATTERN_INVALID`，接入 CLI 后属于退出 `2` 的输入错误：空模式、绝对路径或盘符开头、反斜杠、空段、`.` / `..` 段、开头的 `!`、`[` / `]` / `{` / `}`、C0/DEL/C1 控制字符，以及不独占一段的 `**`。普通名称中间的 `!` 是字面字符。只允许一个尾部 `/`，不将重复分隔符悄悄规范化。

匹配器先验证路径结构：空路径、绝对/盘符路径、空段及点段始终不被排除，供审计保留原始诊断。名称兼容问题与路径结构问题不同；合法相对结构中的保留名或非法名称字符可以被用户显式选择的交付范围排除。

实现使用逐段状态推进和段内字符动态规划，不使用递归遍历目录深度。测试包括独立的有界递归解释器，用另一种实现核对通配符组合；生产匹配仍全部由 MoonBit 完成。

## 范围（M06）

`parse_scope` 对给定模式逐个验证，任一非法即整体失败。配置 `exclude` 与重复 `--exclude` 取并集，按原始拼写去重（`cache` 与 `cache/` 是不同模式，不会折叠），并按 UTF-16 ordinal 排序后作为报告的 `scope`。范围默认为空。

排除的判定与 `pattern_matches` 完全一致：合法结构下的条目若其完整相对路径或其隐含目录前缀被匹配，即被排除；结构非法路径永不排除。被排除的实际目录是「剪枝目录」，只报告最顶层的一个（祖先已被排除者不再重复），因此取消整个子树的枚举后**不需要**也无法统计内部条目数——`excluded_entries` 只统计已知输入里实际匹配的条目。同一范围下，扫描与等价清单产生相同的 `scope`、诊断、`summary` 与 `pruned_directories`。
