@openprd/cli 0.1.11 → 0.1.19

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.
Files changed (147) hide show
  1. package/.openprd/changes/adaptive-project-framing/.openprd.yaml +2 -0
  2. package/.openprd/changes/adaptive-project-framing/design.md +47 -0
  3. package/.openprd/changes/adaptive-project-framing/proposal.md +35 -0
  4. package/.openprd/changes/adaptive-project-framing/specs/agent-requirements/spec.md +16 -0
  5. package/.openprd/changes/adaptive-project-framing/task-events.jsonl +20 -0
  6. package/.openprd/changes/adaptive-project-framing/tasks.md +340 -0
  7. package/.openprd/changes/docs-anti-pollution-and-design-direction-review/.openprd.yaml +2 -0
  8. package/.openprd/changes/docs-anti-pollution-and-design-direction-review/design.md +34 -0
  9. package/.openprd/changes/docs-anti-pollution-and-design-direction-review/proposal.md +23 -0
  10. package/.openprd/changes/docs-anti-pollution-and-design-direction-review/specs/agent-requirements/spec.md +16 -0
  11. package/.openprd/changes/docs-anti-pollution-and-design-direction-review/task-events.jsonl +14 -0
  12. package/.openprd/changes/docs-anti-pollution-and-design-direction-review/tasks.md +238 -0
  13. package/.openprd/changes/loop-isolated-worktree-commit/.openprd.yaml +2 -0
  14. package/.openprd/changes/loop-isolated-worktree-commit/design.md +53 -0
  15. package/.openprd/changes/loop-isolated-worktree-commit/proposal.md +38 -0
  16. package/.openprd/changes/loop-isolated-worktree-commit/specs/agent-requirements/spec.md +16 -0
  17. package/.openprd/changes/loop-isolated-worktree-commit/task-events.jsonl +22 -0
  18. package/.openprd/changes/loop-isolated-worktree-commit/tasks.md +357 -0
  19. package/.openprd/changes/openprd/.openprd.yaml +2 -0
  20. package/.openprd/changes/openprd/design.md +46 -0
  21. package/.openprd/changes/openprd/proposal.md +36 -0
  22. package/.openprd/changes/openprd/specs/agent-requirements/spec.md +16 -0
  23. package/.openprd/changes/openprd/task-events.jsonl +23 -0
  24. package/.openprd/changes/openprd/tasks.md +242 -0
  25. package/.openprd/changes/openprd-generated-change/.openprd.yaml +2 -0
  26. package/.openprd/changes/openprd-generated-change/design.md +50 -0
  27. package/.openprd/changes/openprd-generated-change/proposal.md +37 -0
  28. package/.openprd/changes/openprd-generated-change/specs/agent-requirements/spec.md +16 -0
  29. package/.openprd/changes/openprd-generated-change/task-events.jsonl +21 -0
  30. package/.openprd/changes/openprd-generated-change/tasks.md +357 -0
  31. package/.openprd/changes/pm-human-in-the-loop/.openprd.yaml +2 -0
  32. package/.openprd/changes/pm-human-in-the-loop/design.md +38 -0
  33. package/.openprd/changes/pm-human-in-the-loop/proposal.md +29 -0
  34. package/.openprd/changes/pm-human-in-the-loop/specs/agent-requirements/spec.md +16 -0
  35. package/.openprd/changes/pm-human-in-the-loop/task-events.jsonl +18 -0
  36. package/.openprd/changes/pm-human-in-the-loop/tasks.md +306 -0
  37. package/.openprd/changes/test-strategy-router/.openprd.yaml +2 -0
  38. package/.openprd/changes/test-strategy-router/design.md +65 -0
  39. package/.openprd/changes/test-strategy-router/proposal.md +45 -0
  40. package/.openprd/changes/test-strategy-router/specs/agent-requirements/spec.md +16 -0
  41. package/.openprd/changes/test-strategy-router/task-events.jsonl +1 -0
  42. package/.openprd/changes/test-strategy-router/tasks.md +132 -0
  43. package/.openprd/changes/verify-boundaries-and-historical-refresh/.openprd.yaml +2 -0
  44. package/.openprd/changes/verify-boundaries-and-historical-refresh/design.md +28 -0
  45. package/.openprd/changes/verify-boundaries-and-historical-refresh/proposal.md +23 -0
  46. package/.openprd/changes/verify-boundaries-and-historical-refresh/specs/agent-requirements/spec.md +16 -0
  47. package/.openprd/changes/verify-boundaries-and-historical-refresh/task-events.jsonl +11 -0
  48. package/.openprd/changes/verify-boundaries-and-historical-refresh/tasks.md +66 -0
  49. package/.openprd/changes/vibe-coding-dev-check/.openprd.yaml +2 -0
  50. package/.openprd/changes/vibe-coding-dev-check/design.md +37 -0
  51. package/.openprd/changes/vibe-coding-dev-check/proposal.md +28 -0
  52. package/.openprd/changes/vibe-coding-dev-check/specs/agent-requirements/spec.md +16 -0
  53. package/.openprd/changes/vibe-coding-dev-check/task-events.jsonl +18 -0
  54. package/.openprd/changes/vibe-coding-dev-check/tasks.md +306 -0
  55. package/.openprd/design/README.md +11 -6
  56. package/.openprd/design/README_EN.md +4 -1
  57. package/.openprd/design/active/asset-spec.md +6 -0
  58. package/.openprd/design/active/direction-plan.md +5 -5
  59. package/.openprd/design/active/selected-direction.md +2 -0
  60. package/.openprd/design/anti-slop.md +26 -1
  61. package/.openprd/design/assets/surface-presets.md +6 -0
  62. package/.openprd/design/checklists/ui-quality-gate.md +7 -0
  63. package/.openprd/design/lenses/frontend-lenses.md +28 -0
  64. package/.openprd/design/recipes/content-experience.md +8 -1
  65. package/.openprd/design/recipes/operational-tool.md +7 -0
  66. package/.openprd/design/recipes/product-launch.md +8 -1
  67. package/.openprd/design/templates/README.md +6 -3
  68. package/.openprd/design/templates/README_EN.md +6 -3
  69. package/.openprd/design/templates/ops-dashboard.html +8 -1
  70. package/.openprd/design/templates/product-launch.html +2 -1
  71. package/.openprd/design/themes/theme-catalog.json +23 -5
  72. package/.openprd/engagements/active/prd.md +137 -112
  73. package/.openprd/standards/config.json +7 -1
  74. package/AGENTS.md +13 -3
  75. package/README.md +127 -14
  76. package/README_EN.md +156 -13
  77. package/package.json +1 -1
  78. package/scripts/dev-check-wrapup-copy.mjs +46 -21
  79. package/skills/openprd-diagram-review/SKILL.md +27 -2
  80. package/skills/openprd-diagram-review/references/explanation-svg-patterns.md +131 -0
  81. package/skills/openprd-discovery-loop/SKILL.md +2 -2
  82. package/skills/openprd-frontend-design/SKILL.md +44 -20
  83. package/skills/openprd-frontend-design/references/design-asset-contract.md +6 -0
  84. package/skills/openprd-frontend-design/references/direction-engine.md +29 -4
  85. package/skills/openprd-harness/SKILL.md +22 -16
  86. package/skills/openprd-quality/SKILL.md +10 -8
  87. package/skills/openprd-requirement-intake/SKILL.md +2 -0
  88. package/skills/openprd-router/SKILL.md +2 -1
  89. package/skills/openprd-shared/SKILL.md +26 -16
  90. package/skills/openprd-standards/SKILL.md +4 -2
  91. package/src/agent-canonical-content.js +810 -0
  92. package/src/agent-integration.js +258 -777
  93. package/src/canvas-app-client-app.js +838 -0
  94. package/src/canvas-app-client-helpers.js +1138 -0
  95. package/src/canvas-app-styles.js +898 -0
  96. package/src/canvas-app.html.js +135 -0
  97. package/src/canvas-binding.js +414 -0
  98. package/src/canvas-codex-relay.js +430 -0
  99. package/src/canvas-i18n.js +282 -0
  100. package/src/canvas-scene.js +377 -0
  101. package/src/canvas-session-store.js +148 -0
  102. package/src/canvas-workspace.js +1264 -0
  103. package/src/cli/args.js +72 -4
  104. package/src/cli/basic-print.js +3 -0
  105. package/src/cli/canvas-print.js +106 -0
  106. package/src/cli/doctor-print.js +20 -0
  107. package/src/cli/print.js +2 -0
  108. package/src/cli/quality-commands.js +222 -0
  109. package/src/cli/quality-print.js +28 -2
  110. package/src/cli/run-print.js +5 -0
  111. package/src/cli/workflow-print.js +3 -0
  112. package/src/codex-app-server-relay.js +251 -0
  113. package/src/codex-hook-runner-template.mjs +978 -58
  114. package/src/design-starter-support.js +2 -2
  115. package/src/design-starter.js +39 -3
  116. package/src/dev-standards.js +125 -10
  117. package/src/diagram-core.js +6 -20
  118. package/src/discovery.js +2 -8
  119. package/src/docs-compact.js +125 -0
  120. package/src/execution-strategy.js +1 -1
  121. package/src/fleet.js +9 -3
  122. package/src/guide.js +227 -0
  123. package/src/html-artifacts.js +428 -7
  124. package/src/knowledge.js +45 -11
  125. package/src/language-policy.js +63 -3
  126. package/src/learning-review.js +1 -1
  127. package/src/loop.js +1 -1
  128. package/src/openprd.js +68 -131
  129. package/src/openspec/change-validate.js +2 -2
  130. package/src/openspec/constants.js +6 -7
  131. package/src/openspec/execute.js +2 -2
  132. package/src/openspec/generate.js +318 -102
  133. package/src/openspec/migration.js +168 -0
  134. package/src/openspec/paths.js +1 -22
  135. package/src/prd-core.js +1 -1
  136. package/src/quality-visual-review.js +257 -14
  137. package/src/quality.js +46 -8
  138. package/src/review-presentation.js +60 -0
  139. package/src/run-harness.js +41 -3
  140. package/src/session-evidence.js +363 -0
  141. package/src/standards.js +64 -0
  142. package/src/test-strategy.js +7 -4
  143. package/src/visual-compare-centering.js +622 -0
  144. package/src/visual-compare-core.js +1123 -0
  145. package/src/visual-compare.js +620 -698
  146. package/src/workspace-core.js +94 -4
  147. package/src/workspace-workflow.js +46 -1
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, mark: 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, 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 };
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' };
23
23
  const positionals = [];
24
24
 
25
25
  while (args.length > 0) {
@@ -116,6 +116,26 @@ function parseCommandArgs(argv) {
116
116
  flags.dryRun = true;
117
117
  continue;
118
118
  }
119
+ if (arg === '--daemon' || arg === '--background' || arg === '--persist') {
120
+ flags.daemon = true;
121
+ continue;
122
+ }
123
+ if (arg === '--stop') {
124
+ flags.stop = true;
125
+ continue;
126
+ }
127
+ if (arg === '--bridge-outbox') {
128
+ flags.bridgeOutbox = true;
129
+ continue;
130
+ }
131
+ if (arg === '--bridge-sent') {
132
+ flags.bridgeSent = true;
133
+ continue;
134
+ }
135
+ if (arg === '--bridge-failed') {
136
+ flags.bridgeFailed = true;
137
+ continue;
138
+ }
119
139
  if (arg === '--repair-agent') {
120
140
  flags.repairAgent = true;
121
141
  continue;
@@ -224,6 +244,36 @@ function parseCommandArgs(argv) {
224
244
  flags.agentCommand = args.shift() ?? null;
225
245
  continue;
226
246
  }
247
+ if (arg === '--thread' || arg === '--thread-id' || arg === '--conversation' || arg === '--conversation-id') {
248
+ flags.thread = args.shift() ?? null;
249
+ continue;
250
+ }
251
+ if (arg === '--thread-title' || arg === '--conversation-title' || arg === '--conversation-name') {
252
+ const value = args.shift() ?? null;
253
+ flags.threadTitle = value;
254
+ flags.conversationTitle = value;
255
+ continue;
256
+ }
257
+ if (arg === '--session' || arg === '--session-id') {
258
+ flags.session = args.shift() ?? null;
259
+ continue;
260
+ }
261
+ if (arg === '--session-title' || arg === '--session-name') {
262
+ flags.sessionTitle = args.shift() ?? null;
263
+ continue;
264
+ }
265
+ if (arg === '--host') {
266
+ flags.host = args.shift() ?? null;
267
+ continue;
268
+ }
269
+ if (arg === '--port') {
270
+ flags.port = args.shift() ?? null;
271
+ continue;
272
+ }
273
+ if (arg === '--open-target') {
274
+ flags.openTarget = args.shift() ?? 'auto';
275
+ continue;
276
+ }
227
277
  if (arg === '--worktree') {
228
278
  flags.worktree = args.shift() ?? null;
229
279
  continue;
@@ -332,6 +382,10 @@ function parseCommandArgs(argv) {
332
382
  flags.actualLabel = args.shift() ?? null;
333
383
  continue;
334
384
  }
385
+ if (arg === '--locale' || arg === '--lang') {
386
+ flags.locale = args.shift() ?? null;
387
+ continue;
388
+ }
335
389
  if (arg === '--classify-external') {
336
390
  flags.classifyExternal = args.shift() ?? '';
337
391
  continue;
@@ -488,6 +542,14 @@ function parseCommandArgs(argv) {
488
542
  flags.digest = args.shift() ?? null;
489
543
  continue;
490
544
  }
545
+ if (arg === '--direction') {
546
+ flags.direction = args.shift() ?? null;
547
+ continue;
548
+ }
549
+ if (arg === '--auto-refactor') {
550
+ flags.autoRefactor = args.shift() ?? null;
551
+ continue;
552
+ }
491
553
  if (arg === '--work-unit' || arg === '--work-unit-id') {
492
554
  flags.workUnit = args.shift() ?? null;
493
555
  continue;
@@ -528,6 +590,9 @@ function usage() {
528
590
  ' 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]',
529
591
  ' openprd run [path] [--context|--verify|--record-hook --event <name> --risk <level> --outcome <text> --preview <text>] [--message <text>] [--json]',
530
592
  ' 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
+ ' 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
+ ' openprd canvas [path] --bridge-outbox [--thread <id>|--session <id>] [--claim <next|id>] [--json]',
595
+ ' openprd canvas [path] (--bridge-sent|--bridge-failed) --id <bridge-id> [--thread <id>|--session <id>] [--reason <text>] [--json]',
531
596
  ' openprd classify [path] <consumer|b2b|agent>',
532
597
  ' openprd clarify [path] [--mode <auto|inline|inline-with-checklist>] [--json]',
533
598
  ' openprd capture [path] (--field <section.path> --value <text|json> | --json-file <answers.json> | --artifact-markdown <artifact.md>) [--source <user-confirmed|project-derived|agent-inferred|agent-normalized>] [--append] [--json]',
@@ -538,15 +603,18 @@ function usage() {
538
603
  ' openprd quality [path] [--init|--verify|--report --html|--learn [--review] --from <report-id-or-json-or-diagnostics-or-turn-state>] [--force] [--json]',
539
604
  ' openprd knowledge <candidates|reject|archive|restore> [path-or-id] [--status <pending-review|all|rejected|archived|promoted|merged>] [--id <candidate-id>] [--reason <text>] [--json]',
540
605
  ' openprd visual-prepare [path] --reference <effect-image> ((--grid <columns>x<rows>) | (--boxes <plan.json>)) [--include <csv>] [--id <reference-set-id>] [--title <text>] [--out <dir>] [--json]',
541
- ' openprd visual-compare [path] ((--reference <effect-image> --actual <screenshot-image>) | (--before <before-screenshot> --after <after-screenshot>) | --board <board.json>) [--out <file.jpg>] [--format <jpg|png|webp>] [--quality <1..100>] [--max-panel-width <px>] [--json]',
606
+ ' openprd visual-compare [path] ((--reference <effect-image> --actual <screenshot-image>) | (--before <before-screenshot> --after <after-screenshot>) | --board <board.json>) [--out <file.jpg>] [--format <jpg|png|webp>] [--quality <1..100>] [--max-panel-width <px>] [--locale <zh-CN|en>] [--json]',
607
+ ' --board modes: focus-board, parallel-board, verification-board, alignment-board, centering-board',
542
608
  ' 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]',
543
- ' openprd dev-check [path] <file...> [--json]',
609
+ ' openprd dev-check [path] <file...> [--auto-refactor <on|off>] [--json]',
544
610
  ' openprd grow [path] [--review|--apply --id <candidate-id>|--reject --id <candidate-id>|--init|--check] [--notes <text>] [--json]',
545
611
  ' 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]',
546
612
  ' openprd synthesize [path] [--title <text>] [--owner <text>] [--problem <text>] [--why-now <text>] [--work-unit <id>] [--target-root <path>] [--open] [--json]',
547
- ' openprd review [path] [--open] [--mark <pending-confirmation|confirmed|needs-revision>] [--version <id>] [--digest <sha256>] [--work-unit <id>] [--notes <text>] [--json]',
613
+ ' openprd review [path] [--open] [--mark <pending-confirmation|confirmed|needs-revision>] [--version <id>] [--digest <sha256>] [--work-unit <id>] [--direction <design-direction-id>] [--notes <text>] [--json]',
548
614
  ' openprd brainstorm-presentation [path] [--template] [--presentation <json>] [--write] [--fail-on-violation] [--json]',
549
615
  ' openprd review-presentation [path] [--template] [--version <id>] [--presentation <json>] [--write] [--fail-on-violation] [--json]',
616
+ ' openprd docs-compact [path] [--json]',
617
+ ' openprd guide [path] [--json]',
550
618
  ' openprd diagram [path] [--type <architecture|product-flow>] [--input <contract.json>] [--mark <pending-confirmation|confirmed|needs-revision>] [--open] [--json]',
551
619
  ' openprd diff [path] [--from <version>] [--to <version>]',
552
620
  ' openprd history [path]',
@@ -338,6 +338,9 @@ function printInitResult(result, json) {
338
338
 
339
339
  console.log(`已初始化 OpenPrd 工作区: ${result.ws.workspaceRoot}`);
340
340
  console.log(`场景模板: ${formatTemplatePackDisplay(result.currentState.templatePack, { fallback: '待确认' })}`);
341
+ if (result.templatePackGuidance?.message) {
342
+ console.log(`场景确认: ${result.templatePackGuidance.message}`);
343
+ }
341
344
  console.log(`已复制种子文件: ${result.created}`);
342
345
  if (result.standards) {
343
346
  console.log(`标准化文档: ${result.standards.docsRoot}`);
@@ -0,0 +1,106 @@
1
+ /*
2
+ * Core purpose
3
+ * Print user-facing output for `openprd canvas`.
4
+ *
5
+ * Inputs
6
+ * Receives the canvas workspace result.
7
+ *
8
+ * Outputs
9
+ * Emits either JSON or concise terminal text with the local URL, binding, and
10
+ * archive paths.
11
+ *
12
+ * Positioning
13
+ * CLI presentation layer only; all service and session logic stays in
14
+ * canvas-workspace.js.
15
+ *
16
+ * Maintenance rules
17
+ * Keep output copy user-facing. Do not expose internal API details unless the
18
+ * command is explicitly in JSON mode.
19
+ */
20
+ import { toPrintableCanvasResult } from '../canvas-workspace.js';
21
+
22
+ function printCanvasResult(result, asJson = false) {
23
+ const printable = toPrintableCanvasResult(result);
24
+ if (asJson) {
25
+ console.log(JSON.stringify(printable, null, 2));
26
+ return;
27
+ }
28
+
29
+ if (!printable?.ok) {
30
+ console.log('OpenPrd 画布启动失败。');
31
+ return;
32
+ }
33
+
34
+ if (printable.action === 'canvas-stop') {
35
+ console.log(printable.stopped ? 'OpenPrd 画布后台服务已停止。' : 'OpenPrd 画布后台服务未在运行。');
36
+ const stopBinding = printable.session?.binding ?? {};
37
+ console.log(`绑定: ${stopBinding.kind ?? 'session'} ${stopBinding.displayId ?? stopBinding.id ?? printable.session?.sessionKey}`);
38
+ console.log(`目录: ${printable.paths?.sessionDir}`);
39
+ return;
40
+ }
41
+
42
+ if (printable.action === 'canvas-agent-bridge') {
43
+ const binding = printable.session?.binding ?? {};
44
+ if (printable.bridgeAction === 'outbox') {
45
+ console.log(`待 Codex Agent 前台接力: ${printable.pending?.length ?? 0} 条。`);
46
+ if ((printable.pending?.length ?? 0) > 0) {
47
+ console.log('下一步: Agent 使用 --claim next 领取后,调用 Codex 线程工具发送,再用 --bridge-sent 回写。');
48
+ }
49
+ } else if (printable.bridgeAction === 'claim') {
50
+ console.log(printable.item ? '已认领一条 Codex 前台接力任务。' : '没有可认领的 Codex 前台接力任务。');
51
+ if (printable.item) {
52
+ console.log(`目标线程: ${printable.item.threadId}`);
53
+ console.log('下一步: 调用 codex_app.send_message_to_thread 发送返回的 prompt。');
54
+ }
55
+ } else if (printable.bridgeAction === 'sent') {
56
+ console.log('已标记 Codex 前台接力完成。');
57
+ } else if (printable.bridgeAction === 'failed') {
58
+ console.log('已标记 Codex 前台接力失败。');
59
+ }
60
+ console.log(`绑定: ${binding.kind ?? 'session'} ${binding.displayId ?? binding.id ?? printable.session?.sessionKey}`);
61
+ console.log(`目录: ${printable.paths?.sessionDir}`);
62
+ return;
63
+ }
64
+
65
+ if (printable.dryRun) {
66
+ console.log('OpenPrd 画布会话检查完成。');
67
+ } else if (printable.daemon && printable.alreadyRunning) {
68
+ console.log('OpenPrd 画布后台服务已在运行。');
69
+ } else if (printable.daemon) {
70
+ console.log('OpenPrd 画布后台服务已启动。');
71
+ } else {
72
+ console.log('OpenPrd 画布已启动。');
73
+ }
74
+ if (printable.url) {
75
+ console.log(`地址: ${printable.url}`);
76
+ }
77
+ if (printable.pid) {
78
+ console.log(`进程: ${printable.pid}`);
79
+ }
80
+ if (printable.logPath) {
81
+ console.log(`日志: ${printable.logPath}`);
82
+ }
83
+ if (printable.openResult) {
84
+ const labels = {
85
+ 'codex-browser': 'Codex 右侧 Browser',
86
+ system: '系统默认浏览器',
87
+ };
88
+ const label = labels[printable.openResult.target] ?? printable.openResult.target;
89
+ console.log(`打开方式: ${label}`);
90
+ if (printable.openResult.status === 'requires-agent-browser') {
91
+ console.log('提醒: 当前命令不会打开系统浏览器;请由 Codex Agent 使用右侧 Browser 打开上面的地址。');
92
+ }
93
+ }
94
+ const binding = printable.session?.binding ?? {};
95
+ console.log(`绑定: ${binding.kind ?? 'session'} ${binding.displayId ?? binding.id ?? printable.session?.sessionKey}`);
96
+ console.log(`来源: ${binding.source ?? 'unknown'} (${binding.confidence ?? 'unknown'})`);
97
+ if (binding.note) {
98
+ console.log(`提醒: ${binding.note}`);
99
+ }
100
+ console.log(`目录: ${printable.paths?.sessionDir}`);
101
+ if (!printable.dryRun && !printable.daemon) {
102
+ console.log('保持该命令运行即可继续协同;结束时按 Ctrl+C。');
103
+ }
104
+ }
105
+
106
+ export { printCanvasResult };
@@ -104,6 +104,25 @@ function printCodexRuntimeResult(codexRuntime, prefix = '') {
104
104
  }
105
105
  }
106
106
 
107
+ function printRuntimeEnvironmentSummary(agentIntegration) {
108
+ const active = agentIntegration?.runtimeEnvironment?.active;
109
+ if (active?.activeClient && active.activeClient !== 'unknown') {
110
+ const confidence = typeof active.confidence === 'number' ? `,置信度 ${active.confidence}` : '';
111
+ const capabilities = Array.isArray(active.observedCapabilities) && active.observedCapabilities.length > 0
112
+ ? `,已观察能力 ${active.observedCapabilities.join(', ')}`
113
+ : '';
114
+ console.log(`当前 Agent 环境: ${active.activeClient}${active.surface ? ` / ${active.surface}` : ''}${confidence}${capabilities}`);
115
+ } else {
116
+ console.log('当前 Agent 环境: 未观察到当前会话 hook/session 证据');
117
+ }
118
+ const packs = Array.isArray(agentIntegration?.platformCapabilityPacks)
119
+ ? agentIntegration.platformCapabilityPacks
120
+ : [];
121
+ if (packs.length > 0) {
122
+ console.log(`平台能力包: ${packs.map((pack) => pack.name ?? pack.id).join(',')}`);
123
+ }
124
+ }
125
+
107
126
  function printDoctorResult(result, json) {
108
127
  if (json) {
109
128
  console.log(JSON.stringify(result, null, 2));
@@ -116,6 +135,7 @@ function printDoctorResult(result, json) {
116
135
  if (result.agentIntegration.hookProfile) {
117
136
  console.log(`Hook 模式: ${result.agentIntegration.hookProfile}`);
118
137
  }
138
+ printRuntimeEnvironmentSummary(result.agentIntegration);
119
139
  console.log(`标准化: ${result.standards.ok ? '通过' : '失败'}`);
120
140
  console.log(`工作区验证: ${result.validation.valid ? '通过' : '失败'}`);
121
141
  if (result.agentIntegration.drift) {
package/src/cli/print.js CHANGED
@@ -30,6 +30,7 @@ import {
30
30
  printValidation,
31
31
  } from './basic-print.js';
32
32
  import { printBenchmarkResult } from './benchmark-print.js';
33
+ import { printCanvasResult } from './canvas-print.js';
33
34
  import {
34
35
  printAcceptedSpecsResult,
35
36
  printOpenPrdChangeActionResult,
@@ -115,4 +116,5 @@ export {
115
116
  printOpenPrdChangeActionResult,
116
117
  printAcceptedSpecsResult,
117
118
  printBenchmarkResult,
119
+ printCanvasResult,
118
120
  };
@@ -0,0 +1,222 @@
1
+ /*
2
+ * 核心功能
3
+ * 承载质量与视觉相关 CLI 命令分发:standards、quality、knowledge、visual-prepare、
4
+ * visual-compare、design-starter、grow、docs-compact、guide、dev-check。
5
+ *
6
+ * 输入
7
+ * 接收 main 传入的 command、projectPath、flags、positionals。
8
+ *
9
+ * 输出
10
+ * 命中命令时执行对应 workspace 函数并打印结果,返回退出码;未命中返回 null。
11
+ *
12
+ * 定位
13
+ * 位于 CLI 应用层,从 openprd.js 拆出的命令分组,不承载领域业务规则。
14
+ *
15
+ * 依赖
16
+ * 依赖 standards、quality、knowledge、visual-compare、growth 等 workspace 模块与 cli/print。
17
+ *
18
+ * 维护规则
19
+ * 新增质量/视觉类命令时优先放这里,并同步 usage、打印契约与测试。
20
+ */
21
+ import fs from 'node:fs/promises';
22
+ import path from 'node:path';
23
+ import { checkStandardsWorkspace, classifyExternalReferenceWorkspace, initStandardsWorkspace } from '../standards.js';
24
+ import { qualityWorkspace } from '../quality.js';
25
+ import {
26
+ archiveKnowledgeCandidate,
27
+ listKnowledgeCandidates,
28
+ rejectKnowledgeCandidate,
29
+ restoreKnowledgeCandidate,
30
+ } from '../knowledge.js';
31
+ import { checkDevelopmentStandardsWorkspace } from '../dev-standards.js';
32
+ import {
33
+ applyGrowthCandidateWorkspace,
34
+ checkGrowthWorkspace,
35
+ initGrowthWorkspace,
36
+ rejectGrowthCandidateWorkspace,
37
+ reviewGrowthWorkspace,
38
+ } from '../growth.js';
39
+ import { designStarterWorkspace } from '../design-starter.js';
40
+ import { visualCompareWorkspace } from '../visual-compare.js';
41
+ import { visualPrepareWorkspace } from '../visual-prepare.js';
42
+ import {
43
+ printDesignStarterResult,
44
+ printDevelopmentStandardsResult,
45
+ printGrowthResult,
46
+ printKnowledgeResult,
47
+ printQualityResult,
48
+ printStandardsResult,
49
+ printVisualCompareResult,
50
+ printVisualPrepareResult,
51
+ } from './print.js';
52
+
53
+ async function isDirectoryPath(candidate) {
54
+ const stat = await fs.stat(path.resolve(candidate)).catch(() => null);
55
+ return Boolean(stat?.isDirectory());
56
+ }
57
+
58
+ export async function runQualityCommand(command, { projectPath, flags, positionals }) {
59
+ if (command === 'standards') {
60
+ const result = flags.init
61
+ ? await initStandardsWorkspace(projectPath, { force: flags.force })
62
+ : flags.classifyExternal !== null
63
+ ? await classifyExternalReferenceWorkspace(projectPath, { externalReference: flags.classifyExternal })
64
+ : await checkStandardsWorkspace(projectPath);
65
+ printStandardsResult(result, flags.json);
66
+ return result.ok ? 0 : 1;
67
+ }
68
+
69
+ if (command === 'quality') {
70
+ const result = await qualityWorkspace(projectPath, {
71
+ init: flags.init,
72
+ verify: flags.verify,
73
+ report: flags.report,
74
+ html: flags.html,
75
+ learn: Boolean(flags.learn),
76
+ review: flags.review,
77
+ from: flags.from,
78
+ force: flags.force,
79
+ });
80
+ printQualityResult(result, flags.json);
81
+ return result.ok ? 0 : 1;
82
+ }
83
+
84
+ if (command === 'knowledge') {
85
+ const subcommand = positionals[0] ?? 'candidates';
86
+ const firstArgIsProjectPath = !flags.path && positionals.length > 1 && await isDirectoryPath(positionals[1]);
87
+ const knowledgeProjectPath = path.resolve(flags.path ?? (firstArgIsProjectPath ? positionals[1] : process.cwd()));
88
+ let result;
89
+ if (subcommand === 'candidates' || subcommand === 'list') {
90
+ result = await listKnowledgeCandidates(knowledgeProjectPath, {
91
+ status: flags.status ?? 'pending-review',
92
+ });
93
+ } else if (subcommand === 'reject') {
94
+ result = await rejectKnowledgeCandidate(knowledgeProjectPath, {
95
+ id: flags.id ?? positionals[1] ?? null,
96
+ reason: flags.reason ?? flags.notes,
97
+ });
98
+ } else if (subcommand === 'archive') {
99
+ result = await archiveKnowledgeCandidate(knowledgeProjectPath, {
100
+ id: flags.id ?? positionals[1] ?? null,
101
+ reason: flags.reason ?? flags.notes,
102
+ });
103
+ } else if (subcommand === 'restore') {
104
+ result = await restoreKnowledgeCandidate(knowledgeProjectPath, {
105
+ id: flags.id ?? positionals[1] ?? null,
106
+ });
107
+ } else {
108
+ console.log('Usage: openprd knowledge <candidates|reject|archive|restore> [path-or-id] [--status <pending-review|all|rejected|archived|promoted|merged>] [--id <candidate-id>] [--reason <text>] [--json]');
109
+ return 1;
110
+ }
111
+ printKnowledgeResult(result, flags.json);
112
+ return result.ok ? 0 : 1;
113
+ }
114
+
115
+ if (command === 'visual-prepare') {
116
+ const result = await visualPrepareWorkspace(projectPath, {
117
+ reference: flags.reference,
118
+ grid: flags.grid,
119
+ boxes: flags.boxes,
120
+ include: flags.include,
121
+ id: flags.id,
122
+ title: flags.title,
123
+ out: flags.out,
124
+ });
125
+ printVisualPrepareResult(result, flags.json);
126
+ return result.ok ? 0 : 1;
127
+ }
128
+
129
+ if (command === 'visual-compare') {
130
+ const result = await visualCompareWorkspace(projectPath, {
131
+ reference: flags.reference,
132
+ actual: flags.actual,
133
+ before: flags.before,
134
+ after: flags.after,
135
+ board: flags.board,
136
+ out: flags.out,
137
+ format: flags.format,
138
+ quality: flags.quality,
139
+ maxPanelWidth: flags.maxPanelWidth,
140
+ referenceLabel: flags.referenceLabel,
141
+ actualLabel: flags.actualLabel,
142
+ locale: flags.locale,
143
+ });
144
+ printVisualCompareResult(result, flags.json);
145
+ return result.ok ? 0 : 1;
146
+ }
147
+
148
+ if (command === 'design-starter') {
149
+ const result = await designStarterWorkspace(projectPath, {
150
+ starter: flags.starter,
151
+ out: flags.out,
152
+ title: flags.title,
153
+ brief: flags.brief,
154
+ sections: flags.sections,
155
+ noExternalFacts: flags.noExternalFacts,
156
+ noBrandAssets: flags.noBrandAssets,
157
+ noRealImages: flags.noRealImages,
158
+ force: flags.force,
159
+ });
160
+ printDesignStarterResult(result, flags.json);
161
+ return result.ok ? 0 : 1;
162
+ }
163
+
164
+ if (command === 'grow') {
165
+ const firstArgIsProjectPath = !flags.path && positionals.length > 0 && await isDirectoryPath(positionals[0]);
166
+ const growthProjectPath = path.resolve(flags.path ?? (firstArgIsProjectPath ? positionals[0] : process.cwd()));
167
+ let result;
168
+ if (flags.init) {
169
+ result = await initGrowthWorkspace(growthProjectPath);
170
+ } else if (flags.apply) {
171
+ result = await applyGrowthCandidateWorkspace(growthProjectPath, { id: flags.id });
172
+ } else if (flags.reject) {
173
+ result = await rejectGrowthCandidateWorkspace(growthProjectPath, { id: flags.id, notes: flags.notes });
174
+ } else if (flags.check) {
175
+ result = await checkGrowthWorkspace(growthProjectPath);
176
+ } else {
177
+ result = await reviewGrowthWorkspace(growthProjectPath);
178
+ }
179
+ printGrowthResult(result, flags.json);
180
+ return result.ok ? 0 : 1;
181
+ }
182
+
183
+ if (command === 'docs-compact') {
184
+ const { docsCompactWorkspace, printDocsCompactResult } = await import('../docs-compact.js');
185
+ const result = await docsCompactWorkspace(projectPath);
186
+ printDocsCompactResult(result, flags.json);
187
+ return result.ok ? 0 : 1;
188
+ }
189
+
190
+ if (command === 'guide') {
191
+ const { guideWorkspace, printGuideResult } = await import('../guide.js');
192
+ const result = await guideWorkspace(projectPath);
193
+ printGuideResult(result, flags.json);
194
+ return result.ok ? 0 : 1;
195
+ }
196
+
197
+ if (command === 'dev-check') {
198
+ const firstArgIsProjectPath = !flags.path && positionals.length > 0 && await isDirectoryPath(positionals[0]);
199
+ const devProjectPath = path.resolve(flags.path ?? (firstArgIsProjectPath ? positionals[0] : process.cwd()));
200
+ const files = flags.path
201
+ ? positionals
202
+ : (firstArgIsProjectPath ? positionals.slice(1) : positionals);
203
+ if (flags.autoRefactor !== null) {
204
+ const normalized = String(flags.autoRefactor).trim().toLowerCase();
205
+ if (!['on', 'off'].includes(normalized)) {
206
+ console.error('--auto-refactor 只接受 on 或 off。');
207
+ return 1;
208
+ }
209
+ const { setDevCheckAutoRefactor } = await import('../dev-standards.js');
210
+ const toggleResult = await setDevCheckAutoRefactor(devProjectPath, normalized === 'on');
211
+ if (files.length === 0) {
212
+ printDevelopmentStandardsResult(toggleResult, flags.json);
213
+ return toggleResult.ok ? 0 : 1;
214
+ }
215
+ }
216
+ const result = await checkDevelopmentStandardsWorkspace(devProjectPath, { files });
217
+ printDevelopmentStandardsResult(result, flags.json);
218
+ return result.ok ? 0 : 1;
219
+ }
220
+
221
+ return null;
222
+ }
@@ -86,10 +86,19 @@ function printDevelopmentStandardsResult(result, json) {
86
86
  return;
87
87
  }
88
88
 
89
- console.log(`OpenPrd 后续建议: ${result.ok ? '完成' : '失败'}`);
89
+ if (result.action === 'dev-check-auto-refactor') {
90
+ console.log(`OpenPrd dev-check 自动优化: 已${result.autoRefactor?.enabled ? '开启' : '关闭'}`);
91
+ console.log(`当前模式: ${result.autoRefactor?.enabled ? '自动执行优化(OpenPRD 自动优化报告)' : '仅推荐(后续建议)'}`);
92
+ console.log(`配置文件: ${result.configPath}`);
93
+ return;
94
+ }
95
+
96
+ const autoMode = result.autoRefactor?.enabled !== false;
97
+ console.log(`OpenPrd ${autoMode ? '自动优化报告' : '后续建议'}: ${result.ok ? '完成' : '失败'}`);
98
+ console.log(`自动优化: ${autoMode ? '开启(超行文件本轮直接拆分,收口附开关确认)' : '关闭(仅输出拆分推荐)'}`);
90
99
  const attentionFiles = result.files.filter((file) => ['attention', 'warning', 'error'].includes(file.status));
91
100
  if (attentionFiles.length > 0) {
92
- console.log(`关注程度: 🟡 低风险|建议留意,🟠 中风险|建议优先关注,🔴 高风险|需要先处理。`);
101
+ console.log(`${autoMode ? '问题级别' : '关注程度'}: 🟡 低风险|建议留意,🟠 中风险|建议优先关注,🔴 高风险|需要先处理。`);
93
102
  } else {
94
103
  console.log(`本轮已回顾 ${result.files.length} 个改动文件,暂未发现需要额外说明的影响对象。`);
95
104
  }
@@ -101,6 +110,19 @@ function printDevelopmentStandardsResult(result, json) {
101
110
  if (result.wrapUp?.required && result.wrapUp.markdownTable) {
102
111
  console.log(result.wrapUp.markdownBlock ?? result.wrapUp.markdownTable);
103
112
  }
113
+ if (result.wrapUp?.required && result.wrapUp.switchHint) {
114
+ console.log(`- 开关提示: ${result.wrapUp.switchHint}`);
115
+ }
116
+ if (result.visualEvidence?.required) {
117
+ console.log(`- 视觉证据提醒: ${result.visualEvidence.reason}`);
118
+ console.log(` 涉及界面文件: ${result.visualEvidence.uiFiles.join(', ')}`);
119
+ for (const command of result.visualEvidence.suggestedCommands ?? []) {
120
+ console.log(` 可选命令: ${command}`);
121
+ }
122
+ console.log(` ${result.visualEvidence.chatEmbedHint}`);
123
+ } else if (result.visualEvidence?.uiFiles?.length > 0) {
124
+ console.log(`- 视觉证据: 最近 24 小时已有证据板(${result.visualEvidence.freshEvidence.join(', ')});确认覆盖本轮改动并把证据图嵌入对话流。`);
125
+ }
104
126
  printKnowledgeReview(result.knowledgeReview);
105
127
  for (const error of result.errors ?? []) {
106
128
  console.log(`- ${error}`);
@@ -276,6 +298,10 @@ function printVisualCompareResult(result, json) {
276
298
  if (Array.isArray(result.items) && result.items.length > 0) {
277
299
  console.log(`实验卡片: ${result.items.length}`);
278
300
  }
301
+ if (result.chatEmbed?.markdown) {
302
+ console.log(`对话流嵌入: ${result.chatEmbed.markdown}`);
303
+ console.log(` ${result.chatEmbed.hint}`);
304
+ }
279
305
  for (const action of result.nextActions ?? []) {
280
306
  console.log(`- 下一步: ${action}`);
281
307
  }
@@ -131,6 +131,11 @@ function printRunResult(result, json) {
131
131
 
132
132
  console.log('当前进展参考');
133
133
  console.log(`当前项目: ${result.projectRoot}`);
134
+ if (result.runtimeEnvironment?.active?.activeClient && result.runtimeEnvironment.active.activeClient !== 'unknown') {
135
+ const active = result.runtimeEnvironment.active;
136
+ const confidence = typeof active.confidence === 'number' ? `,置信度 ${active.confidence}` : '';
137
+ console.log(`当前 Agent 环境: ${active.activeClient}${active.surface ? ` / ${active.surface}` : ''}${confidence}`);
138
+ }
134
139
  console.log(`基础检查: ${result.validation.valid ? '通过' : '失败'}`);
135
140
  if (result.lane?.summary) {
136
141
  console.log(`当前处理路径: ${result.lane.summary}`);
@@ -71,6 +71,9 @@ function printReviewResult(result, json) {
71
71
  if (result.marked) {
72
72
  console.log(`状态已从 ${result.previousStatus} 更新为 ${result.status}`);
73
73
  }
74
+ if (result.designDirection) {
75
+ console.log(`选定交互方向: ${result.designDirection.id}(${result.designDirection.title})`);
76
+ }
74
77
  if (result.opened) {
75
78
  console.log('已打开确认页面');
76
79
  }