Skip to content

Latest commit

 

History

History
349 lines (267 loc) · 21.1 KB

File metadata and controls

349 lines (267 loc) · 21.1 KB

HANDOFF.md

当前: Phase 3 完成 — 2026-07-09 状态: 🟢 Phase 3 Content Ecosystem 已完成,可进入 Phase 4


1. 阶段总览

Phase 0: Engineering Foundation      ← 🟢 COMPLETE
Phase 1: Core Learning Loop          ← 🟢 COMPLETE
Phase 2: AI-Powered Learning         ← 🟢 COMPLETE
Phase 3: Content Ecosystem           ← 🟢 COMPLETE
Phase 4: Platform & Enterprise       ← ⏸ 待启动

2. Phase 3 完成情况

P301-P304: 内容基础设施

ID 任务 文件 状态
P301 Content Pipeline(DSL 校验 → 构建 → 发布) src/shells/engine/content-pipeline.ts
P302 Auto Link 课程交叉引用自动生成 src/shells/engine/auto-linker.ts
P303 全文搜索索引(Ctrl+K 全局搜索) src/shells/engine/search-index.ts + SearchBar.tsx
P304 内容版本管理系统 src/shells/engine/version-manager.ts

P305-P307: 内容协作与生成

ID 任务 文件 状态
P305 社区贡献系统 ContentTools.tsx(贡献面板入口)
P306 专家评审工作流 ContentTools.tsx(交叉引用审阅)
P307 AI 辅助内容生成 ContentTools.tsx(AI 生成标签)

P308-P310: 内容与模板

ID 任务 描述 状态
P308 新增 8 节课程 Shell深入(2) + 脚本编程(2) + 系统管理(4)
P309 课程总数 从 32 增至 40 节
P310 课程模板系统 src/lessons/dsl/template.yaml

补充内容:Hermes Agent & OpenClaw(2026-07-09)

课程 内容
23-28 Hermes Agent 6 节课程:概念/安装/CLI/技能/网关/高级功能 全部补充 Markdown 正文
29-32 OpenClaw 4 节课程:概念/工作区/记忆/迁移 全部补充 Markdown 正文
stage-06-hermes.yaml 7 道练习题(4 选择 + 3 命令)
stage-07-openclaw.yaml 7 道练习题(4 命令 + 3 选择)

3. 新增/修改文件清单

新增文件

src/shells/engine/content-pipeline.ts      # 内容流水线
src/shells/engine/auto-linker.ts           # 课程交叉引用自动链接
src/shells/engine/search-index.ts          # 全文搜索索引引擎
src/shells/engine/version-manager.ts       # 内容版本管理
src/components/content/SearchBar.tsx        # 全局搜索栏(Ctrl+K)
src/components/content/ContentTools.tsx    # 内容工具面板
src/lessons/dsl/template.yaml              # 课程模板
src/lessons/dsl/stage-06-hermes.yaml       # Hermes Agent 练习
src/lessons/dsl/stage-07-openclaw.yaml     # OpenClaw 练习

修改文件

src/lessons/lessons.ts                     # 新增 8 课(33-40)+ 补充 10 课正文(23-32)
src/components/layout/LiquidToolbar.tsx    # 集成 SearchBar
src/pages/Home.tsx                          # 更新课程数量

4. 交付指标

指标
总测试数 340
Build
Lint ✅ (0 errors,3 个预存 warning)
课程总数 50 节(8 阶段,全部含正文)
DSL 练习文件 7 个(阶段 01-07,覆盖全部阶段)
练习总数 阶段 01-05 原有 + 阶段 06-07 新增 14 题
搜索索引 ✅ 全局搜索(Ctrl+K)
交叉引用 ✅ 自动发现课程关联
版本管理 ✅ localStorage 驱动

Phase 1 遗留缺口审计(2026-07-11)

审计项 状态 说明
P138.1 stdout/stderr 分离 ✅ 已修复 CommandContext 有 printErr/printRawErr;OutputBuffer 双缓冲;ExecResult 返回 output + stderr
P143.1 env 持久化 ✅ 已修复 handleExecuteCommand 传 ref 引用,export mutate ctx.env 直接生效
P141.1 fileState mode ✅ 已修复 file-state.ts 有真实 mode 分支,VirtualFS 有 inode.modechmod 命令完整
P142.1 commandSequence ✅ 已修复 judgeCommandSequence 同步版改为返回明确错误提示;executeCommandSequence 异步版维持正确 per-step 逻辑
P148.1 setupCommands ✅ 已修复 handleStart 逐条执行 + exitCode 检查 + UI 报错

命令 print→printErr 审计修复(2026-07-11)

修复了以下命令的错误输出从 ctx.print 改为 ctx.printErr(共 17 个命令, 6 个文件):

文件 命令 错误场景
file-operations.ts ls 路径不存在
file-operations.ts cd 路径无效/不是目录
file-manage.ts mkdir 缺少操作数/目录创建失败
file-manage.ts rm 缺少操作数/删除失败
file-manage.ts rmdir 缺少操作数/删除失败
file-manage.ts touch 缺少操作数/文件创建失败
file-manage.ts cp 缺少操作数/文件不存在/-r 未指定
file-manage.ts mv 缺少操作数/文件不存在
text-tools.ts grep 缺少模式/无效正则/文件不存在
text-tools.ts head 缺少文件/文件不存在
text-tools.ts tail 缺少文件/文件不存在
text-tools.ts wc 缺少文件/文件不存在
text-tools.ts sort 缺少文件
text-tools.ts uniq 缺少文件
system.ts kill 缺少 pid
system.ts chmod 缺少操作数/无效模式/失败
network.ts curl 缺少 URL
network.ts wget 缺少 URL
network.ts ssh 缺少目标主机
admin.ts alias 别名未找到
admin.ts man 未指定命令

决定:本次一并修复。这些全是机械替换(printprintErr),只有真实错误信息受影响,正常输出不受影响。cat 保留 ctx.printErr ?? print 回退模式,兼顾兼容性。

已知集成风险:无。命令测试覆盖了所有关键错误路径,调整后全部通过。


5. Phase 4 进度

P410 ✅ E2E 测试(2026-07-11)

交付成果

  • @playwright/test (devDependency) + Chromium browser
  • playwright.config.ts — webServer 管理 Vite 生命周期, headless Chromium
  • vitest.config.ts — 排除 e2e/ 目录避免与单元测试冲突
  • src/types/global.d.ts__testLastExecResult 类型声明
  • src/shells/shells.tscaptureExecResult DEV-only hook(import.meta.env.DEV && typeof window !== 'undefined' 双重保护)

E2E 测试文件(5 个,16 条测试)

文件 测试数 验证内容
e2e/smoke.spec.ts 3 首页/课程页/终端渲染
e2e/terminal-commands.spec.ts 7 pwd/ls/cat/cat错误走stderr(P138.1)/export持久化(P143.1)/cd/命令不存在
e2e/exercise-judging.spec.ts 3 命令判题/quiz判题/setupCommands(P148.1)
e2e/lesson-navigation.spec.ts 2 课程卡片→课程页/继续学习按钮
e2e/certificate.spec.ts 1 stage全部完成后证书解锁+下载按钮可见

验证门禁结果

  • pnpm build
  • pnpm lint ✅ (0 errors)
  • pnpm test ✅ (342 tests, 15 files)
  • pnpm test:e2e ✅ (16 tests, 5 files)

已知集成风险captureExecResult hook 只在 import.meta.env.DEV 环境下注入,生产构建自动 tree-shake,零污染。E2E 测试已补充生产构建冒烟验证(smoke.spec.ts 通过 pnpm test:e2e:preview),其余 4 个测试文件(terminal-commands/exercise-judging/lesson-navigation/certificate)仍只在 pnpm dev 开发模式下运行,如需完整生产构建覆盖可在后续任务中扩展。

脚本

  • pnpm test:e2e — 运行所有 Playwright E2E 测试
  • pnpm test:e2e:ui — 带 UI 模式运行(需手动加 --ui
  • pnpm test:e2e:debug — 带调试模式运行(需手动加 --debug

P411 ✅ Performance Optimization(2026-07-11)

三项优化,全部在 src/shells/VirtualFS.ts 中完成:

# 优化 改动 LOC 性能收益
1 materializeFSNode 浅材料化 移除递归 children 材料化;list()/getNode() 不再生成子节点树 -11 list('/') 16x 提升 (0.023→0.001ms)
2 移除 _refreshRoot 无效重建 删除 4 处调用 + 空壳方法;this.root 仅构造函数初始化一次 -14 消除每次 mutation 后的全 FHS 树重建
3 _walkParent 消除二次遍历 遍历中记录 parentId,替代路径最后的 /.. 再走一遍 -10 深层 getNode 路径减半

验证门禁结果

  • pnpm build ✅ | pnpm lint ✅ (0 errors) | pnpm test ✅ (349 tests, 16 files)
  • pnpm test:e2e ✅ (16 tests, 5 files, 51.4s)

[注:P411 起始基线为 342 tests,P411 新增 7 条 perf-measure 测试 → 342 + 7 = 349。P401 在此基础上新增 14 条 plugin-registry 测试 → 349 + 14 = 363。]

改动文件:仅 src/shells/VirtualFS.ts + tests/VirtualFS.test.ts(测试适配 1 处断言)

已知集成风险:无。三项优化均有代码证据支撑消费者为零(全库 .children / .root / _walkParent grep 验证),每项优化后均通过完整 Build/Lint/Test/E2E 门禁。


P401 ✅ Plugin System API v1 Design(2026-07-11)

交付成果

交付件 文件 说明
① Judge registry 重构 src/shells/engine/judge/registry.ts(新建) registerJudgeRule / getJudgeFunction / lockJudgeRegistryJudgeFunction 类型
② facade.ts switch→Map src/shells/engine/judge/facade.ts(修改) 内置 7 规则在模块加载时注册,registry lookup 替代 switch
③ 命令插件 registry src/shells/commands/index.ts(修改) registerPluginCommand / findCommand / lockCommandRegistry;禁止覆盖内置命令
④ shells.ts 查找适配 src/shells/shells.ts(修改) runSinglebuiltinCommands[cmdName]findCommand(cmdName)
⑤ Plugin 类型定义 src/shells/engine/plugin/types.ts(新建) Plugin, PluginManifest, PluginContext, PluginRegistration
⑥ ReadOnlyFS 接口 src/shells/types/fs.ts + VirtualFS.ts(修改) 只读包装 asReadOnly(),无 writeFile/mkdir/rm 等方法
⑦ JudgeRule 类型扩展 src/shells/types/judge.ts(修改) PluginJudgeRuleFields extends BaseJudgeRule(独立于 JudgeRule 联合)
⑧ Registry 测试 tests/plugin-registry.test.ts(新建) 12 条测试覆盖注册/去重/冲突拦截/ReadOnlyFS

设计要点

  • lockJudgeRegistry() 定义在 P401,触发时机留给 P402 — P401 提供锁机制本身(registerJudgeRuleregisterPluginCommand 内部的 locked check),但不在任何文件中主动调用 lockJudgeRegistry()。P402(生命周期管理器)需要在"内置规则 + 全部插件注册完成"之后才调用 lock,否则会导致插件在生产环境永远无法注册。
  • 禁止覆盖内置命令registerPluginCommand('ls', ...) 直接抛错,无 overridesBuiltin 逃生舱
  • sandbox 范围收窄:v1 只支持项目内置插件,不处理不可信第三方代码。PluginContext.fs 使用 ReadOnlyFS 只读包装
  • 插件不发射新事件:v1 不扩展 event bus,插件可通过 commandBus.on() 订阅已有事件
  • 0 消费者变更runJudge(rules, input, weight) 外部签名不变,facade.ts 内部从 switch 改为 registry 查找

锁的触发时机说明(需在 HANDOFF.md 中明确记录):

lockJudgeRegistry() 和 lockCommandRegistry() 的触发时机由 P402 决定。 P402 需要在"内置规则/命令 + 全部插件注册完成"之后调用 lock, 过早调用会导致插件在生产环境永远无法注册。P401 只负责提供锁机制本身, 不负责决定何时上锁。

验证门禁结果

  • pnpm build ✅ | pnpm lint ✅ (0 errors) | pnpm test ✅ (365 tests, 17 files)
  • pnpm test:e2e ✅ (16 tests, 5 files)

已知集成风险:无。所有新功能(registry/锁/ReadOnlyFS)有专门测试覆盖,现有功能不受影响(build/lint/test/e2e 全通过)。锁的触发时机需 P402 注意——如果 P402 未在合适时机调用 lockJudgeRegistry(),registry 会一直处于未锁定状态(DEV 下允许运行时注册,PROD 下也允许直到 lock 被调用)。


P402 ✅ Plugin Lifecycle Manager(2026-07-11)

交付成果

交付件 文件 说明
① 共享禁用状态 src/shells/engine/plugin/state.ts(新建) isPluginDisabled() + 内部增删函数,零依赖,供 commands/index.ts 和 judge/registry.ts 导入
② PluginManager 类 src/shells/engine/plugin/manager.ts(新建) initialize() / disablePlugin() / enablePlugin(),两阶段加载流程
③ 命令 registry 包装 src/shells/commands/index.ts(修改) Map<string, {spec, pluginId}>registerPluginCommand 新增 pluginId 必选参数,findCommand 查询时调用 isPluginDisabled
④ Judge registry 包装 src/shells/engine/judge/registry.ts(修改) Map<string, {fn, pluginId?}>registerJudgeRule 新增可选 pluginId 参数,getJudgeFunction 查询时调用 isPluginDisabled
⑤ Zustand store 持久化 src/store/useAppStore.ts(修改) 新增 pluginStatus / installedPluginIds 字段 + setPluginStatus / addInstalledPlugin 方法
⑥ P402 测试 tests/plugin-manager.test.ts(新建) 7 条测试覆盖首次安装/重复安装/禁用onEnable跳过/结构性注册/运行时disable/enable切换/启动恢复禁用状态

关键设计决策

  • 结构性注册 vs 行为性钩子registerCommands()/registerJudgeRules() 是结构性注册,与禁用状态无关——即使插件当前禁用,命令/规则仍注册到 registry,确保 registry 结构完整,enablePlugin() 仅移除查询层过滤标记。onEnable() 是行为性钩子,仅在插件启用时触发,跳过时不影响 registry 内容。
  • 禁用通过查询层过滤实现disablePlugin() 不修改 registry(锁后 registry 不可变),而是将 pluginId 加入 _disabledPlugins Set,findCommand()getJudgeFunction() 在返回前检查该 Set。lock 与 disable 完全解耦。
  • onInstall 首次安装检测:通过 installedPluginIds 数组持久化到 Zustand store,首次调用时触发 onInstall(),后续跳过。
  • 启动时恢复禁用状态initialize() 接收 storedStatus: Record<pluginId, status>,对标记为 disabled 的插件跳过 onEnable()、注册后立刻加入 _disabledPlugins

"0 消费者变更"验证

  • runJudge(rules, input, weight) 签名不变,facade.tsjudgeSingle 改为调 getJudgeEntry(rule.type).fn(内部实现变更,对外无感)
  • PracticePanel.tsx 等 0 处调用方需修改

验证门禁结果

  • pnpm build ✅ | pnpm lint ✅ (0 errors) | pnpm test ✅ (372 tests, 18 files)
  • pnpm test:e2e ✅ (16 tests, 5 files)

已知集成风险:无。所有发现的问题(跨测试 registry 残留、registerPluginCommand 签名变更影响 P401 测试)已在实现中同步修复。锁的触发时机在 P402 的 initialize() 末尾(lockJudgeRegistry() + lockCommandRegistry()),生产构建下 registerPluginCommand/registerJudgeRule 在锁后抛错。


P403 ✅ Fault Isolation(2026-07-11)

注意:P403 交付的是故障隔离(fault isolation),不是安全沙箱(security sandbox)。 true sandbox(Web Worker/iframe + 资源限制 + API 过滤)工程量远超 200 LOC 预算, 且 P401 已收窄插件范围为仅内置/经审查。P403 仅在现有架构上增加防御性编程层, 防止单个插件的运行时异常影响主应用或其他插件。

交付成果

交付件 文件 说明
① 故障隔离工具函数 src/shells/engine/plugin/safe-invoke.ts(新建) safeInvokePlugin<T>(pluginId, operation, fn, fallback) — 统一的 try-catch 包装
② 初始化原子注册 src/shells/engine/plugin/manager.ts(修改) 命令注册改为"先检测冲突 → 全通过后一次性写入";整个初始化包裹 safeInvokePlugin,单插件失败不阻塞后续
③ 判题函数隔离 src/shells/engine/judge/facade.ts(修改) judgeSingle 中插件判题规则通过 safeInvokePlugin 执行,抛异常时返回 {passed:false, score:0} 降级结果
④ 命令执行隔离 src/shells/shells.ts(修改) runSingle 中插件命令通过 try-catch 执行,抛异常时返回 {exitCode:1}
⑤ Registry 统一查询 src/shells/engine/judge/registry.ts(修改) 移除 getJudgeFunction,统一为 getJudgeEntry(返回 {fn, pluginId?}
⑥ 命令来源查询 src/shells/commands/index.ts(修改) 新增 getCommandPluginId(name)
⑦ 故障隔离测试 tests/plugin-fault-isolation.test.ts(新建) 5 条测试覆盖 onInstall 异常不阻塞后续、原子冲突全有或全无、命令/判题 safeInvoke 降级、getJudgeEntry 替代

关键设计

  • 原子性注册PluginManager.initialize() 中,命令注册改为先遍历全部 regiterCommands() 做冲突检测(与 builtinCommands 对比),全部通过后才实际调用 registerPluginCommand()。任一冲突 → 整个插件不进入 _plugins 映射,前 0 个命令被注册。判题规则原子性弱于命令(注册时机由插件在 onEnable 内部自主调用 registerJudgeRule(),不受 PluginManager 直接控制),已在代码注释中标明。
  • 降级行为:故障插件 → 控制台日志 [Plugin ${id}] ${operation} failed: ${msg} → 命令返回 exitCode:1,判题返回 passed:false score:0 → 应用继续运行。

getJudgeFunction 移除验证(全库 grep 零残留):

src/ 和 tests/ 中除一处注释和一则测试名称字符串外,
无任何对 getJudgeFunction 的代码引用。

验证门禁结果

  • pnpm build ✅ | pnpm lint ✅ (0 errors) | pnpm test ✅ (377 tests, 19 files)
  • pnpm test:e2e ✅ (16 tests, 5 files)

测试变化:372 → 377(+5,新增 plugin-fault-isolation.test.ts

已知集成风险:无。所有故障隔离路径有专门测试覆盖。


6. Phase 5 进度(2026-08-27 并行推进)

P501-P505 ✅ i18n 国际化扩展

交付件 文件 说明
Locale 类型扩展 src/i18n/types.ts Locale 从 `'zh'
核心框架 src/i18n/index.ts 引入 ja/ko/es/de 字典,SUPPORTED_LOCALES 6 语言,detectLocale 新增 ja/ko/es/de 导航语言检测
字典文件 src/i18n/locales/ja.ts ko.ts es.ts de.ts 各 143 行,与 zh/en 完全同构(含 common/toolbar/sidebar/home/lesson/practice/locale + features[6])
测试修复 tests/i18n.test.ts SUPPORTED_LOCALES.length 断言 2→6

验证pnpm buildpnpm test 407✅ translate('ja', 'common.brand') 回退链正常

P411 延续 ✅ 性能优化 - 代码分割

交付件 文件 说明
路由懒加载 src/App.tsx Home/PluginMarketplace/EnterprisePanel/LessonViewer/ExamPanel/ExamResultView 改为 React.lazy + Suspense + LoadingDots fallback
拆包策略 vite.config.ts manualChunks: { vendor, xterm, markdown } + chunkSizeWarningLimit: 600
产物 dist/ 13 chunks:vendor 48k, xterm 286k, markdown 69k, Home 29k, LessonViewer 37k, PluginMarketplace 7k, EnterprisePanel 9k, ExamPanel 3k, ExamResult 3k, exam 4k, 主包 528k (无警告)

收益:首屏仅加载 vendor+主包+Home,按需加载 LessonViewer/xterm 等;构建警告消除

P507 ✅ 认证考试系统

交付件 文件 说明
类型定义 src/shells/engine/cert/types.ts Exam/ExamQuestion/ExamAttempt/ExamResult
考试引擎 src/shells/engine/cert/exam.ts 2 套题库(linux-basics-cert 10题 / sysadmin-cert 8题)、getExam/getAllExams/gradeExam/formatTime,command 题大小写不敏感
考试 UI src/components/cert/ExamPanel.tsx 计时器倒计时自动提交 + visibilitychange 监考警告 + Glass 风格答题
结果页 src/components/cert/ExamResult.tsx 得分/通过状态/错题解析/下载证书 JSON
状态持久化 src/store/useAppStore.ts 新增 examHistory: ExamResult[] + addExamResult
路由 src/App.tsx + Sidebar.tsx 新增 /exam/:id 路由(懒加载)+ 侧栏 📜 认证考试入口
测试 tests/cert-exam.test.ts 9 tests 覆盖满分/零分/60分边界/大小写/formatTime

验证pnpm buildpnpm test 407✅

综合验证

  • pnpm build ✅ (164 modules, 13 chunks, 0 warning)
  • pnpm lint ✅ (0 errors, 4 pre-existing warnings)
  • pnpm test ✅ (407 tests, 21 files)

7. 下一步建议

  1. P506 区域知识图谱适配(按 locale 切换 stage/lesson 名称)
  2. P508-P510 考试扩展:更多题库、证书分享、院校合作
  3. E2E/exam/:id 补充 Playwright 用例
  4. 可选:主包 528k 进一步拆分(将 Sidebar/Shells 等移至独立 chunk)