@openprd/cli 0.1.19 → 0.1.22
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.
- package/.openprd/changes/openprd-control-plane-v020/.openprd.yaml +2 -0
- package/.openprd/changes/openprd-control-plane-v020/design.md +78 -0
- package/.openprd/changes/openprd-control-plane-v020/proposal.md +54 -0
- package/.openprd/changes/openprd-control-plane-v020/specs/agent-requirements/spec.md +16 -0
- package/.openprd/changes/openprd-control-plane-v020/task-events.jsonl +27 -0
- package/.openprd/changes/openprd-control-plane-v020/tasks-002.md +35 -0
- package/.openprd/changes/openprd-control-plane-v020/tasks.md +427 -0
- package/.openprd/changes/remove-session-tracking-humane-approval/.openprd.yaml +2 -0
- package/.openprd/changes/remove-session-tracking-humane-approval/design.md +52 -0
- package/.openprd/changes/remove-session-tracking-humane-approval/proposal.md +35 -0
- package/.openprd/changes/remove-session-tracking-humane-approval/specs/agent-requirements/spec.md +16 -0
- package/.openprd/changes/remove-session-tracking-humane-approval/task-events.jsonl +1 -0
- package/.openprd/changes/remove-session-tracking-humane-approval/tasks.md +170 -0
- package/.openprd/design/active/asset-spec.md +19 -14
- package/.openprd/design/active/direction-plan.md +19 -3
- package/.openprd/design/active/facts-sheet.md +16 -7
- package/.openprd/design/active/image-preflight.md +6 -5
- package/.openprd/design/active/review-studio-v020-directions/compare-plan.json +34 -0
- package/.openprd/design/active/review-studio-v020-directions/contact-sheet.jpg +0 -0
- package/.openprd/design/active/review-studio-v020-directions/crops/01.png +0 -0
- package/.openprd/design/active/review-studio-v020-directions/crops/02.png +0 -0
- package/.openprd/design/active/review-studio-v020-directions/crops/03.png +0 -0
- package/.openprd/design/active/review-studio-v020-directions/focus-board.template.json +69 -0
- package/.openprd/design/active/review-studio-v020-directions/parallel-board.template.json +45 -0
- package/.openprd/design/active/review-studio-v020-directions/reference-set.json +143 -0
- package/.openprd/design/active/review-studio-v020-directions/source.png +0 -0
- package/.openprd/design/active/selected-direction.md +23 -9
- package/.openprd/engagements/active/control-plane-architecture.json +203 -0
- package/.openprd/engagements/active/control-plane-intake.json +418 -0
- package/.openprd/engagements/active/prd.md +183 -119
- package/.openprd/engagements/active/review-presentation-v0018.json +176 -0
- package/.openprd/i18n-config.json +12 -0
- package/.openprd/ledger/events.jsonl +6 -0
- package/.openprd/ledger/heads/ebea1a71a9daa566f1c91b53.json +10 -0
- package/.openprd/manifest.json +21 -0
- package/AGENTS.md +7 -6
- package/README.md +31 -29
- package/README_EN.md +36 -39
- package/package.json +1 -1
- package/skills/openprd-frontend-design/SKILL.md +16 -0
- package/skills/openprd-harness/SKILL.md +7 -7
- package/skills/openprd-quality/SKILL.md +1 -1
- package/skills/openprd-requirement-intake/SKILL.md +1 -1
- package/skills/openprd-shared/SKILL.md +6 -6
- package/src/adapters/adapter-spi.js +193 -0
- package/src/adapters/capability-envelope.js +98 -0
- package/src/adapters/event-normalizer.js +55 -0
- package/src/adapters/index.js +4 -0
- package/src/adapters/install-safety.js +249 -0
- package/src/agent-canonical-content.js +4 -2
- package/src/agent-integration.js +169 -46
- package/src/cli/args.js +63 -4
- package/src/cli/gate-print.js +17 -0
- package/src/cli/quality-commands.js +18 -0
- package/src/cli/quality-print.js +10 -0
- package/src/cli/runtime-print.js +24 -0
- package/src/codex-hook-runner-template.mjs +129 -199
- package/src/codex-runtime.js +48 -5
- package/src/context/cache.js +245 -0
- package/src/context/compiler.js +438 -0
- package/src/context/constants.js +30 -0
- package/src/context/index.js +39 -0
- package/src/context/redaction.js +84 -0
- package/src/context/stable.js +69 -0
- package/src/context/telemetry.js +42 -0
- package/src/dev-standards.js +57 -0
- package/src/fleet.js +112 -95
- package/src/gates/index.js +2 -0
- package/src/gates/scoped-gates.js +256 -0
- package/src/gates/store.js +126 -0
- package/src/gates/workspace.js +41 -0
- package/src/html-artifacts.js +725 -28
- package/src/kernel/atomic-store.js +299 -0
- package/src/kernel/event-envelope.js +166 -0
- package/src/kernel/index.js +4 -0
- package/src/kernel/project-ledger.js +467 -0
- package/src/kernel/project-manifest.js +205 -0
- package/src/knowledge-v3/index.js +1 -0
- package/src/knowledge-v3/lifecycle.js +290 -0
- package/src/knowledge.js +14 -7
- package/src/openprd.js +71 -2
- package/src/review-model.js +413 -0
- package/src/review-presentation.js +1 -1
- package/src/run-harness.js +432 -38
- package/src/runtime/cli_runtime_README.md +28 -0
- package/src/runtime/errors.js +66 -0
- package/src/runtime/index.js +44 -0
- package/src/runtime/lane-schema.js +141 -0
- package/src/runtime/lane-store.js +279 -0
- package/src/runtime/task-runtime.js +449 -0
- package/src/runtime/workspace.js +179 -0
- package/src/runtime/write-set.js +206 -0
- package/src/session-binding.js +16 -3
- package/src/session-registry.js +59 -1
- package/src/upgrade/fleet-mutation.js +166 -0
- package/src/upgrade/fleet-transaction.js +398 -0
- package/src/upgrade/transaction-store.js +416 -0
- package/src/visual-compare-core.js +66 -27
- package/src/visual-compare.js +18 -12
- package/src/workspace-core.js +109 -7
- package/src/workspace-registry.js +39 -1
- package/src/workspace-workflow.js +18 -15
|
@@ -145,9 +145,11 @@ export const CANONICAL_SKILLS = [
|
|
|
145
145
|
'- 实现就绪还要运行 `openprd quality . --verify`,并审阅 HTML 质量评估报告中的场景标签、必需 EVO 门禁、可观测性、业务护栏、评估执行环境、性能和知识缺口。L2 或跨页面实现的最终回复必须带上最新 HTML 质量报告和 task-scoped 测试报告路径;缺失时只能说“实现完成但项目级收口未完成”。',
|
|
146
146
|
'- 用户要求生成图片、封面图、配图、海报、插画、图标、贴纸、头像、banner、主视觉/KV、运营图、效果图、视觉稿、mockup、先看样子或先确认设计方向时,先按生图路由选工具:Codex 环境用原生 `imagegen`(Image 2),Cursor 环境用内置 `GenerateImage`,两者都是对话工具内免费能力;都不可用或无法判断时,先读 `.openprd/harness/image-generation-preference.json` 的用户已确认偏好,没有偏好就先问用户选哪种生图方式并把答复以 `user-confirmed` 来源写回该文件作为下次默认;绝不擅自调用用户本地或自有付费生图 API。生图工具是工具路径,不是审美豁免。生成前先写清用途、受众、气质、约束和记忆点,并把它们写进 prompt;没有品牌或参考图依据时,用 anti-slop 排除默认紫白/蓝紫渐变、通用字体、白底卡片堆叠和无语境装饰。对 logo、icon、avatar、badge 等开发素材,如果用户未明确要求 mockup、场景图、设备框、卡片承载、名片/包装展示或参考界面复刻,默认按独立素材输出(standalone asset)处理:使用全画布单主体,不额外添加 UI frame、卡片、设备壳、名片、桌面陈列、手持实拍或其他展示容器。只有当用户明确要求 mockup、场景化效果图、容器化呈现,或参考图本身包含这些结构时,才生成对应容器或场景;除非用户明确指定 HTML、SVG、CSS、Canvas、代码稿或可编辑矢量/source artifact,不要改用临时 HTML/SVG/CSS 再截图。只有实际发生生图工具调用后,才能汇报生图结果、失败或限流。生图结果先当候选效果图,不要默认登记到 `.openprd/harness/visual-reviews/`;如果用户还要继续做实现,主动确认是否符合预期、是否纳入后续效果图/实现截图对比、以及是否按此继续实现。',
|
|
147
147
|
'- 对 logo、icon、avatar、badge、贴纸、空态插画、单物件 UI 位图等开发素材,如果最终要接入 UI 并需要透明背景,默认走“候选评审 -> 资产工程化 -> 接入验证”的图标资产链路:先基于用途、受众、气质、约束和记忆点生成 3 个差异足够大的独立素材候选方向,并保持纯 `#00ff00` 绿幕、无文字、无 UI 容器、主体居中且留足裁切边距;用户选定前不写入项目文件。用户选定后再定位源图或 contact sheet,保留绿幕源图,用 `remove_chroma_key.py` 抠成透明 PNG/WebP,按真实 UI 需要裁切居中并导出 384px 或多尺寸资产;接入时按首页卡片、工具格、吸顶栏、偏好预览等实际场景分别调显示比例,而不是只换图片路径。收口时同步写回 `.openprd/design/active/asset-spec.md` 和 `selected-direction.md`,说明选中的方向、资产路径、透明产物、接入位置和验证结果;最终回复必须区分绿幕源图、透明产物和是否已经接入。',
|
|
148
|
-
'- OpenPrd 的 `review.html` 用于需求评审,不能替代图片或效果图生成;`visual-compare` 只用于实现阶段视觉证据:已有确认参考图时对比“效果图 / 实现截图”;没有参考图时先判断新建界面还是修改既有界面,新建界面回到实现前 3 方向方案评审,修改既有界面再对比“修改前 / 修改后”;当局部细节更重要时,优先改用 `--board` 生成“局部焦点证据板”;当并行跑了多个优化方向时,优先改用 `--board` 生成“并行实验证据板”;当普通截图、Computer/Browser/Playwright 实测截图被用作证据时,必须改用 `--board` 生成“截图实测证据板”;当新功能或改动包含同构列表、卡片、网格、表格,或用户反馈排版没对齐时,必须改用 `--board`
|
|
148
|
+
'- OpenPrd 的 `review.html` 用于需求评审,不能替代图片或效果图生成;`visual-compare` 只用于实现阶段视觉证据:已有确认参考图时对比“效果图 / 实现截图”;没有参考图时先判断新建界面还是修改既有界面,新建界面回到实现前 3 方向方案评审,修改既有界面再对比“修改前 / 修改后”;当局部细节更重要时,优先改用 `--board` 生成“局部焦点证据板”;当并行跑了多个优化方向时,优先改用 `--board` 生成“并行实验证据板”;当普通截图、Computer/Browser/Playwright 实测截图被用作证据时,必须改用 `--board` 生成“截图实测证据板”;当新功能或改动包含同构列表、卡片、网格、表格,或用户反馈排版没对齐时,必须改用 `--board` 生成“对齐辅助线证据板”,且同时覆盖容器轨道和内部内容槽位轨道。网格、基线和对齐辅助线只允许出现在 `alignment-board` 的校验材料中;普通截图、最终 before/after、reference/actual 和 verification-board 只展示结果,不得自动叠加网格。当单个 logo、icon、avatar、badge、按钮图形或图片内部需要居中判定、视觉重心评估或用户反馈偏心时,必须改用 `--board` 生成“内部居中证据板”,用红色画布中心线、绿色主体外接框和黄色视觉重心点展示偏移。视觉证据不仅检查位置和结构,也要检查选定气质、层级、字体/色彩/动效/表面角色和记忆点是否保住。当参考图是一张整板、网格图、多对象或多子图组合时,先运行 `openprd visual-prepare` 生成 reference-set、contact sheet 和 board 模板,再进入实现对比。',
|
|
149
149
|
'- 轻量 UI 可视优化也要走视觉证据门:卡片宽度、间距、留白、对齐、颜色、圆角、字号、按钮、图标这类用户可见小改,不自动升级成大界面 3 方向评审,但动手前要有一句本轮审美意图和记忆点,收口前至少补 `visual-compare --before/--after`、`--board <focus-board.json>` 或 `--board <verification-board.json>`;如果存在同构列表、卡片、网格或表格,或用户反馈没对齐,还要补 `--board <alignment-board.json>`,把真实截图、辅助线、容器轨道 spread 和内容槽位 spread 放在一张板里;如果关注单个素材/图标/头像/徽标/按钮图形的内部居中或视觉重心,还要补 `--board <centering-board.json>`,把画布中心、主体外接框中心和视觉重心偏移放在一张板里;build、package、dev-check、单元测试和单张原始截图都不能替代视觉证据。',
|
|
150
150
|
'- 大界面改动在需求分流后、PRD 定稿或实现开工前先做视觉方案评审。先判断这是不是会决定首屏、核心布局、信息架构、主视觉或关键路径的场景,而不是按关键词触发;已有界面时用 Codex Computer Use 进入产品内对应功能并截当前真实界面,冷启动没有现有界面时基于已确认 PRD、用户群体、第一版切片和视觉目标生成设计 brief。brief 必须写清用途、受众、气质端点、约束和记忆点,3 个方向要分别说明审美主张和主动避开的模板味。再按生图路由调用内置生图工具(Codex=`imagegen`/Image 2,Cursor=`GenerateImage`)生成至少 3 个不同设计思想方向;把效果图横向拼成一张带 1/2/3 序号的大图,先作为候选效果图展示给用户。只有用户确认纳入后续对比或继续实现后,才把选定方向、整张图或其中子图整理到 `.openprd/harness/visual-reviews/` 并进入实现。',
|
|
151
|
+
'- 大界面三方向是 Agent 的默认责任,不要求用户另行提出生图;轻量 UI 微调不触发。已有界面时,Codex 原生 App 用 Computer Use、Codex 网页环境用 Chrome 插件任务专用窗口、Cursor 用可用浏览器或项目预览能力截图。三个方向必须共享同一张当前截图作为 image-to-image 参考;除非用户明确要求换风格,否则锁定字体角色、色彩系统、明暗模式、表面语言、圆角/边框/阴影、图标语言、平台框架和信息密度,只探索布局、层级、导航、关键路径与空间构图。冷启动没有现有界面时才使用 design brief,不伪造修改前截图。',
|
|
152
|
+
'- 界面候选图的内建质量标准:先明确用途、目标用户、业务约束、核心动作、气质和唯一记忆点,再从字体角色、色彩、空间构图、信息层级、表面/图标语言和动效含义评审;拒绝无依据蓝紫渐变、默认字体、等权白卡堆叠、过量大圆角、无语境装饰和只换颜色的伪方向。',
|
|
151
153
|
'- 界面、页面、视觉、样式或前端体验开发中,只要已经有效果图、设计稿、图片资产或用户给图并进入实现阶段,阶段性完成后必须先截实现图,再运行 `openprd visual-compare . --reference <效果图> --actual <实现截图> --locale <当前主语言>` 生成左右对比 JPG;中文语境默认标注“效果图 / 实现截图”,英文语境默认标注“Reference / Implementation”。如果这次要审局部细节,就补一份 `--board <focus-board.json>` 的局部焦点证据板。如果一张参考图里有多个子图、网格或对象,先运行 `openprd visual-prepare . --reference <效果图> --grid <列>x<行>` 或 `--boxes <plan.json>`,确认 contact sheet 后再逐项对比。普通截图和 Computer/Browser/Playwright 实测截图只能作为原始素材,收口前要用 `--board <verification-board.json> --locale <当前主语言>` 拼成截图实测证据板。证据板 JSON 里的 title、summary、label、notes、checks 等用户可见文案必须跟随用户当前主语言;中文语境不要整段写英文,英文语境不要混入默认中文标签。新功能开发或既有调整里出现同构列表、卡片、网格、表格时,即使用户没有主动提“对齐”,也要用 `--board <alignment-board.json>` 生成对齐辅助线证据板,先检查卡片外框、列宽、行顶等容器轨道,再检查标题、副标题、描述、标签、状态、价格、按钮、图标和操作区等内部内容槽位是否成轨;用户反馈没对齐/排版漂移时也走同一条路径。单个 logo、icon、avatar、badge、按钮图形或图片内部需要居中判定、视觉重心评估或用户反馈偏心时,要用 `--board <centering-board.json>` 生成内部居中证据板,检查画布中心、主体外接框中心和视觉重心偏移。Agent 必须查看合成图并继续对标,直到结构、气质、层级、字体/色彩/表面角色和记忆点都没有明显差异;如果用户后续说“跟效果图”“不一致”“好丑”“复刻”,至少先产出一份视觉证据图,不能只凭主观判断宣称完成。',
|
|
152
154
|
'- 界面、页面、视觉、样式或前端体验开发中,如果没有明确效果图、设计稿、图片资产或用户给图,要先判断这是新建界面还是修改既有界面:新建界面走实现前 3 方向方案评审;修改既有界面则动手前必须先用 Computer Use、Browser、Playwright 或项目现有工具截取修改前截图。完成后用同一入口、视口、账号和数据状态截取修改后截图,再运行 `openprd visual-compare . --before <修改前截图> --after <修改后截图> --locale <zh-CN|en>`。如果这次并行试了多条优化方向,再补一份 `--board <parallel-board.json>` 的并行实验证据板;如果只是普通截图或 Computer/Browser/Playwright 实测截图,也必须补 `--board <verification-board.json>` 的截图实测证据板。Agent 必须查看合成图,确认预期变化出现且未改区域没有明显漂移。',
|
|
153
155
|
'- 界面任务进入实现前,先用 `.openprd/design/` 锁定设计框架:页面涉及具体产品事实、版本、发布时间、规格、价格、引用数据或地点事实时,先补 `.openprd/design/active/facts-sheet.md`;页面依赖 logo、产品图、UI 图、摄影图、插图、图表或品牌色字体时,先补 `.openprd/design/active/asset-spec.md`;旅游、展览、内容、案例、发布、品牌故事等内容型页面,要先判断真实图片是不是页面成立前提,必要时先补 `.openprd/design/active/image-preflight.md`;没有明确参考方向时,先补 `.openprd/design/active/direction-plan.md`,并确保 3 个方向来自不同生成逻辑;用户选定方向后,再补 `.openprd/design/active/selected-direction.md`,把选中的 lens、theme、layout、组件和风险锁定。如果用户已经给了效果图、设计稿、参考截图或其他明确参考图,先把它当成主参考源:只有现有 starter、theme、layout 足够接近时才复用,不接近就允许偏离默认组合,以参考图为准。空白工作区的静态原型优先从 `.openprd/design/templates/` 里挑最近模板;如果当前轮用户已经把页面主题、模块范围或“直接实现”的意图说清,优先运行 `openprd run . --context --message <用户原话>`。如果页面主题和模块范围已经明确,优先运行 `openprd design-starter . --starter <starter-id> --out index.html --brief "<页面主题>" --sections "<模块1|模块2|模块3>"`,让 starter 一次写实 active design artifacts 和第一版真实页面。只有像个人博客、工具台、纯结构化产品页这类确认不靠真实图片成立的页面,才在 active design artifacts 写清无依赖并补 `--no-external-facts --no-brand-assets --no-real-images`;若题目更像旅游、导览、展览、博物馆、城市、自然观察或案例内容页,先不要带 `--no-real-images`,让 starter 先尝试补首批真实图片;若这类冷启动即使带 message 仍短暂返回 `clarify-user`,把它当成摘要级提醒,先用 3 到 5 行 mini-plan 收口,再继续。starter 落地后默认进入 `Patch Mode`:必须直接在生成的入口文件上补丁修改;即使结构要大改,也是在同一路径内覆盖,不做 delete-first,更不要删除 `index.html` 后另起新稿。如果确实要整页重写,先把完整新稿写到 sibling draft,例如 `index.next.html`,确认内容成形后再覆盖回 `index.html`,不要让正式入口出现空窗。starter 一落地后,只允许做一轮就地对焦:快速读一次生成的入口文件和必要的 active design artifacts;这轮对焦结束后,下一步就必须是真实写入口,不要再回头搜网页、翻 `docs/basic/` 或继续模板漫游。把最后一批必要的查事实、查图、读模板动作放在口头宣布之前做完;一旦已经说“开始覆盖入口文件”或“开始整页重写”,下一步必须出现真实写文件动作,而不是继续只读浏览、压图或停在口头承诺;必要时 hook 会把这类非写入动作挡回去。`Patch Mode` 完成不等于只补合同、只下载素材或只写计划;至少要把入口文件本体改完、主要占位清掉,并把已准备好的真实图片或参考约束真正落进页面。',
|
|
@@ -178,7 +180,7 @@ export const CANONICAL_SKILLS = [
|
|
|
178
180
|
'- 用户明确要求 Computer Use 时优先使用 Computer Use,并尽量在 Codex-owned browser window 中操作;对提交、删除、发送、切换账号、退出登录、支付、关闭标签页等高风险网页动作先确认窗口归属。',
|
|
179
181
|
'- 写产品界面上用户能看到的文案时,先站在“正在使用这个界面的用户”视角:这句话要说明用户现在能做什么、会发生什么、为什么值得点、下一步怎么走;不要站在开发者、产品经理或运营分类视角描述“适合某类用户”“这个模块用于承载某能力”。除非这是面向开发者或专业技术人员的技术型产品,否则默认用普通人能看懂的语言写结果、状态、限制和行动。',
|
|
180
182
|
'- 产品界面文案要先做一次用户视角改写检查:坏例“适合想保留全部工具入口的用户”,改成“首页会显示所有工具入口,你可以直接选择需要的功能”;坏例“API 请求失败,错误码 500”,改成“暂时保存失败,请稍后再试”;坏例“专业版会启用高级 pipeline”,改成“打开更多编辑工具,方便继续细调”。如果确实是技术型产品,可以保留必要术语,但仍要写清用户动作、影响和修复路径,例如“Webhook 验证失败,请检查签名密钥和权限”。',
|
|
181
|
-
'- 修改用户可见文案前,先检查 `i18n`、`locales`、`translations`、`Localizable` 或其他语言资源;若项目已有多语言结构,用户可见文案要同步维护到所有已支持语言,并避免暴露 API、SDK
|
|
183
|
+
'- 修改用户可见文案前,先检查 `i18n`、`locales`、`translations`、`Localizable` 或其他语言资源;若项目已有多语言结构,用户可见文案要同步维护到所有已支持语言,并避免暴露 API、SDK、模型、数据库、缓存或错误码等实现细节。如果项目有 `.openprd/i18n-config.json`,按配置的 `defaultLanguage` 和 `supportedLanguages` 执行;最佳实践参考 `docs/basic/i18n-guidelines.md`。',
|
|
182
184
|
'',
|
|
183
185
|
'## 写入纪律',
|
|
184
186
|
'',
|
package/src/agent-integration.js
CHANGED
|
@@ -10,6 +10,19 @@ import { renderApprovedBenchmarkRegistrySection } from './benchmark.js';
|
|
|
10
10
|
import { timestamp } from './time.js';
|
|
11
11
|
import { upsertWorkspaceRegistryEntry } from './workspace-registry.js';
|
|
12
12
|
import { readOperationalGitignoreStatus } from './workspace-core.js';
|
|
13
|
+
import {
|
|
14
|
+
ADAPTER_CONTRACT_VERSION,
|
|
15
|
+
CAPABILITY_STATUS,
|
|
16
|
+
adapterContractDigest,
|
|
17
|
+
describeCapabilities,
|
|
18
|
+
} from './adapters/index.js';
|
|
19
|
+
import {
|
|
20
|
+
INSTALL_SAFETY_PROTOCOL_VERSION,
|
|
21
|
+
normalizeSymlinkStrategy,
|
|
22
|
+
safeAppendText,
|
|
23
|
+
safeEnsureDirectory,
|
|
24
|
+
safeWriteText,
|
|
25
|
+
} from './adapters/install-safety.js';
|
|
13
26
|
|
|
14
27
|
const PACKAGE_ROOT = path.resolve(fileURLToPath(new URL('..', import.meta.url)));
|
|
15
28
|
const OPENPRD_AGENT_TOOLS = ['codex', 'claude', 'cursor'];
|
|
@@ -249,18 +262,16 @@ async function readJson(filePath) {
|
|
|
249
262
|
return JSON.parse(await readText(filePath));
|
|
250
263
|
}
|
|
251
264
|
|
|
252
|
-
async function writeText(filePath, text) {
|
|
253
|
-
await
|
|
254
|
-
await fs.writeFile(filePath, text, 'utf8');
|
|
265
|
+
async function writeText(filePath, text, options = {}) {
|
|
266
|
+
await safeWriteText(filePath, text, options);
|
|
255
267
|
}
|
|
256
268
|
|
|
257
|
-
async function writeJson(filePath, value) {
|
|
258
|
-
await writeText(filePath, `${JSON.stringify(value, null, 2)}\n
|
|
269
|
+
async function writeJson(filePath, value, options = {}) {
|
|
270
|
+
await writeText(filePath, `${JSON.stringify(value, null, 2)}\n`, options);
|
|
259
271
|
}
|
|
260
272
|
|
|
261
|
-
async function appendJsonl(filePath, value) {
|
|
262
|
-
await
|
|
263
|
-
await fs.appendFile(filePath, `${JSON.stringify(value)}\n`, 'utf8');
|
|
273
|
+
async function appendJsonl(filePath, value, options = {}) {
|
|
274
|
+
await safeAppendText(filePath, `${JSON.stringify(value)}\n`, options);
|
|
264
275
|
}
|
|
265
276
|
|
|
266
277
|
async function packageVersion() {
|
|
@@ -426,7 +437,7 @@ async function writeGeneratedFile(filePath, params, options, changes) {
|
|
|
426
437
|
});
|
|
427
438
|
return;
|
|
428
439
|
}
|
|
429
|
-
await writeText(filePath, body);
|
|
440
|
+
await writeText(filePath, body, options);
|
|
430
441
|
changes.push({ path: rel, status: current ? 'updated' : 'created' });
|
|
431
442
|
recordManagedFile(options, {
|
|
432
443
|
path: rel,
|
|
@@ -443,7 +454,7 @@ async function upsertTextBlockFile(filePath, id, blockBody, options, changes) {
|
|
|
443
454
|
const rel = normalizedRelativePath(options.projectRoot, filePath);
|
|
444
455
|
const current = await readText(filePath).catch(() => '');
|
|
445
456
|
const next = upsertManagedBlock(current || '', id, blockBody);
|
|
446
|
-
await writeText(filePath, next);
|
|
457
|
+
await writeText(filePath, next, options);
|
|
447
458
|
changes.push({ path: rel, status: current ? 'updated-block' : 'created' });
|
|
448
459
|
recordManagedFile(options, {
|
|
449
460
|
path: rel,
|
|
@@ -465,7 +476,7 @@ async function writeProjectCommandCatalog(projectRoot, options, changes) {
|
|
|
465
476
|
}
|
|
466
477
|
|
|
467
478
|
function agentContractBody() {
|
|
468
|
-
|
|
479
|
+
const lines = [
|
|
469
480
|
'## OpenPrd Harness',
|
|
470
481
|
'',
|
|
471
482
|
'本项目由 OpenPrd 管理。Agent 应优先遵循 repo-local skills 和 hooks;`AGENTS.md` 只保留轻量入口合同。',
|
|
@@ -500,6 +511,7 @@ function agentContractBody() {
|
|
|
500
511
|
'5. 纯图片、封面图、配图、海报、插画、图标、贴纸、mockup 或“先看样子”请求按生图路由选工具:先判断当前对话工具面,Codex 环境用原生 `imagegen`(Image 2),Cursor 环境用内置 `GenerateImage`,两者都是工具内免费能力;都不可用或无法判断时,先读 `.openprd/harness/image-generation-preference.json` 的用户已确认偏好,没有偏好就先问用户选哪种生图方式,并把答复以 `user-confirmed` 来源写回该文件作为下次默认;绝不擅自调用用户本地或自有付费生图 API。生图工具是工具路径,不是审美豁免,生成前先写清用途、受众、气质、约束和记忆点,并用 anti-slop 避免默认紫白/蓝紫渐变、通用字体、白底卡片堆叠和无语境装饰;其中 logo、icon、avatar、badge 等开发素材在用户未明确要求场景化展示时,默认按独立素材输出(standalone asset)生成:全画布单主体,不额外添加卡片、设备框或其他展示容器;只有实际发生生图工具调用后,才能汇报生图结果、失败或限流。生图结果先当候选效果图,不要默认登记到 `.openprd/harness/visual-reviews/`。Agent 要主动确认是否符合预期、是否纳入后续效果图/实现截图对比、以及是否按此继续实现;只有确认后,才把选定方向、整张图或其中子图整理成 reference-set 并进入实现。进入实现阶段时,已有确认参考图用 `openprd visual-compare --reference/--actual`;如果参考图是一张整板、网格图或多对象组合,先运行 `openprd visual-prepare --reference <效果图> --grid <列>x<行>` 或 `--boxes <plan.json>`,确认 contact sheet 后再逐项对比;没有参考图时先判断新建界面还是修改既有界面,新建界面先按用户目标、信息架构变化、视觉决策成本和验证风险完成 3 方向方案评审,修改既有界面用 `openprd visual-compare --before/--after`;局部细节重点则补 `openprd visual-compare --board <focus-board.json>`,多方向实验则补 `openprd visual-compare --board <parallel-board.json>`;普通截图、Computer/Browser/Playwright 实测截图要作为证据时补 `openprd visual-compare --board <verification-board.json>`;同构列表、卡片、网格、表格或用户反馈没对齐时补 `openprd visual-compare --board <alignment-board.json>`,并同时覆盖容器轨道与内部内容槽位轨道;单个 logo、icon、avatar、badge、按钮图形或图片内部居中/视觉重心/偏心问题补 `openprd visual-compare --board <centering-board.json>`,并同时覆盖画布中心、主体外接框中心和视觉重心偏移。visual evidence 同时检查气质、层级、字体/色彩/表面角色和记忆点,不只检查截图是否存在。用户后续如果说“跟效果图”“不一致”“好丑”“复刻”,不能只口头说对比过了,至少先产出一份视觉证据图。如果用户目标是把工作转成可学习、可复用、可回看、可教学或可沉淀的材料,先按期望产物是否需要章节结构、证据锚点、图文讲解、检索练习、工作示例或长期阅读体验来判断;需要时优先走 `openprd learn .` 生成学习包和阅读器,不要用关键词表触发;“仙侠风格的学习材料”这类短请求也按学习型交付物处理,风格只作为 `--genre` 题材参数。',
|
|
501
512
|
'5a. 对 logo、icon、avatar、badge、贴纸、空态插画、单物件 UI 位图等开发素材,如果最终要接入 UI 并需要透明背景,默认走“候选评审 -> 资产工程化 -> 接入验证”的图标资产链路:先基于用途、受众、气质、约束和记忆点生成 3 个差异足够大的独立素材候选方向,并保持纯 `#00ff00` 绿幕、无文字、无 UI 容器、主体居中且留足裁切边距;用户选定前不写入项目文件。用户选定后再定位源图或 contact sheet,保留绿幕源图,用 `remove_chroma_key.py` 抠成透明 PNG/WebP,按真实 UI 需要裁切居中并导出 384px 或多尺寸资产;接入时按首页卡片、工具格、吸顶栏、偏好预览等实际场景分别调显示比例,而不是只换图片路径。收口时同步写回 `.openprd/design/active/asset-spec.md` 和 `selected-direction.md`,说明选中的方向、资产路径、透明产物、接入位置和验证结果;最终回复必须区分绿幕源图、透明产物和是否已经接入。',
|
|
502
513
|
'5b. 卡片宽度、间距、留白、对齐、颜色、圆角、字号、按钮或图标等轻量 UI 可视优化,仍可按 L0/L1 小范围修正推进,不自动升级成大界面 3 方向方案评审;但它是用户可见变化,动手前要有一句审美意图和记忆点,收口必须补 `visual-compare` 修改前后图、局部焦点证据板、截图实测证据板、对齐辅助线证据板或内部居中证据板,并检查气质、层级、颜色、字号、间距和表面角色是否成立。只要界面里有同构列表、卡片、网格或表格,就把容器轨道以及标题、副标题、描述、标签、状态、价格、按钮、图标、操作区等相同文案类型/相同组件槽位的对齐当作默认验收项,不等用户先投诉;只量外框、列宽或行顶不算完整对齐验收。只要任务在判断单个素材/图标/头像/徽标/按钮图形的内部居中、偏心或视觉重心,就把 centering-board 当作默认验收项;单张原始截图或主观“看起来居中”不算完整居中验收。build、package、`openprd dev-check` 和单张原始截图不能替代视觉证据。`visual-compare` 每次都会返回 `chatEmbed` markdown;生成证据板后必须把这行 `` 直接嵌入最终回复,让证据在对话流里可见,不要只报告文件路径;`openprd dev-check` 在触达界面文件且最近 24 小时没有证据板时也会输出视觉证据提醒,收口前按提醒补齐。',
|
|
514
|
+
'5c. 大界面三方向是 Agent 的默认责任,不要求用户另行提出生图;轻量 UI 微调不触发。已有界面时,Codex 原生 App 用 Computer Use、Codex 网页环境用 Chrome 插件任务专用窗口、Cursor 用可用浏览器或项目预览能力截图;三个方向必须共享同一张当前截图作为 image-to-image 参考。除非用户明确要求换风格,否则锁定字体角色、色彩系统、明暗模式、表面语言、圆角/边框/阴影、图标语言、平台框架和信息密度,只探索布局、信息层级、导航、关键路径与空间构图。冷启动没有现有界面时才使用 design brief,不伪造修改前截图。候选图先明确用途、目标用户、业务约束、核心动作、气质和唯一记忆点,并检查字体、色彩、空间、层级、表面/图标语言和动效含义;拒绝无依据蓝紫渐变、默认字体、等权白卡堆叠、过量大圆角、无语境装饰和只换颜色的伪方向。',
|
|
503
515
|
'6. 界面、页面、视觉、样式、信息架构或前端体验任务进入实现前,先读取 `$openprd-frontend-design`,并用 `.openprd/design/active/` 补齐 `facts-sheet / asset-spec / image-preflight / direction-plan / selected-direction`;空白工作区优先从 `.openprd/design/templates/` 选最近模板。若用户已经给了效果图、设计稿、参考截图或其他明确参考图,先把它当成主参考源;只有现有 starter、theme、layout 足够接近时才复用,不接近就允许偏离默认组合,以参考图为准。若当前轮用户已经把页面主题、模块范围或“直接实现”的意图说清,优先运行 `openprd run . --context --message <用户原话>`。若页面主题和模块范围已经明确,优先运行 `openprd design-starter . --starter <starter-id> --out index.html --brief "<页面主题>" --sections "<模块1|模块2|模块3>"` 起第一版真实页面;只有这个页面本来就不依赖外部产品事实、品牌素材或真实图片时,才在 active design artifacts 写清无依赖并补 `--no-external-facts --no-brand-assets --no-real-images`;若题目更像旅游、导览、展览、博物馆、城市、自然观察或案例内容页,先不要带 `--no-real-images`,让 starter 先尝试补首批真实图片;若这类冷启动即使带 message 仍短暂返回 `clarify-user`,把它当成摘要级提醒,先用 3 到 5 行 mini-plan 收口,再继续。starter 落地后默认进入 `Patch Mode`,必须直接在生成的入口文件上补丁修改;即使结构要大改,也是在同一路径内覆盖,不做 delete-first,更不要删除 `index.html` 后另起新稿。如果确实要整页重写,先把完整新稿写到 sibling draft,例如 `index.next.html`,确认内容成形后再覆盖回 `index.html`,不要让正式入口出现空窗;starter 一落地后,只允许做一轮就地对焦:快速读一次生成的入口文件和必要的 active design artifacts;这轮对焦结束后,下一步就必须是真实写入口,不要再回头搜网页、翻 `docs/basic/` 或继续模板漫游;把最后一批必要的查事实、查图、读模板动作放在口头宣布之前做完;一旦已经说“开始覆盖入口文件”或“开始整页重写”,下一步必须出现真实写文件动作,而不是继续只读浏览、压图或停在口头承诺;必要时 hook 会把这类非写入动作挡回去;`Patch Mode` 完成不等于只补合同、只下载素材或只写计划;至少要把入口文件本体改完、主要占位清掉,并把已准备好的真实图片或参考约束真正落进页面;没有明确参考方向时,不要直接落回同一种安全极简解。',
|
|
504
516
|
'6a. 写产品界面上用户能看到的文案时,必须站在当前用户视角写:这句话要说明用户现在能做什么、会发生什么、为什么值得点、下一步怎么走。除非这是面向开发者或专业技术人员的技术型产品,否则默认用普通人能看懂的语言写结果、状态、限制和行动,不要暴露 API、SDK、模型、数据库、缓存、错误码或内部模块。正反例:不要写“适合想保留全部工具入口的用户”,改写成“首页会显示所有工具入口,你可以直接选择需要的功能”;不要写“API 请求失败,错误码 500”,改写成“暂时保存失败,请稍后再试”;技术型产品可以保留必要术语,但仍要写清用户动作、影响和修复路径。',
|
|
505
517
|
'6b. 开发新功能出现新的入口、按钮、tab、卡片、空态或工具格时,默认自动配图标,不等用户提出:先复用项目已有图标体系;项目没有时按图标最佳实践路由选型(UI 图标 Phosphor,落码 Lucide/Tabler/React Icons,AI 品牌 LobeHub Icons,技术栈 Tech Icons,功能图标/插画 iconfont),把图标名、来源、用途登记到 `.openprd/design/active/asset-spec.md` 的“功能图标”行再接入。只有语义确实不需要图标或用户明确说不要时才跳过,并在收口说明原因。',
|
|
@@ -515,8 +527,8 @@ function agentContractBody() {
|
|
|
515
527
|
'- requirement:需求未完成 `clarify/review/change/tasks` 前阻断实现写入;tasks 就绪后,只有用户原始意图已明确要求实现,或后续在看过执行确认清单后明确发出执行指令时才放行。',
|
|
516
528
|
'- research:公开 GitHub 架构/对标先 DeepWiki;第三方技术用法、配置、限制、版本差异或迁移先查本地证据,不足时再按 `resolve_library_id -> query_docs` 使用 Context7。',
|
|
517
529
|
'- design:界面、页面、视觉和前端体验任务进入实现前,先读取 `$openprd-frontend-design`,并补齐 `.openprd/design/active/` 下的事实、素材、图片和方向合同。',
|
|
518
|
-
'- skill-visualization:修改 skill、`SKILL.md`、`AGENTS.md` 或相关 workflow
|
|
519
|
-
'- autonomy-risk
|
|
530
|
+
'- skill-visualization:修改 skill、`SKILL.md`、`AGENTS.md` 或相关 workflow 前,用彩色 Mermaid 说明方案;它是可视说明,不是用户授权门禁,用户已要求修改时可继续执行。',
|
|
531
|
+
'- autonomy-risk:风险识别只向 Agent 提供配置化、迁移、标准状态机、幂等脚本和可回滚发布路径建议,不承担授权审批;OpenPrd 不因生产、支付、删除、账号切换、兜底、写死或材料缺失阻断工具调用,也不索取用户确认,是否需要确认由当前 Agent 依据宿主安全规则和真实上下文自行判断。',
|
|
520
532
|
'- secrets / weapp / browser / copy:分别处理 `secrets-vault`、按需的小程序运行态验证、窗口归属与 i18n/普通用户文案提醒。',
|
|
521
533
|
'- 需要细节时,读 router 指向的 skill 和 command catalog,而不是继续扩写 `AGENTS.md`。',
|
|
522
534
|
'',
|
|
@@ -526,7 +538,25 @@ function agentContractBody() {
|
|
|
526
538
|
'If the quality report says `productionReady=false`, do not claim overall readiness. Reuse `openprd run . --verify` to separate current-task status from workspace-level debt, list the missing evidence or gates, and when only `feature-coverage` is pending describe it as task-ledger or evidence debt rather than a failed implementation.',
|
|
527
539
|
'The only baseline documentation path is `docs/basic/`.',
|
|
528
540
|
'',
|
|
529
|
-
]
|
|
541
|
+
];
|
|
542
|
+
const overrides = new Map([
|
|
543
|
+
['- requirement:', '- requirement:requirement、PRD、review、change 和 tasks 是 Agent 后台维护材料,不阻断实现、不索取用户确认。'],
|
|
544
|
+
]);
|
|
545
|
+
return lines.map((line) => {
|
|
546
|
+
for (const [prefix, replacement] of overrides) {
|
|
547
|
+
if (line.startsWith(prefix)) {
|
|
548
|
+
return replacement;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
return line
|
|
552
|
+
.replace('只有新功能/新流程方案才先走 requirement intake、对话内 requirement 摘要确认,再 `review/change/tasks`,最后才实现。', '新功能/新流程方案的 requirement、PRD、review、change 和 tasks 由 Agent 在后台维护,不阻断用户已经要求的实现。')
|
|
553
|
+
.replace('L2 的首轮澄清只能承诺“我先整理需求摘要给你确认”,不能把 requirement 摘要确认、review 和实现压成一句“你回我一句我就开始实现”。', 'L2 的需求摘要是 Agent 后台维护材料,不创建用户确认停顿。')
|
|
554
|
+
.replace('如果 `openprd run . --context` 仍然建议 `clarify-user`,当前这轮回复的目标就只能是 `需求摘要` 或 `1 个最高价值澄清点`,不要写成“我先按默认方案实现”。', '如果 `openprd run . --context` 建议 `clarify-user`,Agent 优先选择可逆默认方案并显式说明假设;是否提问由 Agent 自行判断。')
|
|
555
|
+
.replace('单纯的“请帮我实现/继续实现”只表示有执行意图,不表示可以跳过 requirement 摘要确认、`capture/classify/synthesize` 写入路径或 review;只有用户明确表示“不需要进行任何确认”时,才允许静默走完整 requirement write path。', '“请帮我实现/继续实现”表示执行意图;requirement、capture/classify/synthesize 和 review 由 Agent 在后台维护,不追加 OpenPrd 确认门禁。')
|
|
556
|
+
.replace('`review.html` 是稳定评审 artifact,不再默认等于唯一的人类停顿点;默认按 decision-points approval policy 执行,只有当前 lane 仍要求人类决策时才在 final answer 主体里停下请求确认;当 review 已确认且 tasks 已就绪但还需要执行授权时,先给执行确认清单再请用户确认。', '`review.html` 是稳定评审 artifact,不是授权门禁;Agent 自行记录并继续,OpenPrd 不要求用户批准评审稿或执行清单。')
|
|
557
|
+
.replace('Agent 要主动确认是否符合预期、是否纳入后续效果图/实现截图对比、以及是否按此继续实现;只有确认后,才把选定方向、整张图或其中子图整理成 reference-set 并进入实现。', 'Agent 应展示候选方向并说明采用的可逆默认方向;OpenPrd 不强制等待确认,用户明确选择时再覆盖默认方向并更新 reference-set。')
|
|
558
|
+
.replace('用户选定前不写入项目文件。用户选定后再定位源图或 contact sheet', 'Agent 可先保留候选源图并选择可逆默认方向;用户明确选定时优先采用。进入资产工程化后再定位源图或 contact sheet');
|
|
559
|
+
}).join('\n');
|
|
530
560
|
}
|
|
531
561
|
|
|
532
562
|
function parseSkillSourceMarkdown(text) {
|
|
@@ -562,7 +592,7 @@ async function loadSourceSkill(skill) {
|
|
|
562
592
|
};
|
|
563
593
|
}
|
|
564
594
|
|
|
565
|
-
async function renderSkill(skill,
|
|
595
|
+
async function renderSkill(skill, projectRoot) {
|
|
566
596
|
const source = await loadSourceSkill(skill);
|
|
567
597
|
const sections = [
|
|
568
598
|
'---',
|
|
@@ -595,7 +625,7 @@ async function listFilesRecursive(rootDir) {
|
|
|
595
625
|
return files;
|
|
596
626
|
}
|
|
597
627
|
|
|
598
|
-
async function writeSourceSkillReferences(projectRoot, skill, destinationDir,
|
|
628
|
+
async function writeSourceSkillReferences(projectRoot, skill, destinationDir, version, options, changes) {
|
|
599
629
|
if (!skill.sourceSkill) {
|
|
600
630
|
return;
|
|
601
631
|
}
|
|
@@ -606,7 +636,7 @@ async function writeSourceSkillReferences(projectRoot, skill, destinationDir, ad
|
|
|
606
636
|
const body = await readText(filePath);
|
|
607
637
|
await writeGeneratedFile(
|
|
608
638
|
cjoin(destinationDir, 'references', relative),
|
|
609
|
-
{ adapter, source: `${skill.id}:reference:${relative}`, version, body },
|
|
639
|
+
{ adapter: 'shared', source: `${skill.id}:reference:${relative}`, version, body },
|
|
610
640
|
options,
|
|
611
641
|
changes,
|
|
612
642
|
);
|
|
@@ -743,7 +773,7 @@ async function writeCodexConfig(projectRoot, options, changes) {
|
|
|
743
773
|
const current = await readText(configPath).catch(() => '');
|
|
744
774
|
let next = normalizeFeatureHooks(current || '');
|
|
745
775
|
next = upsertTomlManagedBlock(next, 'CODEX-HOOKS', codexHooksTomlBlock(projectRoot, options));
|
|
746
|
-
await writeText(configPath, next);
|
|
776
|
+
await writeText(configPath, next, options);
|
|
747
777
|
changes.push({ path: rel, status: current ? 'updated' : 'created' });
|
|
748
778
|
recordManagedFile(options, {
|
|
749
779
|
path: rel,
|
|
@@ -758,7 +788,7 @@ async function writeCodexUserConfig(options, changes) {
|
|
|
758
788
|
const current = await readText(configPath).catch(() => '');
|
|
759
789
|
const next = normalizeFeatureHooks(current || '');
|
|
760
790
|
if (next !== current) {
|
|
761
|
-
await writeText(configPath, next);
|
|
791
|
+
await writeText(configPath, next, options);
|
|
762
792
|
}
|
|
763
793
|
changes.push({
|
|
764
794
|
path: displayPath(configPath),
|
|
@@ -814,7 +844,7 @@ async function writeCodexHooksJson(projectRoot, options, changes) {
|
|
|
814
844
|
delete next[eventName];
|
|
815
845
|
}
|
|
816
846
|
}
|
|
817
|
-
await writeJson(hooksPath, next);
|
|
847
|
+
await writeJson(hooksPath, next, options);
|
|
818
848
|
changes.push({ path: rel, status: existed ? 'updated' : 'created' });
|
|
819
849
|
recordManagedFile(options, {
|
|
820
850
|
path: rel,
|
|
@@ -827,15 +857,15 @@ async function writeCodexHooksJson(projectRoot, options, changes) {
|
|
|
827
857
|
async function writeCodexAdapter(projectRoot, options, changes) {
|
|
828
858
|
const version = await packageVersion();
|
|
829
859
|
for (const skill of CANONICAL_SKILLS) {
|
|
830
|
-
const body = await renderSkill(skill,
|
|
860
|
+
const body = await renderSkill(skill, projectRoot);
|
|
831
861
|
const skillDir = cjoin(projectRoot, '.codex', 'skills', skill.id);
|
|
832
862
|
await writeGeneratedFile(
|
|
833
863
|
cjoin(skillDir, 'SKILL.md'),
|
|
834
|
-
{ adapter: '
|
|
864
|
+
{ adapter: 'shared', source: skill.id, version, body },
|
|
835
865
|
options,
|
|
836
866
|
changes,
|
|
837
867
|
);
|
|
838
|
-
await writeSourceSkillReferences(projectRoot, skill, skillDir,
|
|
868
|
+
await writeSourceSkillReferences(projectRoot, skill, skillDir, version, options, changes);
|
|
839
869
|
}
|
|
840
870
|
for (const command of CANONICAL_COMMANDS) {
|
|
841
871
|
await writeGeneratedFile(
|
|
@@ -864,15 +894,15 @@ async function writeClaudeAdapter(projectRoot, options, changes) {
|
|
|
864
894
|
const version = await packageVersion();
|
|
865
895
|
await upsertTextBlockFile(cjoin(projectRoot, 'CLAUDE.md'), 'CLAUDE', agentContractBody(), options, changes);
|
|
866
896
|
for (const skill of CANONICAL_SKILLS) {
|
|
867
|
-
const body = await renderSkill(skill,
|
|
897
|
+
const body = await renderSkill(skill, projectRoot);
|
|
868
898
|
const skillDir = cjoin(projectRoot, '.claude', 'skills', skill.id);
|
|
869
899
|
await writeGeneratedFile(
|
|
870
900
|
cjoin(skillDir, 'SKILL.md'),
|
|
871
|
-
{ adapter: '
|
|
901
|
+
{ adapter: 'shared', source: skill.id, version, body },
|
|
872
902
|
options,
|
|
873
903
|
changes,
|
|
874
904
|
);
|
|
875
|
-
await writeSourceSkillReferences(projectRoot, skill, skillDir,
|
|
905
|
+
await writeSourceSkillReferences(projectRoot, skill, skillDir, version, options, changes);
|
|
876
906
|
}
|
|
877
907
|
for (const command of CANONICAL_COMMANDS) {
|
|
878
908
|
await writeGeneratedFile(
|
|
@@ -902,8 +932,8 @@ async function writeCursorAdapter(projectRoot, options, changes) {
|
|
|
902
932
|
}
|
|
903
933
|
}
|
|
904
934
|
|
|
905
|
-
async function ensureHarnessState(projectRoot) {
|
|
906
|
-
await
|
|
935
|
+
async function ensureHarnessState(projectRoot, options = {}) {
|
|
936
|
+
await safeEnsureDirectory(harnessPath(projectRoot, OPENPRD_HARNESS_DIR), options);
|
|
907
937
|
const hookStatePath = harnessPath(projectRoot, OPENPRD_HARNESS_HOOK_STATE);
|
|
908
938
|
if (!(await exists(hookStatePath))) {
|
|
909
939
|
await writeJson(hookStatePath, {
|
|
@@ -915,11 +945,11 @@ async function ensureHarnessState(projectRoot) {
|
|
|
915
945
|
suppressions: {
|
|
916
946
|
inputLock: false,
|
|
917
947
|
},
|
|
918
|
-
});
|
|
948
|
+
}, options);
|
|
919
949
|
}
|
|
920
950
|
const eventsPath = harnessPath(projectRoot, OPENPRD_HARNESS_EVENTS);
|
|
921
951
|
if (!(await exists(eventsPath))) {
|
|
922
|
-
await writeText(eventsPath, '');
|
|
952
|
+
await writeText(eventsPath, '', options);
|
|
923
953
|
}
|
|
924
954
|
const runtimeEnvironmentPath = harnessPath(projectRoot, OPENPRD_HARNESS_RUNTIME_ENVIRONMENT);
|
|
925
955
|
if (!(await exists(runtimeEnvironmentPath))) {
|
|
@@ -940,7 +970,7 @@ async function ensureHarnessState(projectRoot) {
|
|
|
940
970
|
runtimeDetection: RUNTIME_DETECTION_PROTOCOL,
|
|
941
971
|
platformCapabilityPacks: [],
|
|
942
972
|
updatedAt: null,
|
|
943
|
-
});
|
|
973
|
+
}, options);
|
|
944
974
|
}
|
|
945
975
|
}
|
|
946
976
|
|
|
@@ -1021,16 +1051,85 @@ function configuredOptionalCapabilityIds(optionalCapabilities) {
|
|
|
1021
1051
|
.map((capability) => capability.id);
|
|
1022
1052
|
}
|
|
1023
1053
|
|
|
1054
|
+
function capabilityGapsForEnvelope(envelope) {
|
|
1055
|
+
return Object.entries(envelope?.capabilities ?? {})
|
|
1056
|
+
.filter(([, status]) => typeof status === 'string' && status !== CAPABILITY_STATUS.NATIVE)
|
|
1057
|
+
.map(([capability, status]) => ({
|
|
1058
|
+
capability,
|
|
1059
|
+
status,
|
|
1060
|
+
requirement: status === CAPABILITY_STATUS.SURFACE_DEPENDENT
|
|
1061
|
+
? 'current-surface-evidence'
|
|
1062
|
+
: 'adapter-or-platform-support',
|
|
1063
|
+
}));
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
function buildAdapterProtocol(tools) {
|
|
1067
|
+
const clients = normalizeTools(tools).map((client) => {
|
|
1068
|
+
const capabilityEnvelope = describeCapabilities(client);
|
|
1069
|
+
return {
|
|
1070
|
+
client,
|
|
1071
|
+
contractVersion: ADAPTER_CONTRACT_VERSION,
|
|
1072
|
+
capabilitySchema: capabilityEnvelope.schema,
|
|
1073
|
+
lifecycleEventSchema: 'openprd.adapter-event.v1',
|
|
1074
|
+
capabilityEnvelope,
|
|
1075
|
+
capabilityGaps: capabilityGapsForEnvelope(capabilityEnvelope),
|
|
1076
|
+
};
|
|
1077
|
+
});
|
|
1078
|
+
return {
|
|
1079
|
+
schema: 'openprd.adapter-protocol.v1',
|
|
1080
|
+
protocolVersion: ADAPTER_CONTRACT_VERSION,
|
|
1081
|
+
capabilityVersion: ADAPTER_CONTRACT_VERSION,
|
|
1082
|
+
lifecycleEventVersion: ADAPTER_CONTRACT_VERSION,
|
|
1083
|
+
contractDigest: adapterContractDigest(),
|
|
1084
|
+
generatedContent: {
|
|
1085
|
+
version: 1,
|
|
1086
|
+
sharedSkillMarker: 'shared',
|
|
1087
|
+
sharedSkillConsumers: clients
|
|
1088
|
+
.map((entry) => entry.client)
|
|
1089
|
+
.filter((client) => client === 'codex' || client === 'claude'),
|
|
1090
|
+
platformSpecificKinds: ['command', 'hook', 'rule', 'config'],
|
|
1091
|
+
},
|
|
1092
|
+
clients,
|
|
1093
|
+
};
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1024
1096
|
function buildPlatformCapabilityPacks(tools, optionalCapabilities = []) {
|
|
1025
1097
|
const toolSet = new Set(normalizeTools(tools));
|
|
1026
1098
|
const configuredOptional = configuredOptionalCapabilityIds(optionalCapabilities);
|
|
1027
1099
|
return PLATFORM_CAPABILITY_PACK_REGISTRY
|
|
1028
1100
|
.filter((pack) => toolSet.has(pack.client))
|
|
1029
|
-
.map((pack) =>
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1101
|
+
.map((pack) => {
|
|
1102
|
+
const capabilityEnvelope = describeCapabilities(pack.client);
|
|
1103
|
+
return {
|
|
1104
|
+
...pack,
|
|
1105
|
+
adapterContractVersion: ADAPTER_CONTRACT_VERSION,
|
|
1106
|
+
capabilityEnvelope,
|
|
1107
|
+
capabilityGaps: capabilityGapsForEnvelope(capabilityEnvelope),
|
|
1108
|
+
capabilities: pack.capabilities.map((capability) => ({ ...capability })),
|
|
1109
|
+
optionalEnhancements: configuredOptional,
|
|
1110
|
+
};
|
|
1111
|
+
});
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
function installSafetyManifest(projectRoot, options = {}) {
|
|
1115
|
+
const events = Array.isArray(options.installSafetyEvents) ? options.installSafetyEvents : [];
|
|
1116
|
+
const eventPath = (filePath) => {
|
|
1117
|
+
const relative = normalizedRelativePath(projectRoot, filePath);
|
|
1118
|
+
return relative === '..' || relative.startsWith('../') ? displayPath(filePath) : relative;
|
|
1119
|
+
};
|
|
1120
|
+
return {
|
|
1121
|
+
protocolVersion: INSTALL_SAFETY_PROTOCOL_VERSION,
|
|
1122
|
+
symlinkStrategy: normalizeSymlinkStrategy(options.symlinkStrategy),
|
|
1123
|
+
materializationCount: events.length,
|
|
1124
|
+
materializations: events.map((event) => ({
|
|
1125
|
+
strategy: event.strategy,
|
|
1126
|
+
path: eventPath(event.symlinkPath),
|
|
1127
|
+
linkTarget: event.linkTarget,
|
|
1128
|
+
backupPath: displayPath(event.backupPath),
|
|
1129
|
+
materializedType: event.materializedType,
|
|
1130
|
+
at: event.at,
|
|
1131
|
+
})),
|
|
1132
|
+
};
|
|
1034
1133
|
}
|
|
1035
1134
|
|
|
1036
1135
|
async function writeInstallManifest(projectRoot, options, changes, tools, optionalCapabilities) {
|
|
@@ -1053,23 +1152,26 @@ async function writeInstallManifest(projectRoot, options, changes, tools, option
|
|
|
1053
1152
|
runtimeEnvironment: OPENPRD_HARNESS_RUNTIME_ENVIRONMENT,
|
|
1054
1153
|
driftReport: OPENPRD_HARNESS_DRIFT,
|
|
1055
1154
|
},
|
|
1155
|
+
adapterProtocol: buildAdapterProtocol(tools),
|
|
1056
1156
|
runtimeDetection: RUNTIME_DETECTION_PROTOCOL,
|
|
1057
1157
|
platformCapabilityPacks: buildPlatformCapabilityPacks(tools, optionalCapabilities),
|
|
1058
1158
|
imageGenerationRouting: IMAGE_GENERATION_ROUTING,
|
|
1159
|
+
installSafety: installSafetyManifest(projectRoot, options),
|
|
1059
1160
|
optionalCapabilities,
|
|
1060
1161
|
};
|
|
1061
1162
|
const manifestPath = harnessPath(projectRoot, OPENPRD_HARNESS_MANIFEST);
|
|
1062
1163
|
const existed = await exists(manifestPath);
|
|
1063
|
-
await writeJson(manifestPath, manifest);
|
|
1164
|
+
await writeJson(manifestPath, manifest, options);
|
|
1064
1165
|
const runtimeEnvironmentPath = harnessPath(projectRoot, OPENPRD_HARNESS_RUNTIME_ENVIRONMENT);
|
|
1065
1166
|
const runtimeEnvironment = await readJson(runtimeEnvironmentPath).catch(() => null);
|
|
1066
1167
|
await writeJson(runtimeEnvironmentPath, {
|
|
1067
1168
|
version: 1,
|
|
1068
1169
|
...(runtimeEnvironment ?? {}),
|
|
1170
|
+
adapterProtocol: manifest.adapterProtocol,
|
|
1069
1171
|
runtimeDetection: RUNTIME_DETECTION_PROTOCOL,
|
|
1070
1172
|
platformCapabilityPacks: manifest.platformCapabilityPacks,
|
|
1071
1173
|
imageGenerationRouting: IMAGE_GENERATION_ROUTING,
|
|
1072
|
-
});
|
|
1174
|
+
}, options);
|
|
1073
1175
|
const configuredCount = optionalCapabilities.filter((capability) => capability.configured).length;
|
|
1074
1176
|
changes.push({ path: OPENPRD_HARNESS_MANIFEST, status: existed ? 'updated' : 'created' });
|
|
1075
1177
|
await appendJsonl(harnessPath(projectRoot, OPENPRD_HARNESS_EVENTS), {
|
|
@@ -1082,7 +1184,11 @@ async function writeInstallManifest(projectRoot, options, changes, tools, option
|
|
|
1082
1184
|
platformCapabilityPackCount: manifest.platformCapabilityPacks.length,
|
|
1083
1185
|
optionalCapabilityConfiguredCount: configuredCount,
|
|
1084
1186
|
optionalCapabilityRecommendedCount: optionalCapabilities.length - configuredCount,
|
|
1085
|
-
|
|
1187
|
+
adapterContractVersion: manifest.adapterProtocol.protocolVersion,
|
|
1188
|
+
adapterContractDigest: manifest.adapterProtocol.contractDigest,
|
|
1189
|
+
symlinkStrategy: manifest.installSafety.symlinkStrategy,
|
|
1190
|
+
materializationCount: manifest.installSafety.materializationCount,
|
|
1191
|
+
}, options);
|
|
1086
1192
|
return manifest;
|
|
1087
1193
|
}
|
|
1088
1194
|
|
|
@@ -1113,7 +1219,7 @@ async function inspectManagedFile(projectRoot, entry) {
|
|
|
1113
1219
|
return { ...entry, ok: true, reason: 'ok' };
|
|
1114
1220
|
}
|
|
1115
1221
|
|
|
1116
|
-
async function computeDriftReport(projectRoot, tools) {
|
|
1222
|
+
async function computeDriftReport(projectRoot, tools, options = {}) {
|
|
1117
1223
|
const manifest = await readInstallManifest(projectRoot);
|
|
1118
1224
|
const checks = [];
|
|
1119
1225
|
if (!manifest) {
|
|
@@ -1124,7 +1230,7 @@ async function computeDriftReport(projectRoot, tools) {
|
|
|
1124
1230
|
errors: [`${OPENPRD_HARNESS_MANIFEST} is missing. Run openprd update .`],
|
|
1125
1231
|
checks,
|
|
1126
1232
|
};
|
|
1127
|
-
await writeJson(harnessPath(projectRoot, OPENPRD_HARNESS_DRIFT), report);
|
|
1233
|
+
await writeJson(harnessPath(projectRoot, OPENPRD_HARNESS_DRIFT), report, options);
|
|
1128
1234
|
return report;
|
|
1129
1235
|
}
|
|
1130
1236
|
for (const entry of manifest.managedFiles ?? []) {
|
|
@@ -1142,7 +1248,7 @@ async function computeDriftReport(projectRoot, tools) {
|
|
|
1142
1248
|
errors,
|
|
1143
1249
|
checks,
|
|
1144
1250
|
};
|
|
1145
|
-
await writeJson(harnessPath(projectRoot, OPENPRD_HARNESS_DRIFT), report);
|
|
1251
|
+
await writeJson(harnessPath(projectRoot, OPENPRD_HARNESS_DRIFT), report, options);
|
|
1146
1252
|
return report;
|
|
1147
1253
|
}
|
|
1148
1254
|
|
|
@@ -1151,14 +1257,22 @@ export async function setupOpenPrdAgentIntegration(projectRoot, options = {}) {
|
|
|
1151
1257
|
const hookProfile = normalizeHookProfile(options.hookProfile);
|
|
1152
1258
|
const changes = [];
|
|
1153
1259
|
const managedFiles = [];
|
|
1260
|
+
const installSafetyEvents = [];
|
|
1261
|
+
const managedWriteRoots = [projectRoot];
|
|
1262
|
+
if (options.enableUserCodexConfig) {
|
|
1263
|
+
managedWriteRoots.push(resolveCodexHome(options));
|
|
1264
|
+
}
|
|
1154
1265
|
const normalizedOptions = {
|
|
1155
1266
|
...options,
|
|
1156
1267
|
projectRoot,
|
|
1157
1268
|
hookProfile,
|
|
1158
1269
|
managedFiles,
|
|
1270
|
+
installSafetyEvents,
|
|
1271
|
+
managedWriteRoots,
|
|
1272
|
+
symlinkStrategy: normalizeSymlinkStrategy(options.symlinkStrategy),
|
|
1159
1273
|
};
|
|
1160
1274
|
|
|
1161
|
-
await ensureHarnessState(projectRoot);
|
|
1275
|
+
await ensureHarnessState(projectRoot, normalizedOptions);
|
|
1162
1276
|
await writeProjectCommandCatalog(projectRoot, normalizedOptions, changes);
|
|
1163
1277
|
await upsertTextBlockFile(cjoin(projectRoot, 'AGENTS.md'), 'AGENTS', agentContractBody(), normalizedOptions, changes);
|
|
1164
1278
|
|
|
@@ -1187,7 +1301,7 @@ export async function setupOpenPrdAgentIntegration(projectRoot, options = {}) {
|
|
|
1187
1301
|
registryPath: registry.registryPath,
|
|
1188
1302
|
status: registry.status,
|
|
1189
1303
|
knownTotal: registry.knownTotal,
|
|
1190
|
-
});
|
|
1304
|
+
}, normalizedOptions);
|
|
1191
1305
|
|
|
1192
1306
|
const doctor = await doctorOpenPrdAgentIntegration(projectRoot, {
|
|
1193
1307
|
tools,
|
|
@@ -1363,7 +1477,16 @@ async function smokeTestCodexHook(projectRoot, options = {}) {
|
|
|
1363
1477
|
export async function doctorOpenPrdAgentIntegration(projectRoot, options = {}) {
|
|
1364
1478
|
const tools = normalizeTools(options.tools);
|
|
1365
1479
|
const checks = [];
|
|
1366
|
-
|
|
1480
|
+
const managedWriteRoots = [projectRoot];
|
|
1481
|
+
if (options.enableUserCodexConfig) {
|
|
1482
|
+
managedWriteRoots.push(resolveCodexHome(options));
|
|
1483
|
+
}
|
|
1484
|
+
const safetyOptions = {
|
|
1485
|
+
...options,
|
|
1486
|
+
projectRoot,
|
|
1487
|
+
managedWriteRoots,
|
|
1488
|
+
};
|
|
1489
|
+
await ensureHarnessState(projectRoot, safetyOptions);
|
|
1367
1490
|
const manifest = await readInstallManifest(projectRoot);
|
|
1368
1491
|
const hookProfile = normalizeHookProfile(options.hookProfile ?? manifest?.hooks?.profile);
|
|
1369
1492
|
const optionalCapabilities = await collectOptionalCapabilities(projectRoot, tools, options);
|
|
@@ -1425,7 +1548,7 @@ export async function doctorOpenPrdAgentIntegration(projectRoot, options = {}) {
|
|
|
1425
1548
|
await collectGeneratedDoctorCheck(projectRoot, checks, '.cursor/rules/openprd.mdc', 'Cursor OpenPrd rule');
|
|
1426
1549
|
await collectGeneratedDoctorCheck(projectRoot, checks, '.cursor/commands/openprd-next.md', 'Cursor OpenPrd next command');
|
|
1427
1550
|
}
|
|
1428
|
-
const drift = await computeDriftReport(projectRoot, tools);
|
|
1551
|
+
const drift = await computeDriftReport(projectRoot, tools, safetyOptions);
|
|
1429
1552
|
for (const error of drift.errors) {
|
|
1430
1553
|
checks.push({ path: OPENPRD_HARNESS_DRIFT, ok: false, message: error });
|
|
1431
1554
|
}
|
package/src/cli/args.js
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
*/
|
|
20
20
|
function parseCommandArgs(argv) {
|
|
21
21
|
const args = [...argv];
|
|
22
|
-
const flags = { json: false, force: false, fix: false, open: false, append: false, init: false, check: false, review: false, reject: false, resume: false, advance: false, verify: false, evidenceRequired: false, next: false, generate: false, validate: false, apply: false, archive: false, activate: false, close: false, keep: false, write: false, dryRun: false, repairAgent: false, allowDirtyMain: false, fleet: false, updateOpenprd: false, backfillWorkUnits: false, syncRegistry: false, setupMissing: false, doctor: false, context: false, recordHook: false, hookInject: false, plan: false, prompt: false, loopRun: false, finish: false, commit: false, html: false, template: false, failOnViolation: false, noExternalFacts: false, noBrandAssets: false, noRealImages: false, daemon: false, stop: false, bridgeOutbox: false, bridgeSent: false, bridgeFailed: false, mark: null, direction: null, autoRefactor: null, type: 'architecture', mode: 'auto', input: null, field: null, value: null, set: null, jsonFile: null, artifactMarkdown: null, contentJson: null, presentation: null, source: null, reference: null, actual: null, before: null, after: null, board: null, grid: null, boxes: null, out: null, format: null, quality: null, maxPanelWidth: null, referenceLabel: null, actualLabel: null, locale: null, classifyExternal: null, maxIterations: null, maxDepth: null, include: null, exclude: null, report: null, item: null, id: null, status: null, claim: null, notes: null, reason: null, confidence: null, threshold: null, change: null, starter: null, sections: null, brief: null, tools: 'all', hookProfile: null, templatePack: null, target: 'openprd', targetRoot: null, path: null, productType: null, title: null, owner: null, problem: null, whyNow: null, evidence: null, from: null, to: null, version: null, digest: null, workUnit: null, event: null, risk: null, outcome: null, preview: null, learn: null, genre: null, style: null, topic: null, enable: false, disable: false, agent: 'codex', agentCommand: null, worktree: null, branch: null, message: null, thread: null, threadTitle: null, conversationTitle: null, session: null, sessionTitle: null, host: null, port: null, openTarget: 'auto' };
|
|
22
|
+
const flags = { json: false, force: false, fix: false, open: false, append: false, init: false, check: false, review: false, reject: false, resume: false, advance: false, verify: false, evidenceRequired: false, next: false, generate: false, validate: false, apply: false, archive: false, activate: false, close: false, keep: false, write: false, dryRun: false, repairAgent: false, allowDirtyMain: false, fleet: false, updateOpenprd: false, backfillWorkUnits: false, syncRegistry: false, setupMissing: false, doctor: false, context: false, recordHook: false, hookInject: false, plan: false, prompt: false, loopRun: false, finish: false, commit: false, html: false, template: false, failOnViolation: false, noExternalFacts: false, noBrandAssets: false, noRealImages: false, daemon: false, stop: false, bridgeOutbox: false, bridgeSent: false, bridgeFailed: false, mark: null, direction: null, autoRefactor: null, type: 'architecture', mode: 'auto', input: null, field: null, value: null, set: null, jsonFile: null, artifactMarkdown: null, contentJson: null, presentation: null, source: null, reference: null, actual: null, before: null, after: null, board: null, grid: null, boxes: null, out: null, format: null, quality: null, maxPanelWidth: null, referenceLabel: null, actualLabel: null, locale: null, classifyExternal: null, maxIterations: null, maxDepth: null, include: null, exclude: null, report: null, item: null, id: null, status: null, claim: null, notes: null, reason: null, confidence: null, threshold: null, change: null, starter: null, sections: null, brief: null, tools: 'all', hookProfile: null, templatePack: null, target: 'openprd', targetRoot: null, path: null, productType: null, title: null, owner: null, problem: null, whyNow: null, evidence: null, from: null, to: null, version: null, digest: null, workUnit: null, event: null, risk: null, outcome: null, preview: null, learn: null, genre: null, style: null, topic: null, enable: false, disable: false, agent: 'codex', agentCommand: null, worktree: null, branch: null, message: null, thread: null, threadTitle: null, conversationTitle: null, session: null, sessionTitle: null, host: null, port: null, openTarget: 'auto', runtimeAction: null, lane: null, task: null, claimId: null, client: null, leaseMs: null, expectedRevision: null, expectedLaneRevision: null, writeSet: null, baseCommit: null, gateId: null, approvalId: null, symlinkStrategy: null, installSafetyBackupRoot: null };
|
|
23
23
|
const positionals = [];
|
|
24
24
|
|
|
25
25
|
while (args.length > 0) {
|
|
@@ -28,6 +28,62 @@ function parseCommandArgs(argv) {
|
|
|
28
28
|
flags.json = true;
|
|
29
29
|
continue;
|
|
30
30
|
}
|
|
31
|
+
if (arg === '--runtime-action' || arg === '--action') {
|
|
32
|
+
flags.runtimeAction = args.shift() ?? null;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (arg === '--lane') {
|
|
36
|
+
flags.lane = args.shift() ?? null;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (arg === '--task') {
|
|
40
|
+
flags.task = args.shift() ?? null;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (arg === '--claim-id') {
|
|
44
|
+
flags.claimId = args.shift() ?? null;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (arg === '--client') {
|
|
48
|
+
flags.client = args.shift() ?? null;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (arg === '--lease-ms') {
|
|
52
|
+
flags.leaseMs = args.shift() ?? null;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (arg === '--expected-revision') {
|
|
56
|
+
flags.expectedRevision = args.shift() ?? null;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (arg === '--expected-lane-revision') {
|
|
60
|
+
flags.expectedLaneRevision = args.shift() ?? null;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (arg === '--write-set') {
|
|
64
|
+
flags.writeSet = args.shift() ?? null;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (arg === '--base-commit') {
|
|
68
|
+
flags.baseCommit = args.shift() ?? null;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (arg === '--gate' || arg === '--gate-id') {
|
|
72
|
+
flags.gateId = args.shift() ?? null;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (arg === '--approval-id') {
|
|
76
|
+
flags.approvalId = args.shift() ?? null;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (arg === '--symlink-strategy') {
|
|
80
|
+
flags.symlinkStrategy = args.shift() ?? null;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (arg === '--install-safety-backup-root') {
|
|
84
|
+
flags.installSafetyBackupRoot = args.shift() ?? null;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
31
87
|
if (arg === '--force') {
|
|
32
88
|
flags.force = true;
|
|
33
89
|
continue;
|
|
@@ -581,14 +637,16 @@ function usage() {
|
|
|
581
637
|
'OpenPrd CLI',
|
|
582
638
|
'',
|
|
583
639
|
'Usage:',
|
|
584
|
-
' openprd init [path] [--template-pack <base|consumer|b2b|agent>] [--tools <all|codex,claude,cursor>] [--hook-profile <lite|guarded|full>] [--force]',
|
|
585
|
-
' openprd setup [path] [--tools <all|codex,claude,cursor>] [--hook-profile <lite|guarded|full>] [--force] [--json]',
|
|
586
|
-
' openprd update [path] [--tools <all|codex,claude,cursor>] [--hook-profile <lite|guarded|full>] [--force] [--json]',
|
|
640
|
+
' openprd init [path] [--template-pack <base|consumer|b2b|agent>] [--tools <all|codex,claude,cursor>] [--hook-profile <lite|guarded|full>] [--symlink-strategy <block|materialize>] [--install-safety-backup-root <path>] [--force]',
|
|
641
|
+
' openprd setup [path] [--tools <all|codex,claude,cursor>] [--hook-profile <lite|guarded|full>] [--symlink-strategy <block|materialize>] [--install-safety-backup-root <path>] [--force] [--json]',
|
|
642
|
+
' openprd update [path] [--tools <all|codex,claude,cursor>] [--hook-profile <lite|guarded|full>] [--symlink-strategy <block|materialize>] [--install-safety-backup-root <path>] [--force] [--json]',
|
|
587
643
|
' openprd self-update [--check] [--dry-run] [--json]',
|
|
588
644
|
' openprd upgrade [path] [--fleet] [--dry-run] [--tools <all|codex,claude,cursor>] [--hook-profile <lite|guarded|full>] [--max-depth <n>] [--include <csv>] [--exclude <csv>] [--report <file>] [--force] [--json]',
|
|
589
645
|
' openprd doctor [path] [--tools <all|codex,claude,cursor>] [--hook-profile <lite|guarded|full>] [--fix] [--json]',
|
|
590
646
|
' openprd fleet <root> [--dry-run|--doctor|--update-openprd|--backfill-work-units|--sync-registry|--setup-missing] [--hook-profile <lite|guarded|full>] [--max-depth <n>] [--include <csv>] [--exclude <csv>] [--report <file>] [--json]',
|
|
591
647
|
' openprd run [path] [--context|--verify|--record-hook --event <name> --risk <level> --outcome <text> --preview <text>] [--message <text>] [--json]',
|
|
648
|
+
' openprd runtime [path] --action <status|create-lane|ensure-lane|add-task|claim|heartbeat|release|finish|recover> [--lane <id>] [--task <id>] [--claim-id <id>] [--write-set <csv>] [--lease-ms <n>] [--expected-revision <n>] [--expected-lane-revision <n>] [--client <codex|claude|cursor>] [--session <id>] [--thread <id>] [--worktree <path>] [--branch <name>] [--base-commit <sha>] [--evidence <ref>] [--json]',
|
|
649
|
+
' openprd gate [path] --action <status|define|evidence|approve|evaluate> [--json-file <record.json>] [--gate <id>] [--approval-id <id>] [--json]',
|
|
592
650
|
' openprd loop [path] [--init|--plan|--next|--prompt|--run|--verify|--finish] [--change <id>] [--item <task-id-or-handle>] [--agent <codex|claude>] [--agent-command <cmd>] [--repair-agent] [--worktree <path>] [--branch <name>] [--allow-dirty-main] [--commit] [--dry-run] [--message <text>] [--json]',
|
|
593
651
|
' openprd canvas [path] [--thread <id> [--thread-title <name>]|--session <id> [--session-title <name>]] [--host <host>] [--port <number>] [--daemon|--stop] [--open] [--open-target <auto|system|codex-browser>] [--dry-run] [--json]',
|
|
594
652
|
' openprd canvas [path] --bridge-outbox [--thread <id>|--session <id>] [--claim <next|id>] [--json]',
|
|
@@ -607,6 +665,7 @@ function usage() {
|
|
|
607
665
|
' --board modes: focus-board, parallel-board, verification-board, alignment-board, centering-board',
|
|
608
666
|
' openprd design-starter [path] [--starter <content-home|product-launch|ops-dashboard>] [--out <index.html>] [--title <name>] [--brief <text>] [--sections <a|b|c>] [--no-external-facts] [--no-brand-assets] [--no-real-images] [--force] [--json]',
|
|
609
667
|
' openprd dev-check [path] <file...> [--auto-refactor <on|off>] [--json]',
|
|
668
|
+
' openprd i18n-init [path] [--locale <zh-Hans|en|...>] [--json]',
|
|
610
669
|
' openprd grow [path] [--review|--apply --id <candidate-id>|--reject --id <candidate-id>|--init|--check] [--notes <text>] [--json]',
|
|
611
670
|
' openprd benchmark <add|observe|list|approve|verify> [target-or-id] [path-for-list-or-verify] [--path <project>] [--notes <text>] [--threshold <n>] [--id <benchmark-id>] [--json]',
|
|
612
671
|
' openprd synthesize [path] [--title <text>] [--owner <text>] [--problem <text>] [--why-now <text>] [--work-unit <id>] [--target-root <path>] [--open] [--json]',
|