diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 2c935e3..6df0eba 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -74,3 +74,5 @@ | P12 窄屏溢出修复与打磨批 | `docs/specs/2026-10-02-p12-narrow-viewport-overflow-design.md` | `docs/plans/2026-10-02-p12-narrow-viewport-overflow.md`(已执行,2026-10-02:crearte 0.25.0 / server 0.17.1 / deploy 0.7.1) | | P13 暗色模式 | `docs/specs/2026-10-03-p13-dark-mode-design.md` | `docs/plans/2026-10-03-p13-dark-mode.md`(已执行,2026-10-03:crearte 0.26.0) | | P14 拆分 ContentService god object | `docs/specs/2026-10-03-p14-content-service-split-design.md` | `docs/plans/2026-10-03-p14-content-service-split.md`(已执行,2026-10-03:crearte-server `fe810dd` 0.18.0) | +| P15-A 拆分 Approve 七阶段 | `docs/specs/2026-10-03-p15a-approve-phases-design.md` | `docs/plans/2026-10-03-p15a-approve-phases.md`(进行中,2026-10-03:crearte-server) | +| P15-B bootstrap 暗色 + 守卫补钉 + 死令牌清理 | `docs/specs/2026-10-03-p15b-bootstrap-dark-design.md` | `docs/plans/2026-10-03-p15b-bootstrap-dark.md`(进行中,2026-10-03:crearte) | diff --git a/docs/plans/2026-10-03-p15a-approve-phases.md b/docs/plans/2026-10-03-p15a-approve-phases.md new file mode 100644 index 0000000..cb8ff98 --- /dev/null +++ b/docs/plans/2026-10-03-p15a-approve-phases.md @@ -0,0 +1,123 @@ +# P15-A 实现计划:拆分 `Approve` 169 行七阶段 + +- **spec(权威)**:`docs/specs/2026-10-03-p15a-approve-phases-design.md` +- **取证账本**:`crearte-server/.superpowers/sdd-p15a/survey.md`(控制者派发前落盘;P15 前期取证在 `crearte/.superpowers/sdd-p15/survey.md` §8.1) +- **仓**:`crearte-server`(**仅此一个**)· **base** `fe810dd`(0.18.0,master)· 分支 `chore/p15a-approve-phases` + > ⚠️ 分支前缀用 `chore/`:AGENTS.md 只允许 `{feat|fix|docs|chore}/` 四种(**`refactor/` 不允许**,P14 控制者曾写错)。纯结构重构归 `chore/`。 + > ⚠️ inner repo 红线:**不得在 `master` 上提交**。`git commit` 前先 `git branch --show-current` 确认。 +- **性质**:纯结构重构,**零行为变更**(spec D-G) +- **目标版本**:crearte-server **0.19.0** · wrapper **0.3.8** +- **派发**:**一个**实现者子代理做完 T1→T3(所有任务同动单一工作树与 git index,`sdd-parallel-dispatch` §1 一仓一写者) +- **并行**:P15-B 在 `crearte` 仓同时进行 → **不得触碰 `crearte/`、`crearte-deploy/`、wrapper 的任何文件** + +> spec 与本计划冲突时 **spec 赢**。spec 全文(§0–§8)必读,尤其 **§1.2(④⑥不同形,禁止合并)**、§1.3(helper 形态实测)、§2 决策表 D-A…D-I、§3.2(逐阶段要求,含 6 处"逐字保留")、§5-T1(mutation a–h 及 (b) 的已知盲区警告)、§7 风险表、§8 纪律 10 条。 + +--- + +## 任务表 + +| 任务 | 交付物 | 要求 | 完成判据 | +|---|---|---|---| +| **T1** | `internal/service/function_length_test.go`(**新增文件**,不改 `architecture_test.go`) | **RED 先行**(spec §5-T1)。三条断言:① `TestNoOversizedFunction`:`service/` 内非测试 `.go` 无任何函数 >100 行(阈值依据见 spec §0 普查:当前只有 `Approve` 169 超过,次大 83)② `TestApproveIsSmall`:`Approve` <60 行 ③ `TestApproveHelpersExist`:五个 helper 名存在——**必须用源码文本匹配,不能用 reflect**(spec §1.3:未导出标识符 reflect 不可见)。断言内须含"至少扫到 N 个 `.go` 文件"的前提检查(spec §5 mutation (e) 的教训) | RED 输出**逐字存证** `.superpowers/sdd-p15a/impl-evidence/t1-red.txt`,且**红的原因是断言失败而非编译错误**(与 P14 T1 的编译红形态**不同**:本批引用的都是既有类型)。三条各自的红须可分辨 | +| **T2** | `internal/service/moderation.go`(改) | 按 spec §3.1 骨架 + §3.2 逐阶段要求执行拆分:`loadApprovePlan`(①②③) / `precheckApprove`(④) / `copyApprovalObjects`(⑤,**收 `*approvePlan`**) / `applyApprovalInTx`(⑥,**包级函数**收 `tx repository.ContentRepository`) / `cleanupApprovalUploads`(⑦) + 包级私有 struct `approvePlan`(spec D-B 七字段)。**六处"逐字保留"**见 spec §3.2 的 ⚠️ 标注 | `go test ./... -count=1` **11 包全绿**;**既有 `*_test.go` 修改数 = 0**;`architecture_test.go` **修改行数 = 0**;P14 七断言 `-v` 全 PASS | +| **T3** | 字节级保真证明(加做项,P14 同款) | 写脚本对 base `fe810dd` 的 `Approve` 函数体与新树的 `Approve`+5 helper 做**语句级比对**(归一化缩进与接收者前缀),产出 `verbatim` / `changed`(逐条给理由)/ `missing`(**必须 0**) 三张清单。**脚本的三个已知坑见 spec §5-T3 的 ⚠️**(字符串字面量里的 `//` 被当注释、单行 `var` 须提前终止、意外值先怀疑自己的模式) | 三份清单存证 `.superpowers/sdd-p15a/impl-evidence/t3-verbatim.txt`;`missing = 0`;`changed` 每条有理由且都落在 spec §3.2 允许的范围内 | + +--- + +## 验收(控制者独立复跑,不采信自报) + +### 四门(dockerized Go,AGENTS.md 硬红线) + +```bash +cd crearte-server && docker run --rm -v "$PWD/src:/src" -w /src \ + -v crearte_gomod:/go/pkg/mod -e GOCACHE=/gocache -v crearte_gocache:/gocache \ + -e GOPROXY=https://goproxy.cn,direct -e GOSUMDB=sum.golang.google.cn \ + golang:1.24-alpine sh -c 'gofmt -l . ; go vet ./... ; go build ./... ; go test ./... -count=1' +``` + +基线(spec §0,已实测):`gofmt -l .` 空 · `go vet` exit 0 · `go build` exit 0 · `go test ./... -count=1` **11 包 ok**(api ~11.0s 真库集成、service ~3.7s)。 + +⚠️ **host Go 1.18 不可用**;**必须带 `GOPROXY=https://goproxy.cn,direct`**(`proxy.golang.org` 本机不可达,默认下载**挂死并杀代理**);冷构建 1–3 min → `background: true` + 耐心 poll,**不要 sleep 循环、不要因没立刻返回就断定失败**。 +⚠️ **`-count=1` 强制实跑**(禁缓存)。 +⚠️ **落盘证据用 `setsid nohup` 脱离 + 宿主侧重定向**(容器只挂 `/src` 写不了 `.superpowers/`;exec session 一断容器就被杀)。 +⚠️ **`grep -c` 输出 0 时 exit 1**,放 `&&` 链里会静默中断后续步骤(含 `git commit`)。 +⚠️ **`pkill -f ` 会匹配到自己的命令行**(P14 控制者把自己 SIGKILL)→ 用 `[p]attern` 括号技巧。 + +### 结构指标(spec §5 表,交付时须报实测数字,**一律 `wc -l` 口径**) + +| 指标 | 基线 | 目标 | +|---|---|---| +| `Approve` 行数 | **169** | **< 60** | +| `service/` 包内 >100 行的函数数 | **1** | **0** | +| `service/` 包内最大单函数行数 | **169** | **< 100**(次大者 83,故落点应在 83–100) | +| `moderation.go` 行数 | 298 | **不作指标**(spec D-H:helper 同文件,可能不降反升;报了即可) | +| 既有 `*_test.go` 修改文件数 | — | **0** | +| `architecture_test.go` 修改行数 | — | **0** | +| `Approve` 签名 | `(ctx context.Context, adminID, submissionID, note string) error` | **逐字不变** | +| 字节级比对 `missing` | — | **0** | + +> ⚠️ **不要用 `len(text.split('\n'))` 数行数**:文件以换行结尾时它多算一个空段。P14 F5 就因此让控制者报出 83/299 而真值是 82/298,被任务级审查者抓到。`wc -l` 是本仓钉死的口径(P14 spec §5 RE-PIN)。 + +### mutation 抽查(spec §5-T1 的 a–h,控制者独立复现,不复用实现者结果) + +**八条**,每条测"预期红 + 其余绿 + **非编译红** + 恢复证明": + +| # | mutation | 预期 | +|---|---|---| +| (a) | helper 存在但 `Approve` 不调它、改为原地展开 | `TestApproveIsSmall` 红 | +| (b) | 构造一个 **>100 行**的 helper(⚠️ spec §5 已警告:把⑥整体 80 行搬进 `applyApprovalInTx` **不会红**,那是阈值 100 的已知盲区,不是守卫失效) | `TestNoOversizedFunction` 红 | +| (c) | 删掉一个 helper(内容合并进 `Approve`) | `TestApproveHelpersExist` 红 | +| (d) | helper 改名(如 `loadApprovePlan`→`loadApproveInputs`) | `TestApproveHelpersExist` 红(**(c) 的对照**:证明钉的是名字集合而非"存在任意 5 个函数") | +| (e) | 扫描范围改成空目录 | 红(防"扫 0 文件报 0 违规") | +| (f) | 阈值 100 → 1000 | 红(防阈值被放宽;断言须自证阈值字面量) | +| (g) | **给 `ModerationService` 加字段**(`clock func() time.Time`) | **P14 断言 6 `TestLineFields` 红**(跨批回归) | +| (h) | **在 `ContentService` 上加方法** | **P14 断言 5 红**,且**须同时测有名与匿名两种接收者形态**(P14 FR-1 教训:只测有名会漏掉 `func (*ContentService) M()`) | + +⚠️ **`restore()` 只回滚 mutation 触及的文件,绝不用 `git checkout -- .`**(P14 控制者第 24 次自伤:全量回滚抹掉了未提交的守卫编辑,导致后四轮全在测没有修复的树、汇总报"绿=3")。每轮恢复后核 `git diff HEAD --exit-code -- ` 空 + 守卫文件 `func Test` 计数不变。 +⚠️ **mutation 若产生编译错误,不算"守卫有牙"**(P14 控制者第 22 次错误:正则截断签名致编译红,却被报成"守卫拦住了遮蔽")。 + +### 行为不变的额外证据 + +- **既有 13 个测试函数就是行为守卫**(spec §1.6 表):`TestApproveConcurrentOnlyOneWins`(⑥行锁+重校)· `TestApproveSameWorkIDConcurrent`(④⑥的 `ErrWorkIDTaken`,断言文本含 `want ErrWorkIDTaken or ErrSubmissionConflict`)· `TestApproveMetadataChangeFeaturesSemantics`(⑥ 的 `Features` **三态语义**,用 `meta-absent` 与 `,"features":{}` 两个 payload 精确区分)· `TestApproveOptionalFieldsRoundTrip` · `TestMetadataChangeRuntimeImmutable` · `TestNewVersionMetadataChangeOwnership` · api 层 5 个 + 2 个真库发布链 +- **错误文案逐字比对**:`service: load submission: %w` / `service: resolve submitter: %w` / **`service: approve precheck: %w`(两处,只在④)** / `service: copy bundle: %w` / `service: copy cover: %w`;以及 `ErrContentNotFound`(①) vs **`ErrSubmissionConflict`(⑥ 的 `GetByIDForUpdate` 未找到)** 这个**刻意的差异**(spec §3.2 ⑥) +- **两条 slog 逐字**:`slog.Error("approve: pending delete failed", "key", key, "error", err)`(⑦ helper 内)· `slog.Info("approve", "submission", …, "work", …, "kind", …, "admin", …)`(**留在 `Approve` 本体**,spec §3.2 ⑦) +- **`Approve` 只有一个非测试调用点** `handler/admin.go:62`(spec §1.7)→ helper 无须导出;签名变更会打破 `moderationPort` 满足性 → `serve.go` 编译红 + +--- + +## 边界(**不做**,spec §4) + +- **不合并④⑥的 switch**(spec §1.2 四处实质差异 + 乐观/悲观语义 + 错误文案不同;被否方案 A/B) +- 不动 `Reject`/`Unpublish`/`Republish`/`UpdateWorkFeatures`/`ListQueue`(同文件其它方法) +- 不动 `deleteAccountLocked`(83)/`UpdateSubmission`(76)/`ValidateWorkFields`(75)/`ImportDir`(70)/`Run`(70)/`checkSubmissionRules`(66) —— 六个 >60 行函数各自独立成批(spec §4 挂账) +- 不新增/修改任何测试(既有测试即守卫,spec §2.1 被否方案 D) +- **不改 `architecture_test.go`**(P14 七断言)——若某条因新 helper 变红,**停下来报告**,不自行改守卫 +- 不碰 `docs/`、`go.mod`/`go.sum`(实现者红线;记账由控制者做) +- 不碰其它三个仓 + +--- + +## 记账(控制者做,实现者禁动 `docs/`) + +- `crearte-server/docs/CHANGELOG.md` 新增 **`## [0.19.0] - 2026-10-03`**(插 `## [0.18.0]` 前) +- wrapper `docs/CHANGELOG.md` 新增 **`## [0.3.8] - 2026-10-03`**(插 `## [0.3.7]` 前),小节 `### Done / 完成`;**与 P15-B 合并为一条还是分两条按合并时序定** +- `docs/ROADMAP.md`:第三波表新增 **P15-A 行** + 文档索引表新增一行(**哈希引 merge commit**,P12/P13/P14 惯例) +- **挂账新增**:六个 >60 行函数 · `TestNoOversizedFunction` 阈值 100 的已知盲区(80 行 helper 不被拦) +- 格式:**同条目英文行紧跟中文行、无空行**;不同条目**空一行**;小节标题双语(`### Changed / 变更`、`### Tests / 测试`、`### Context / 背景`) +- **纪律**:显式 `git add `,**禁 `git add -A`**;wrapper 的 `IDENTITY.md`/`SOUL.md`/`USER.md` 三个未跟踪文件**绝不 stage** +- **合并**:CHANGELOG 改动提交到**特性分支** → `git checkout master` → `git merge --no-ff` → 记 merge 哈希 → 推送(**禁管道**)→ `git ls-remote origin master` **非空**对账(`-z` 检查,防空变量假 MATCH)→ 删分支 + +--- + +## 审查阶梯(P11–P14 惯例,不可省) + +1. **实现者自报** + 证据落盘(RED 输出、四门、mutation、字节级比对) +2. **控制者独立核实**(全部自己跑,不采信自报数字;P14 控制者因此抓到实现者报告可信度高、也抓到自己 6 处错) +3. **任务级审查者**(只读,**自设 mutation 不照抄 spec 的 a–h**,审守卫恒真/恒假、跨任务缝隙、spec 自身问题) + - P13 先例:10 条自设 mutation 挖出 5 条 findings,**M1 抓到控制者自己写错两次的恒真守卫** + - P14 先例:抓到 **spec D-J 的守卫目的无断言覆盖** + **spec 里 positive control 的绕行路径** + 控制者的行数口径错 +4. **控制者逐条裁定**(`sdd-pre-merge-review` §3:**未裁定的 note 阻塞合并**,"PASS with notes" 不等于门过了) +5. **全分支终审者**(只读;spec 全文自洽性 + **审控制者的裁定工作** + 独立复现 mutation) + - P11 先例:终审裁定过「**需修复后合并**」→ **不是橡皮章** + - P14 先例:终审在**第一轮修复自身**里找到洞(匿名接收者绕过源码扫描),并**推翻控制者两处已入库的全称声明** +6. **spec re-pin**(裁定后一次性批量改,**勿在审查者读 spec 期间改**=移动靶);**代码改了 spec 必须同步**(P14:正则字面量写进 spec,改代码后 spec 立刻不一致) diff --git a/docs/plans/2026-10-03-p15b-bootstrap-dark.md b/docs/plans/2026-10-03-p15b-bootstrap-dark.md new file mode 100644 index 0000000..0811833 --- /dev/null +++ b/docs/plans/2026-10-03-p15b-bootstrap-dark.md @@ -0,0 +1,133 @@ +# P15-B 实现计划:bootstrap 加载屏暗色 + 对比度守卫补钉 + 死令牌清理 + +- **spec(权威)**:`docs/specs/2026-10-03-p15b-bootstrap-dark-design.md` +- **取证账本**:`crearte/.superpowers/sdd-p15/survey.md`(§2/§3/§5/§7/§8 全部实测;含"叉积当违规清单"与"三元互斥分支当同元素共现"两次自我纠错) +- **仓**:`crearte`(**仅此一个**)· **base** `f823195`(master)· 分支 `feat/p15b-bootstrap-dark-and-guard-pins` + > ⚠️ 分支前缀用 `feat/`(AGENTS.md 只允许 `{feat|fix|docs|chore}/`)。本批含用户可感知的暗色改动 → `feat/`;若实现者认为纯守卫部分占主体,**也不得改用 `refactor/`**(不在允许集内)。 + > ⚠️ inner repo 红线:**不得在 `master` 上提交**。`git commit` 前先 `git branch --show-current` 确认。 +- **性质**:用户可见改动(加载屏暗色)+ 守卫补强 + 死代码清理,**三者一批**(spec D-A…D-H) +- **目标版本**:crearte **0.27.0** · wrapper **0.3.8** +- **派发**:**一个**实现者子代理做完 T1→T4(所有任务同动 crearte 单一工作树与 git index,`sdd-parallel-dispatch` §1 一仓一写者) +- **并行**:P15-A 在 `crearte-server` 仓同时进行 → **不得触碰 `crearte-server/`、`crearte-deploy/`、wrapper 的任何文件** + +> spec 与本计划冲突时 **spec 赢**。spec 全文(§0–§8)必读,尤其 **§1.1 的 🔴 架构陷阱(响应式注入会让运行中的游戏被重载)**、§1.3 的连带面表(5 文件)、§1.4(为何不能扩 `noHardcodedColor`)、§2.1 被否方案 A–F、§5-T1 的 10 腿清单 + ⚠️ 恒真审视三条、§8 纪律 12 条(前端 4 条加粗)。 + +--- + +## 关键路径约定(与后端不同,勿照搬 P15-A) + +- **前端源码与 `package.json` 都在 `crearte/src/`,不在仓根**。所有 `npx`/`npm` 命令须 `cd crearte/src`。(P15 取证期控制者两次因 `cd` 层级写错路径而拿到假结果:`cd` 仓根却写 `app/...`(真根 `src/app/...`)致 grep 全失败输出 `bg-surface=0`;`ls ../e2e` 猜错致空输出。**任何"零命中"结论都要先证明扫描范围非空。**) +- **测试在宿主机跑 `npx vitest run`,不进容器**(P13 plan 第 58 行同款先例:`npx vitest run app/lib/tableOverflow.test.ts app/lib/tableScope.test.ts`)。`crearte/src/node_modules` 已装(232M)。AGENTS.md 的"一切在容器内"针对的是 compose 栈与应用服务,**不针对前端单测**;e2e 需要浏览器,同样在宿主机跑。 +- **vitest 环境是 node**(`src/vite.config.ts` 的 `test` 段只有 `exclude`,**无 `environment` 键**)→ 源码级守卫可用 `fileURLToPath(new URL('../..', import.meta.url))` 读文件。**P13 教训:happy-dom 下该写法抛 `ERR_INVALID_URL_SCHEME`**;本批新增守卫**不得引入需要 DOM 的断言**。 + +--- + +## 任务表 + +| 任务 | 交付物 | 要求 | 完成判据 | +|---|---|---|---| +| **T1** | `app/lib/bootstrapTheme.test.ts`(**新增**) | **RED 先行**(spec §5-T1)。**10 腿**:①pre-paint 脚本在 `