310 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
phase: 01-credential-slot-api
plan: 02
type: execute
wave: 2
depends_on:
- 01-01
files_modified:
- docs/修改记录.md
autonomous: true
requirements:
- CRED-FE-01
must_haves:
truths:
- "docs/修改记录.md 顶部(『修改历史』段第一条)追加 [2026-05-08] Phase 1 条目"
- "条目包含『配套服务端 Phase』『覆盖前端需求』前置元数据引用 commit 46d72b8"
- "条目跨项目联动字段写明:本 phase 不引入新跨项目契约,无需再次互引(后端 Phase 2 已建立互引)"
- "npm run lint 退出码为 0ESLint 检查 lib/api/credential-slot.ts 与 lib/api/index.ts"
- "npx tsc --noEmit 退出码为 0项目级类型检查全部通过含 plan 01 新增的 CredentialSlot / CredentialSlotUpdatePayload"
- "外部组件文件能成功 import { getCredentialSlot, type CredentialSlot } from '@/lib/api'(通过临时 .tsx 探针验证)"
artifacts:
- path: "docs/修改记录.md"
provides: "本仓库变更日志,顶部新增 Phase 1 条目"
contains:
- "[2026-05-08] Phase 1"
- "CRED-FE-01"
- "lib/api/credential-slot.ts"
- "lib/api/index.ts"
- "46d72b8"
- "accessTokenMasked"
- "accessToken"
key_links:
- from: "docs/修改记录.md 顶部新条目"
to: "qy_lty 后端 Phase 2 commit 46d72b8 已建立的前后端互引"
via: "条目『服务端联动』字段中文引用"
pattern: "46d72b8"
- from: "外部消费者Phase 2/3 组件文件)"
to: "lib/api 入口 barrel"
via: "import { getCredentialSlot, type CredentialSlot } from '@/lib/api'"
pattern: "from\\s+['\"]@/lib/api['\"]"
---
<objective>
为 plan 01 落地的代码补齐两件事:
1.`docs/修改记录.md` 顶部按项目「修改格式说明」追加一条 Phase 1 条目CLAUDE.md L70-95 强制规则)。
2. 跑两条**独立**验证命令,确认 plan 01 的代码质量:
- `npm run lint` —— ESLint 检查(`next lint`
- `npx tsc --noEmit` —— TypeScript 类型检查(**不**能省per RESEARCH 问题 7`npm run lint` 不跑 tsc
并通过一个临时探针 `.tsx` 文件验证外部消费者可以从 `@/lib/api` 入口解析新增的 4 个符号;探针验证完成后删除。
Purpose把 Phase 1 的「成功 = 代码 + 文档 + 类型可被消费」三件套全部落地,为 Phase 2/3 提供干净起点。
Output`docs/修改记录.md`修改plan 落地后无新代码文件残留(探针文件验证后删除)。
</objective>
<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/phases/01-credential-slot-api/01-CONTEXT.md
@.planning/phases/01-credential-slot-api/01-RESEARCH.md
@.planning/phases/01-credential-slot-api/01-01-SUMMARY.md
@CLAUDE.md
@docs/修改记录.md
@package.json
@next.config.mjs
@lib/api/credential-slot.ts
@lib/api/index.ts
</context>
<tasks>
<task type="auto" tdd="false">
<name>Task 1在 docs/修改记录.md 顶部追加 Phase 1 条目</name>
<read_first>
- `docs/修改记录.md` L1-50特别是 L9-20 头部「修改格式说明」 + L24-47 已存在的 [2026-05-07] Phase 2 条目作为格式模板)
- `CLAUDE.md` L70-95「项目修改记录规则」 — 强制每次代码改动追加到顶部、跨项目独立维护
- `.planning/phases/01-credential-slot-api/01-CONTEXT.md` <decisions> 节「修改记录」段L152-156— 锁定的跨项目联动文案
- `.planning/phases/01-credential-slot-api/01-RESEARCH.md` 「Code Examples」节内的「`docs/修改记录.md` 顶部追加条目」完整模板
</read_first>
<files>docs/修改记录.md</files>
<action>
`docs/修改记录.md` 中找到这一行:
```
<!-- 新的修改记录添加在此处下方,最新的在最前面 -->
```
在该注释行之后、紧贴现有 `### [2026-05-07] Phase 2 — 锁定后端通用凭据槽位 REST 接口契约(消费方文档化)` 条目之前,**插入**以下完整 Markdown 块(之间留 1 个空行):
```markdown
### [2026-05-08] Phase 1前端凭据槽位 API 客户端
配套服务端 Phase[../qy_lty/.planning/phases/02-admin-rest/](../../qy_lty/.planning/phases/02-admin-rest/)已落地commit 46d72b8
覆盖前端需求CRED-FE-01
- **文件路径**
- `lib/api/credential-slot.ts`(新增)
- `lib/api/index.ts`(修改)
- **修改类型**: 新增API 客户端层;纯逻辑,无 UI 改动)
- **修改内容**:
- 新建 `lib/api/credential-slot.ts`,封装:
- 类型 `CredentialSlot { appId, accessTokenMasked, updatedAt }`(脱敏掩码语义命名)
- 类型 `CredentialSlotUpdatePayload { appId, accessToken }`(明文语义命名)
- 适配器 `mapBackendCredentialSlot()`snake_case → camelCase
- API 函数 `getCredentialSlot()` / `updateCredentialSlot(payload)`,分别走 `apiClient.get` / `apiClient.put` 命中 `/v1/admin/credential-slot/`,沿用仓库统一的 `response.data?.data || response.data` 双保险解包PUT body 仅传 `{ app_id, access_token }`,不携 `updated_at`(与 `updateAiModel` / `updateOutfit` 风格一致)
- `lib/api/index.ts` 末尾追加具名 re-export让组件层可 `import { getCredentialSlot, type CredentialSlot } from '@/lib/api'`
- **修改原因**:
- 启动 Milestone v1.0「通用凭据槽位前端集成」,本 phase 为后续 Phase 2RBAC + 入口控件、Phase 3编辑对话框 + Sonner 反馈)提供调用层基础
- `accessTokenMasked` vs `accessToken` 故意命名不同,让 TS 编译期捕捉「把脱敏字符串当真值回写 PUT」的 bug
- **服务端联动**: 无 — Phase 1 是纯 API client 层落地(无 UI 改动),调用的后端接口由 qy_lty 后端 Milestone v1.0 Phase 2 提供commit `46d72b8` 已建立前后端互引修改记录);本 phase 不引入新跨项目代码契约,无需再次互引。前端 UI 集成Phase 2 + 3引入实质用户能力时再评估是否需要新一轮互引
```
**关键约束**
1. 插入位置**必须**在 `<!-- 新的修改记录添加在此处下方,最新的在最前面 -->` 注释**之后**、`### [2026-05-07] Phase 2 — 锁定后端通用凭据槽位 REST 接口契约(消费方文档化)` 标题**之前**(项目约定:最新在前)
2. 顶部 `### [2026-05-08]` 必须用今天日期 `2026-05-08`(与 RESEARCH 的 「Researched: 2026-05-08」一致不是 2026-05-07
3. 必须出现关键字符串:`CRED-FE-01`、`46d72b8`、`accessTokenMasked`、`accessToken`、`lib/api/credential-slot.ts`、`lib/api/index.ts`、`/v1/admin/credential-slot/`
4. 「服务端联动」字段必须明确写出「无 — ... commit 46d72b8 已建立 ... 本 phase 不引入新跨项目代码契约无需再次互引」per CONTEXT.md 锁定文案)
5. **不要**修改 `docs/修改记录.md` 中任何已有条目;只做顶部插入
6. **不要**在 qy_lty 项目侧建立新条目CONTEXT.md 锁定:本 phase 不需要新建跨项目互引)
</action>
<acceptance_criteria>
- `grep -nF "[2026-05-08] Phase 1前端凭据槽位 API 客户端" docs/修改记录.md` 命中 1 次
- 该新条目的行号 < 现有 `[2026-05-07] Phase 2 — 锁定后端通用凭据槽位 REST 接口契约` 标题的行号(最新在前顺序正确)
- `grep -F "CRED-FE-01" docs/修改记录.md` 命中 ≥1 次
- `grep -F "46d72b8" docs/修改记录.md` 命中 ≥1 次plan 01 之前已存在 1 次,本 plan 后应为 ≥2 次)
- `grep -F "accessTokenMasked" docs/修改记录.md` 命中 ≥1 次
- `grep -F "lib/api/credential-slot.ts" docs/修改记录.md` 命中 ≥1 次
- `grep -F "/v1/admin/credential-slot/" docs/修改记录.md` 命中 ≥1 次
- `grep -F "无需再次互引" docs/修改记录.md` 命中 ≥1 次
- 现有 [2026-05-07] Phase 2 条目内容**完全不变**(行级 diff 仅为顶部插入,不修改既有行)
</acceptance_criteria>
<verify>
<automated>node -e "const c = require('fs').readFileSync('docs/修改记录.md','utf8'); const newIdx = c.indexOf('[2026-05-08] Phase 1前端凭据槽位 API 客户端'); const oldIdx = c.indexOf('[2026-05-07] Phase 2 — 锁定后端通用凭据槽位 REST 接口契约'); if (newIdx < 0) { console.error('FAIL: 新条目缺失'); process.exit(1); } if (oldIdx < 0) { console.error('FAIL: 旧条目消失'); process.exit(1); } if (newIdx >= oldIdx) { console.error('FAIL: 新条目未置顶'); process.exit(1); } const must = ['CRED-FE-01', '46d72b8', 'accessTokenMasked', 'accessToken', 'lib/api/credential-slot.ts', 'lib/api/index.ts', '/v1/admin/credential-slot/', '无需再次互引', '配套服务端 Phase', '覆盖前端需求']; for (const s of must) { if (!c.includes(s)) { console.error('FAIL: 缺失关键字 ' + s); process.exit(1); } } console.log('OK'); "</automated>
</verify>
<done>
- `docs/修改记录.md` 顶部「修改历史」段第一条为 `[2026-05-08] Phase 1前端凭据槽位 API 客户端`
- 现有 [2026-05-07] Phase 2 条目位置后移、内容不变
- `verify.automated` 命令退出码为 0 且打印 `OK`
- 所有关键字段CRED-FE-01 / 46d72b8 / accessTokenMasked / 文件路径 / 服务端联动文案)齐全
</done>
</task>
<task type="auto" tdd="false">
<name>Task 2跑双重验证npm run lint + npx tsc --noEmit+ 临时探针验证 barrel 入口可解析</name>
<read_first>
- `package.json` L9 — 确认 `lint` 脚本是 `next lint`per RESEARCH 问题 7只跑 ESLint不跑 tsc
- `next.config.mjs`L17 / L20— 确认 `eslint.ignoreDuringBuilds` 与 `typescript.ignoreBuildErrors` 仅影响 `next build`,不影响显式 `next lint` 与 `npx tsc --noEmit`
- `tsconfig.json` — 确认 strict 模式开启
- `lib/api/credential-slot.ts`plan 01 落地)+ `lib/api/index.ts`plan 01 落地)— 类型与导出已就位
- `.planning/phases/01-credential-slot-api/01-RESEARCH.md` 「Pitfall 5: 包管理器混用导致 lockfile 漂移」节 — 验证步骤**只读不写**,不要跑 `npm install`
</read_first>
<files>(不创建持久化新文件;仅临时探针 `lib/api/__phase1_probe__.ts`,验证后删除)</files>
<action>
按以下顺序**严格**执行,每步退出码必须为 0任一步失败必须先排错再继续
**步骤 1创建临时类型探针文件 `lib/api/__phase1_probe__.ts`**(用 `.ts` 而非 `.tsx`,因不引入 React`__` 前后缀降低被 IDE/lint 误识别为业务文件的风险)
写入以下完整内容:
```typescript
// 临时探针 — Phase 1 plan 02 验证 barrel 入口可正确解析新增符号;验证后立即删除
import {
getCredentialSlot,
updateCredentialSlot,
type CredentialSlot,
type CredentialSlotUpdatePayload,
} from "@/lib/api"
async function __probe(): Promise<void> {
const slot: CredentialSlot = await getCredentialSlot()
const payload: CredentialSlotUpdatePayload = {
appId: slot.appId,
accessToken: "plaintext-not-masked", // 故意写明文type 系统应允许
}
const next: CredentialSlot = await updateCredentialSlot(payload)
void next.accessTokenMasked // 触达字段证明类型形状
}
void __probe
```
**步骤 2跑 `npx tsc --noEmit`**
```bash
npx tsc --noEmit
```
退出码必须为 0。如有错误
- 若错误指向 `__phase1_probe__.ts` 的 `import "@/lib/api"` —— 说明 plan 01 的 `index.ts` re-export 有问题,回 plan 01 排错
- 若错误指向 `lib/api/credential-slot.ts` —— 说明 plan 01 的类型定义有问题,回 plan 01 排错
- 若错误指向**其他文件**(项目中的存量类型问题,与本 phase 无关)—— 记录错误清单到 SUMMARY但本 task 仍判定**通过**,因为本 phase 的责任范围是新增文件不引入类型回归
**关键判定规则**:把 `npx tsc --noEmit` 输出存到临时文件,过滤出**仅与 `lib/api/credential-slot.ts` 或 `__phase1_probe__.ts` 或 `lib/api/index.ts` 相关**的错误行;这三处错误数必须为 0。其他文件的存量错误不算 phase 1 失败。
**步骤 3跑 `npm run lint`**
```bash
npm run lint
```
退出码必须为 0。如有警告/错误:
- 若错误/警告指向新增文件 `lib/api/credential-slot.ts` 或 `__phase1_probe__.ts` —— 必须修复
- 若错误/警告指向 `lib/api/index.ts` 但**仅限**新增的具名 re-export 块 —— 必须修复
- 若错误/警告指向**其他存量文件** —— 记录到 SUMMARY本 task 仍判定通过
注意 `next lint` 默认对项目所有 `.ts` / `.tsx` 跑;如果 ESLint 配置严格,`__phase1_probe__.ts` 中的 `void __probe` 与未使用变量可能触发 `no-unused-vars`。如真触发,添加文件级 `// eslint-disable-next-line @typescript-eslint/no-unused-vars` 注释或将 `__probe` / `__probe2` 等命名调整避免触发,但**优先**调整代码而非禁用规则。
**步骤 4删除临时探针文件**
```bash
rm lib/api/__phase1_probe__.ts
```
Windows PowerShell 等价:`Remove-Item lib/api/__phase1_probe__.ts`
**步骤 5再跑一次 `npx tsc --noEmit` 与 `npm run lint`**,确认删除探针后两条命令仍全部退出码 0防止漏删导致后续 phase 把临时文件当真)
**关键约束per RESEARCH Pitfall 5**
1. **不**要跑 `npm install` / `pnpm install` / `yarn install`lockfile 漂移风险,本 phase 不引入新依赖)
2. **不**要修改 `package.json` / `package-lock.json` / `yarn.lock` / `pnpm-lock.yaml`
3. **不**要修改 `next.config.mjs` / `tsconfig.json` / `.eslintrc*`CONTEXT.md / RESEARCH.md 均隐含锁定)
4. 探针文件**必须**删除,不允许残留任何临时文件到 phase 末态
5. 两条验证命令的退出码与「与新增文件相关的错误数」都必须为 0不可用 `--force` / `--no-warnings` 等屏蔽手段
</action>
<acceptance_criteria>
- 步骤 2`npx tsc --noEmit` 在创建探针后退出码 0如有非 0输出中**没有**任何包含 `lib/api/credential-slot.ts` / `lib/api/__phase1_probe__.ts` / `lib/api/index.ts` 路径的错误行(执行者负责把过滤判定写入 SUMMARY 证据段)
- 步骤 3`npm run lint` 退出码 0同上规则新增/修改的文件零错误零警告
- 步骤 4`lib/api/__phase1_probe__.ts` 不存在grep / Test-Path 验证)
- 步骤 5删除探针后 `npx tsc --noEmit` 与 `npm run lint` 仍退出码 0
- `git status --short`(如可用)显示只有 `lib/api/credential-slot.ts`(新增)+ `lib/api/index.ts`(修改)+ `docs/修改记录.md`(修改)三个改动;不允许有 `__phase1_probe__.ts` / `pnpm-lock.yaml` / `yarn.lock` / `package-lock.json` / `package.json` 出现在 diff 中(探针残留或包管理器混用信号)
</acceptance_criteria>
<verify>
<automated>node -e "const fs = require('fs'); const cp = require('child_process'); function run(cmd){ try { cp.execSync(cmd, { stdio: 'pipe' }); return { code: 0, out: '' }; } catch (e) { return { code: e.status || 1, out: (e.stdout?.toString() || '') + (e.stderr?.toString() || '') }; } } if (fs.existsSync('lib/api/__phase1_probe__.ts')) { console.error('FAIL: 临时探针文件未删除'); process.exit(1); } const tsc = run('npx tsc --noEmit'); if (tsc.code !== 0) { const lines = tsc.out.split(/\r?\n/).filter(l => /lib\/api\/(credential-slot|__phase1_probe__|index)\.ts/.test(l)); if (lines.length > 0) { console.error('FAIL: tsc 在新增/修改文件上报错:\n' + lines.join('\n')); process.exit(1); } else { console.error('WARN: tsc 在存量文件上有错误,但与本 phase 无关:\n' + tsc.out.split(/\r?\n/).slice(0, 20).join('\n')); } } const lint = run('npm run lint'); if (lint.code !== 0) { const lines = lint.out.split(/\r?\n/).filter(l => /lib\/api\/(credential-slot|__phase1_probe__|index)\.ts/.test(l)); if (lines.length > 0) { console.error('FAIL: lint 在新增/修改文件上报错:\n' + lines.join('\n')); process.exit(1); } else { console.error('WARN: lint 在存量文件上有错误,但与本 phase 无关:\n' + lint.out.split(/\r?\n/).slice(0, 20).join('\n')); } } console.log('OK'); "</automated>
</verify>
<done>
- 探针验证完整跑过:先创建 -> tsc 0 (新文件零错) -> lint 0 (新文件零错) -> 删除探针 -> 再跑 tsc 0 + lint 0
- 临时探针 `lib/api/__phase1_probe__.ts` 已删除git diff 不残留它
- 包管理器 lockfile 状态未变git status 不显示 `package-lock.json` / `yarn.lock` / `pnpm-lock.yaml` / `package.json` 的改动)
- `verify.automated` 命令退出码为 0 且打印 `OK`
- SUMMARY 中记录两条验证命令的退出码、与新增文件相关的错误清单(应为空)、存量错误清单(如有,仅作信息)
</done>
</task>
</tasks>
<verification>
本 plan 完成意味着 Phase 1 全部交付完成。汇总验证:
1. **结构性**plan 01 + plan 02 联合):
- `lib/api/credential-slot.ts` 文件存在、4 个公共符号导出、路径与解包行齐全
- `lib/api/index.ts` 末尾具名 re-export 4 个符号
- `docs/修改记录.md` 顶部新增 [2026-05-08] Phase 1 条目
2. **工具链**(本 plan task 2 兜底):
- `npx tsc --noEmit` 退出码 0新增/修改文件零类型错误)
- `npm run lint` 退出码 0新增/修改文件零 ESLint 错误)
3. **可消费性**(本 plan task 2 探针验证):
- `import { getCredentialSlot, type CredentialSlot } from '@/lib/api'` 在临时探针文件中类型解析通过
- 探针文件删除后两条验证命令仍退出码 0确认 plan 02 没引入残留文件依赖)
4. **跨项目联动**
- 修改记录条目明确写出「无需再次互引(后端 commit 46d72b8 已建立)」
- **不**修改 `qy_lty/docs/修改记录.md`CONTEXT.md 锁定)
5. **包管理器零漂移**
- `git status` 不显示 `package.json` / `package-lock.json` / `yarn.lock` / `pnpm-lock.yaml` 的任何修改
</verification>
<success_criteria>
- [ ] `docs/修改记录.md` 顶部第一条为 `[2026-05-08] Phase 1前端凭据槽位 API 客户端`
- [ ] 该条目包含全部锁定关键字:`CRED-FE-01`、`46d72b8`、`accessTokenMasked`、`accessToken`、`lib/api/credential-slot.ts`、`lib/api/index.ts`、`/v1/admin/credential-slot/`、`无需再次互引`
- [ ] 现有 [2026-05-07] Phase 2 条目内容不变、位置下移
- [ ] `npx tsc --noEmit` 退出码 0新增/修改文件零类型错误(存量错误不影响本 phase 判定)
- [ ] `npm run lint` 退出码 0新增/修改文件零 ESLint 错误
- [ ] 临时探针 `lib/api/__phase1_probe__.ts` 已删除git diff 不残留
- [ ] `git status --short` 仅显示 `lib/api/credential-slot.ts`(新增) + `lib/api/index.ts`(修改) + `docs/修改记录.md`(修改)+ `.planning/...` 内的 PLAN/SUMMARY 文档;**不**显示 `package.json` / 任一 lockfile 改动
</success_criteria>
<output>
完成后创建 `.planning/phases/01-credential-slot-api/01-02-SUMMARY.md`,记录:
- 修改记录条目最终落地行号区间(在 docs/修改记录.md 中的位置)+ 关键字命中清单
- `npx tsc --noEmit` 退出码 + 与新增文件相关的错误行数(应为 0
- `npm run lint` 退出码 + 与新增文件相关的错误行数(应为 0
- 探针文件删除前后两次验证的输出对比(最关心:删除后命令仍 0
- 存量类型/lint 错误数量(仅作信息,不计入失败判定;为后续 PERM-06 候选优先级 #3 留追踪锚点)
- 跨项目联动确认:本 phase 未修改 `../qy_lty/docs/修改记录.md`
完成本 SUMMARY 即可宣告 Phase 1 全部交付完成;下一步运行 `/gsd-plan-phase 2` 启动 RBAC 收敛 + AI 模型页入口。
</output>