@openprd/cli 0.1.19 → 0.1.21
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 +1 -0
- package/README.md +2 -0
- package/README_EN.md +2 -0
- package/package.json +1 -1
- package/skills/openprd-frontend-design/SKILL.md +16 -0
- package/skills/openprd-harness/SKILL.md +1 -1
- package/skills/openprd-quality/SKILL.md +1 -1
- package/skills/openprd-requirement-intake/SKILL.md +1 -1
- package/skills/openprd-shared/SKILL.md +1 -1
- 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 +147 -42
- 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 +65 -77
- 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 +431 -37
- 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
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": "openprd.stream-head",
|
|
3
|
+
"version": 1,
|
|
4
|
+
"projectId": "project_a63099e6-6438-424c-ac39-49337f485ede",
|
|
5
|
+
"streamId": "workflow:workspace",
|
|
6
|
+
"revision": 6,
|
|
7
|
+
"lastEventId": "evt_19b2167661a74c24174705119098e339",
|
|
8
|
+
"ledgerPosition": 6,
|
|
9
|
+
"updatedAt": "2026-07-11T11:23:43.123Z"
|
|
10
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": "openprd.project-manifest",
|
|
3
|
+
"schemaVersion": 1,
|
|
4
|
+
"projectId": "project_a63099e6-6438-424c-ac39-49337f485ede",
|
|
5
|
+
"createdAt": "2026-07-10T15:49:37.742Z",
|
|
6
|
+
"compatibility": {
|
|
7
|
+
"minimumReaderVersion": 1,
|
|
8
|
+
"minimumWriterVersion": 1
|
|
9
|
+
},
|
|
10
|
+
"formats": {
|
|
11
|
+
"eventEnvelope": 1,
|
|
12
|
+
"ledger": 1,
|
|
13
|
+
"projection": 1,
|
|
14
|
+
"contextCapsule": 1,
|
|
15
|
+
"taskRuntime": 1,
|
|
16
|
+
"scopedGate": 1,
|
|
17
|
+
"reviewModel": 1,
|
|
18
|
+
"knowledge": 3,
|
|
19
|
+
"adapter": 1
|
|
20
|
+
}
|
|
21
|
+
}
|
package/AGENTS.md
CHANGED
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
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` 题材参数。
|
|
34
34
|
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`,说明选中的方向、资产路径、透明产物、接入位置和验证结果;最终回复必须区分绿幕源图、透明产物和是否已经接入。
|
|
35
35
|
5b. 卡片宽度、间距、留白、对齐、颜色、圆角、字号、按钮或图标等轻量 UI 可视优化,仍可按 L0/L1 小范围修正推进,不自动升级成大界面 3 方向方案评审;但它是用户可见变化,动手前要有一句审美意图和记忆点,收口必须补 `visual-compare` 修改前后图、局部焦点证据板、截图实测证据板、对齐辅助线证据板或内部居中证据板,并检查气质、层级、颜色、字号、间距和表面角色是否成立。只要界面里有同构列表、卡片、网格或表格,就把容器轨道以及标题、副标题、描述、标签、状态、价格、按钮、图标、操作区等相同文案类型/相同组件槽位的对齐当作默认验收项,不等用户先投诉;只量外框、列宽或行顶不算完整对齐验收。只要任务在判断单个素材/图标/头像/徽标/按钮图形的内部居中、偏心或视觉重心,就把 centering-board 当作默认验收项;单张原始截图或主观“看起来居中”不算完整居中验收。build、package、`openprd dev-check` 和单张原始截图不能替代视觉证据。`visual-compare` 每次都会返回 `chatEmbed` markdown;生成证据板后必须把这行 `` 直接嵌入最终回复,让证据在对话流里可见,不要只报告文件路径;`openprd dev-check` 在触达界面文件且最近 24 小时没有证据板时也会输出视觉证据提醒,收口前按提醒补齐。
|
|
36
|
+
5c. 大界面三方向是 Agent 的默认责任,不要求用户另行提出生图;轻量 UI 微调不触发。已有界面时,Codex 原生 App 用 Computer Use、Codex 网页环境用 Chrome 插件任务专用窗口、Cursor 用可用浏览器或项目预览能力截图;三个方向必须共享同一张当前截图作为 image-to-image 参考。除非用户明确要求换风格,否则锁定字体角色、色彩系统、明暗模式、表面语言、圆角/边框/阴影、图标语言、平台框架和信息密度,只探索布局、信息层级、导航、关键路径与空间构图。冷启动没有现有界面时才使用 design brief,不伪造修改前截图。候选图先明确用途、目标用户、业务约束、核心动作、气质和唯一记忆点,并检查字体、色彩、空间、层级、表面/图标语言和动效含义;拒绝无依据蓝紫渐变、默认字体、等权白卡堆叠、过量大圆角、无语境装饰和只换颜色的伪方向。
|
|
36
37
|
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` 完成不等于只补合同、只下载素材或只写计划;至少要把入口文件本体改完、主要占位清掉,并把已准备好的真实图片或参考约束真正落进页面;没有明确参考方向时,不要直接落回同一种安全极简解。
|
|
37
38
|
6a. 写产品界面上用户能看到的文案时,必须站在当前用户视角写:这句话要说明用户现在能做什么、会发生什么、为什么值得点、下一步怎么走。除非这是面向开发者或专业技术人员的技术型产品,否则默认用普通人能看懂的语言写结果、状态、限制和行动,不要暴露 API、SDK、模型、数据库、缓存、错误码或内部模块。正反例:不要写“适合想保留全部工具入口的用户”,改写成“首页会显示所有工具入口,你可以直接选择需要的功能”;不要写“API 请求失败,错误码 500”,改写成“暂时保存失败,请稍后再试”;技术型产品可以保留必要术语,但仍要写清用户动作、影响和修复路径。
|
|
38
39
|
6b. 开发新功能出现新的入口、按钮、tab、卡片、空态或工具格时,默认自动配图标,不等用户提出:先复用项目已有图标体系;项目没有时按图标最佳实践路由选型(UI 图标 Phosphor,落码 Lucide/Tabler/React Icons,AI 品牌 LobeHub Icons,技术栈 Tech Icons,功能图标/插画 iconfont),把图标名、来源、用途登记到 `.openprd/design/active/asset-spec.md` 的“功能图标”行再接入。只有语义确实不需要图标或用户明确说不要时才跳过,并在收口说明原因。
|
package/README.md
CHANGED
|
@@ -543,6 +543,8 @@ openprd visual-compare /path/to/project \
|
|
|
543
543
|
表格这类重复结构属于默认触发场景,不需要等用户先指出“没有对齐”;只量外框、
|
|
544
544
|
列宽或行顶不算完整对齐验收。
|
|
545
545
|
|
|
546
|
+
网格、基线和对齐辅助线只用于这条布局校验路径。普通 `before/after`、`reference/actual` 与 `verification-board` 不会自动叠加网格;它们默认使用紧凑的暖色结果画布,让截图和结论占据主要空间。
|
|
547
|
+
|
|
546
548
|
如果要判断单个 logo、icon、avatar、badge、按钮图形或图片裁切内部是否居中,
|
|
547
549
|
或者用户反馈“偏心”“视觉重心不对”,Agent 应先裁出目标元素,再生成内部居中证据板:
|
|
548
550
|
|
package/README_EN.md
CHANGED
|
@@ -626,6 +626,8 @@ prices, buttons, icons, and action areas. Repeated card lists, card grids, and
|
|
|
626
626
|
tables are default triggers even when the user did not explicitly complain
|
|
627
627
|
about alignment; measuring only outer frames, columns, or row tops is not enough.
|
|
628
628
|
|
|
629
|
+
Grid, baseline, and alignment overlays are exclusive to this layout-validation path. Regular `before/after`, `reference/actual`, and `verification-board` outputs do not add grids automatically; they use a compact warm result canvas that keeps screenshots and verdicts prominent.
|
|
630
|
+
|
|
629
631
|
When a single logo, icon, avatar, badge, button graphic, or image crop needs
|
|
630
632
|
internal centering review, or when the user reports that its visual weight feels
|
|
631
633
|
off-center, crop the target if needed and generate a centering evidence board:
|
package/package.json
CHANGED
|
@@ -135,6 +135,21 @@ description: 为 OpenPrd 的界面、页面、视觉、样式和前端体验任
|
|
|
135
135
|
- 每个方向都要写清:为什么它和另外两版不同、适合什么场景、主要风险是什么。
|
|
136
136
|
- 每个方向还要写清:审美主张是什么、用户会记住什么、它避免了哪种通用 AI 模板味。
|
|
137
137
|
|
|
138
|
+
### 5a. 界面效果图生成质量合同
|
|
139
|
+
|
|
140
|
+
- Agent 不需要等待用户补一句“请生图”。当语义判断属于大界面改动且视觉决策成本高时,默认主动生成 3 个可评审方向,并在实现前停下来让用户选择。
|
|
141
|
+
- 每次生成前都要写清用途、目标用户、产品气质、业务约束、核心动作和唯一记忆点;效果图必须服务于任务路径,不能只追求展示感。
|
|
142
|
+
- 视觉质量从字体角色、色彩系统、空间构图、信息层级、表面语言、图标语言、动效含义和氛围细节一起判断。复杂度要与审美主张匹配,工具界面优先扫描效率和状态清晰,叙事界面才允许更强的视觉张力。
|
|
143
|
+
- 避免通用 AI 模板味:无依据的蓝紫渐变、默认字体、等权白卡堆叠、过量大圆角、无语境装饰、只换颜色的伪方向,以及为了“高级”牺牲可读性。
|
|
144
|
+
- 3 个方向必须改变真正的设计决策,例如信息架构、首屏重心、任务路径、内容密度或空间组织;不能只改变颜色、插图或装饰。
|
|
145
|
+
|
|
146
|
+
### 5b. 既有界面的视觉 DNA 继承
|
|
147
|
+
|
|
148
|
+
- 只要已有产品截图、设计稿或可运行界面,默认进入 `preserve-existing`,而不是自由改风格。除非用户明确要求换风格,否则锁定现有字体角色、色彩系统、明暗模式、表面语言、圆角/边框/阴影体系、图标语言、平台框架和信息密度。
|
|
149
|
+
- 既有界面的 3 个方向用同一张当前界面截图做 image-to-image 参考,每次生图都必须携带该参考图;探索范围主要放在布局、信息层级、导航、关键路径和空间构图,不得把品牌视觉重做成三个陌生产品。
|
|
150
|
+
- 用户明确要求参考另一种风格、品牌升级或整体重设计时,才把对应锁定项解开,并记录在 `direction-plan.md` 与 `selected-direction.md`。
|
|
151
|
+
- 冷启动新功能没有现有界面时,不伪造“修改前截图”;改用已确认 PRD、用户、首版范围、业务约束和视觉目标形成设计 brief,再生成异质方向。
|
|
152
|
+
|
|
138
153
|
### 6. Theme Lock Before Build
|
|
139
154
|
|
|
140
155
|
- 一旦方向已选,先锁定 `lens + theme + layout + component set + aesthetic direction + memory point`,再进入编码。
|
|
@@ -176,6 +191,7 @@ description: 为 OpenPrd 的界面、页面、视觉、样式和前端体验任
|
|
|
176
191
|
- 视觉证据不仅看像不像,还要看选定的气质、记忆点、字体/色彩/动效/表面层级有没有被实现保住。
|
|
177
192
|
- 卡片宽度、间距、留白、对齐、颜色、圆角、字号、按钮和图标这类轻量 UI 可视优化,不需要自动升级成 3 方向方案评审;但只要最终会被用户看见,收口时就要有 `visual-compare` 的修改前后图、局部焦点证据板、截图实测证据板或对齐辅助线证据板。构建通过、打包成功、`dev-check` 通过和单张截图都不能替代视觉证据。
|
|
178
193
|
- 新功能或改动里只要出现同构列表、卡片、网格或表格,就默认把容器轨道以及相同文案类型/相同组件槽位的对齐纳入验收;Agent 应截真实页面、叠辅助线,同时量卡片外框、列宽、行顶等容器轨道,以及标题、副标题、描述、标签、状态、价格、按钮、图标、操作区等内部内容槽位的 x/y/宽高/baseline spread,并用 `openprd visual-compare . --board <alignment-board.json>` 生成对齐证据。只量外框、列宽或行顶不算完整对齐验收。
|
|
194
|
+
- 网格、基线和对齐辅助线属于布局校验材料,只在布局、间距、对齐、同构列表、卡片、网格或表格命中 `alignment-board` 时生成。普通截图、最终 before/after、reference/actual 和 verification-board 只负责展示结果,不默认叠加网格或基线。
|
|
179
195
|
- 单个 logo、icon、avatar、badge、按钮图形或图片裁切需要判断内部居中、视觉重心或偏心时,默认使用 `openprd visual-compare . --board <centering-board.json>` 生成内部居中证据板;Agent 要先裁出目标元素,再用类似 Canvas 的像素 mask 量画布中心、主体外接框中心和视觉重心偏移。证据板必须显示红色画布中心线、绿色主体外接框、黄色视觉重心点和 offset 数值;单张截图或主观“看起来居中”不算完整验收。
|
|
180
196
|
|
|
181
197
|
## 输出要求
|
|
@@ -152,7 +152,7 @@ description: 驱动 OpenPrd 工作区完成从澄清到 handoff 的主流程。
|
|
|
152
152
|
- 生成 `spec.md` 和 tasks 时跟随用户当前主语言;无法判断时用简体中文兜底。必要专有名词、品牌名、命令名、路径、字段名和 API 术语可以保留原文
|
|
153
153
|
- `synthesize` 生成 `review.html` 后,必须把 HTML 路径告诉用户,并自动打开它(或紧接着运行 `openprd review <path> --open`)
|
|
154
154
|
- 评审页里的需求关系图、需求流程图和重点摘要不要靠 HTML 截断;`openprd synthesize` 生成版本快照后,不要直接把 review 给用户确认。必须先用 `openprd review-presentation <path> --template` 查看展示文案契约,让 Agent 按 `reviewPresentation` 写短文案,再用 `openprd review-presentation <path> --presentation <json> --write --fail-on-violation` 校验并写回。脚本通过后会写入校验元信息并重渲染可确认 review.html;超限时按脚本返回的 jsonPath 和字数限制重新提炼,不手工改快照、不裁剪原文
|
|
155
|
-
- L1/L2
|
|
155
|
+
- L1/L2 中只有语义判断为大界面改动、视觉决策成本高或需要用户选方向时,才在 review-presentation 写入前自动补交互方向评审;轻量 UI 微调不触发。按当前工具面用 Codex `imagegen`(Image 2)或 Cursor `GenerateImage` 生成 3 个差异足够大的交互稿方向,图片保存到 `.openprd/design/active/`,再把每个方向的 `id/title/image/summary/tradeoff` 写入 `reviewPresentation.designDirections.directions[]`(title 15 字内、summary/tradeoff 30 字内)。已有界面时三个方向必须共享同一张当前截图作为 image-to-image 参考并默认保持视觉 DNA;冷启动才使用 design brief。review.html 会渲染 3 方向评审卡片,用户点“选这个方向”后回传 `openprd review <path> --mark confirmed --direction <id>` 命令;选定方向会记录进评审状态,后续实现必须按选定方向推进。不涉及界面或只是轻量微调时不要携带 designDirections 字段
|
|
156
156
|
- 用户想在交互稿上圈选、批注或做视觉级反馈时,引导走画布评审协同:画布右栏顶部“评审协同”区显示当前 7 步流程位置,“导入评审方向”会把当前 review 的 designDirections 连图带说明摆上画布(已选定方向会标出),用户圈选批注后用“发送给 Codex”回传反馈
|
|
157
157
|
- 用户问“现在到哪一步了”“接下来做什么”“OpenPrd 到底是干嘛的”时,先运行 `openprd guide <path>`:它按工作区状态输出 7 步流程(需求澄清 → 需求摘要 → 评审确认 → 交互方向选择 → 任务拆解 → 实现执行 → 验证收口)、当前位置和下一步动作,用它的输出向用户解释,不要口头即兴复述流程
|
|
158
158
|
- 把生成的 `review.html` 当作首选稳定评审 artifact。默认 approval policy 是 `decision-points`:当前 lane 仍需要人类决策时,请用户先评审问题定义、范围、主流程、风险和开放问题,再运行带精确 `--version`、`--digest`、`--work-unit` 的 `openprd review <path> --mark confirmed`。L2 下先确认对话内 requirement 摘要,再写回确认事实、classify/synthesize,并进入这一步 review。若用户一开始已经明确要求直接做,并显式表示“不需要进行任何确认”,lane 才可进入 `silent-record`;单纯的“请帮我实现/继续实现”或“不要评审”都不触发这个豁免。进入 `silent-record` 后也只允许记录当前精确匹配的稳定 artifact,不能借机替别的版本补确认。review 记录完成且 tasks 就绪后,如果用户刚刚确认的是现有功能优化(L1)的 mini-plan、范围边界或正式产品边界,后续承接要写成“已确认,我按这个继续”,不要写成“确认,我们就按这个……”这种像再次索取确认的句子;如果用户原始意图已经明确要求实现,可直接继续执行;否则先输出执行确认清单,列出本轮目标、将执行内容、不做事项、验证方式和已知风险,再请求明确执行授权,不能只要求用户回复一句确认
|
|
@@ -34,7 +34,7 @@ description: 评估 OpenPrd 的可观测性、业务成本与滥用护栏、评
|
|
|
34
34
|
- `openprd visual-compare <path> --before <修改前截图> --after <修改后截图> --locale <zh-CN|en>`
|
|
35
35
|
- `openprd visual-compare <path> --board <focus-board.json|parallel-board.json|verification-board.json|alignment-board.json> --locale <zh-CN|en>`
|
|
36
36
|
- 大界面改动的实现前方案评审:
|
|
37
|
-
-
|
|
37
|
+
- 先按用户目标、信息架构变化、视觉决策成本和验证风险判断方案评审形态,用户无需另行提出生图;已有界面时按平台能力截取当前真实界面,三个方向共享同一截图并默认保持原视觉 DNA;冷启动用已确认 PRD、用户群体、第一版切片、视觉目标、气质端点和记忆点生成 design brief;再按工具面用 Codex `imagegen`(Image 2)或 Cursor `GenerateImage` 生成至少 3 个设计方向,并保存横向拼接评审大图到 `.openprd/harness/visual-reviews/`
|
|
38
38
|
- 基于已审查报告生成或刷新项目级经验:
|
|
39
39
|
- `openprd quality <path> --learn --from <candidate-dir|report-id-or-json>`
|
|
40
40
|
- 审查执行中发现的配置、规则候选或 user-local 偏好:
|
|
@@ -42,7 +42,7 @@ description: OpenPrd 需求入口与 PRD 分流 skill。用于用户提出产品
|
|
|
42
42
|
用户审查时优先把路由码并进“需求类型:直接处理(L0)”这类标签里,不要把内部调度码单独抬成标题。只有审查或调试真的受益时,才额外补“内部路由码:L1”这类信息,并保留上面的对照关系。
|
|
43
43
|
用户侧表达优先使用“面向个人消费者场景 / 面向企业服务场景 / 以 Agent 为主要使用场景”这类自然语言,不要把 `consumer`、`b2b`、`agent` 直接当展示词抛给用户。
|
|
44
44
|
|
|
45
|
-
界面、页面、视觉、样式或前端体验需求需要额外判断 UI
|
|
45
|
+
界面、页面、视觉、样式或前端体验需求需要额外判断 UI 影响面。若会明显改变信息架构、核心布局、主视觉、关键路径、组件层级/密度,或用户需要先选择设计方向,即使属于“现有功能优化”,也要自动走“大界面改动视觉方案评审”,不要求用户另行提出生图:已有界面时,在 Codex 原生 App 用 Computer Use、Codex 网页环境用 Chrome 插件的任务专用窗口、Cursor 用当前可用的浏览器或项目预览能力截取真实界面;同一截图作为每个方向的 image-to-image 参考,除非用户明确要求换风格,否则保持现有视觉 DNA。冷启动没有现有界面时基于已确认 PRD、用户群体、第一版切片和视觉目标生成 design brief。随后按当前工具面选择 Codex `imagegen`(Image 2)或 Cursor `GenerateImage` 生成至少 3 个方向,横向拼接带 1/2/3 序号的大图给用户确认。卡片宽度、间距、对齐、颜色、圆角、字号等轻量微调不自动升级为三方向评审。
|
|
46
46
|
|
|
47
47
|
如果同一句话同时包含“继续旧任务”和“新增范围”,先判断新增范围是否超出旧 PRD。超出时必须回到需求入口,更新 PRD/change/tasks,不能把“继续”当作实现授权。
|
|
48
48
|
|
|
@@ -99,7 +99,7 @@ description: OpenPrd 工作区与产物的共用守则。凡是需要查看、
|
|
|
99
99
|
13. 大界面改动实现前必须先完成视觉方案评审。
|
|
100
100
|
- 触发条件:会明显改变页面信息架构、主视觉、核心布局、关键路径、组件层级/密度,或用户需要先选择设计方向。
|
|
101
101
|
- 位置:需求分流之后、PRD 定稿或实现开工之前;它不是 `review.html`,也不是实现后的 `visual-compare`。
|
|
102
|
-
-
|
|
102
|
+
- 步骤:用户不需要额外提出生图。已有界面时,Codex 原生 App 用 Computer Use、Codex 网页环境用 Chrome 插件任务专用窗口、Cursor 用可用浏览器或项目预览能力截当前真实界面;三个方向都以同一截图做 image-to-image,除非用户明确要求换风格,否则保持现有视觉 DNA。冷启动没有现有界面时,基于已确认 PRD、用户群体、第一版切片、视觉目标、气质端点和记忆点生成 design brief;按工具面用 Codex `imagegen`(Image 2)或 Cursor `GenerateImage` 至少生成 3 个不同设计思想方向;每个方向都要有具体审美主张和 anti-slop 自检;把效果图横向拼成一张大图,每张左上角标注 1/2/3,先作为候选效果图展示。
|
|
103
103
|
- 交互:主动确认是否符合预期、是否纳入后续效果图/实现截图对比、以及是否按此继续实现;只有确认后才把选定方向、整张图或其中子图整理到 `.openprd/harness/visual-reviews/`。用户确认前,不进入大 UI 实现,也不要声称界面方案已定。
|
|
104
104
|
14. 界面任务进入实现前,先用 `.openprd/design/` 锁定设计框架。
|
|
105
105
|
- 页面涉及具体产品事实、版本、发布时间、规格、价格、引用数据或地点事实时,先补 `.openprd/design/active/facts-sheet.md`,不要凭记忆写页面。
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import {
|
|
3
|
+
ADAPTER_CONTRACT_VERSION,
|
|
4
|
+
capabilityAvailable,
|
|
5
|
+
describeCapabilities,
|
|
6
|
+
normalizeClient,
|
|
7
|
+
} from './capability-envelope.js';
|
|
8
|
+
import { normalizeLifecycleEvent } from './event-normalizer.js';
|
|
9
|
+
|
|
10
|
+
const DETECTION_PRIORITY = Object.freeze({
|
|
11
|
+
'hook-payload': 100,
|
|
12
|
+
'session-binding': 95,
|
|
13
|
+
'explicit-launcher': 90,
|
|
14
|
+
'sdk-session': 80,
|
|
15
|
+
'process-env': 50,
|
|
16
|
+
'config-probe': 20,
|
|
17
|
+
'agent-self-report': 10,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
function normalizedEvidenceList(evidence) {
|
|
21
|
+
const list = Array.isArray(evidence) ? evidence : [evidence];
|
|
22
|
+
return list
|
|
23
|
+
.filter((item) => item && typeof item === 'object' && !Array.isArray(item))
|
|
24
|
+
.map((item, index) => ({
|
|
25
|
+
source: String(item.source ?? 'agent-self-report'),
|
|
26
|
+
client: item.client ? String(item.client).toLowerCase() : null,
|
|
27
|
+
surface: item.surface ?? 'unknown',
|
|
28
|
+
executionMode: item.executionMode ?? 'interactive',
|
|
29
|
+
sessionId: item.sessionId ?? null,
|
|
30
|
+
threadId: item.threadId ?? null,
|
|
31
|
+
observedCapabilities: Array.isArray(item.observedCapabilities) ? item.observedCapabilities : [],
|
|
32
|
+
confidence: Number.isFinite(item.confidence) ? Math.max(0, Math.min(1, item.confidence)) : null,
|
|
33
|
+
index,
|
|
34
|
+
}))
|
|
35
|
+
.filter((item) => ['codex', 'claude', 'cursor'].includes(item.client));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function detectRuntime(evidence = []) {
|
|
39
|
+
const candidates = normalizedEvidenceList(evidence)
|
|
40
|
+
.map((item) => {
|
|
41
|
+
const priority = DETECTION_PRIORITY[item.source] ?? 0;
|
|
42
|
+
return {
|
|
43
|
+
...item,
|
|
44
|
+
priority,
|
|
45
|
+
confidence: item.confidence ?? Math.min(1, Math.max(0.1, priority / 100)),
|
|
46
|
+
};
|
|
47
|
+
})
|
|
48
|
+
.sort((left, right) => right.priority - left.priority
|
|
49
|
+
|| right.confidence - left.confidence
|
|
50
|
+
|| left.index - right.index);
|
|
51
|
+
const selected = candidates[0] ?? null;
|
|
52
|
+
if (!selected) {
|
|
53
|
+
return {
|
|
54
|
+
schema: 'openprd.runtime-detection.v1',
|
|
55
|
+
activeClient: 'unknown',
|
|
56
|
+
surface: 'unknown',
|
|
57
|
+
executionMode: 'interactive',
|
|
58
|
+
sessionId: null,
|
|
59
|
+
threadId: null,
|
|
60
|
+
confidence: 0,
|
|
61
|
+
evidence: [],
|
|
62
|
+
capabilityEnvelope: null,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
schema: 'openprd.runtime-detection.v1',
|
|
67
|
+
activeClient: selected.client,
|
|
68
|
+
surface: selected.surface,
|
|
69
|
+
executionMode: selected.executionMode,
|
|
70
|
+
sessionId: selected.sessionId,
|
|
71
|
+
threadId: selected.threadId,
|
|
72
|
+
confidence: selected.confidence,
|
|
73
|
+
evidence: candidates,
|
|
74
|
+
capabilityEnvelope: describeCapabilities(selected.client, {
|
|
75
|
+
surface: selected.surface,
|
|
76
|
+
observedCapabilities: selected.observedCapabilities,
|
|
77
|
+
}),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function renderContextMarkdown(client, capsule) {
|
|
82
|
+
const normalized = normalizeClient(client);
|
|
83
|
+
if (capsule?.schemaVersion === 1 && Array.isArray(capsule.entries)) {
|
|
84
|
+
const lines = [
|
|
85
|
+
`OpenPrd ContextCapsule v1 (${normalized})`,
|
|
86
|
+
`digest: ${capsule.digest ?? 'unknown'}`,
|
|
87
|
+
`revision: ${capsule.revision ?? 'unknown'}`,
|
|
88
|
+
`delivery: ${capsule.delivery ?? 'full'}`,
|
|
89
|
+
`budget: ${capsule.budget?.estimatedTokens ?? 0}/${capsule.budget?.limitTokens ?? 'unknown'} tokens`,
|
|
90
|
+
];
|
|
91
|
+
for (const entry of capsule.entries) {
|
|
92
|
+
const value = entry?.value;
|
|
93
|
+
if (entry?.id === 'recommended-next-step') {
|
|
94
|
+
if (value?.title) lines.push(`next: ${value.title}`);
|
|
95
|
+
if (value?.reason) lines.push(`reason: ${value.reason}`);
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (entry?.id === 'task-progress') {
|
|
99
|
+
lines.push(`task: ${value?.summary ?? value?.lane ?? 'current'}`);
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (entry?.id === 'runtime-environment') {
|
|
103
|
+
lines.push(`runtime: ${value?.activeClient ?? 'unknown'} / ${value?.surface ?? 'unknown'}`);
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (entry?.id === 'requirement-gate') {
|
|
107
|
+
lines.push(`gate: ${value?.status ?? 'active'} (${value?.relevance ?? 'primary'})`);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (entry?.id === 'project-health') {
|
|
111
|
+
lines.push(`health: ${value?.valid ? 'valid' : 'attention-required'}`);
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (entry?.id === 'current-focus') {
|
|
115
|
+
const focus = value?.focus?.changeId ?? value?.activeChange ?? value?.latestPrd?.title;
|
|
116
|
+
if (focus) lines.push(`focus: ${focus}`);
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (entry?.id === 'project-knowledge') {
|
|
120
|
+
lines.push(`knowledge: ${Array.isArray(value) ? value.length : 0} scoped item(s)`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (capsule.delivery === 'delta' && capsule.entries.length === 0) {
|
|
124
|
+
lines.push(`unchanged: reuse ${capsule.delta?.previousCapsuleDigest ?? 'previous capsule'}`);
|
|
125
|
+
}
|
|
126
|
+
return `${lines.join('\n')}\n`;
|
|
127
|
+
}
|
|
128
|
+
const sections = capsule?.sections ?? {};
|
|
129
|
+
const lines = [
|
|
130
|
+
`OpenPrd context (${normalized})`,
|
|
131
|
+
`capsule: ${capsule?.capsuleId ?? 'unknown'}`,
|
|
132
|
+
`revision: ${capsule?.projectRevision ?? 'unknown'}`,
|
|
133
|
+
];
|
|
134
|
+
if (sections.taskContract) {
|
|
135
|
+
lines.push(`task: ${sections.taskContract.title ?? sections.taskContract.taskId ?? 'current'}`);
|
|
136
|
+
}
|
|
137
|
+
for (const fact of sections.pinnedFacts ?? []) {
|
|
138
|
+
lines.push(`fact: ${fact.text ?? fact.value ?? String(fact)}`);
|
|
139
|
+
}
|
|
140
|
+
for (const gate of sections.applicableGates ?? []) {
|
|
141
|
+
lines.push(`gate: ${gate.summary ?? gate.gateType ?? String(gate)}`);
|
|
142
|
+
}
|
|
143
|
+
return `${lines.join('\n')}\n`;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function renderDecision(client, decision = {}) {
|
|
147
|
+
const normalized = normalizeClient(client);
|
|
148
|
+
const allowed = decision.decision === 'allow' || decision.allowed === true;
|
|
149
|
+
return {
|
|
150
|
+
schema: 'openprd.adapter-decision.v1',
|
|
151
|
+
adapterVersion: ADAPTER_CONTRACT_VERSION,
|
|
152
|
+
client: normalized,
|
|
153
|
+
allowed,
|
|
154
|
+
approvalId: decision.approvalId ?? null,
|
|
155
|
+
message: decision.message ?? (allowed ? '已允许继续当前操作。' : '当前操作尚未获准。'),
|
|
156
|
+
reason: decision.reason ?? null,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function createAdapter(client, overrides = {}) {
|
|
161
|
+
const normalized = normalizeClient(client);
|
|
162
|
+
return Object.freeze({
|
|
163
|
+
client: normalized,
|
|
164
|
+
contractVersion: ADAPTER_CONTRACT_VERSION,
|
|
165
|
+
detectRuntime: overrides.detectRuntime ?? detectRuntime,
|
|
166
|
+
describeCapabilities: (options = {}) => describeCapabilities(normalized, options),
|
|
167
|
+
normalizeLifecycleEvent: (raw) => normalizeLifecycleEvent(normalized, raw),
|
|
168
|
+
renderContext: overrides.renderContext ?? ((capsule) => ({
|
|
169
|
+
schema: 'openprd.adapter-context.v1',
|
|
170
|
+
client: normalized,
|
|
171
|
+
format: 'text/markdown',
|
|
172
|
+
content: renderContextMarkdown(normalized, capsule),
|
|
173
|
+
capsuleDigest: capsule?.digests?.content ?? capsule?.digest ?? null,
|
|
174
|
+
})),
|
|
175
|
+
renderDecision: overrides.renderDecision ?? ((decision) => renderDecision(normalized, decision)),
|
|
176
|
+
can: (capability, options = {}) => capabilityAvailable(describeCapabilities(normalized, options), capability),
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function adapterContractDigest() {
|
|
181
|
+
const contract = ['codex', 'claude', 'cursor'].map((client) => describeCapabilities(client));
|
|
182
|
+
return crypto.createHash('sha256').update(JSON.stringify(contract)).digest('hex');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export {
|
|
186
|
+
ADAPTER_CONTRACT_VERSION,
|
|
187
|
+
DETECTION_PRIORITY,
|
|
188
|
+
adapterContractDigest,
|
|
189
|
+
createAdapter,
|
|
190
|
+
detectRuntime,
|
|
191
|
+
renderContextMarkdown,
|
|
192
|
+
renderDecision,
|
|
193
|
+
};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
const ADAPTER_CONTRACT_VERSION = 1;
|
|
2
|
+
|
|
3
|
+
const CAPABILITY_STATUS = Object.freeze({
|
|
4
|
+
NATIVE: 'native',
|
|
5
|
+
SURFACE_DEPENDENT: 'surface-dependent',
|
|
6
|
+
UNSUPPORTED: 'unsupported',
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
const BASE_CAPABILITIES = Object.freeze({
|
|
10
|
+
codex: Object.freeze({
|
|
11
|
+
hooks: CAPABILITY_STATUS.NATIVE,
|
|
12
|
+
beforeTool: CAPABILITY_STATUS.NATIVE,
|
|
13
|
+
afterTool: CAPABILITY_STATUS.NATIVE,
|
|
14
|
+
approvalUI: CAPABILITY_STATUS.SURFACE_DEPENDENT,
|
|
15
|
+
browser: CAPABILITY_STATUS.SURFACE_DEPENDENT,
|
|
16
|
+
computerUse: CAPABILITY_STATUS.SURFACE_DEPENDENT,
|
|
17
|
+
imageGeneration: CAPABILITY_STATUS.SURFACE_DEPENDENT,
|
|
18
|
+
currentThreadMessaging: CAPABILITY_STATUS.SURFACE_DEPENDENT,
|
|
19
|
+
backgroundAgents: CAPABILITY_STATUS.SURFACE_DEPENDENT,
|
|
20
|
+
maxRecommendedContext: 800,
|
|
21
|
+
}),
|
|
22
|
+
claude: Object.freeze({
|
|
23
|
+
hooks: CAPABILITY_STATUS.UNSUPPORTED,
|
|
24
|
+
beforeTool: CAPABILITY_STATUS.UNSUPPORTED,
|
|
25
|
+
afterTool: CAPABILITY_STATUS.UNSUPPORTED,
|
|
26
|
+
approvalUI: CAPABILITY_STATUS.UNSUPPORTED,
|
|
27
|
+
browser: CAPABILITY_STATUS.UNSUPPORTED,
|
|
28
|
+
computerUse: CAPABILITY_STATUS.UNSUPPORTED,
|
|
29
|
+
imageGeneration: CAPABILITY_STATUS.UNSUPPORTED,
|
|
30
|
+
currentThreadMessaging: CAPABILITY_STATUS.UNSUPPORTED,
|
|
31
|
+
backgroundAgents: CAPABILITY_STATUS.SURFACE_DEPENDENT,
|
|
32
|
+
maxRecommendedContext: 800,
|
|
33
|
+
}),
|
|
34
|
+
cursor: Object.freeze({
|
|
35
|
+
hooks: CAPABILITY_STATUS.UNSUPPORTED,
|
|
36
|
+
beforeTool: CAPABILITY_STATUS.UNSUPPORTED,
|
|
37
|
+
afterTool: CAPABILITY_STATUS.UNSUPPORTED,
|
|
38
|
+
approvalUI: CAPABILITY_STATUS.UNSUPPORTED,
|
|
39
|
+
browser: CAPABILITY_STATUS.UNSUPPORTED,
|
|
40
|
+
computerUse: CAPABILITY_STATUS.UNSUPPORTED,
|
|
41
|
+
imageGeneration: CAPABILITY_STATUS.SURFACE_DEPENDENT,
|
|
42
|
+
currentThreadMessaging: CAPABILITY_STATUS.UNSUPPORTED,
|
|
43
|
+
backgroundAgents: CAPABILITY_STATUS.SURFACE_DEPENDENT,
|
|
44
|
+
maxRecommendedContext: 800,
|
|
45
|
+
}),
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
function normalizeClient(value) {
|
|
49
|
+
const client = String(value ?? '').trim().toLowerCase();
|
|
50
|
+
if (!Object.prototype.hasOwnProperty.call(BASE_CAPABILITIES, client)) {
|
|
51
|
+
throw new Error(`Unsupported OpenPrd adapter client: ${value}`);
|
|
52
|
+
}
|
|
53
|
+
return client;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function normalizeObservedCapabilities(observed) {
|
|
57
|
+
if (!Array.isArray(observed)) return new Set();
|
|
58
|
+
return new Set(observed.map((item) => String(item ?? '').trim()).filter(Boolean));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function describeCapabilities(client, options = {}) {
|
|
62
|
+
const normalized = normalizeClient(client);
|
|
63
|
+
const observed = normalizeObservedCapabilities(options.observedCapabilities);
|
|
64
|
+
const baseline = BASE_CAPABILITIES[normalized];
|
|
65
|
+
const capabilities = {};
|
|
66
|
+
for (const [name, status] of Object.entries(baseline)) {
|
|
67
|
+
if (name === 'maxRecommendedContext') {
|
|
68
|
+
capabilities[name] = Number.isFinite(options.maxRecommendedContext)
|
|
69
|
+
? Math.max(1, Math.trunc(options.maxRecommendedContext))
|
|
70
|
+
: status;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
capabilities[name] = status === CAPABILITY_STATUS.SURFACE_DEPENDENT && observed.has(name)
|
|
74
|
+
? CAPABILITY_STATUS.NATIVE
|
|
75
|
+
: status;
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
schema: 'openprd.adapter-capabilities.v1',
|
|
79
|
+
contractVersion: ADAPTER_CONTRACT_VERSION,
|
|
80
|
+
client: normalized,
|
|
81
|
+
surface: options.surface ?? 'unknown',
|
|
82
|
+
capabilities,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function capabilityAvailable(envelope, name) {
|
|
87
|
+
return envelope?.capabilities?.[name] === CAPABILITY_STATUS.NATIVE;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export {
|
|
91
|
+
ADAPTER_CONTRACT_VERSION,
|
|
92
|
+
BASE_CAPABILITIES,
|
|
93
|
+
CAPABILITY_STATUS,
|
|
94
|
+
capabilityAvailable,
|
|
95
|
+
describeCapabilities,
|
|
96
|
+
normalizeClient,
|
|
97
|
+
};
|
|
98
|
+
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import { ADAPTER_CONTRACT_VERSION, normalizeClient } from './capability-envelope.js';
|
|
3
|
+
|
|
4
|
+
const EVENT_ALIASES = Object.freeze({
|
|
5
|
+
SessionStart: 'session.started',
|
|
6
|
+
session_start: 'session.started',
|
|
7
|
+
UserPromptSubmit: 'prompt.submitted',
|
|
8
|
+
user_prompt_submit: 'prompt.submitted',
|
|
9
|
+
PreToolUse: 'tool.before',
|
|
10
|
+
before_tool: 'tool.before',
|
|
11
|
+
PostToolUse: 'tool.after',
|
|
12
|
+
after_tool: 'tool.after',
|
|
13
|
+
Stop: 'turn.stopped',
|
|
14
|
+
stop: 'turn.stopped',
|
|
15
|
+
SubagentStart: 'agent.started',
|
|
16
|
+
SubagentStop: 'agent.stopped',
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
function stableEventId(client, raw) {
|
|
20
|
+
const seed = JSON.stringify({
|
|
21
|
+
client,
|
|
22
|
+
event: raw.event ?? raw.eventName ?? raw.type ?? null,
|
|
23
|
+
sessionId: raw.sessionId ?? raw.session_id ?? null,
|
|
24
|
+
threadId: raw.threadId ?? raw.thread_id ?? null,
|
|
25
|
+
turnId: raw.turnId ?? raw.turn_id ?? null,
|
|
26
|
+
toolUseId: raw.toolUseId ?? raw.tool_use_id ?? null,
|
|
27
|
+
occurredAt: raw.occurredAt ?? raw.timestamp ?? raw.at ?? null,
|
|
28
|
+
});
|
|
29
|
+
return `evt_${crypto.createHash('sha256').update(seed).digest('hex').slice(0, 24)}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function normalizeLifecycleEvent(client, rawEvent = {}) {
|
|
33
|
+
const normalizedClient = normalizeClient(client);
|
|
34
|
+
const raw = rawEvent && typeof rawEvent === 'object' && !Array.isArray(rawEvent) ? rawEvent : {};
|
|
35
|
+
const rawType = String(raw.event ?? raw.eventName ?? raw.type ?? 'unknown');
|
|
36
|
+
const occurredAt = raw.occurredAt ?? raw.timestamp ?? raw.at ?? new Date().toISOString();
|
|
37
|
+
return {
|
|
38
|
+
schema: 'openprd.adapter-event.v1',
|
|
39
|
+
adapterVersion: ADAPTER_CONTRACT_VERSION,
|
|
40
|
+
eventId: raw.eventId ?? raw.event_id ?? stableEventId(normalizedClient, raw),
|
|
41
|
+
client: normalizedClient,
|
|
42
|
+
eventType: EVENT_ALIASES[rawType] ?? rawType,
|
|
43
|
+
occurredAt,
|
|
44
|
+
sessionId: raw.sessionId ?? raw.session_id ?? null,
|
|
45
|
+
threadId: raw.threadId ?? raw.thread_id ?? raw.conversationId ?? null,
|
|
46
|
+
turnId: raw.turnId ?? raw.turn_id ?? null,
|
|
47
|
+
toolUseId: raw.toolUseId ?? raw.tool_use_id ?? null,
|
|
48
|
+
cwd: raw.cwd ?? raw.workspaceRoot ?? null,
|
|
49
|
+
payload: raw.payload && typeof raw.payload === 'object' ? raw.payload : {},
|
|
50
|
+
rawEventType: rawType,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export { EVENT_ALIASES, normalizeLifecycleEvent };
|
|
55
|
+
|