fix: 修复 OPENSPEC_DIR 环境变量读取及路径函数不一致 (closes #2)#3
Conversation
优先级链:env > options > config > default - server.ts: 在 createOpencodeSpec() 入口处先读取 process.env.OPENSPEC_DIR - options.directory 仅在 env var 未设置时才调用 setOpenspecDir - test/plugin.test.ts: 添加环境变量读取测试,afterEach 清理 env var 防止泄漏
验证 env > options > config 优先级链: - env var 与 options.directory 同时设置时 env var 胜出 - env var 与 config.openspec.directory 同时设置时 env var 胜出
将硬编码 path.join(targetDir, 'design.md') 替换为 designPath(input.projectDir, slug) 移除不再需要的 node:path import
将硬编码 path.join(targetDir, 'tasks.md') 替换为 tasksPath(input.projectDir, slug) 移除不再需要的 node:path import
devcxl
left a comment
There was a problem hiding this comment.
审查报告 — PR #3 (fix/issue-2-env-var-and-path-consistency)
[CRITICAL] process.env.OPENSPEC_DIR 在 options.directory 设置时被静默覆盖
文件: src/plugin/server.ts:31
问题: 当环境变量已设置时,options.directory 的处理逻辑仍然会无条件执行 process.env.OPENSPEC_DIR = dir(第 31 行),导致环境变量被覆盖。虽然 _openspecDir 模块变量保持正确(getOpenspecDir() 返回 env-wins),但 process.env.OPENSPEC_DIR 变成了 options-dir。技能参考脚本 assets/skills/_shared/references/openspec.js 直接读取 process.env.OPENSPEC_DIR,会拿到错误的值。
当前代码:
if (options?.directory && typeof options.directory === "string" && options.directory.trim()) {
const dir = options.directory.trim()
if (!process.env.OPENSPEC_DIR?.trim()) {
setOpenspecDir(dir)
}
process.env.OPENSPEC_DIR = dir // ← BUG: 无条件覆盖环境变量
}修复方案: 将 process.env.OPENSPEC_DIR = dir 移入同一个 guard 内:
if (options?.directory && typeof options.directory === "string" && options.directory.trim()) {
const dir = options.directory.trim()
if (!process.env.OPENSPEC_DIR?.trim()) {
setOpenspecDir(dir)
process.env.OPENSPEC_DIR = dir
}
}验证方法: 在测试中增加对 process.env.OPENSPEC_DIR 的断言:
it("环境变量优先级高于 options.directory 且 env var 不被覆盖", async () => {
process.env.OPENSPEC_DIR = "env-wins"
try {
await OpencodeSpec(ctx, { directory: "options-dir" })
expect(getOpenspecDir()).toBe("env-wins")
expect(process.env.OPENSPEC_DIR).toBe("env-wins") // ← 当前会失败
} finally {
delete process.env.OPENSPEC_DIR
}
})[WARNING] config 钩子中的 guard 使用硬编码字符串 "openspec"
文件: src/plugin/server.ts:45
问题: if (getOpenspecDir() === "openspec") 将默认值与字面量 "openspec" 耦合。如果 src/util/paths.ts 中的默认值被修改,这个 guard 会静默失效。
建议: 提取默认值为常量(如 DEFAULT_OPENSPEC_DIR),供 server.ts 引用。但这是预存问题,非本次 PR 引入,可作为后续优化。
[SUGGESTION] 测试未覆盖 options.directory > config.openspec.directory 优先级
文件: test/plugin.test.ts
问题: 新增的 3 个测试覆盖了 env var 优先级的三个场景,但缺少以下测试:
options.directory优先级高于config.openspec.directory(无环境变量时)config.openspec.directory单独生效(无环境变量、无 options 时)
建议补充以完整覆盖优先级链。
[SUGGESTION] design.ts / tasks.ts 中 targetDir 变量与 filePath 存在冗余计算
文件: src/core/artifact/design.ts:17-18、src/core/artifact/tasks.ts:18-19
问题: targetDir = changeDir(...) 和 filePath = designPath(...) 内部都调用了 slugify() 和 changeDir(),存在重复计算。targetDir 等价于 path.dirname(filePath)。微小优化,不影响正确性。
逐条验收标准检查
| 验收标准 | 状态 | 说明 |
|---|---|---|
设置 OPENSPEC_DIR 后 getOpenspecDir() 返回环境变量值 |
✅ | 测试通过 |
环境变量优先级高于 options.directory |
getOpenspecDir() 正确,但 process.env.OPENSPEC_DIR 被覆盖(CRITICAL) |
|
环境变量优先级高于 config.openspec.directory |
✅ | 测试通过 |
未设置时 options.directory / config 正常生效 |
✅ | 已有测试覆盖 |
均未设置时使用默认值 "openspec" |
✅ | 已有测试覆盖 |
design.ts 使用 designPath() |
✅ | 已正确替换 |
tasks.ts 使用 tasksPath() |
✅ | 已正确替换 |
| 所有测试通过 | ✅ | 63/65 passed(2 个 reference-scripts 失败为已有问题) |
| 无回归 | ✅ | 基线测试全部通过 |
| 无越界修改 | ✅ | 仅修改了 Issue 范围内的文件 |
审查结论
🔴 Request Changes — 存在 1 个 CRITICAL 问题:process.env.OPENSPEC_DIR 在 options.directory 设置时被静默覆盖,导致技能参考脚本读取到错误的目录名。
修复该问题后,其余变更质量良好:路径替换正确、测试覆盖充分、无越界修改。
CRITICAL: 将 process.env.OPENSPEC_DIR = dir 移入 env var 守卫内, 确保环境变量设置后不会被 options/config 覆盖。 同时补充 process.env.OPENSPEC_DIR 断言测试。
devcxl
left a comment
There was a problem hiding this comment.
审查结论:✅ 通过(Approve)
上一次审查发现的 CRITICAL 问题已正确修复,无新增 Critical/High 问题。
修复确认
CRITICAL #1:process.env.OPENSPEC_DIR 被 options.directory 无条件覆盖
✅ 已修复。src/plugin/server.ts 新增了环境变量优先读取逻辑,优先级链 env > options > config > default 正确实现:
- 第 21-24 行:启动时先读取
process.env.OPENSPEC_DIR并调用setOpenspecDir() - 第 28 行:
options.directory仅在!process.env.OPENSPEC_DIR?.trim()时才生效 - 第 45/49 行:
config.openspec.directory同样受 env 保护
CRITICAL #2:design.ts / tasks.ts 硬编码路径
✅ 已修复。两个文件均改用 designPath() / tasksPath(),同时清理了不再需要的 import path from "node:path"。
测试结果
Test Files 1 failed | 9 passed (10)
Tests 2 failed | 63 passed (65)
- 3 个新增测试全部通过
- 2 个失败是已有的
reference-scripts测试(mark-tasks/archive),非本次引入 - TypeScript 类型检查(
tsc --noEmit)通过
发现的问题
[MEDIUM] config 钩子中缺少 config.openspec.directory 独立生效的测试
test/plugin.test.ts 中新增的 3 个测试覆盖了 env 优先级场景,但缺少 config.openspec.directory 在无 env 时独立生效的测试用例。这是已有测试覆盖缺口,非本次引入。
[SUGGESTION] envDir 变量命名
src/plugin/server.ts:21 中 const envDir = process.env.OPENSPEC_DIR?.trim() — 变量名 envDir 暗示是目录路径,实际只是目录名(如 "openspec")。不过与代码库其他位置的命名风格一致,无需修改。
变更说明
修复两个代码审查发现的问题:
1. OPENSPEC_DIR 环境变量只写不读
src/plugin/server.ts中process.env.OPENSPEC_DIR仅被写入,但核心模块src/util/paths.ts的openspecRoot()使用模块级变量_openspecDir,从不读取环境变量。README 声明的功能未实现。修复:在
createOpencodeSpec()入口处、options 检查之前,先检查process.env.OPENSPEC_DIR并调用setOpenspecDir()。优先级链:env > options > config > default
2. design.ts / tasks.ts 硬编码路径
design.ts和tasks.ts使用path.join(targetDir, "design.md")/path.join(targetDir, "tasks.md")硬编码文件名,而src/util/paths.ts中已定义designPath()和tasksPath()函数(proposal.ts已正确使用proposalPath())。修复:统一使用
designPath()/tasksPath()函数生成路径。关联 Issue
Closes #2
Agent Brief 验收标准对照
变更文件
验证