更新换行算法和APP-PUSH
This commit is contained in:
179
spec_kit/Text Wrap/modules/scoring-tiebreak/plan.md
Normal file
179
spec_kit/Text Wrap/modules/scoring-tiebreak/plan.md
Normal file
@@ -0,0 +1,179 @@
|
||||
# scoring-tiebreak(技术计划)
|
||||
|
||||
## 1. 计划目标
|
||||
|
||||
基于 `spec.md` 与 `设计说明文档/文档换行算法.md v1.2.1`,在客户端实现可解释的评分模型与确定性 tie-break,保证:
|
||||
|
||||
- **整数评分**:所有评分项使用整数,避免浮点误差
|
||||
- **权重写死客户端**:首版权重/词表内置在客户端代码(后续如需灰度,用 `configVersion` 管理)
|
||||
- **短语匹配口径固定**:emotionPhrases/protectedPhrases 必须是“连续 token 完全匹配”
|
||||
- **更长短语优先**:采用方案 A——在触发拆分惩罚时按“被拆分短语长度”追加惩罚(确定性)
|
||||
- **debug 可解释**:输出 score breakdown;debug=true 时输出 Top-3,按规则优先级排序(不是按 |delta|)
|
||||
- **tieKey 固定**:严格按文档第 11 节顺序构造 tieKey;Widget approx mode 仍使用数值 width(可为近似单位),确保 tie-break 可用
|
||||
|
||||
本模块不负责搜索(DP/Beam),只负责对“候选 layout”打分并给出可比较的 tieKey。
|
||||
|
||||
## 2. 默认技术决策(本计划采用)
|
||||
|
||||
### 2.1 评分数值体系
|
||||
|
||||
- 全部评分项使用 **整数**
|
||||
- 不引入 EPS(因为不使用浮点);分数相等即为相等
|
||||
|
||||
### 2.2 权重与默认值(写死客户端)
|
||||
|
||||
首版采用文档 10.3 的建议量级(可在实现中集中定义为常量/配置对象):
|
||||
|
||||
- `P_EMOTION_SPLIT = 10000`
|
||||
- `P_PROTECTED_SPLIT = 10000`
|
||||
- `P_WIDOW_WORD = 800`
|
||||
- `P_WIDOW_LINE = 500`
|
||||
- `P_SHORT_LASTLINE = 300`
|
||||
- `P_PARTICLE_ISO = 200`
|
||||
- `R_PUNCT_BREAK = 80`
|
||||
- `R_SHIFT_BREAK = 60`
|
||||
- `R_ACCUM_BREAK = 40`
|
||||
- `R_SELF_BREAK = 20`
|
||||
- `P_OVER_MAXLEN = 30`
|
||||
- `P_TOO_SHORT = 10`
|
||||
|
||||
> 说明:权重必须集中在一个文件,禁止散落在各函数内;后续调整通过 `configVersion` 记录。
|
||||
|
||||
### 2.3 最小词表(TC/EN)
|
||||
|
||||
首版提供最小可用词表(来源:文档第 7 节):
|
||||
|
||||
- TC:
|
||||
- shift:`但/可是/然而/却/只是/偏偏`
|
||||
- accum:`已经/一直/曾经/终于/还是/到现在`
|
||||
- self:`你/我/自己/我们/别人`
|
||||
- emotionWords(可选):`累/痛/怕/孤单/委屈/撑/崩溃/放弃`
|
||||
- EN(全词等值匹配,使用 core-contract 的 normalize 规则):
|
||||
- shift:`but/yet/so`(and 轻量可选)
|
||||
- accum:`already/still/even/just/really`
|
||||
- self:`you/yourself/me/we`
|
||||
- emotionWords(可选):`tired/afraid/lonely/hurt/overwhelmed/give up`
|
||||
|
||||
emotionPhrasesTC/EN 与 protectedPhrases:
|
||||
|
||||
- 首版允许为空数组(先把匹配与惩罚机制写死)
|
||||
- 若业务侧已有短语清单,后续直接填充并通过 Golden Cases 回归
|
||||
|
||||
### 2.4 宽度派(tieKey 使用 width)
|
||||
|
||||
- layoutCandidate.lines[*].width 字段作为 tieKey 的 width 来源
|
||||
- 在 width unknown/approx mode 时,上游仍需给出“数值宽度”(例如用 wordCount/graphemeCount 作为 width 近似值),保证 tie-break 可执行
|
||||
|
||||
## 3. 目录与产物(客户端侧实现)
|
||||
|
||||
建议代码落位:
|
||||
|
||||
- `client/src/features/textWrap/scoring/`
|
||||
- `types.ts`(Layout/LineSegment/ScoreBreakdown)
|
||||
- `weights.ts`(权重常量,写死)
|
||||
- `lexicons.ts`(最小词表,写死)
|
||||
- `phraseMatch.ts`(连续 token 完全匹配 + span 预计算)
|
||||
- `score.ts`(按 10.1 顺序累计分数)
|
||||
- `tieKey.ts`(按 11 节构造 tieKey)
|
||||
- `debug.ts`(Top-3 提取与排序)
|
||||
- `index.ts`
|
||||
- `__tests__/scoringTiebreak.test.ts`
|
||||
|
||||
## 4. 输入/输出契约(实现阶段写死)
|
||||
|
||||
### 4.1 输入
|
||||
|
||||
- `layoutCandidate`:
|
||||
- `breaks: number[]`
|
||||
- `lines: Array<{ start; end; text; width; tokenCount; charCount }>`
|
||||
- `lang: 'TC' | 'EN'`
|
||||
- `context: 'APP' | 'WIDGET'`
|
||||
- `config`:
|
||||
- `weights: Record<string, number>`(首版由内置默认值生成)
|
||||
- `idealWidthRatio: { APP: 0.90; WIDGET: 0.95 }`(来自文档 8.3A)
|
||||
- `ellipsisToken: "…"`(用于溢出模块;本模块只保留配置入口)
|
||||
- `particleWhitelistTC: string[]`(语尾语助词白名单;惩罚减半)
|
||||
- `lexicons`(首版使用内置最小词表;允许调用方覆盖)
|
||||
|
||||
### 4.2 输出
|
||||
|
||||
- `scoredLayout`:
|
||||
- `score: number`
|
||||
- `flags: { emotionSplit?: boolean; overflowed?: boolean; fallback?: boolean }`
|
||||
- `tieKey: Array<number | string>`
|
||||
- `scoreBreakdown?: { total: number; terms: Array<{ key: string; delta: number; detail?: any }> }`
|
||||
|
||||
## 5. 实现步骤(按落地顺序)
|
||||
|
||||
### 5.1 Phrase 匹配与 span 预计算(连续 token 完全匹配)
|
||||
|
||||
实现口径(必须):
|
||||
|
||||
- EN:对输入 tokens 使用 `core-contract` 的 `normalizeWhitespace + tokenizeEN`
|
||||
- TC:对输入 tokens 使用 `grapheme-segmentation` 的 clusters
|
||||
- 对 phrase:
|
||||
- EN:先 normalizeWhitespace,再按空格切分为词序列(不做 substring)
|
||||
- TC:按 grapheme clusters 切分
|
||||
- 匹配方式:在 token 序列中寻找 **连续区间** 完全匹配
|
||||
- 输出 spans:`{ start, end, length }`(end 为半开区间)
|
||||
|
||||
### 5.2 评分项(严格按 10.1 顺序累计)
|
||||
|
||||
按文档 10.1 固定顺序计算并记录 breakdown:
|
||||
|
||||
1. **情绪短语/受保护短语拆分惩罚**(最高优先)
|
||||
- 若某 phrase span 被 breaks 拆到不同的行:
|
||||
- `score -= P_*`
|
||||
- 并按方案 A 追加惩罚:`score -= phraseLength`(或 `k * phraseLength`,k 写死为 1,确保确定性)
|
||||
- flags:`emotionSplit=true`(或 protectedSplit 也可复用同一 flag/额外字段,需写死)
|
||||
2. **行长度(ideal/over/too short)**
|
||||
- `idealWidth = availableWidth * idealWidthRatio[context]`(availableWidth 由上游传入)
|
||||
- 以 width-based 作为主要项:`score += -abs(line.width - idealWidth)`
|
||||
- overMaxLen/tooShort 按配置阈值与权重惩罚(首版可先按文档默认区间落地)
|
||||
3. **Widow / 单字行惩罚**
|
||||
4. **TC 标点断点奖励**
|
||||
5. **Shift/Accum/Self 断点奖励(含 EN 行首/行尾感知)**
|
||||
6. **尾行过短惩罚(SHORT_LASTLINE)**
|
||||
|
||||
> 注意:必须保留每一项的确定性记录,不允许“覆盖”前序裁决结果(见文档 10.1A)。
|
||||
|
||||
### 5.3 Debug Top-3 输出
|
||||
|
||||
- 输出结构:`ScoreBreakdown = { total, terms[] }`
|
||||
- debug=true 时只保留 Top-3 term,但排序规则必须按优先级(10.1 顺序),同优先级再按出现顺序稳定排序
|
||||
|
||||
### 5.4 tieKey 构造(按 11 节固定顺序)
|
||||
|
||||
按文档顺序生成 tieKey(比较时逐项比较):
|
||||
|
||||
1. `emotionSplit=false` 优先(可用 `0/1`)
|
||||
2. `overflowed=false` 优先
|
||||
3. `lastLineWidth` 更大优先(因此 tieKey 可存 `-lastLineWidth` 或比较时反向)
|
||||
4. 行宽分布更均匀优先(`maxWidth-minWidth` 更小优先)
|
||||
5. 断点更接近理想切分点优先(距离和更小)
|
||||
6. breaks 字典序更靠前优先(使用 core-contract 的 breaks 比较规则)
|
||||
|
||||
## 6. 测试计划(Vitest)
|
||||
|
||||
### 6.1 Phrase 匹配
|
||||
|
||||
- EN:确保全词等值匹配(`but,` 命中 `but`;`rebuttal` 不命中)
|
||||
- TC:emoji/组合字符的 phrase 不被拆(依赖 grapheme-segmentation 已覆盖)
|
||||
|
||||
### 6.2 评分顺序与 breakdown
|
||||
|
||||
- 构造触发 EmotionSplit 的 layout,断言 breakdown 中第一项为 EMOTION_SPLIT(或对应 key),且其记录不会被后续项覆盖
|
||||
|
||||
### 6.3 tieKey 确定性
|
||||
|
||||
- 构造同分 layout,验证 tieKey 顺序能稳定选出同一个最优
|
||||
|
||||
## 7. 完成定义(DoD)
|
||||
|
||||
- 整数评分全链路跑通(无浮点)
|
||||
- phrase 连续 token 完全匹配实现完毕(EN/TC)
|
||||
- scoring 按 10.1 顺序累计,并输出可解释 breakdown
|
||||
- debug Top-3 按优先级输出
|
||||
- tieKey 按 11 节固定顺序构造与比较
|
||||
- 单测覆盖匹配、评分顺序、tieKey 稳定性
|
||||
|
||||
54
spec_kit/Text Wrap/modules/scoring-tiebreak/spec.md
Normal file
54
spec_kit/Text Wrap/modules/scoring-tiebreak/spec.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# scoring-tiebreak(子模块规范)
|
||||
|
||||
## 子模块名称
|
||||
|
||||
scoring-tiebreak(评分模型与确定性裁决)
|
||||
|
||||
## 目标描述
|
||||
|
||||
定义并实现可解释的评分模型(Scoring Model)与固定 tie-break 规则,用于在“合法断点组合”中选出最优布局(layout),并保证跨端完全一致。
|
||||
|
||||
本模块不负责搜索(DP/Beam),但负责:
|
||||
|
||||
- 每行/每断点的评分项计算顺序(必须固定)
|
||||
- 情绪短语/受保护短语的匹配口径(连续 token 完全匹配)
|
||||
- Debug breakdown 的结构与 Top-3 输出排序(按优先级而非 |delta|)
|
||||
- tieKey 的构造与比较规则(含 breaks 字典序定义)
|
||||
|
||||
## 输入/输出定义
|
||||
|
||||
### 输入
|
||||
|
||||
- `layoutCandidate: { breaks: number[]; lines: Array<{ start: number; end: number; text: string; width: number; tokenCount: number; charCount: number }> }`
|
||||
- `lang: 'TC' | 'EN'`
|
||||
- `context: 'APP' | 'WIDGET'`
|
||||
- `config: { weights: Record<string, number>; idealWidthRatio: { APP: number; WIDGET: number }; ellipsisToken: string; particleWhitelistTC: string[]; eps?: number }`
|
||||
- `lexicons: { emotionPhrasesTC: string[]; emotionPhrasesEN: string[]; protectedPhrases?: string[]; shiftWordsTC: string[]; shiftWordsEN: string[]; accumWordsTC: string[]; accumWordsEN: string[]; selfWordsTC: string[]; selfWordsEN: string[]; emotionWordsTC?: string[]; emotionWordsEN?: string[] }`
|
||||
|
||||
### 输出
|
||||
|
||||
- `scoredLayout: { score: number; flags: { emotionSplit?: boolean; overflowed?: boolean; fallback?: boolean }; tieKey: Array<number | string>; scoreBreakdown?: { total: number; terms: Array<{ key: string; delta: number; detail?: any }> } }`
|
||||
|
||||
## 验收标准(可验证)
|
||||
|
||||
- **评分项顺序固定**:评分必须严格按算法文档 10.1 的顺序计算并记录(不可重排)
|
||||
- **短语匹配口径正确**:
|
||||
- emotionPhrase/protectedPhrases:连续 token 完全匹配(不允许跳 token、不允许模糊)
|
||||
- EN 关键词命中遵循 `core-contract` 的“全词等值匹配”
|
||||
- **冲突裁决一致**:
|
||||
- emotionPhrases 与 protectedPhrases 同级强保护
|
||||
- 同等条件下更长短语优先(需在惩罚或 tieKey 中体现,且确定性)
|
||||
- **奖励折减规则一致**:
|
||||
- EmotionWord 与 Accum 同时命中时:Accum 奖励按 0.5 倍(先算 EmotionWord 再折减)
|
||||
- **tie-break 一致**:
|
||||
- 规则顺序固定(emotionSplit/overflowed/lastLineWidth/均衡度/理想距离/breaks 字典序)
|
||||
- breaks 字典序定义:逐项比较;公共前缀相同则更短数组更小
|
||||
- **debug Top-3 输出排序一致**:
|
||||
- debug 模式关键项按优先级输出(非 |delta|)
|
||||
- **整数分优先**:尽量使用整数评分;若使用浮点必须定义 EPS,并用 `abs(a-b)<=EPS` 判断近似相等
|
||||
|
||||
## 依赖与关联
|
||||
|
||||
- **依赖**:`core-contract`(索引、文本重组、EN 命中工具)
|
||||
- **被依赖**:`search-engine-app`、`search-engine-widget`、`overflow-fallback`、`golden-tests`
|
||||
|
||||
122
spec_kit/Text Wrap/modules/scoring-tiebreak/tasks.md
Normal file
122
spec_kit/Text Wrap/modules/scoring-tiebreak/tasks.md
Normal file
@@ -0,0 +1,122 @@
|
||||
# scoring-tiebreak(任务清单)
|
||||
|
||||
> 目标:实现“评分模型 + 确定性 tie-break”,用于对候选 layout 打分并产出可比较的 `tieKey`。
|
||||
> 约束:**整数评分**、**权重/最小词表写死客户端**、短语匹配为**连续 token 完全匹配**、debug Top-3 按**优先级顺序**输出。
|
||||
|
||||
## 0. 准备与对齐
|
||||
|
||||
- [x] 阅读并对齐口径
|
||||
- [x] 复核 `spec_kit/Text Wrap/modules/scoring-tiebreak/spec.md`
|
||||
- [x] 复核 `spec_kit/Text Wrap/modules/scoring-tiebreak/plan.md`
|
||||
- [x] 复核 `设计说明文档/文档换行算法.md` 中 10.x(评分)与 11(tie-break)章节的条目顺序
|
||||
- [x] 在客户端创建模块目录
|
||||
- [x] 新建 `client/src/features/textWrap/scoring/`
|
||||
- [x] 确认后续文件均落在该目录下,避免与 `core/`、`breakpoints/`、`measure/` 混放
|
||||
|
||||
## 1. 定义类型与对外接口
|
||||
|
||||
- [x] 新建 `client/src/features/textWrap/scoring/types.ts`
|
||||
- [x] 定义 `TextWrapContext = 'APP' | 'WIDGET'`
|
||||
- [x] 定义 `LayoutCandidate`(与 spec 一致:`breaks` + `lines[]`)
|
||||
- [x] 定义 `LineInfo`:`start/end/text/width/tokenCount/charCount`
|
||||
- [x] 定义 `ScoreTerm`:`{ key: string; delta: number; detail?: any }`
|
||||
- [x] 定义 `ScoreBreakdown`:`{ total: number; terms: ScoreTerm[] }`
|
||||
- [x] 定义 `ScoredLayout`:`{ score; flags; tieKey; scoreBreakdown? }`
|
||||
- [x] 明确评分函数需要的“可用宽度 availableWidth”传入方式(二选一,必须确定并全链路一致)
|
||||
- [x] 方案 A:作为 `scoreLayout({ availableWidth, ... })` 的必填字段
|
||||
- [x] 方案 B:作为 `config.availableWidth`(不推荐,但允许)
|
||||
- [x] 新建 `client/src/features/textWrap/scoring/index.ts`
|
||||
- [x] 统一导出 types 与核心函数(后续实现)
|
||||
|
||||
## 2. 权重与最小词表(写死客户端)
|
||||
|
||||
- [x] 新建 `client/src/features/textWrap/scoring/weights.ts`
|
||||
- [x] 以对象形式集中定义权重常量(全部整数)
|
||||
- [x] 写入 plan.md 里的首版默认值(P_/R_ 系列)
|
||||
- [x] 提供 `DEFAULT_WEIGHTS`(只读)与可选的 `mergeWeights(overrides)`(用于调用方覆盖)
|
||||
- [x] 新建 `client/src/features/textWrap/scoring/lexicons.ts`
|
||||
- [x] 定义 `Lexicons` 类型(与 spec 输入一致)
|
||||
- [x] 写入最小词表(TC/EN:shift/accum/self,emotionWords 可选)
|
||||
- [x] 提供 `DEFAULT_LEXICONS`(只读)与可选的 `mergeLexicons(overrides)`
|
||||
- [x] 明确 `emotionPhrasesTC/EN`、`protectedPhrases` 首版可为空数组
|
||||
|
||||
## 3. Phrase 匹配(连续 token 完全匹配)
|
||||
|
||||
- [x] 新建 `client/src/features/textWrap/scoring/phraseMatch.ts`
|
||||
- [x] 定义 `PhraseSpan = { start: number; end: number; length: number }`(end 半开)
|
||||
- [x] 实现 EN phrase 预处理
|
||||
- [x] 使用 `core-contract`:`normalizeWhitespace` + `tokenizeEN`
|
||||
- [x] phrase 输入为原始字符串:先 normalizeWhitespace,再按空格切分为词序列
|
||||
- [x] 实现 TC phrase 预处理
|
||||
- [x] 使用 `grapheme-segmentation`:`segmentGraphemes` 得到 clusters
|
||||
- [x] phrase 输入为原始字符串:按 grapheme clusters 切分
|
||||
- [x] 实现连续区间完全匹配查找(禁止跳 token / 禁止模糊)
|
||||
- [x] 输出所有命中的 spans(稳定顺序:按 start 升序,start 相同按 length 降序)
|
||||
- [x] 实现 `isSpanSplitByBreaks(span, breaks)`:判断 span 是否跨行(被拆分)
|
||||
- [x] 单测覆盖:
|
||||
- [x] EN:`but,` 命中 `but`;`rebuttal` 不命中 `but`
|
||||
- [x] TC:包含 emoji/组合字符时不应被拆(依赖 grapheme 模块;这里只验证匹配结果可逆)
|
||||
|
||||
## 4. 评分实现(按 10.1 固定顺序,整数累计)
|
||||
|
||||
- [x] 新建 `client/src/features/textWrap/scoring/score.ts`
|
||||
- [x] 定义 `scoreLayout(args)`(入参包含:layoutCandidate、lang、context、config、lexicons、availableWidth、debug?)
|
||||
- [x] 实现 score 累计(必须严格按文档 10.1 的顺序)
|
||||
- [x] 1) 情绪短语/受保护短语拆分惩罚(最高优先)
|
||||
- [x] 匹配 emotionPhrases + protectedPhrases spans
|
||||
- [x] 若 span 被拆分:`score -= P_*`
|
||||
- [x] 采用方案 A:追加“更长短语优先”惩罚:`score -= phraseLength`(k=1 写死)
|
||||
- [x] 设置 `flags.emotionSplit=true`(如需区分 protected 可扩展 detail,但保持确定性)
|
||||
- [x] 2) 行长度(ideal/overMaxLen/tooShort)
|
||||
- [x] `idealWidth = availableWidth * idealWidthRatio[context]`(取整规则必须固定:建议 `Math.round` 并写注释)
|
||||
- [x] width-based 主项:`score += -abs(line.width - idealWidth)`(确保整数)
|
||||
- [x] over/tooShort 按权重惩罚(阈值若来自文档,集中定义常量,禁止散落)
|
||||
- [x] 3) Widow / 单字行惩罚(基于 tokenCount/charCount 口径写死)
|
||||
- [x] 4) TC 标点断点奖励(使用断点 meta/kind 或基于行首字符推断;口径写注释)
|
||||
- [x] 5) Shift/Accum/Self 断点奖励(TC/EN 词表)
|
||||
- [x] EN 必须使用 `core-contract` 的全词等值匹配(复用 `normalizeENKeyword/matchENKeyword`)
|
||||
- [x] 实现 spec 要求的折减:EmotionWord 与 Accum 同时命中时,Accum 奖励按 0.5 倍
|
||||
- [x] 因为整体使用整数:折减采用“整除/四舍五入”的固定规则(建议 `Math.floor(reward/2)` 并写注释)
|
||||
- [x] 6) 尾行过短惩罚(SHORT_LASTLINE)
|
||||
- [x] 记录 `scoreBreakdown`
|
||||
- [x] 每个 term 写入 `{ key, delta, detail }`
|
||||
- [x] terms 的产生顺序必须与 10.1 顺序一致(用于 debug 输出排序)
|
||||
- [x] debug Top-3(不按 |delta|,按优先级)
|
||||
- [x] 若 `debug=true`:只保留前 3 个“优先级最高的关键项”
|
||||
- [x] 规则:先按 10.1 顺序,必要时同优先级保持稳定(按出现顺序)
|
||||
- [x] 单测覆盖:
|
||||
- [x] 评分顺序固定:构造 layout 触发 EmotionSplit,断言 breakdown 第一项为对应 key
|
||||
- [x] 整数性:断言所有 delta 与 total 都是整数
|
||||
|
||||
## 5. tieKey 构造与比较(按 11 节固定顺序)
|
||||
|
||||
- [x] 新建 `client/src/features/textWrap/scoring/tieKey.ts`
|
||||
- [x] 实现 `buildTieKey(scoredLayout, layoutCandidate, config)`,严格按 11 节顺序产出 `Array<number | string>`
|
||||
- [x] emotionSplit(false 优先)
|
||||
- [x] overflowed(false 优先)
|
||||
- [x] lastLineWidth(更大优先:采用 `-lastLineWidth` 进入 tieKey,或在比较函数中反向比较,二选一且写注释)
|
||||
- [x] 宽度分布均衡度:`maxWidth - minWidth`(更小优先)
|
||||
- [x] 与理想切分点距离(更小优先;距离定义需与 breakpoints 模块/上游一致)
|
||||
- [x] breaks 字典序(复用 `core-contract` 的 breaks 比较函数)
|
||||
- [x] 单测覆盖:
|
||||
- [x] 同分 layout:通过 tieKey 能稳定选出同一个最优
|
||||
- [x] Widget approx:即使 width 近似(wordCount/graphemeCount),tieKey 仍可比较(不抛错)
|
||||
|
||||
## 6. 组合导出与回归测试
|
||||
|
||||
- [x] 完成 `client/src/features/textWrap/scoring/index.ts` 导出
|
||||
- [x] 导出 `DEFAULT_WEIGHTS/DEFAULT_LEXICONS`
|
||||
- [x] 导出 `scoreLayout`、`buildTieKey`
|
||||
- [x] 新建 `client/src/features/textWrap/scoring/__tests__/scoringTiebreak.test.ts`
|
||||
- [x] 覆盖 EN/TC 两套 tokenization(EN 用 core,TC 用 grapheme)
|
||||
- [x] 覆盖短语拆分惩罚 + 方案 A 的“更长短语更重惩罚”
|
||||
- [x] 覆盖 debug Top-3 按优先级输出(非 |delta|)
|
||||
- [x] 覆盖 tieKey 顺序项的比较方向(尤其 lastLineWidth)
|
||||
|
||||
## 7. 文档与收尾(完成后必须做)
|
||||
|
||||
- [x] 将 `spec_kit/Text Wrap/modules/scoring-tiebreak/tasks.md` 全部任务勾选完成
|
||||
- [x] 更新 `spec_kit/overview.md`
|
||||
- [x] 标记 `scoring-tiebreak` 已完成编码(阶段性)
|
||||
- [x] 在 overview 中记录本次变更文件清单(至少包含新增的客户端文件与测试文件)
|
||||
|
||||
Reference in New Issue
Block a user