# core-contract（技术计划）

## 1. 计划目标

基于 `spec.md` 与 `设计说明文档/文档换行算法.md v1.2.1`，落地跨端一致的“基础口径与契约”，为后续断点生成、搜索与评分提供稳定输入与确定性工具，确保：

- EN/TC 的 **token 索引体系** 与断点 `pos` 语义固定
- 文本 **可重组**：任意 `[start..end)` 区间可稳定还原为行文本
- EN 关键词命中规则严格为 **全词等值匹配**（避免 substring 误伤）
- 空白归一化策略可配置但默认一致（推荐 NORMALIZE）
- 提供可复用的 **确定性比较工具**（用于 breaks 字典序/tieKey 比较）

## 2. 默认技术决策（本计划采用）

- **空白策略**：默认 `whitespacePolicy=NORMALIZE`
  - 行为：折叠连续空白为 1 个空格、去首尾空白
  - 并在 meta（后续模块）中打点 `hadMultiWhitespace`（本模块先预留布尔返回位）
- **EN tokenize**：仅生成 WORD token（不生成 SPACE token），以空白分隔；标点按“极简派”保留在词内
- **TC tokenize**：本模块只定义接口，具体分割由 `grapheme-segmentation` 提供
- **EN 关键词命中**：`lowercase → strip 两端常见标点 → 等值比较`，禁止 contains/substring
- **确定性比较**：breaks 字典序比较按“逐项比较 + 公共前缀相同则更短者更小”

## 3. 目录与产物

本子模块目录：

- `spec_kit/Text Wrap/modules/core-contract/spec.md`
- `spec_kit/Text Wrap/modules/core-contract/plan.md`（本文）

建议未来代码落位（实现阶段再定，不在本计划强制）：

- `client/src/features/textWrap/core/`（或 `client/src/utils/textWrap/`）

## 4. 设计与实现要点（按落地顺序）

### 4.1 文本预处理：normalizeWhitespace

实现 `normalizeWhitespace(text) -> { normalizedText, hadMultiWhitespace }`：

- 规则：
  - 把任意连续空白（空格/制表/换行等）折叠为单个空格
  - 去除首尾空白
- 注意：
  - 该规范会改变输入文本；必须作为“算法契约”的一部分固定下来
  - 若后续产品需要保留原始空白，则走 `PRESERVE` 分支并输出 `rawSeparators`（见 4.3）

### 4.2 EN tokenize：word tokens（极简派）

实现 `tokenizeEN(normalizedText) -> tokens[]`：

- 以空格分隔生成 token
- token 只包含 WORD，标点视为词内字符（例如 `tired.`、`Wait...`、`hello—world`、`don't` 都是单 token）
- 输出 token 的 `text/start/end`（start/end 为原始或 normalized 的字符区间，需固定口径；建议以 normalizedText 为基准）

### 4.3 文本重组：joinTokens

实现 `joinTokens(tokens, start, end, separators?) -> string`：

- EN 默认：使用单空格 `" "` join `[start..end)` 的 token.text
- 若 `whitespacePolicy=PRESERVE`：
  - 需要 `rawSeparators[i]` 表示 tokens[i] 与 tokens[i+1] 间的原始分隔符
  - 重组时按 separators 拼接（本计划仅定义接口与行为）

### 4.4 EN 关键词命中：normalizeENKeyword + matchENKeyword

实现：

- `normalizeENKeyword(tokenText) -> string`
  - `lowercase`
  - `strip` 两端常见标点（集合需配置化并全端一致，默认参考文档：`, . ! ? : ; " ' … — – ( ) [ ] { }`）
- `matchENKeyword(tokenText, keyword) -> boolean`
  - `normalizeENKeyword(tokenText) === normalizeENKeyword(keyword)`
  - 禁止 `includes/contains` 类 substring 命中

### 4.5 断点/区间的索引契约

固化约定（后续模块必须复用，不得自行发挥）：

- token 索引：`tokens[0..N-1]`
- 断点 `pos`：位于 token 边界，切分为 `[0..pos)` 与 `[pos..N)`
- 行区间：`[start..end)` 表示 `tokens[start] ... tokens[end-1]`

## 5. 回归用例与验证方式

### 5.1 必测示例（EN）

- `"I am so tired"` → tokens=`[I, am, so, tired]`
  - `pos=2` → `"I am"` / `"so tired"`
- 标点极简派：
  - `"tired."` 为单 token
- 关键词命中：
  - keyword=`"but"`：`"but,"` 命中；`"rebuttal"` 不命中

### 5.2 确定性检查

- 同一输入在同一配置下多次调用：
  - `normalizedText`、`tokens[]`、`joinTokens()`、`matchENKeyword()` 输出完全一致

## 6. 风险与规避

- **start/end 索引口径漂移**：若不同端选择以原始 text 或 normalizedText 计数，可能导致 span 对不齐
  - 规避：本模块明确 start/end 以 normalizedText 为准（或在实现阶段统一选择一种并写入 README/注释）
- **标点集合不一致**：strip 集合若跨端不同会导致命中差异
  - 规避：将 `punctuationStripSetEN` 写入配置并版本化，禁止散落常量

## 7. 完成定义（DoD）

- `core-contract/spec.md` 中定义的输入/输出与验收条目均有可运行的最小实现或可验证的约束说明
- EN tokenize / 空白归一化 / 关键词命中 / breaks 字典序比较口径写清楚且可复现
- 关键示例用例可在本地/CI 以单元测试或脚本方式验证（实现阶段落地）

