ai-delivery-workflow 0.2.2

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 (211) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +112 -0
  3. package/bin/ai-delivery.mjs +138 -0
  4. package/bin/validate-skills.mjs +12 -0
  5. package/docs/DUAL-REPOSITORY-WORKSPACE.zh-CN.md +125 -0
  6. package/docs/EVOLUTION-MAINTENANCE.zh-CN.md +75 -0
  7. package/docs/EVOLUTION-USER-GUIDE.zh-CN.md +160 -0
  8. package/docs/FILE-REFERENCE.zh-CN.md +503 -0
  9. package/docs/PROJECT-MANUAL.zh-CN.md +931 -0
  10. package/docs/STATE-CLI-MAINTENANCE.zh-CN.md +135 -0
  11. package/docs/STATE-CLI-USER-GUIDE.zh-CN.md +186 -0
  12. package/docs/VIEWER-MAINTENANCE.zh-CN.md +373 -0
  13. package/docs/VIEWER-USER-GUIDE.zh-CN.md +235 -0
  14. package/lib/delivery-state.mjs +1220 -0
  15. package/lib/evolution.mjs +971 -0
  16. package/lib/project-bootstrap.mjs +927 -0
  17. package/lib/project-installer.mjs +1184 -0
  18. package/lib/skill-validator.mjs +154 -0
  19. package/lib/toml-hooks.mjs +97 -0
  20. package/lib/workspace.mjs +302 -0
  21. package/lib/yaml-runtime.mjs +18 -0
  22. package/package.json +39 -0
  23. package/skills/ai-delivery-assemble-release/SKILL.md +27 -0
  24. package/skills/ai-delivery-assemble-release/agents/openai.yaml +4 -0
  25. package/skills/ai-delivery-assemble-release/assets/release-candidate-manifest.yaml +22 -0
  26. package/skills/ai-delivery-assemble-release/assets/release-git-plan.yaml +9 -0
  27. package/skills/ai-delivery-assemble-release/assets/release-material-index.csv +2 -0
  28. package/skills/ai-delivery-assemble-release/assets/version-inclusion.csv +2 -0
  29. package/skills/ai-delivery-assemble-release/references/release-assembly-contract.md +7 -0
  30. package/skills/ai-delivery-assemble-release/scripts/plan-release.mjs +298 -0
  31. package/skills/ai-delivery-bootstrap/SKILL.md +59 -0
  32. package/skills/ai-delivery-bootstrap/agents/openai.yaml +4 -0
  33. package/skills/ai-delivery-bootstrap/references/bootstrap-contract.md +70 -0
  34. package/skills/ai-delivery-checkpoint-task/SKILL.md +63 -0
  35. package/skills/ai-delivery-checkpoint-task/agents/openai.yaml +4 -0
  36. package/skills/ai-delivery-checkpoint-task/assets/codex-hook-config.toml +92 -0
  37. package/skills/ai-delivery-checkpoint-task/assets/runtime-template/resume.md +15 -0
  38. package/skills/ai-delivery-checkpoint-task/assets/runtime-template/task.json +30 -0
  39. package/skills/ai-delivery-checkpoint-task/assets/runtime-template/version-archive.json +10 -0
  40. package/skills/ai-delivery-checkpoint-task/references/checkpoint-contract.md +95 -0
  41. package/skills/ai-delivery-checkpoint-task/references/checkpoint-recovery.zh-CN.md +111 -0
  42. package/skills/ai-delivery-checkpoint-task/scripts/hook-event.mjs +131 -0
  43. package/skills/ai-delivery-checkpoint-task/scripts/task-state.mjs +1073 -0
  44. package/skills/ai-delivery-close-version/SKILL.md +33 -0
  45. package/skills/ai-delivery-close-version/agents/openai.yaml +4 -0
  46. package/skills/ai-delivery-close-version/assets/line-advance-record.yaml +12 -0
  47. package/skills/ai-delivery-close-version/assets/version-lineage.yaml +12 -0
  48. package/skills/ai-delivery-close-version/assets/version-manifest.yaml +20 -0
  49. package/skills/ai-delivery-close-version/references/version-closeout-contract.md +20 -0
  50. package/skills/ai-delivery-define-product/SKILL.md +76 -0
  51. package/skills/ai-delivery-define-product/agents/openai.yaml +4 -0
  52. package/skills/ai-delivery-define-product/assets/product-template/acceptance-criteria.csv +1 -0
  53. package/skills/ai-delivery-define-product/assets/product-template/discovery-baseline.md +19 -0
  54. package/skills/ai-delivery-define-product/assets/product-template/feature-detail.md +19 -0
  55. package/skills/ai-delivery-define-product/assets/product-template/feature-terminals.csv +1 -0
  56. package/skills/ai-delivery-define-product/assets/product-template/features.csv +1 -0
  57. package/skills/ai-delivery-define-product/assets/product-template/product-baseline.yaml +8 -0
  58. package/skills/ai-delivery-define-product/assets/product-template/product-manual.md +24 -0
  59. package/skills/ai-delivery-define-product/assets/product-template/terminals.csv +1 -0
  60. package/skills/ai-delivery-define-product/assets/product-template/user-stories.csv +1 -0
  61. package/skills/ai-delivery-define-product/references/product-contract.md +57 -0
  62. package/skills/ai-delivery-deploy-production/SKILL.md +45 -0
  63. package/skills/ai-delivery-deploy-production/agents/openai.yaml +4 -0
  64. package/skills/ai-delivery-deploy-production/references/deployment-contract.md +7 -0
  65. package/skills/ai-delivery-design-architecture/SKILL.md +67 -0
  66. package/skills/ai-delivery-design-architecture/agents/openai.yaml +4 -0
  67. package/skills/ai-delivery-design-architecture/assets/architecture-template/architecture-baseline.md +25 -0
  68. package/skills/ai-delivery-design-architecture/assets/architecture-template/architecture-gate.yaml +15 -0
  69. package/skills/ai-delivery-design-architecture/assets/architecture-template/prototype-architecture-validation.yaml +58 -0
  70. package/skills/ai-delivery-design-architecture/references/architecture-contract.md +32 -0
  71. package/skills/ai-delivery-design-experience/SKILL.md +71 -0
  72. package/skills/ai-delivery-design-experience/agents/openai.yaml +4 -0
  73. package/skills/ai-delivery-design-experience/assets/experience-template/canvas-catalog.csv +1 -0
  74. package/skills/ai-delivery-design-experience/assets/experience-template/canvas-pages.csv +1 -0
  75. package/skills/ai-delivery-design-experience/assets/experience-template/component-state-matrix.csv +1 -0
  76. package/skills/ai-delivery-design-experience/assets/experience-template/design-tokens.json +19 -0
  77. package/skills/ai-delivery-design-experience/assets/experience-template/experience-baseline.yaml +17 -0
  78. package/skills/ai-delivery-design-experience/assets/experience-template/experience-change-domains.csv +1 -0
  79. package/skills/ai-delivery-design-experience/assets/experience-template/experience-change-set.csv +1 -0
  80. package/skills/ai-delivery-design-experience/assets/experience-template/feature-screen-coverage.csv +1 -0
  81. package/skills/ai-delivery-design-experience/assets/experience-template/formal-ui-confirmation.yaml +33 -0
  82. package/skills/ai-delivery-design-experience/assets/experience-template/interaction-contract.csv +1 -0
  83. package/skills/ai-delivery-design-experience/assets/experience-template/low-fidelity-confirmation.yaml +32 -0
  84. package/skills/ai-delivery-design-experience/assets/experience-template/page-catalog.csv +1 -0
  85. package/skills/ai-delivery-design-experience/assets/experience-template/page-component-map.csv +1 -0
  86. package/skills/ai-delivery-design-experience/assets/experience-template/product-prototype-reconciliation.yaml +28 -0
  87. package/skills/ai-delivery-design-experience/assets/experience-template/prototype-adjustment-log.csv +1 -0
  88. package/skills/ai-delivery-design-experience/assets/experience-template/prototype-confirmation.yaml +99 -0
  89. package/skills/ai-delivery-design-experience/assets/experience-template/prototype-file-terminals.csv +1 -0
  90. package/skills/ai-delivery-design-experience/assets/experience-template/prototype-files.csv +1 -0
  91. package/skills/ai-delivery-design-experience/assets/experience-template/prototype-impact-assessment.yaml +25 -0
  92. package/skills/ai-delivery-design-experience/assets/experience-template/prototype-manifest.yaml +74 -0
  93. package/skills/ai-delivery-design-experience/assets/experience-template/prototype-product-coverage.csv +1 -0
  94. package/skills/ai-delivery-design-experience/assets/experience-template/prototype-review-comments.csv +1 -0
  95. package/skills/ai-delivery-design-experience/assets/experience-template/prototype-review-decision.yaml +29 -0
  96. package/skills/ai-delivery-design-experience/assets/experience-template/prototype-review-sessions.csv +1 -0
  97. package/skills/ai-delivery-design-experience/assets/experience-template/prototype-set.yaml +22 -0
  98. package/skills/ai-delivery-design-experience/assets/experience-template/prototype-tool-candidates.csv +1 -0
  99. package/skills/ai-delivery-design-experience/assets/experience-template/prototype-tool-selection.yaml +22 -0
  100. package/skills/ai-delivery-design-experience/assets/experience-template/prototype-traceability.csv +1 -0
  101. package/skills/ai-delivery-design-experience/assets/experience-template/requirement-change-set.csv +1 -0
  102. package/skills/ai-delivery-design-experience/assets/experience-template/screen-states.csv +1 -0
  103. package/skills/ai-delivery-design-experience/assets/experience-template/ui-framework-candidates.csv +1 -0
  104. package/skills/ai-delivery-design-experience/assets/experience-template/ui-framework-selection.yaml +18 -0
  105. package/skills/ai-delivery-design-experience/assets/experience-template/ui-handoff.yaml +44 -0
  106. package/skills/ai-delivery-design-experience/assets/experience-template/visual-direction.yaml +16 -0
  107. package/skills/ai-delivery-design-experience/assets/experience-template/visual-system-confirmation.yaml +31 -0
  108. package/skills/ai-delivery-design-experience/references/experience-contract.md +82 -0
  109. package/skills/ai-delivery-design-experience/references/prototype-management-contract.md +68 -0
  110. package/skills/ai-delivery-design-tests/SKILL.md +56 -0
  111. package/skills/ai-delivery-design-tests/agents/openai.yaml +4 -0
  112. package/skills/ai-delivery-design-tests/assets/test-design-template/iteration-test-contract.md +19 -0
  113. package/skills/ai-delivery-design-tests/assets/test-design-template/prototype-test-scope.csv +1 -0
  114. package/skills/ai-delivery-design-tests/assets/test-design-template/prototype-test-validation.yaml +73 -0
  115. package/skills/ai-delivery-design-tests/references/test-design-contract.md +31 -0
  116. package/skills/ai-delivery-develop-iteration/SKILL.md +66 -0
  117. package/skills/ai-delivery-develop-iteration/agents/openai.yaml +4 -0
  118. package/skills/ai-delivery-develop-iteration/assets/development-template/task-experience-change-traceability.csv +1 -0
  119. package/skills/ai-delivery-develop-iteration/assets/development-template/task-prototype-traceability.csv +1 -0
  120. package/skills/ai-delivery-develop-iteration/references/development-contract.md +39 -0
  121. package/skills/ai-delivery-evolve-workflow/SKILL.md +57 -0
  122. package/skills/ai-delivery-evolve-workflow/agents/openai.yaml +4 -0
  123. package/skills/ai-delivery-evolve-workflow/references/evolution-contract.md +42 -0
  124. package/skills/ai-delivery-execute-test/SKILL.md +34 -0
  125. package/skills/ai-delivery-execute-test/agents/openai.yaml +4 -0
  126. package/skills/ai-delivery-execute-test/references/test-execution-contract.md +7 -0
  127. package/skills/ai-delivery-execute-work-package/SKILL.md +53 -0
  128. package/skills/ai-delivery-execute-work-package/agents/openai.yaml +4 -0
  129. package/skills/ai-delivery-execute-work-package/references/work-package-contract.md +20 -0
  130. package/skills/ai-delivery-manage-git/SKILL.md +60 -0
  131. package/skills/ai-delivery-manage-git/agents/openai.yaml +4 -0
  132. package/skills/ai-delivery-manage-git/references/git-policy.md +42 -0
  133. package/skills/ai-delivery-manage-standards/SKILL.md +44 -0
  134. package/skills/ai-delivery-manage-standards/agents/openai.yaml +4 -0
  135. package/skills/ai-delivery-manage-standards/assets/standards-template/standard.md +25 -0
  136. package/skills/ai-delivery-manage-standards/references/standards-contract.md +20 -0
  137. package/skills/ai-delivery-orchestrate/SKILL.md +105 -0
  138. package/skills/ai-delivery-orchestrate/agents/openai.yaml +4 -0
  139. package/skills/ai-delivery-orchestrate/assets/project-template/.codex/config.toml +92 -0
  140. package/skills/ai-delivery-orchestrate/assets/project-template/.gitattributes +6 -0
  141. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/delivery/artifact-registry.yaml +3 -0
  142. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/delivery/project.yaml +6 -0
  143. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/delivery/standards-baseline.yaml +8 -0
  144. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/delivery/workflow-state.yaml +34 -0
  145. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/evolution/decisions/.gitkeep +0 -0
  146. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/evolution/experiments/.gitkeep +0 -0
  147. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/evolution/feedback/.gitkeep +0 -0
  148. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/evolution/index.yaml +7 -0
  149. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/evolution/promotion/.gitkeep +0 -0
  150. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/evolution/proposals/.gitkeep +0 -0
  151. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/extensions/.gitkeep +0 -0
  152. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/standards/STD-ARTIFACT-PATH-001.md +47 -0
  153. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/standards/STD-DOC-LANG-001.md +62 -0
  154. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/standards/STD-TEST-LOCATION-001.md +23 -0
  155. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/standards/index.yaml +36 -0
  156. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/bootstrap/bootstrap.mjs +33 -0
  157. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/evolution/evolve.mjs +15 -0
  158. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/hooks/hook-event.mjs +131 -0
  159. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/state/state.mjs +21 -0
  160. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/viewer/public/app.js +546 -0
  161. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/viewer/public/index.html +199 -0
  162. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/viewer/public/styles.css +529 -0
  163. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/viewer/server.mjs +780 -0
  164. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/viewer/start.cmd +4 -0
  165. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/viewer/start.ps1 +2 -0
  166. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/viewer/start.sh +4 -0
  167. package/skills/ai-delivery-orchestrate/assets/project-template/AGENTS.md +44 -0
  168. package/skills/ai-delivery-orchestrate/references/formal-state-contract.md +64 -0
  169. package/skills/ai-delivery-orchestrate/references/workflow-model.md +29 -0
  170. package/skills/ai-delivery-orchestrate-release/SKILL.md +33 -0
  171. package/skills/ai-delivery-orchestrate-release/agents/openai.yaml +4 -0
  172. package/skills/ai-delivery-orchestrate-release/assets/release-request.yaml +9 -0
  173. package/skills/ai-delivery-orchestrate-release/assets/version-selection.yaml +16 -0
  174. package/skills/ai-delivery-orchestrate-release/references/release-workflow-contract.md +11 -0
  175. package/skills/ai-delivery-plan-iteration/SKILL.md +57 -0
  176. package/skills/ai-delivery-plan-iteration/agents/openai.yaml +4 -0
  177. package/skills/ai-delivery-plan-iteration/assets/planning-template/backlog.csv +1 -0
  178. package/skills/ai-delivery-plan-iteration/assets/planning-template/slice-dependencies.csv +1 -0
  179. package/skills/ai-delivery-plan-iteration/assets/planning-template/slice-experience-change-traceability.csv +1 -0
  180. package/skills/ai-delivery-plan-iteration/assets/planning-template/slice-prototype-traceability.csv +1 -0
  181. package/skills/ai-delivery-plan-iteration/assets/planning-template/slice-sources.csv +1 -0
  182. package/skills/ai-delivery-plan-iteration/assets/planning-template/vertical-slices.csv +1 -0
  183. package/skills/ai-delivery-plan-iteration/references/planning-contract.md +40 -0
  184. package/skills/ai-delivery-prepare-platform/SKILL.md +49 -0
  185. package/skills/ai-delivery-prepare-platform/agents/openai.yaml +4 -0
  186. package/skills/ai-delivery-prepare-platform/assets/platform-template/environment-ledger.csv +1 -0
  187. package/skills/ai-delivery-prepare-platform/references/platform-contract.md +18 -0
  188. package/skills/ai-delivery-prepare-release/SKILL.md +47 -0
  189. package/skills/ai-delivery-prepare-release/agents/openai.yaml +4 -0
  190. package/skills/ai-delivery-prepare-release/assets/release-template/delivery-items.csv +1 -0
  191. package/skills/ai-delivery-prepare-release/assets/release-template/release-manifest.yaml +18 -0
  192. package/skills/ai-delivery-prepare-release/references/release-contract.md +21 -0
  193. package/skills/ai-delivery-review-change/SKILL.md +40 -0
  194. package/skills/ai-delivery-review-change/agents/openai.yaml +4 -0
  195. package/skills/ai-delivery-review-change/references/review-contract.md +20 -0
  196. package/skills/ai-delivery-triage-failure/SKILL.md +45 -0
  197. package/skills/ai-delivery-triage-failure/agents/openai.yaml +4 -0
  198. package/skills/ai-delivery-triage-failure/references/failure-routing-contract.md +13 -0
  199. package/skills/ai-delivery-validate-artifacts/SKILL.md +37 -0
  200. package/skills/ai-delivery-validate-artifacts/agents/openai.yaml +4 -0
  201. package/skills/ai-delivery-validate-artifacts/references/artifact-contract.md +44 -0
  202. package/skills/ai-delivery-verify-candidate/SKILL.md +56 -0
  203. package/skills/ai-delivery-verify-candidate/agents/openai.yaml +4 -0
  204. package/skills/ai-delivery-verify-candidate/assets/verification-template/defect-artifacts.csv +1 -0
  205. package/skills/ai-delivery-verify-candidate/assets/verification-template/defects.csv +1 -0
  206. package/skills/ai-delivery-verify-candidate/assets/verification-template/test-work-packages.csv +1 -0
  207. package/skills/ai-delivery-verify-candidate/references/candidate-verification-contract.md +23 -0
  208. package/skills/ai-delivery-verify-production/SKILL.md +50 -0
  209. package/skills/ai-delivery-verify-production/agents/openai.yaml +4 -0
  210. package/skills/ai-delivery-verify-production/assets/production-verification-template/production-verification.md +15 -0
  211. package/skills/ai-delivery-verify-production/references/production-verification-contract.md +14 -0
@@ -0,0 +1,780 @@
1
+ #!/usr/bin/env node
2
+
3
+ import crypto from "node:crypto";
4
+ import fs from "node:fs";
5
+ import http from "node:http";
6
+ import path from "node:path";
7
+ import { spawn, spawnSync } from "node:child_process";
8
+ import { createRequire } from "node:module";
9
+ import { fileURLToPath } from "node:url";
10
+
11
+ const viewerRoot = path.dirname(fileURLToPath(import.meta.url));
12
+ const require = createRequire(import.meta.url);
13
+ const vendoredYaml = path.join(viewerRoot, "..", "state", "vendor", "yaml", "dist", "index.js");
14
+ const yaml = require(fs.existsSync(vendoredYaml) ? vendoredYaml : "yaml");
15
+ const publicRoot = path.join(viewerRoot, "public");
16
+ const defaultProjectRoot = path.resolve(viewerRoot, "..", "..", "..");
17
+ const MAX_PREVIEW_BYTES = 2 * 1024 * 1024;
18
+ const MAX_SCAN_FILES = 10_000;
19
+ const TEXT_EXTENSIONS = new Set([
20
+ ".csv", ".html", ".ini", ".json", ".jsonl", ".md", ".mdx", ".properties",
21
+ ".rst", ".toml", ".txt", ".xml", ".yaml", ".yml",
22
+ ]);
23
+ const IMAGE_EXTENSIONS = new Set([".gif", ".jpeg", ".jpg", ".png", ".webp"]);
24
+
25
+ const DEVELOPMENT_NODE_DEFINITIONS = [
26
+ ["00-bootstrap", "引导"],
27
+ ["01-product-discovery", "产品发现"],
28
+ ["02-product-definition", "产品定义"],
29
+ ["03-architecture-initial", "初始架构"],
30
+ ["04-experience-design", "体验设计"],
31
+ ["05-architecture-reconcile", "架构校准"],
32
+ ["06-platform-preparation", "平台准备"],
33
+ ["07-iteration-planning", "迭代规划"],
34
+ ["08-test-design", "测试设计"],
35
+ ["09-development", "开发实现"],
36
+ ["10-change-review", "变更审查"],
37
+ ["11-candidate-verification", "候选验证"],
38
+ ["12-version-closeout", "版本收尾"],
39
+ ].map(([id, label], sequence) => ({ id, label, sequence, group: "development" }));
40
+
41
+ const RELEASE_NODE_DEFINITIONS = [
42
+ ["R00-release-request", "发布请求"],
43
+ ["R01-version-selection", "版本选择"],
44
+ ["R02-release-eligibility", "发布资格"],
45
+ ["R03-material-aggregation", "物料整合"],
46
+ ["R04-release-candidate", "发布候选"],
47
+ ["R05-release-verification", "发布验证"],
48
+ ["R06-release-preparation", "发布准备"],
49
+ ["R07-production-approval", "生产审批"],
50
+ ["R08-production-deployment", "生产部署"],
51
+ ["R09-production-verification", "生产验证"],
52
+ ["R10-release-archive", "发布归档"],
53
+ ].map(([id, label], sequence) => ({ id, label, sequence, group: "release" }));
54
+
55
+ const LEGACY_NODE_DEFINITIONS = [
56
+ ["12-release-preparation", "历史发布准备"],
57
+ ["13-production-approval", "历史生产审批"],
58
+ ["14-production-deployment", "历史生产部署"],
59
+ ["15-production-verification", "历史生产验证"],
60
+ ["16-version-archive", "历史版本归档"],
61
+ ].map(([id, label], sequence) => ({ id, label, sequence, group: "legacy" }));
62
+
63
+ const NODE_DEFINITIONS = [
64
+ ...DEVELOPMENT_NODE_DEFINITIONS,
65
+ ...RELEASE_NODE_DEFINITIONS,
66
+ ...LEGACY_NODE_DEFINITIONS,
67
+ ];
68
+
69
+ const NODE_ALIASES = new Map([
70
+ ["bootstrap", "00-bootstrap"],
71
+ ["product-discovery", "01-product-discovery"],
72
+ ["product", "02-product-definition"],
73
+ ["product-definition", "02-product-definition"],
74
+ ["architecture-initial", "03-architecture-initial"],
75
+ ["experience", "04-experience-design"],
76
+ ["experience-design", "04-experience-design"],
77
+ ["architecture", "05-architecture-reconcile"],
78
+ ["architecture-reconcile", "05-architecture-reconcile"],
79
+ ["platform-preparation", "06-platform-preparation"],
80
+ ["iteration-planning", "07-iteration-planning"],
81
+ ["planning", "07-iteration-planning"],
82
+ ["test-design", "08-test-design"],
83
+ ["development", "09-development"],
84
+ ["change-review", "10-change-review"],
85
+ ["review", "10-change-review"],
86
+ ["candidate-verification", "11-candidate-verification"],
87
+ ["version-closeout", "12-version-closeout"],
88
+ ["release-request", "R00-release-request"],
89
+ ["version-selection", "R01-version-selection"],
90
+ ["release-eligibility", "R02-release-eligibility"],
91
+ ["material-aggregation", "R03-material-aggregation"],
92
+ ["release-candidate", "R04-release-candidate"],
93
+ ["release-verification", "R05-release-verification"],
94
+ ["R06-release-preparation", "R06-release-preparation"],
95
+ ["R07-production-approval", "R07-production-approval"],
96
+ ["R08-production-deployment", "R08-production-deployment"],
97
+ ["R09-production-verification", "R09-production-verification"],
98
+ ["release-archive", "R10-release-archive"],
99
+ ["release-preparation", "12-release-preparation"],
100
+ ["production-approval", "13-production-approval"],
101
+ ["production-deployment", "14-production-deployment"],
102
+ ["production-verification", "15-production-verification"],
103
+ ["version-archive", "16-version-archive"],
104
+ ["standards", "standards"],
105
+ ["delivery", "governance"],
106
+ ]);
107
+
108
+ const STATUS_META = {
109
+ approved: { label: "已批准", tone: "success" },
110
+ valid: { label: "有效", tone: "success" },
111
+ frozen: { label: "已冻结", tone: "success" },
112
+ immutable: { label: "不可变", tone: "success" },
113
+ deployed: { label: "已部署", tone: "success" },
114
+ released: { label: "已发布", tone: "success" },
115
+ "release-ready": { label: "待发布", tone: "success" },
116
+ "included-in-release": { label: "已纳入发布", tone: "success" },
117
+ completed: { label: "已完成", tone: "success" },
118
+ passed: { label: "已通过", tone: "success" },
119
+ ready: { label: "已就绪", tone: "success" },
120
+ draft: { label: "草稿", tone: "neutral" },
121
+ "review-draft": { label: "审阅草稿", tone: "neutral" },
122
+ planned: { label: "已规划", tone: "neutral" },
123
+ queued: { label: "待执行", tone: "neutral" },
124
+ pending: { label: "待确认", tone: "warning" },
125
+ "pending-approval": { label: "待批准", tone: "warning" },
126
+ "pending-review": { label: "待审阅", tone: "warning" },
127
+ running: { label: "进行中", tone: "warning" },
128
+ observed: { label: "已观察", tone: "neutral" },
129
+ proposed: { label: "已提案", tone: "warning" },
130
+ trial: { label: "试运行", tone: "warning" },
131
+ active: { label: "已启用", tone: "success" },
132
+ disabled: { label: "已禁用", tone: "warning" },
133
+ rejected: { label: "已拒绝", tone: "danger" },
134
+ expired: { label: "已失效", tone: "neutral" },
135
+ checkpointed: { label: "已检查点", tone: "warning" },
136
+ stale: { label: "已过期", tone: "warning" },
137
+ "rolled-back": { label: "已回滚", tone: "warning" },
138
+ superseded: { label: "已替代", tone: "neutral" },
139
+ archived: { label: "已归档", tone: "success" },
140
+ changed: { label: "已变更", tone: "warning" },
141
+ blocked: { label: "已阻塞", tone: "danger" },
142
+ invalid: { label: "无效", tone: "danger" },
143
+ failed: { label: "失败", tone: "danger" },
144
+ missing: { label: "缺失", tone: "danger" },
145
+ abandoned: { label: "已放弃", tone: "danger" },
146
+ unknown: { label: "未标记", tone: "muted" },
147
+ };
148
+
149
+ function toPosix(value) {
150
+ return value.replaceAll("\\", "/");
151
+ }
152
+
153
+ function parseArguments(argv) {
154
+ const readValue = (flag, fallback) => {
155
+ const index = argv.indexOf(flag);
156
+ return index >= 0 && argv[index + 1] ? argv[index + 1] : fallback;
157
+ };
158
+ const port = Number(readValue("--port", process.env.AI_DELIVERY_VIEWER_PORT || "4173"));
159
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
160
+ throw new Error(`Invalid port: ${port}`);
161
+ }
162
+ return {
163
+ host: readValue("--host", process.env.AI_DELIVERY_VIEWER_HOST || "127.0.0.1"),
164
+ port,
165
+ projectRoot: path.resolve(readValue("--project", defaultProjectRoot)),
166
+ open: !argv.includes("--no-open"),
167
+ };
168
+ }
169
+
170
+ function isInside(candidate, root) {
171
+ const relative = path.relative(path.resolve(root), path.resolve(candidate));
172
+ return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
173
+ }
174
+
175
+ function readText(file, limit = MAX_PREVIEW_BYTES) {
176
+ const stat = fs.statSync(file);
177
+ if (stat.size > limit) return null;
178
+ return fs.readFileSync(file, "utf8");
179
+ }
180
+
181
+ function scalar(text, keys) {
182
+ if (!text) return null;
183
+ for (const key of keys) {
184
+ const expression = new RegExp(`^\\s*${key}\\s*:\\s*[\"']?([^\\r\\n#\"']+)`, "im");
185
+ const match = text.match(expression);
186
+ if (match?.[1]?.trim()) return match[1].trim();
187
+ }
188
+ return null;
189
+ }
190
+
191
+ function structuredData(text, extension) {
192
+ if (!text) return null;
193
+ try {
194
+ if (extension === ".yaml" || extension === ".yml") return yaml.parse(text);
195
+ if (extension === ".json") return JSON.parse(text);
196
+ } catch {
197
+ return null;
198
+ }
199
+ return null;
200
+ }
201
+
202
+ function normalizeStatus(value, changed) {
203
+ if (changed) return { key: "changed", ...STATUS_META.changed };
204
+ const raw = String(value || "").trim().toLowerCase().replaceAll("_", "-");
205
+ const aliases = {
206
+ accepted: "valid",
207
+ "changes-required": "invalid",
208
+ pass: "passed",
209
+ success: "completed",
210
+ };
211
+ const key = STATUS_META[raw] ? raw : aliases[raw] || "unknown";
212
+ return { key, ...STATUS_META[key] };
213
+ }
214
+
215
+ function fileType(extension) {
216
+ const types = {
217
+ ".md": "Markdown", ".mdx": "Markdown", ".csv": "CSV", ".yaml": "YAML",
218
+ ".yml": "YAML", ".json": "JSON", ".jsonl": "JSONL", ".html": "原型",
219
+ ".png": "图片", ".jpg": "图片", ".jpeg": "图片", ".webp": "图片",
220
+ ".pdf": "PDF", ".txt": "文本", ".toml": "TOML",
221
+ };
222
+ return types[extension] || (extension ? extension.slice(1).toUpperCase() : "文件");
223
+ }
224
+
225
+ function titleFor(relativePath, text) {
226
+ const extension = path.extname(relativePath).toLowerCase();
227
+ if (extension === ".md" || extension === ".mdx") {
228
+ const heading = text?.match(/^#\s+(.+)$/m)?.[1]?.trim();
229
+ if (heading) return heading;
230
+ }
231
+ return scalar(text, ["title", "name", "artifact_id", "baseline_id", "release_id"])
232
+ || path.basename(relativePath);
233
+ }
234
+
235
+ function normalizeNode(value) {
236
+ if (!value) return null;
237
+ if (NODE_DEFINITIONS.some((node) => node.id === value)) return value;
238
+ if (value === "00-governance") return "governance";
239
+ const withoutSequence = value.replace(/^\d{2}-/, "");
240
+ return NODE_ALIASES.get(value) || NODE_ALIASES.get(withoutSequence) || value;
241
+ }
242
+
243
+ function nodeFor(relativePath, text) {
244
+ const parts = relativePath.split("/");
245
+ if (parts[1] === "iterations" && parts[3]) {
246
+ return normalizeNode(parts[3]);
247
+ }
248
+ if (parts[1] === "releases" && parts[3]) {
249
+ return normalizeNode(parts[3]);
250
+ }
251
+ if (parts[1] === "product") return "02-product-definition";
252
+ if (parts[1] === "architecture") return "05-architecture-reconcile";
253
+ if (parts[1] === "experience") return "04-experience-design";
254
+ if (parts[1] === "standards") return "standards";
255
+ if (parts[1] === "evolution" || parts[1] === "extensions") return "evolution";
256
+ if (parts[1] === "delivery") {
257
+ if (parts[2] === "bootstrap") return "00-bootstrap";
258
+ if (parts[2] === "runtime" && parts.includes("archive")) return "16-version-archive";
259
+ return "governance";
260
+ }
261
+ const declared = scalar(text, ["node", "node_id", "stage"]);
262
+ return normalizeNode(declared) || "unassigned";
263
+ }
264
+
265
+ function releaseFor(relativePath, text) {
266
+ const parts = relativePath.split("/");
267
+ if (parts[1] === "releases" && parts[2]) return parts[2];
268
+ return scalar(text, ["release_id"]) || null;
269
+ }
270
+
271
+ function iterationFor(relativePath, text) {
272
+ const parts = relativePath.split("/");
273
+ if (parts[1] === "iterations" && parts[2]) return parts[2];
274
+ return scalar(text, ["iteration_id", "current_iteration_id"]) || null;
275
+ }
276
+
277
+ function gitChanges(projectRoot) {
278
+ const result = spawnSync("git", ["status", "--porcelain=v1", "--untracked-files=all", "--", ".workflow"], {
279
+ cwd: projectRoot,
280
+ encoding: "utf8",
281
+ windowsHide: true,
282
+ });
283
+ if (result.status !== 0) return new Map();
284
+ const changes = new Map();
285
+ for (const line of result.stdout.split(/\r?\n/).filter(Boolean)) {
286
+ const code = line.slice(0, 2).trim() || "M";
287
+ let file = line.slice(3).trim();
288
+ if (file.includes(" -> ")) file = file.split(" -> ").at(-1);
289
+ file = file.replace(/^"|"$/g, "").replaceAll("\\", "/");
290
+ changes.set(file, code);
291
+ }
292
+ return changes;
293
+ }
294
+
295
+ function repositoryStatus(repositoryRoot, tagPattern) {
296
+ const run = (args) => spawnSync("git", args, {
297
+ cwd: repositoryRoot,
298
+ encoding: "utf8",
299
+ windowsHide: true,
300
+ });
301
+ const top = run(["rev-parse", "--show-toplevel"]);
302
+ if (top.status !== 0 || path.resolve(top.stdout.trim()).toLowerCase() !== path.resolve(repositoryRoot).toLowerCase()) {
303
+ return { repository: false, root: toPosix(repositoryRoot), tags: [] };
304
+ }
305
+ const branch = run(["branch", "--show-current"]);
306
+ const head = run(["rev-parse", "HEAD"]);
307
+ const status = run(["status", "--porcelain=v1", "--untracked-files=all"]);
308
+ const upstream = run(["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{upstream}"]);
309
+ let ahead = null;
310
+ let behind = null;
311
+ if (upstream.status === 0) {
312
+ const divergence = run(["rev-list", "--left-right", "--count", "HEAD...@{upstream}"]);
313
+ if (divergence.status === 0) [ahead, behind] = divergence.stdout.trim().split(/\s+/).map(Number);
314
+ }
315
+ const dirtyPaths = status.stdout.split(/\r?\n/).filter(Boolean).map((line) => toPosix(line.slice(3)));
316
+ const tags = run(["tag", "--list", tagPattern, "--sort=version:refname"]);
317
+ return {
318
+ repository: true,
319
+ root: toPosix(repositoryRoot),
320
+ branch: branch.stdout.trim() || "main",
321
+ head: head.status === 0 ? head.stdout.trim() : null,
322
+ dirty_paths: dirtyPaths,
323
+ upstream: upstream.status === 0 ? upstream.stdout.trim() : null,
324
+ ahead,
325
+ behind,
326
+ sync_status: dirtyPaths.length ? "uncommitted"
327
+ : ahead > 0 ? "unpushed"
328
+ : behind > 0 ? "behind"
329
+ : upstream.status === 0 ? "synchronized" : "no-upstream",
330
+ tags: tags.status === 0 ? tags.stdout.split(/\r?\n/).filter(Boolean) : [],
331
+ };
332
+ }
333
+
334
+ function workspaceStatus(projectRoot) {
335
+ const configFile = path.join(projectRoot, ".workflow", "config", "workspace.yaml");
336
+ let configuration = null;
337
+ try {
338
+ configuration = yaml.parse(fs.readFileSync(configFile, "utf8"));
339
+ } catch {
340
+ configuration = { code_repository: { repo_id: "app", path: "code" } };
341
+ }
342
+ const codePath = configuration?.code_repository?.path || "code";
343
+ const codeRoot = path.resolve(projectRoot, codePath);
344
+ const relative = path.relative(projectRoot, codeRoot);
345
+ if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) {
346
+ throw new Error(`Invalid code repository path in workspace configuration: ${codePath}`);
347
+ }
348
+ const tasksRoot = path.join(projectRoot, ".workflow", "control", "tasks");
349
+ const checkpointsRoot = path.join(projectRoot, ".workflow", "control", "checkpoints");
350
+ const taskFiles = fs.existsSync(tasksRoot)
351
+ ? fs.readdirSync(tasksRoot).filter((name) => /\.(json|ya?ml)$/i.test(name)) : [];
352
+ const checkpointFiles = [];
353
+ const visit = (directory) => {
354
+ if (!fs.existsSync(directory)) return;
355
+ for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
356
+ const absolute = path.join(directory, entry.name);
357
+ if (entry.isDirectory()) visit(absolute);
358
+ else if (entry.isFile() && /\.(json|ya?ml)$/i.test(entry.name)) checkpointFiles.push(absolute);
359
+ }
360
+ };
361
+ visit(checkpointsRoot);
362
+ return {
363
+ configuration,
364
+ workflow_git: repositoryStatus(projectRoot, "workflow-ready/*"),
365
+ code_git: repositoryStatus(codeRoot, "version-ready/*"),
366
+ shared_state: {
367
+ task_count: taskFiles.length,
368
+ checkpoint_count: checkpointFiles.length,
369
+ latest_checkpoint: checkpointFiles
370
+ .map((file) => ({ path: toPosix(path.relative(projectRoot, file)), mtime: fs.statSync(file).mtimeMs }))
371
+ .sort((left, right) => right.mtime - left.mtime)[0]?.path || null,
372
+ },
373
+ };
374
+ }
375
+
376
+ function walkWorkflow(workflowRoot) {
377
+ const files = [];
378
+ let truncated = false;
379
+ const visit = (directory) => {
380
+ if (truncated) return;
381
+ for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
382
+ if (files.length >= MAX_SCAN_FILES) {
383
+ truncated = true;
384
+ break;
385
+ }
386
+ const absolute = path.join(directory, entry.name);
387
+ const relativeParts = path.relative(workflowRoot, absolute).split(path.sep);
388
+ if (entry.isSymbolicLink()) continue;
389
+ if (entry.isDirectory() && (
390
+ relativeParts[0] === "tools"
391
+ || (relativeParts[0] === "delivery" && relativeParts[1] === "runtime")
392
+ )) continue;
393
+ if (entry.isDirectory()) visit(absolute);
394
+ else if (entry.isFile() && entry.name !== ".gitkeep") files.push(absolute);
395
+ }
396
+ };
397
+ if (fs.existsSync(workflowRoot)) visit(workflowRoot);
398
+ return { files, truncated };
399
+ }
400
+
401
+ function referencesFor(relativePath, text, knownPaths) {
402
+ if (!text) return [];
403
+ const references = new Set();
404
+ const tokens = text.match(/(?:\.workflow\/|(?:\.\.\/|\.\/)+)[A-Za-z0-9._/@-]+/g) || [];
405
+ for (const token of tokens) {
406
+ const cleaned = token.replace(/[),.;:'"]+$/g, "");
407
+ const resolved = cleaned.startsWith(".workflow/")
408
+ ? cleaned
409
+ : toPosix(path.normalize(path.join(path.dirname(relativePath), cleaned)));
410
+ if (knownPaths.has(resolved)) references.add(resolved);
411
+ }
412
+ return [...references].sort();
413
+ }
414
+
415
+ function artifactRegistry(workflowRoot) {
416
+ const registryFile = path.join(workflowRoot, "delivery", "artifact-registry.yaml");
417
+ if (!fs.existsSync(registryFile)) return new Map();
418
+ try {
419
+ const parsed = yaml.parse(fs.readFileSync(registryFile, "utf8"));
420
+ const entries = Array.isArray(parsed?.artifacts) ? parsed.artifacts : [];
421
+ return new Map(entries
422
+ .filter((artifact) => artifact && typeof artifact.path === "string")
423
+ .map((artifact) => [artifact.path.replaceAll("\\", "/"), artifact]));
424
+ } catch {
425
+ return new Map();
426
+ }
427
+ }
428
+
429
+ function evolutionSummary(workflowRoot) {
430
+ const indexFile = path.join(workflowRoot, "evolution", "index.yaml");
431
+ const empty = {
432
+ schema_version: 1, revision: 0, feedback_count: 0, proposal_count: 0,
433
+ trial_count: 0, active_count: 0, high_risk_count: 0, rollback_count: 0,
434
+ hook_result_count: 0, updated_at: null,
435
+ };
436
+ if (!fs.existsSync(indexFile)) return empty;
437
+ try {
438
+ const index = yaml.parse(fs.readFileSync(indexFile, "utf8")) || {};
439
+ const extensions = Array.isArray(index.extensions) ? index.extensions : [];
440
+ const hookRoot = path.join(workflowRoot, "evolution", "experiments", "hook-results");
441
+ return {
442
+ schema_version: index.schema_version || 1,
443
+ revision: index.revision || 0,
444
+ feedback_count: Array.isArray(index.feedback) ? index.feedback.length : 0,
445
+ proposal_count: Array.isArray(index.proposals) ? index.proposals.filter((item) => item.status === "proposed").length : 0,
446
+ trial_count: extensions.filter((item) => item.state === "trial").length,
447
+ active_count: extensions.filter((item) => item.state === "active").length,
448
+ high_risk_count: extensions.filter((item) => item.effective_risk === "high" && ["trial", "active"].includes(item.state)).length,
449
+ rollback_count: extensions.filter((item) => item.state === "rolled-back").length,
450
+ hook_result_count: fs.existsSync(hookRoot)
451
+ ? fs.readdirSync(hookRoot, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name !== ".gitkeep").length
452
+ : 0,
453
+ updated_at: index.updated_at || null,
454
+ };
455
+ } catch {
456
+ return { ...empty, invalid: true };
457
+ }
458
+ }
459
+
460
+ export function buildCatalog(projectRoot) {
461
+ const workflowRoot = path.join(projectRoot, ".workflow");
462
+ const { files, truncated } = walkWorkflow(workflowRoot);
463
+ const changes = gitChanges(projectRoot);
464
+ const registeredArtifacts = artifactRegistry(workflowRoot);
465
+ const prepared = files.map((absolute) => {
466
+ const relativePath = toPosix(path.relative(projectRoot, absolute));
467
+ const extension = path.extname(absolute).toLowerCase();
468
+ const stat = fs.statSync(absolute);
469
+ const text = TEXT_EXTENSIONS.has(extension) ? readText(absolute) : null;
470
+ const data = structuredData(text, extension);
471
+ const changedCode = changes.get(relativePath) || null;
472
+ const registered = registeredArtifacts.get(relativePath);
473
+ const declaredStatus = registered?.status
474
+ || scalar(text, ["status", "decision", "version_status", "state"]);
475
+ const status = normalizeStatus(declaredStatus, Boolean(changedCode));
476
+ return {
477
+ path: relativePath,
478
+ title: titleFor(relativePath, text),
479
+ file_name: path.basename(relativePath),
480
+ extension,
481
+ type: fileType(extension),
482
+ node_id: normalizeNode(registered?.node) || nodeFor(relativePath, text),
483
+ iteration_id: iterationFor(relativePath, text),
484
+ release_id: releaseFor(relativePath, text),
485
+ release_mode: data?.mode || scalar(text, ["mode"]) || null,
486
+ target_version_id: data?.target_version || data?.target_version_id
487
+ || scalar(text, ["target_version", "target_version_id"]) || null,
488
+ current_production_version: data?.current_production_version
489
+ || scalar(text, ["current_production_version"]) || null,
490
+ newly_included_versions: Array.isArray(data?.newly_included_versions)
491
+ ? data.newly_included_versions.map(String) : [],
492
+ status,
493
+ declared_status: declaredStatus,
494
+ version: registered?.version
495
+ || scalar(text, ["version", "artifact_version", "schema_version"]) || null,
496
+ artifact_id: registered?.artifact_id || registered?.id
497
+ || scalar(text, ["artifact_id", "baseline_id", "release_id", "deployment_id"]) || null,
498
+ standards: [...new Set(text?.match(/STD-[A-Z0-9-]+/g) || [])].sort(),
499
+ modified_at: stat.mtime.toISOString(),
500
+ size_bytes: stat.size,
501
+ checksum: crypto.createHash("sha256").update(fs.readFileSync(absolute)).digest("hex"),
502
+ git_status: changedCode,
503
+ preview_kind: TEXT_EXTENSIONS.has(extension) && stat.size <= MAX_PREVIEW_BYTES
504
+ ? "text"
505
+ : IMAGE_EXTENSIONS.has(extension) ? "image" : "none",
506
+ previewable: (TEXT_EXTENSIONS.has(extension) && stat.size <= MAX_PREVIEW_BYTES)
507
+ || IMAGE_EXTENSIONS.has(extension),
508
+ _text: text,
509
+ };
510
+ });
511
+ const knownPaths = new Set(prepared.map((item) => item.path));
512
+ const inbound = new Map(prepared.map((item) => [item.path, []]));
513
+ for (const item of prepared) {
514
+ item.outbound = referencesFor(item.path, item._text, knownPaths);
515
+ for (const target of item.outbound) inbound.get(target)?.push(item.path);
516
+ }
517
+ for (const item of prepared) {
518
+ item.inbound = (inbound.get(item.path) || []).sort();
519
+ delete item._text;
520
+ }
521
+ prepared.sort((left, right) =>
522
+ left.node_id.localeCompare(right.node_id)
523
+ || right.modified_at.localeCompare(left.modified_at)
524
+ || left.path.localeCompare(right.path));
525
+
526
+ const projectText = fs.existsSync(path.join(workflowRoot, "delivery", "project.yaml"))
527
+ ? readText(path.join(workflowRoot, "delivery", "project.yaml"))
528
+ : "";
529
+ const workflowText = fs.existsSync(path.join(workflowRoot, "delivery", "workflow-state.yaml"))
530
+ ? readText(path.join(workflowRoot, "delivery", "workflow-state.yaml"))
531
+ : "";
532
+ const formalState = structuredData(workflowText, ".yaml") || {};
533
+ const currentIteration = formalState.development_state?.scope_id
534
+ || scalar(workflowText, ["iteration_id"])
535
+ || scalar(projectText, ["current_iteration_id"])
536
+ || prepared.map((item) => item.iteration_id).filter(Boolean).sort().at(-1)
537
+ || null;
538
+ const iterations = [...new Set([
539
+ ...(currentIteration ? [currentIteration] : []),
540
+ ...prepared.map((item) => item.iteration_id).filter(Boolean),
541
+ ])].sort().reverse();
542
+ const releaseNodeIds = new Set(RELEASE_NODE_DEFINITIONS.map((node) => node.id));
543
+ const releaseDocuments = prepared.filter((item) => item.release_id && releaseNodeIds.has(item.node_id))
544
+ .sort((left, right) => right.modified_at.localeCompare(left.modified_at));
545
+ const activeReleaseId = formalState.release_state?.scope_id
546
+ || releaseDocuments[0]?.release_id
547
+ || null;
548
+ const activeReleaseDocuments = releaseDocuments.filter((item) => item.release_id === activeReleaseId);
549
+ const releaseSelection = activeReleaseDocuments.find((item) => item.node_id === "R01-version-selection")
550
+ || activeReleaseDocuments.find((item) => item.target_version_id)
551
+ || null;
552
+ const releases = [...new Set(releaseDocuments.map((item) => item.release_id))];
553
+ const nodes = [
554
+ { id: "governance", label: "流程治理", sequence: -2, group: "governance" },
555
+ { id: "standards", label: "项目规范", sequence: -1, group: "governance" },
556
+ { id: "evolution", label: "自进化", sequence: 0, group: "governance" },
557
+ ...NODE_DEFINITIONS,
558
+ ].map((node) => {
559
+ const documents = prepared.filter((item) => {
560
+ if (item.node_id !== node.id) return false;
561
+ if (node.group === "development") {
562
+ return !currentIteration || !item.iteration_id || item.iteration_id === currentIteration;
563
+ }
564
+ if (node.group === "release") return !activeReleaseId || item.release_id === activeReleaseId;
565
+ return true;
566
+ });
567
+ const tones = documents.map((item) => item.status.tone);
568
+ const tone = tones.includes("danger") ? "danger"
569
+ : tones.includes("warning") ? "warning"
570
+ : tones.includes("success") ? "success" : "muted";
571
+ return { ...node, document_count: documents.length, tone };
572
+ });
573
+ const counts = Object.fromEntries(["success", "warning", "danger", "neutral", "muted"]
574
+ .map((tone) => [tone, prepared.filter((item) => item.status.tone === tone).length]));
575
+ const workspace = workspaceStatus(projectRoot);
576
+ const traceability = prepared.filter((item) => item.node_id === "12-version-closeout")
577
+ .map((item) => ({
578
+ version_id: item.iteration_id || item.artifact_id,
579
+ manifest_path: item.path,
580
+ manifest_sha256: item.checksum,
581
+ code_tag: scalar(fs.readFileSync(path.join(projectRoot, item.path), "utf8"), ["code_tag", "version_ready_tag"]),
582
+ workflow_tag: scalar(fs.readFileSync(path.join(projectRoot, item.path), "utf8"), ["workflow_tag", "workflow_ready_tag"]),
583
+ }))
584
+ .filter((item) => item.code_tag || item.workflow_tag);
585
+ return {
586
+ schema_version: 1,
587
+ scanned_at: new Date().toISOString(),
588
+ project: {
589
+ id: scalar(projectText, ["project_id"]) || path.basename(projectRoot),
590
+ name: scalar(projectText, ["name", "project_name"]) || path.basename(projectRoot),
591
+ root: toPosix(projectRoot),
592
+ workflow_root: ".workflow",
593
+ current_iteration_id: currentIteration,
594
+ production_version_id: scalar(workflowText, ["production_version_id"])
595
+ || scalar(projectText, ["production_version_id"]) || null,
596
+ production_release_id: scalar(workflowText, ["production_release_id"])
597
+ || scalar(projectText, ["production_release_id"]) || null,
598
+ latest_line_version_id: scalar(workflowText, ["latest_line_version_id"])
599
+ || scalar(projectText, ["latest_line_version_id"]) || null,
600
+ release_ready_count: new Set(prepared
601
+ .filter((item) => item.node_id === "12-version-closeout" && item.status.key === "release-ready")
602
+ .map((item) => item.iteration_id || item.artifact_id || item.path)).size,
603
+ active_release_id: activeReleaseId,
604
+ selected_target_version: releaseSelection?.target_version_id || null,
605
+ release_mode: releaseSelection?.release_mode || null,
606
+ newly_included_versions: releaseSelection?.newly_included_versions || [],
607
+ },
608
+ iterations,
609
+ releases,
610
+ nodes,
611
+ documents: prepared,
612
+ counts,
613
+ repositories: {
614
+ workflow: workspace.workflow_git,
615
+ code: workspace.code_git,
616
+ code_repo_id: workspace.configuration?.code_repository?.repo_id || "app",
617
+ },
618
+ shared_state: workspace.shared_state,
619
+ traceability,
620
+ evolution: evolutionSummary(workflowRoot),
621
+ truncated,
622
+ };
623
+ }
624
+
625
+ function sendJson(response, status, value) {
626
+ const body = `${JSON.stringify(value)}\n`;
627
+ response.writeHead(status, {
628
+ "Content-Type": "application/json; charset=utf-8",
629
+ "Cache-Control": "no-store",
630
+ "Content-Length": Buffer.byteLength(body),
631
+ });
632
+ response.end(body);
633
+ }
634
+
635
+ function serveStatic(response, pathname) {
636
+ const relative = pathname === "/" ? "index.html" : pathname.slice(1);
637
+ const file = path.resolve(publicRoot, relative);
638
+ if (!isInside(file, publicRoot) || !fs.existsSync(file) || !fs.statSync(file).isFile()) {
639
+ sendJson(response, 404, { error: "Not found" });
640
+ return;
641
+ }
642
+ const types = {
643
+ ".html": "text/html; charset=utf-8",
644
+ ".css": "text/css; charset=utf-8",
645
+ ".js": "text/javascript; charset=utf-8",
646
+ ".png": "image/png",
647
+ ".svg": "image/svg+xml",
648
+ };
649
+ const body = fs.readFileSync(file);
650
+ response.writeHead(200, {
651
+ "Content-Type": types[path.extname(file)] || "application/octet-stream",
652
+ "Content-Length": body.length,
653
+ "Cache-Control": "no-cache",
654
+ "Content-Security-Policy": "default-src 'self'; img-src 'self' data:; style-src 'self'; script-src 'self'; connect-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'",
655
+ "X-Content-Type-Options": "nosniff",
656
+ "Referrer-Policy": "no-referrer",
657
+ });
658
+ response.end(body);
659
+ }
660
+
661
+ export function createViewerServer(projectRoot) {
662
+ const root = path.resolve(projectRoot);
663
+ const workflowRoot = path.join(root, ".workflow");
664
+ return http.createServer((request, response) => {
665
+ try {
666
+ const url = new URL(request.url || "/", "http://localhost");
667
+ if (request.method !== "GET") {
668
+ sendJson(response, 405, { error: "Method not allowed" });
669
+ return;
670
+ }
671
+ if (url.pathname === "/api/health") {
672
+ sendJson(response, 200, { ok: true, project_root: toPosix(root) });
673
+ return;
674
+ }
675
+ if (url.pathname === "/api/catalog") {
676
+ sendJson(response, 200, buildCatalog(root));
677
+ return;
678
+ }
679
+ if (url.pathname === "/api/document") {
680
+ const requested = url.searchParams.get("path") || "";
681
+ const absolute = path.resolve(root, requested);
682
+ if (!requested.startsWith(".workflow/") || !isInside(absolute, workflowRoot)) {
683
+ sendJson(response, 403, { error: "Only .workflow documents may be read" });
684
+ return;
685
+ }
686
+ if (!fs.existsSync(absolute) || !fs.statSync(absolute).isFile()) {
687
+ sendJson(response, 404, { error: "Document not found" });
688
+ return;
689
+ }
690
+ const stat = fs.statSync(absolute);
691
+ if (stat.size > MAX_PREVIEW_BYTES) {
692
+ sendJson(response, 413, { error: "Document is larger than the 2 MiB preview limit" });
693
+ return;
694
+ }
695
+ const extension = path.extname(absolute).toLowerCase();
696
+ if (!TEXT_EXTENSIONS.has(extension)) {
697
+ sendJson(response, 415, { error: "This file type has no text preview" });
698
+ return;
699
+ }
700
+ sendJson(response, 200, {
701
+ path: requested,
702
+ extension,
703
+ content: fs.readFileSync(absolute, "utf8"),
704
+ });
705
+ return;
706
+ }
707
+ if (url.pathname === "/api/file") {
708
+ const requested = url.searchParams.get("path") || "";
709
+ const absolute = path.resolve(root, requested);
710
+ if (!requested.startsWith(".workflow/") || !isInside(absolute, workflowRoot)) {
711
+ sendJson(response, 403, { error: "Only .workflow files may be read" });
712
+ return;
713
+ }
714
+ if (!fs.existsSync(absolute) || !fs.statSync(absolute).isFile()) {
715
+ sendJson(response, 404, { error: "File not found" });
716
+ return;
717
+ }
718
+ const extension = path.extname(absolute).toLowerCase();
719
+ if (!IMAGE_EXTENSIONS.has(extension)) {
720
+ sendJson(response, 415, { error: "Only image previews are served by this endpoint" });
721
+ return;
722
+ }
723
+ const contentTypes = {
724
+ ".gif": "image/gif",
725
+ ".jpeg": "image/jpeg",
726
+ ".jpg": "image/jpeg",
727
+ ".png": "image/png",
728
+ ".webp": "image/webp",
729
+ };
730
+ const body = fs.readFileSync(absolute);
731
+ response.writeHead(200, {
732
+ "Content-Type": contentTypes[extension],
733
+ "Content-Length": body.length,
734
+ "Cache-Control": "no-store",
735
+ "X-Content-Type-Options": "nosniff",
736
+ });
737
+ response.end(body);
738
+ return;
739
+ }
740
+ serveStatic(response, url.pathname);
741
+ } catch (error) {
742
+ sendJson(response, 500, { error: error.message });
743
+ }
744
+ });
745
+ }
746
+
747
+ function openBrowser(url) {
748
+ const command = process.platform === "win32"
749
+ ? ["cmd", ["/c", "start", "", url]]
750
+ : process.platform === "darwin"
751
+ ? ["open", [url]]
752
+ : ["xdg-open", [url]];
753
+ try {
754
+ spawn(command[0], command[1], {
755
+ detached: true,
756
+ stdio: "ignore",
757
+ windowsHide: true,
758
+ }).unref();
759
+ } catch {
760
+ // The printed URL remains usable when no desktop browser opener is available.
761
+ }
762
+ }
763
+
764
+ if (path.resolve(process.argv[1] || "") === path.resolve(fileURLToPath(import.meta.url))) {
765
+ try {
766
+ const options = parseArguments(process.argv.slice(2));
767
+ if (!fs.existsSync(options.projectRoot) || !fs.statSync(options.projectRoot).isDirectory()) {
768
+ throw new Error(`Project directory does not exist: ${options.projectRoot}`);
769
+ }
770
+ const server = createViewerServer(options.projectRoot);
771
+ server.listen(options.port, options.host, () => {
772
+ const url = `http://${options.host}:${options.port}`;
773
+ process.stdout.write(`AI Delivery Viewer\nProject: ${options.projectRoot}\nURL: ${url}\nPress Ctrl+C to stop.\n`);
774
+ if (options.open) openBrowser(url);
775
+ });
776
+ } catch (error) {
777
+ process.stderr.write(`ai-delivery-viewer: ${error.message}\n`);
778
+ process.exitCode = 1;
779
+ }
780
+ }