@openprd/cli 0.1.8 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,7 @@
2
2
  "version": 1,
3
3
  "enabled": true,
4
4
  "strategy": "semver",
5
- "currentVersion": "0.1.8",
5
+ "currentVersion": "0.1.9",
6
6
  "versions": [
7
7
  {
8
8
  "version": "0.1.2",
@@ -286,9 +286,9 @@
286
286
  },
287
287
  {
288
288
  "version": "0.1.8",
289
- "status": "current",
289
+ "status": "released",
290
290
  "createdAt": "2026-06-06 14:06:15",
291
- "updatedAt": "2026-06-06 14:06:53",
291
+ "updatedAt": "2026-06-06 15:46:43",
292
292
  "notes": null,
293
293
  "items": [
294
294
  {
@@ -337,8 +337,51 @@
337
337
  }
338
338
  ],
339
339
  "tag": null
340
+ },
341
+ {
342
+ "version": "0.1.9",
343
+ "status": "current",
344
+ "createdAt": "2026-06-06 15:46:43",
345
+ "updatedAt": "2026-06-06 15:46:44",
346
+ "notes": null,
347
+ "items": [
348
+ {
349
+ "type": "优化",
350
+ "summary": "优化",
351
+ "detail": "收工时发现可复用经验后,会先用更好懂的说法说明这次情况、后续怎么复用,再询问是否保留",
352
+ "sentence": "优化:收工时发现可复用经验后,会先用更好懂的说法说明这次情况、后续怎么复用,再询问是否保留",
353
+ "recordedAt": "2026-06-06 15:46:44",
354
+ "source": {
355
+ "kind": "manual-note",
356
+ "manualId": "manual-note-1780732004017"
357
+ }
358
+ },
359
+ {
360
+ "type": "新增",
361
+ "summary": "新增",
362
+ "detail": "项目经验回顾会同步生成面向用户的说明内容,减少只剩内部术语、用户看不懂的情况",
363
+ "sentence": "新增:项目经验回顾会同步生成面向用户的说明内容,减少只剩内部术语、用户看不懂的情况",
364
+ "recordedAt": "2026-06-06 15:46:44",
365
+ "source": {
366
+ "kind": "manual-note",
367
+ "manualId": "manual-note-1780732004151"
368
+ }
369
+ },
370
+ {
371
+ "type": "新增",
372
+ "summary": "新增",
373
+ "detail": "README 补充内置最佳实践能力说明,覆盖 DeepWiki、Context7 和图标资源等默认增强能力",
374
+ "sentence": "新增:README 补充内置最佳实践能力说明,覆盖 DeepWiki、Context7 和图标资源等默认增强能力",
375
+ "recordedAt": "2026-06-06 15:46:44",
376
+ "source": {
377
+ "kind": "manual-note",
378
+ "manualId": "manual-note-1780732004293"
379
+ }
380
+ }
381
+ ],
382
+ "tag": null
340
383
  }
341
384
  ],
342
385
  "createdAt": "2026-06-02 21:28:04",
343
- "updatedAt": "2026-06-06 14:06:53"
386
+ "updatedAt": "2026-06-06 15:46:44"
344
387
  }
package/README.md CHANGED
@@ -59,6 +59,24 @@ OpenPrd 解决的问题,不只是“把 spec 写出来”,也不只是“把
59
59
  OpenPrd 最有特色的地方,在于它把“这次到底在做什么、谁来确认、凭什么继续往下走”
60
60
  做成稳定可见的协作面,而不是只留在 spec 文件或 prompt 流程里。
61
61
 
62
+ ## 内置最佳实践与默认增强能力
63
+
64
+ OpenPrd 不只负责把需求流程排好。对于“先参考什么更靠谱”这件事,它也内置了一层最佳实践路由,先帮 Agent 找对证据源,再继续改设计、写文案或动代码。
65
+
66
+ - **公开仓库架构理解**:需要看 GitHub 公开仓库的整体结构、关键流程和实现线索时,默认优先 `DeepWiki`
67
+ - **第三方技术文档**:需要确认库、框架、API、CLI、MCP 的用法、配置、限制、版本差异或迁移路径时,默认优先 `Context7`
68
+ - **图标与视觉资源**:需要选 UI 图标、AI 品牌图标、技术栈图标、3D 素材或功能图标时,会先按用途路由到更合适的来源,而不是让 Agent 临时乱找
69
+ - **项目级长期参考**:被反复采纳的外部来源可以沉淀成项目自己的最佳实践清单,后面类似任务会优先复用
70
+
71
+ 这层能力的目的不是堆资料链接,而是把“先查哪里、为什么查这里、查到什么程度就够”变成稳定协作的一部分。对于 AI 编程来说,这会直接影响方案质量、技术判断和最终实现的稳定性。
72
+
73
+ 目前 OpenPrd 内置的常见增强方向包括:
74
+
75
+ - `DeepWiki`:理解 GitHub 公开仓库的架构、模块关系和关键流程
76
+ - `Context7`:获取最新的第三方技术文档、配置说明、版本差异和迁移信息
77
+ - 图标与视觉资源路由:按场景优先参考 `Phosphor Icons`、`LobeHub Icons`、`Tech Icons`、`Thiings`、`iconfont`
78
+ - 图标实现库建议:真正落到前端代码时,再按项目情况选择 `Lucide`、`Tabler`、`React Icons`
79
+
62
80
  ## 典型真实场景
63
81
 
64
82
  最近 30 天的 Codex 项目记录里,OpenPrd 反复出现在几类连续工作里:模糊需求澄清、
@@ -131,6 +149,8 @@ OpenPrd 会沿着两条看得见的循环,越用越贴合你们的协作方式
131
149
  - **场景感知协同**:区分空项目冷启动、已有项目首次接入、持续推进中的 workspace
132
150
  - **自我成长机制**:把真实项目里确认过的做法沉淀成可复用的 `项目级 Skill`,并按场景沉淀 `动态参数配置`
133
151
  - **来源感知采集**:支持 `user-confirmed` / `project-derived` / `agent-inferred` / `agent-normalized`
152
+ - **最佳实践路由**:在公开仓库理解、第三方技术文档、图标资源和协作方式优化这类任务里,先把请求路由到更合适的证据源
153
+ - **项目级 benchmark registry**:支持 `benchmark add / observe / approve / verify`,把被反复采纳的外部来源沉淀成项目自己的长期参考
134
154
  - **图形评审工件**:支持 `architecture` 和 `product-flow`
135
155
  - **界面视觉对比工件**:把效果图与实现截图合成左右对比 JPG,用于阶段性复刻评审
136
156
  - **Contract 驱动图渲染**:支持从 JSON contract 显式渲染
package/README_EN.md CHANGED
@@ -64,6 +64,24 @@ OpenPrd is strongest when the hard part is not just "what code should be written
64
64
  but "what should people confirm, what should stay visible, and what evidence is
65
65
  enough to move forward."
66
66
 
67
+ ## Built-In Best-Practice Routing And Default Enhancers
68
+
69
+ OpenPrd does more than sequence requirement work. It also ships with a best-practice routing layer so the agent can look in the right place before it changes design, copy, or code.
70
+
71
+ - **Public-repo architecture understanding**: when the task is to understand a public GitHub repository, its subsystem layout, or a key execution flow, OpenPrd pushes the agent toward `DeepWiki` first
72
+ - **Third-party technical documentation**: when the task depends on library, framework, API, CLI, or MCP usage, config, limits, version differences, or migration paths, OpenPrd pushes the agent toward `Context7` first
73
+ - **Icons and visual assets**: when the task is to choose UI icons, AI brand icons, tech-stack icons, 3D assets, or functional icon resources, OpenPrd routes that request toward more appropriate sources instead of making the agent guess on the fly
74
+ - **Project-level long-term references**: repeatedly adopted external sources can be promoted into the project's own reusable best-practice registry
75
+
76
+ The goal is not to pile up links. The goal is to make "where should we look first, why this source, and when is the evidence sufficient" part of the normal collaboration flow. For AI coding work, that directly affects design quality, technical judgment, and implementation stability.
77
+
78
+ Common built-in enhancement directions include:
79
+
80
+ - `DeepWiki`: understand public GitHub repository architecture, module relationships, and key flows
81
+ - `Context7`: retrieve current third-party technical docs, config guidance, version differences, and migration notes
82
+ - Icon and visual-asset routing: prefer sources such as `Phosphor Icons`, `LobeHub Icons`, `Tech Icons`, `Thiings`, and `iconfont` by use case
83
+ - Icon implementation library suggestions: when the work reaches frontend code, narrow to `Lucide`, `Tabler`, or `React Icons` based on project fit
84
+
67
85
  ## Common Real-World Scenarios
68
86
 
69
87
  Recent Codex project usage kept clustering around the same kinds of work: fuzzy
@@ -150,6 +168,8 @@ automatically.
150
168
  - **Scenario-aware collaboration**: distinguish greenfield cold start, existing-project cold start, and continuing workspaces
151
169
  - **Self-evolving collaboration**: turn confirmed project habits into reusable `Project-Level Skill`s and adapt `Dynamic Parameter Config` by scenario
152
170
  - **Source-aware capture**: mark inputs as `user-confirmed`, `project-derived`, `agent-inferred`, or `agent-normalized`
171
+ - **Best-practice routing**: route public-repo understanding, third-party technical docs, icon resources, and workflow optimization requests to stronger evidence sources before implementation
172
+ - **Project-level benchmark registry**: support `benchmark add / observe / approve / verify` so repeatedly adopted external sources can become durable project references
153
173
  - **Diagram review artifacts**: generate both architecture and product-flow diagrams
154
174
  - **UI visual comparison artifacts**: combine reference images and implementation screenshots into side-by-side JPG reviews for visual replication work
155
175
  - **Contract-driven diagrams**: render from validated JSON contracts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openprd/cli",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "AI-native PRD workspace and lifecycle CLI",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -40,7 +40,7 @@ description: 驱动 OpenPrd 工作区完成从澄清到 handoff 的主流程。
40
40
  18. 用户要求基线文档、文件说明书、文件夹 README 标准或实现就绪检查时,路由到 `$openprd-standards`
41
41
  19. 用户要求日志、链路追踪、业务成本护栏、免费额度、滥用防护、评估执行环境、冒烟覆盖、性能基线、极端场景、HTML 质量评估报告或项目级经验 Skill 时,路由到 `$openprd-quality`
42
42
  20. 用户需要可视化说明,或系统/产品形态仍不清晰时,在进入需求定稿前路由到 `$openprd-diagram-review`
43
- 21. 默认保持 Codex hooks 轻量。除非项目明确需要完整工具级遥测,否则 `openprd setup/update` 使用 `--hook-profile lite`;默认 `lite` 会保留 `Stop` 收工回顾,用于在本轮结束前提醒是否值得沉淀项目经验
43
+ 21. 默认保持 Codex hooks 轻量。除非项目明确需要完整工具级遥测,否则 `openprd setup/update` 使用 `--hook-profile lite`;默认 `lite` 会保留 `Stop` 收工回顾,用于在本轮结束前提醒是否值得沉淀项目经验,并要求 Agent 用“本次情况 / 准备整理成的经验 / 以后如何复用 / 只保留在当前项目里”的结构化人话向用户确认
44
44
  22. hook 会强制阻断几类场景:需求入口未完成就写实现、外部证据不足就直接改第三方集成、skill/AGENTS 变更未先可视化确认、以及敏感信息场景下直接读原始 vault 文件
45
45
  23. 当 `doctor` 报告生成引导漂移时,读取 `.openprd/harness/drift-report.json`
46
46
 
@@ -68,7 +68,7 @@ description: 驱动 OpenPrd 工作区完成从澄清到 handoff 的主流程。
68
68
  - 不要把 `run --context` 建议当作直接用户命令
69
69
  - 用户给出会话 ID 续接历史任务时,使用 `openprd run <path> --context --message <用户原话或会话ID>` 保留通用会话 ID 语义;先恢复指定会话,不要让当前 active change 抢主线
70
70
  - 用户没有给 ID、但明确描述了已有需求/任务对象时,也使用 `openprd run <path> --context --message <用户原话>`;先解析对应的 change/task/work unit,再决定是否沿用当前工作区状态
71
- - 默认 lite Codex hooks 会为明确的 OpenPrd、PRD、深度调研、对标复刻、standards、fleet、文档标准化提示词,以及结构上较复杂的需求注入 `$openprd-requirement-intake` 分流提示;快速修正(L0)不打开 requirement gate,现有功能优化(L1)用对话内 mini-plan 承接,新功能/新流程方案(L2)才进入 PRD/review/change/tasks;轻量 `PreToolUse` 写入门禁会在需求入口未确认前阻断过早实现;本轮准备结束时,`Stop` 会基于 touched files 和 verify/finish 信号回顾是否要生成项目经验草案
71
+ - 默认 lite Codex hooks 会为明确的 OpenPrd、PRD、深度调研、对标复刻、standards、fleet、文档标准化提示词,以及结构上较复杂的需求注入 `$openprd-requirement-intake` 分流提示;快速修正(L0)不打开 requirement gate,现有功能优化(L1)用对话内 mini-plan 承接,新功能/新流程方案(L2)才进入 PRD/review/change/tasks;轻量 `PreToolUse` 写入门禁会在需求入口未确认前阻断过早实现;本轮准备结束时,`Stop` 会基于 touched files 和 verify/finish 信号回顾是否要生成项目经验草案,并要求 Agent 先用人话向用户确认是否保留到当前项目里
72
72
  - 只有当项目确实需要完整 hook 遥测或临时深度诊断时,才用 `openprd update <path> --hook-profile full`
73
73
  - 长程实现循环使用:
74
74
  - `openprd loop <path> --init`
@@ -90,7 +90,7 @@ description: 驱动 OpenPrd 工作区完成从澄清到 handoff 的主流程。
90
90
  - 声称单个任务完成前,只运行本任务最小足够验证,并通过 `--evidence`、测试报告或任务 metadata 留下 task-scoped evidence;不要在每个任务里反复运行 `openprd quality <path> --verify` 或全局 `openprd run <path> --verify`
91
91
  - 阶段收口、全部任务完成、handoff/commit/release/publish 前,再运行 `openprd quality <path> --verify` 并审阅生成的 HTML 质量评估报告,检查场景标签、必需 EVO 门禁、日志、业务护栏、冒烟覆盖、任务完整性、性能基线、极端场景和知识缺口
92
92
  - 如果当前任务是在发布 OpenPrd 自身到 GitHub,新版本必须同时具备匹配的项目版本号、版本条目、版本 tag 和 GitHub Release;只有 push/tag 没有 Release 不算发布闭环。优先先用 `openprd release <path> --notes ...` 累计版本条目,再用 `node scripts/openprd-github-release-notes.mjs <path> --version <x.y.z> --tag <vX.Y.Z>` 生成发布文案,并确认仓库 workflow 已成功 create/edit 对应 release
93
- - 如果本轮修复已经出现可复用模式,可先运行 `openprd quality <path> --learn --review --from .openprd/harness/turn-state.json` 生成 knowledge candidate / draft skill;确认值得长期保留后,再运行 `openprd quality <path> --learn --from <candidate-dir>` promote 为正式项目经验
93
+ - 如果本轮修复已经出现可复用模式,可先运行 `openprd quality <path> --learn --review --from .openprd/harness/turn-state.json` 生成项目经验候选;对用户呈现时先用“这次我观察到的情况 / 我计划保留的一条项目经验 / 以后如果再遇到类似任务我会怎么复用 / 这条经验只保留在当前项目里,要不要一起保留”这套结构化人话询问。确认值得长期保留后,再运行 `openprd quality <path> --learn --from <candidate-dir>` 正式沉淀为项目经验
94
94
  - `openprd loop <path> --finish` 应同时留下 Markdown 和 HTML 回归证据到 `.openprd/harness/test-reports/`,把它们视作任务交付物的一部分
95
95
  - 每个 loop 任务都对应一个全新 Codex 或 Claude 会话边界,不要在同一会话里继续下一项任务
96
96
  - 处理历史项目集群前先审计:
@@ -57,7 +57,7 @@ description: 评估 OpenPrd 的可观测性、业务成本与滥用护栏、评
57
57
  - 评估执行环境:冒烟测试、功能覆盖、正常性能和极端数据场景是否存在并持续维护
58
58
  - 视觉评审证据:大界面改动进入实现前,应存在 3 方向效果图横向评审大图和用户选定方向;涉及界面视觉实现且已有参考效果图时,确认 `.openprd/harness/visual-reviews/` 下存在本次 `openprd visual-compare` 输出的“效果图 / 实现截图”JPG,并且 Agent 已基于合成图复核差异;无参考图但改动界面时,确认存在“修改前 / 修改后”JPG,并已检查预期变化和未改区域漂移;当验收关注局部细节时,确认存在“局部焦点证据板”;当并行跑了多个优化方向时,确认存在“并行实验证据板”
59
59
  - HTML 质量评估报告:`.openprd/quality/reports/` 下的人类审查产物是否存在,且足以支持就绪判断
60
- - 知识 Skill:已验证修复是否应该先生成 `.openprd/knowledge/candidates/` `.openprd/knowledge/drafts/` 下的草案,再 promote `.openprd/knowledge/skills/` 下可复用的项目经验
60
+ - 项目经验沉淀:已验证修复是否应该先生成当前项目内的项目经验候选;若值得保留,先用“本次观察到的情况 / 计划保留的经验 / 以后怎么复用 / 只保留在当前项目里”的结构化人话询问用户,再沉淀为 `.openprd/knowledge/skills/` 下可复用的项目经验
61
61
  - 自我成长:配置缺口、文件识别、命令习惯或用户偏好优先沉淀为 `.openprd/growth` 候选,经用户确认后固化;不要把个人偏好混进项目共享质量经验
62
62
 
63
63
  ## 可观测性规则
@@ -186,7 +186,7 @@ const CANONICAL_SKILLS = [
186
186
  '- 维护 OpenPrd 本身时,只要新增或修改配置类能力(阈值、规则、识别、豁免、命令别名、环境差异、用户偏好或策略开关),都要做 grow-aware 自检:高置信应可成长时默认纳入 `openprd grow`;不确定时主动问用户;明确一次性或固定规则时才保持静态配置。',
187
187
  '- 只要实现新增或修改文件,就做文档影响检查;缺失的 `docs/basic/`、文件说明书和文件夹 README 要补齐,已有文档受影响时要更新。',
188
188
  '- 涉及后端、脚本、Agent、工具链、服务或数据处理变更时,把 CLI 与 API 视为同级接入面:检查命令入口、参数、输出契约、`help`、`doctor`、`dry-run`、`status` 与接口协议、返回结构、身份边界是否受影响,并同步更新 `docs/basic/backend-structure.md`;若某一面不适用也要明确写原因。',
189
- '- Codex hooks 默认使用 `lite`:`UserPromptSubmit` 注入上下文、轻量 `PreToolUse` 写入门禁,以及 `Stop` 本轮收工回顾。只有项目明确需要更重的工具级遥测时,才切到 `full`。',
189
+ '- Codex hooks 默认使用 `lite`:`UserPromptSubmit` 注入上下文、轻量 `PreToolUse` 写入门禁,以及 `Stop` 本轮收工回顾。若发现可复用的项目经验,`Stop` 会要求 Agent 在最终回复结尾用人话说明“本次观察到的情况 / 计划保留的项目经验 / 以后怎么复用 / 只保留在当前项目里”,再询问用户是否保留;只有项目明确需要更重的工具级遥测时,才切到 `full`。',
190
190
  '- 需求分流优先使用 `$openprd-requirement-intake`,不要按固定关键词判断。用户可见需求类型和内部路由码的固定对照为:快速修正=L0、现有功能优化=L1、新功能/新流程方案=L2。用户审查默认把路由码并进“需求类型:快速修正(L0)”这类标签里;只有内部排障确实受益时,才额外附“内部路由码”。L0 直接处理并事后说明,不打开正式 PRD/review/change/tasks;L1 先给对话内 mini-plan,默认不生成正式 PRD/change/tasks;只有 L2 才进入 requirement intake 与 PRD/review/change/tasks。L2 的对话内 requirement 摘要默认按“需求判断 / 需求理解 / 功能范围 / 技术方案”四段来写,其中“功能范围”和“技术方案”优先用 Markdown 表格,帮助用户先总后分地看清范围和实现方向。如果用户刚刚已经确认了现有功能优化(L1)的 mini-plan、范围边界或正式产品边界,后续承接要明确写成“已确认,我按这个继续/收口/落地”,不要用“确认,我们就按这个……”这类像再次索取确认的句子。单纯的“请帮我实现/继续实现”不等于跳过 requirement 摘要确认、`capture/classify/synthesize` 写入路径或 review;只有用户明确表示“不需要进行任何确认”时,才允许静默走完整 requirement write path。若当前仍在 L2 的首轮澄清或 requirement 摘要确认阶段,不要写成“你回我一句我就开始实现”;只能承诺“我先整理需求摘要给你确认,确认后再进入 PRD / review 流程”。若用户原始意图已经明确要求实现,review 已确认且 tasks 就绪后可直接进入执行;否则在请求执行授权前,先输出执行确认清单,列出本轮目标、将执行内容、不做事项、验证方式和已知风险,不能只要求用户回复一句确认。',
191
191
  '- 涉及最佳实践、benchmark、对标、参考产品、prompt engineering、Agent harness、context engineering、图标资源、CLI 或 skill 体系设计时,先使用 `$openprd-benchmark-router` 选择证据源,再进入 Context7、DeepWiki 或官方资料调研。',
192
192
  '- 入口路由优先看 `$openprd-router`;具体命令速查优先看 `.openprd/harness/command-catalog.md`。',
@@ -205,7 +205,7 @@ const CANONICAL_SKILLS = [
205
205
  '- 下一道门禁没看清之前,不要贸然执行写入命令。',
206
206
  '- 面对规划、分析、审查类请求,不要运行 `openprd loop --run`、`openprd tasks --advance`、`openprd discovery --advance`、`openprd loop --finish --commit`、git commit 或 git push。',
207
207
  '- 代码改动完成后,要回顾 `openprd dev-check` 输出;若出现需要关注的文件,直接复用 dev-check 生成的 **后续建议** 表格,并保留“关注程度”列里的完整风险标签,不要缩成纯 emoji。',
208
- '- 代码改动完成后,要回顾自我成长项:已自动补齐的低风险工具识别项简短说明;仍待确认的偏好、项目规矩或 OpenPrd 默认行为再用 `openprd grow . --review` 集中呈现。',
208
+ '- 代码改动完成后,要回顾自我成长项:已自动补齐的低风险工具识别项简短说明;仍待确认的偏好、项目规矩或 OpenPrd 默认行为再用 `openprd grow . --review` 集中呈现;若 `Stop` 提醒本轮有可沉淀的项目经验,结尾要先用人话说明“这次情况 / 计划保留的经验 / 以后怎么复用 / 只保留在当前项目里”,再问用户要不要保留。',
209
209
  '- 代码改动完成后,要说明 `docs/basic/`、文件说明书和文件夹 README 是新增、更新还是有意不变。',
210
210
  '- 用户要求生成图片、封面图、配图、海报、插画、图标、贴纸、头像、banner、主视觉/KV、运营图、效果图、视觉稿、mockup、先看样子或先确认设计方向时,最终回复应给出 `imagegen` 生成的图片结果;只有实际发生 `imagegen` 调用后,才能汇报生图结果、失败或限流。大界面改动进入实现前应给出 3 方向横向效果图大图并等待用户选择方向;如果是 logo、icon、avatar、badge 等开发素材且用户未明确要求 mockup 或场景化呈现,默认给出独立素材输出结果。进入实现阶段后,已有参考图才给出 `openprd visual-compare --reference/--actual` 生成的 JPG 路径,无参考图但改动界面时给出 `openprd visual-compare --before/--after` 生成的 JPG 路径;局部细节重点则补 `openprd visual-compare --board <focus-board.json>`,并行实验则补 `openprd visual-compare --board <parallel-board.json>`,并说明是否仍有差异或漂移。',
211
211
  '- `freeze`、`handoff`、`change --apply`、`change --archive`、commit、push、release、publish 等高风险动作都要求前置门禁全绿。',
@@ -354,7 +354,7 @@ const CANONICAL_SKILLS = [
354
354
  '## hook 驱动循环',
355
355
  '',
356
356
  '- 把 `.openprd/harness/run-state.json` 和 `iterations.jsonl` 当成持久循环状态。',
357
- '- 默认 lite hooks 不记录每一轮工具细节,但会在明确 OpenPrd / 深度工作提示词和产品、模块、流程需求下注入上下文;复杂或模糊需求提示先做三轮 Requirement Intake Reflection,轻量写入门禁会阻断过早改代码;本轮准备结束时再通过 `Stop` 做一次轻量项目经验回顾。',
357
+ '- 默认 lite hooks 不记录每一轮工具细节,但会在明确 OpenPrd / 深度工作提示词和产品、模块、流程需求下注入上下文;复杂或模糊需求提示先做三轮 Requirement Intake Reflection,轻量写入门禁会阻断过早改代码;本轮准备结束时再通过 `Stop` 做一次轻量项目经验回顾,并要求 Agent 先用人话明确“这条经验只会保留在当前项目里”,再向用户确认是否保留。',
358
358
  '- 只有项目确实需要完整遥测时才使用 `--hook-profile full`。',
359
359
  '- 上下文注入后,hooks 会从 OpenPrd 状态里推荐下一项 task、discovery 或 workflow 动作。',
360
360
  '- 门禁失败时,任务或覆盖项保持未完成状态,让下一轮继续重试。',
@@ -536,7 +536,7 @@ const CANONICAL_SKILLS = [
536
536
  '- `openprd quality . --init`:初始化 `.openprd/quality/config.json` 和 `.openprd/knowledge/`',
537
537
  '- `openprd quality . --verify`:在 `.openprd/quality/reports/` 下生成 JSON 和 HTML 质量评估报告',
538
538
  '- `openprd quality . --learn --from <report-id-or-json>`:把已修复或已审查的质量问题沉淀为项目级经验 Skill',
539
- '- `openprd grow . --review`:审查执行中发现的可复用配置、规则候选或 user-local 偏好;和 `quality --learn` 互补,前者沉淀操作配置,后者沉淀已验证质量经验。',
539
+ '- `openprd grow . --review`:审查执行中发现的可复用配置、规则候选或 user-local 偏好;和 `quality --learn` 互补,前者沉淀操作配置,后者沉淀已验证质量经验。若 `quality --learn --review` 命中项目经验候选,先在最终回复结尾用人话说明“本次情况 / 计划保留的经验 / 以后怎么复用 / 只保留在当前项目里”,再询问用户是否保留。',
540
540
  '',
541
541
  '## 审查契约',
542
542
  '',
@@ -1328,7 +1328,7 @@ function codexHookGroup(projectRoot, eventName, options = {}) {
1328
1328
  {
1329
1329
  type: 'command',
1330
1330
  command: hookCommand(projectRoot, eventName, options),
1331
- timeout: 15000,
1331
+ timeout: 30000,
1332
1332
  },
1333
1333
  ],
1334
1334
  };
@@ -1835,7 +1835,7 @@ async function smokeTestCodexHook(projectRoot, options = {}) {
1835
1835
  cwd: smokeRoot,
1836
1836
  input: JSON.stringify(codexHookSmokePayload(smokeRoot, eventName, smokeId)),
1837
1837
  encoding: 'utf8',
1838
- timeout: 15000,
1838
+ timeout: 30000,
1839
1839
  env: {
1840
1840
  ...process.env,
1841
1841
  OPENPRD_CLI: process.env.OPENPRD_CLI || cjoin(PACKAGE_ROOT, 'bin', 'openprd.js'),
@@ -1946,7 +1946,7 @@ function runOpenPrd(args, cwd) {
1946
1946
  const result = spawnSync(command, commandArgs, {
1947
1947
  cwd,
1948
1948
  encoding: 'utf8',
1949
- timeout: 15000,
1949
+ timeout: 30000,
1950
1950
  env: process.env,
1951
1951
  });
1952
1952
  return {
@@ -2557,7 +2557,7 @@ function runGateChecks(cwd, payload, risk) {
2557
2557
  output: change.stdout || change.stderr,
2558
2558
  });
2559
2559
  }
2560
- if (risk.level === 'high' && shouldRunDoctorForHighRisk(payload)) {
2560
+ if (risk.level === 'high' && shouldRunDoctorForHighRisk(payload) && runWorkspaceReady) {
2561
2561
  const doctor = runOpenPrd(['doctor', '.', '--tools', 'codex', '--json'], cwd);
2562
2562
  const doctorParsed = parseJsonOutput(doctor.stdout);
2563
2563
  checks.push({
@@ -2902,6 +2902,7 @@ function handle(eventName, cwd, payload) {
2902
2902
  && !parsedReview.skipped
2903
2903
  && parsedReview.ok !== false
2904
2904
  && parsedReview.candidateId
2905
+ && parsedReview.status === 'pending-review'
2905
2906
  && parsedReview.candidateId !== turnState.lastKnowledgePromptCandidateId
2906
2907
  ) {
2907
2908
  writeTurnState(root, {
@@ -2909,14 +2910,26 @@ function handle(eventName, cwd, payload) {
2909
2910
  lastKnowledgePromptCandidateId: parsedReview.candidateId,
2910
2911
  lastKnowledgePromptAt: now(),
2911
2912
  });
2913
+ const userFacingMessage = String(parsedReview.userFacingExperience?.message || '').trim();
2912
2914
  return allowHook([
2913
- 'OpenPrd 在本轮 Stop 回顾里发现了可沉淀的项目经验草案。',
2914
- `Draft Skill: ${parsedReview.files?.draftSkill ?? 'unknown'}`,
2915
- `候选目录: ${parsedReview.files?.candidateDir ?? 'unknown'}`,
2915
+ 'OpenPrd 在本轮收工回顾里发现了一条适合沉淀为当前项目经验的内容。',
2916
+ '请在最终回复结尾主动用下面这段结构化人话询问用户,不要改成内部术语,也不要提 Draft Skill、candidate、promote、路径或命令:',
2917
+ userFacingMessage || [
2918
+ '这次我观察到一个以后可能重复出现的情况:',
2919
+ '这次任务里已经形成了一种以后可能重复出现的处理方式。',
2920
+ '',
2921
+ '我计划保留一条项目经验:',
2922
+ '以后再遇到类似任务时,我会优先复用这次已经验证过的处理顺序和注意事项。',
2923
+ '',
2924
+ '以后如果再遇到类似任务,我会优先按这套经验来处理,减少重复解释和重复判断。',
2925
+ '这条经验只会保留在当前项目里。',
2926
+ '要我把它一起保留下来吗?',
2927
+ ].join('\n'),
2916
2928
  parsedReview.suggestedLearnCommand
2917
- ? `后续 promote: ${parsedReview.suggestedLearnCommand}`
2929
+ ? `如果用户明确同意,再执行:${parsedReview.suggestedLearnCommand}`
2918
2930
  : null,
2919
- '在最终回复里说明这次修复是否值得沉淀,以及草案已写到哪里。',
2931
+ `如果用户明确表示暂不保留,执行:openprd knowledge reject --path . --id ${parsedReview.candidateId} --reason "<用户原话>"`,
2932
+ `如果用户表示先不处理、以后再说,执行:openprd knowledge archive --path . --id ${parsedReview.candidateId} --reason "<用户原话>"`,
2920
2933
  ].filter(Boolean).join('\n'));
2921
2934
  }
2922
2935
  } catch {}
package/src/knowledge.js CHANGED
@@ -1000,10 +1000,11 @@ function buildKnowledgeCandidateMeta({
1000
1000
  existingCandidate,
1001
1001
  abstraction,
1002
1002
  }) {
1003
+ const existingStatus = normalizeCandidateStatus(existingCandidate?.status);
1003
1004
  return {
1004
1005
  version: 1,
1005
1006
  candidateId,
1006
- status: existingCandidate?.status === 'promoted' ? 'promoted' : 'pending-review',
1007
+ status: isReviewedKnowledgeCandidateStatus(existingStatus) ? existingStatus : 'pending-review',
1007
1008
  createdAt: existingCandidate?.createdAt ?? timestamp(),
1008
1009
  updatedAt: timestamp(),
1009
1010
  sourceKind: source.kind,
@@ -1025,6 +1026,140 @@ function buildKnowledgeCandidateMeta({
1025
1026
  };
1026
1027
  }
1027
1028
 
1029
+ function cleanUserFacingExperienceText(value, fallback = null, max = 160) {
1030
+ let text = String(value ?? '').replace(/\s+/g, ' ').trim();
1031
+ if (!text) {
1032
+ return fallback;
1033
+ }
1034
+ const replacements = [
1035
+ [/\bknowledge candidate\b/gi, '候选经验'],
1036
+ [/\bdraft skill\b/gi, '项目经验'],
1037
+ [/\bskill\b/gi, '经验'],
1038
+ [/\bpromote\b/gi, '保留'],
1039
+ [/\breject\b/gi, '暂不保留'],
1040
+ [/\barchive\b/gi, '先归档'],
1041
+ [/\bhook\b/gi, '收尾流程'],
1042
+ [/\bharness\b/gi, '项目流程'],
1043
+ [/\bturn-state\b/gi, '本轮记录'],
1044
+ [/\brun-state\b/gi, '当前状态'],
1045
+ [/\bOpenPrd\b/gi, '当前流程'],
1046
+ [/\.openprd\/[^\s,。;]+/g, ''],
1047
+ [/[A-Za-z0-9_.-]+\/[A-Za-z0-9_./-]+/g, ''],
1048
+ ];
1049
+ for (const [pattern, replacement] of replacements) {
1050
+ text = text.replace(pattern, replacement);
1051
+ }
1052
+ text = text
1053
+ .replace(/[()]/g, '')
1054
+ .replace(/[;;]+/g, ',')
1055
+ .replace(/\s+,/g, ',')
1056
+ .replace(/,+/g, ',')
1057
+ .replace(/\s+/g, ' ')
1058
+ .replace(/^[,,。\s]+|[,,。\s]+$/g, '')
1059
+ .trim();
1060
+ if (!text) {
1061
+ return fallback;
1062
+ }
1063
+ return trimPreview(text, max) ?? fallback;
1064
+ }
1065
+
1066
+ function observedSituationFromSource({ source, reviewSignals, touchedFiles }) {
1067
+ const direct = [
1068
+ source.symptoms[0],
1069
+ reviewSignals.find((signal) => signal.summary)?.summary,
1070
+ source.abstractPattern,
1071
+ source.title,
1072
+ ]
1073
+ .map((item) => cleanUserFacingExperienceText(item))
1074
+ .find(Boolean);
1075
+ if (direct) {
1076
+ return direct;
1077
+ }
1078
+ if (touchedFiles.length > 0) {
1079
+ return `这次任务在 ${touchedFiles.slice(0, 3).join('、')} 这些位置形成了一套后续可能会重复用到的处理方式。`;
1080
+ }
1081
+ return '这次任务里已经形成了一种以后可能重复出现的处理方式。';
1082
+ }
1083
+
1084
+ function plannedExperienceFromContext({ source, categories, touchedFiles }) {
1085
+ const touchesDocs = touchedFiles.some((file) => String(file).startsWith('docs/basic/'));
1086
+ const touchesTests = touchedFiles.some((file) => /^(test|tests)\//.test(String(file)));
1087
+ if (categories.includes('agent-misjudgment')) {
1088
+ return '以后做类似任务收尾时,我会先把本次情况、准备保留的经验和适用场景整理清楚,再用人话确认是否保留为当前项目经验。';
1089
+ }
1090
+ if (categories.includes('hidden-debug-knowledge')) {
1091
+ return '以后再遇到类似问题时,我会优先复用这次已经验证过的判断依据、排查顺序和收尾检查方式,而不是从零开始重新摸索。';
1092
+ }
1093
+ if (touchesDocs && touchesTests) {
1094
+ return '以后再遇到类似任务时,我会把处理方式、相关说明和验证方式一起整理成可复用经验,减少重复补充。';
1095
+ }
1096
+ if (touchesTests) {
1097
+ return '以后再遇到类似任务时,我会连同这次已经跑通的验证方式一起复用,避免只记住改法、忘记检查方式。';
1098
+ }
1099
+ const prevention = cleanUserFacingExperienceText(source.prevention[0]);
1100
+ if (prevention) {
1101
+ return prevention;
1102
+ }
1103
+ return '以后再遇到类似任务时,我会把这次已经验证过的处理顺序和注意事项保留下来,作为当前项目的默认经验。';
1104
+ }
1105
+
1106
+ function futureHandlingFromContext({ categories, touchedFiles }) {
1107
+ const touchesDocs = touchedFiles.some((file) => String(file).startsWith('docs/basic/'));
1108
+ if (categories.includes('agent-misjudgment')) {
1109
+ return '以后如果再遇到类似任务,我会优先按这套说法和处理顺序来收尾,减少重复解释和重复判断。';
1110
+ }
1111
+ if (touchesDocs) {
1112
+ return '以后如果再遇到类似任务,我会优先把处理方式和相关说明一起整理,减少来回补充。';
1113
+ }
1114
+ return '以后如果再遇到类似任务,我会优先按这套经验来处理,减少重复解释和重复判断。';
1115
+ }
1116
+
1117
+ function knowledgeReviewStateNote(status) {
1118
+ const normalized = normalizeCandidateStatus(status);
1119
+ if (normalized === 'rejected') {
1120
+ return '这条经验之前已经标记为暂不保留,本轮不会再次向用户追问。';
1121
+ }
1122
+ if (normalized === 'archived') {
1123
+ return '这条经验之前已经先归档,本轮不会再次向用户追问。';
1124
+ }
1125
+ if (normalized === 'promoted' || normalized === 'merged') {
1126
+ return '这条经验已经保留为当前项目经验,本轮不需要再次确认。';
1127
+ }
1128
+ return null;
1129
+ }
1130
+
1131
+ function buildKnowledgeUserFacingExperience({ candidate, source, touchedFiles, reviewSignals, categories }) {
1132
+ const observedSituation = observedSituationFromSource({ source, reviewSignals, touchedFiles });
1133
+ const plannedExperience = plannedExperienceFromContext({ source, categories, touchedFiles });
1134
+ const futureHandling = futureHandlingFromContext({ categories, touchedFiles });
1135
+ const scopeNote = '这条经验只会保留在当前项目里。';
1136
+ const reviewStateNote = knowledgeReviewStateNote(candidate.status);
1137
+ const shouldAskUser = normalizeCandidateStatus(candidate.status) === 'pending-review';
1138
+ const question = shouldAskUser ? '要我把它一起保留下来吗?' : null;
1139
+ const messageLines = [
1140
+ '这次我观察到一个以后可能重复出现的情况:',
1141
+ observedSituation,
1142
+ '',
1143
+ '我计划保留一条项目经验:',
1144
+ plannedExperience,
1145
+ '',
1146
+ futureHandling,
1147
+ scopeNote,
1148
+ question ?? reviewStateNote,
1149
+ ].filter(Boolean);
1150
+ return {
1151
+ observedSituation,
1152
+ plannedExperience,
1153
+ futureHandling,
1154
+ scopeNote,
1155
+ question,
1156
+ reviewStateNote,
1157
+ shouldAskUser,
1158
+ projectOnly: true,
1159
+ message: messageLines.join('\n'),
1160
+ };
1161
+ }
1162
+
1028
1163
  export async function recordKnowledgeReviewSignal(projectRoot, signal = {}) {
1029
1164
  const statePath = knowledgePath(projectRoot, OPENPRD_HARNESS_TURN_STATE);
1030
1165
  const normalized = normalizeReviewSignal(projectRoot, signal);
@@ -1202,6 +1337,13 @@ export async function reviewKnowledgeWorkspace(projectRoot, options = {}) {
1202
1337
  ...draftCandidate,
1203
1338
  abstraction,
1204
1339
  };
1340
+ const userFacingExperience = buildKnowledgeUserFacingExperience({
1341
+ candidate,
1342
+ source,
1343
+ touchedFiles: substantiveTouchedFiles,
1344
+ reviewSignals,
1345
+ categories,
1346
+ });
1205
1347
  await writeJson(candidatePath, candidate);
1206
1348
  await writeJson(diagnosticReportPath, buildCandidateDiagnosticReport({
1207
1349
  candidateId,
@@ -1257,7 +1399,9 @@ export async function reviewKnowledgeWorkspace(projectRoot, options = {}) {
1257
1399
  skillName: names.skillName,
1258
1400
  categories,
1259
1401
  reasons,
1402
+ status: candidate.status,
1260
1403
  summary: reviewSummary,
1404
+ userFacingExperience,
1261
1405
  suggestedLearnCommand: candidate.suggestedLearnCommand,
1262
1406
  files: {
1263
1407
  candidate: candidatePath,