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,546 @@
1
+ const state = {
2
+ catalog: null,
3
+ documents: [],
4
+ filtered: [],
5
+ selectedPath: null,
6
+ selectedNode: "",
7
+ preview: null,
8
+ previewMode: "rendered",
9
+ };
10
+
11
+ const elements = Object.fromEntries([
12
+ "project-name", "iteration-select", "search-input", "refresh-button", "scan-time",
13
+ "node-total", "node-list", "iteration-title", "progress-caption", "progress-value",
14
+ "progress-rail", "release-title", "release-caption", "release-value", "release-progress-rail",
15
+ "production-version", "line-version", "ready-count", "release-target", "release-mode",
16
+ "workflow-git", "workflow-sync", "code-git", "code-dirty", "shared-checkpoints",
17
+ "latest-checkpoint", "traceability-count",
18
+ "evolution-revision", "evolution-feedback", "evolution-proposals", "evolution-trials",
19
+ "evolution-active", "evolution-high-risk", "evolution-rollbacks", "evolution-hooks",
20
+ "status-filter", "type-filter", "node-filter", "changed-filter",
21
+ "reset-filters", "filter-count", "document-rows", "empty-state", "inspector-empty",
22
+ "inspector-content", "detail-status", "detail-title", "detail-path", "close-inspector",
23
+ "detail-metadata", "inbound-count", "inbound-list", "outbound-count", "outbound-list",
24
+ "detail-checksum", "copy-checksum", "standards-list", "preview-loading",
25
+ "preview-content", "project-root", "success-count", "warning-count", "danger-count",
26
+ "document-count", "toast",
27
+ ].map((id) => [id, document.getElementById(id)]));
28
+
29
+ const nodeNames = new Map();
30
+ let toastTimer;
31
+
32
+ function escapeHtml(value) {
33
+ return String(value ?? "")
34
+ .replaceAll("&", "&")
35
+ .replaceAll("<", "&lt;")
36
+ .replaceAll(">", "&gt;")
37
+ .replaceAll('"', "&quot;")
38
+ .replaceAll("'", "&#039;");
39
+ }
40
+
41
+ function icon(name) {
42
+ const paths = {
43
+ file: '<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8Z"></path><path d="M14 2v6h6"></path><path d="M8 13h8M8 17h5"></path>',
44
+ link: '<path d="M10 13a5 5 0 0 0 7.5.5l2-2a5 5 0 0 0-7-7l-1.1 1.1"></path><path d="M14 11a5 5 0 0 0-7.5-.5l-2 2a5 5 0 0 0 7 7l1.1-1.1"></path>',
45
+ };
46
+ return `<svg viewBox="0 0 24 24" aria-hidden="true">${paths[name]}</svg>`;
47
+ }
48
+
49
+ function formatDate(value, short = false) {
50
+ if (!value) return "--";
51
+ const date = new Date(value);
52
+ if (Number.isNaN(date.getTime())) return value;
53
+ return new Intl.DateTimeFormat("zh-CN", short
54
+ ? { month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit" }
55
+ : { year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit" })
56
+ .format(date);
57
+ }
58
+
59
+ function formatBytes(value) {
60
+ if (value < 1024) return `${value} B`;
61
+ if (value < 1024 * 1024) return `${(value / 1024).toFixed(1)} KiB`;
62
+ return `${(value / 1024 / 1024).toFixed(1)} MiB`;
63
+ }
64
+
65
+ function showToast(message) {
66
+ clearTimeout(toastTimer);
67
+ elements.toast.textContent = message;
68
+ elements.toast.classList.add("visible");
69
+ toastTimer = setTimeout(() => elements.toast.classList.remove("visible"), 1800);
70
+ }
71
+
72
+ function uniqueValues(key) {
73
+ return [...new Set(state.documents.map((document) => document[key]).filter(Boolean))].sort();
74
+ }
75
+
76
+ function populateSelect(select, values, labelFor = (value) => value) {
77
+ const current = select.value;
78
+ select.replaceChildren(new Option("全部", ""));
79
+ for (const value of values) select.add(new Option(labelFor(value), value));
80
+ select.value = values.includes(current) ? current : "";
81
+ }
82
+
83
+ function currentIteration() {
84
+ return elements["iteration-select"].value || state.catalog?.project.current_iteration_id || "";
85
+ }
86
+
87
+ function documentsForIteration() {
88
+ const iteration = currentIteration();
89
+ return state.documents.filter((document) => !iteration || !document.iteration_id || document.iteration_id === iteration);
90
+ }
91
+
92
+ function renderNodes() {
93
+ elements["node-list"].replaceChildren();
94
+ const iterationDocuments = documentsForIteration();
95
+ let lastGroup = null;
96
+ let visibleCount = 0;
97
+ for (const node of state.catalog.nodes) {
98
+ nodeNames.set(node.id, node.label);
99
+ const count = iterationDocuments.filter((document) => document.node_id === node.id).length;
100
+ if (node.group === "legacy" && count === 0) continue;
101
+ if (node.group !== lastGroup) {
102
+ const heading = document.createElement("div");
103
+ heading.className = "node-group";
104
+ heading.textContent = ({
105
+ governance: "治理",
106
+ development: "研发流程",
107
+ release: "发布流程",
108
+ legacy: "历史兼容",
109
+ })[node.group] || node.group;
110
+ elements["node-list"].append(heading);
111
+ lastGroup = node.group;
112
+ }
113
+ const button = document.createElement("button");
114
+ button.type = "button";
115
+ button.className = `node-item${state.selectedNode === node.id ? " active" : ""}`;
116
+ button.dataset.node = node.id;
117
+ const sequence = node.sequence >= 0 ? String(node.sequence).padStart(2, "0") : "·";
118
+ button.innerHTML = `
119
+ <span class="node-sequence">${sequence}</span>
120
+ <span class="node-label">${escapeHtml(node.label)}</span>
121
+ <span class="node-count ${node.tone}">${count}</span>`;
122
+ button.addEventListener("click", () => {
123
+ state.selectedNode = state.selectedNode === node.id ? "" : node.id;
124
+ elements["node-filter"].value = state.selectedNode;
125
+ renderNodes();
126
+ applyFilters();
127
+ });
128
+ elements["node-list"].append(button);
129
+ visibleCount += 1;
130
+ }
131
+ elements["node-total"].textContent = `${visibleCount} 个节点`;
132
+ }
133
+
134
+ function renderTrack(group, documents, rail, caption, value) {
135
+ const nodes = state.catalog.nodes.filter((node) => node.group === group);
136
+ const completed = nodes.filter((node) => {
137
+ const nodeDocuments = documents.filter((item) => item.node_id === node.id);
138
+ const tones = nodeDocuments.map((item) => item.status.tone);
139
+ return nodeDocuments.length && tones.includes("success")
140
+ && !tones.includes("warning") && !tones.includes("danger");
141
+ }).length;
142
+ const percent = nodes.length ? Math.round((completed / nodes.length) * 100) : 0;
143
+ caption.textContent = `${completed}/${nodes.length} 个节点完成,${documents.length} 项物料`;
144
+ value.textContent = `${percent}%`;
145
+ rail.replaceChildren();
146
+ rail.style.setProperty("--node-count", nodes.length);
147
+ for (const node of nodes) {
148
+ const nodeDocuments = documents.filter((item) => item.node_id === node.id);
149
+ const tones = nodeDocuments.map((item) => item.status.tone);
150
+ const tone = tones.includes("danger") ? "danger"
151
+ : tones.includes("warning") ? "warning"
152
+ : nodeDocuments.length && tones.includes("success") ? "success" : "muted";
153
+ const item = document.createElement("div");
154
+ item.className = `progress-node ${tone}`;
155
+ item.title = `${node.label}:${nodeDocuments.length} 项物料`;
156
+ item.innerHTML = `<i></i><span>${escapeHtml(node.label)}</span>`;
157
+ rail.append(item);
158
+ }
159
+ }
160
+
161
+ function renderProgress() {
162
+ const developmentDocuments = documentsForIteration().filter((item) => {
163
+ const node = state.catalog.nodes.find((candidate) => candidate.id === item.node_id);
164
+ return node?.group === "development";
165
+ });
166
+ const activeRelease = state.catalog.project.active_release_id;
167
+ const releaseDocuments = state.documents.filter((item) => item.release_id === activeRelease);
168
+ const iteration = currentIteration() || "未指定版本";
169
+ elements["iteration-title"].textContent = `${iteration} 研发流程`;
170
+ elements["release-title"].textContent = activeRelease ? `${activeRelease} 发布流程` : "发布流程";
171
+ renderTrack("development", developmentDocuments, elements["progress-rail"], elements["progress-caption"], elements["progress-value"]);
172
+ renderTrack("release", releaseDocuments, elements["release-progress-rail"], elements["release-caption"], elements["release-value"]);
173
+ if (!activeRelease) elements["release-caption"].textContent = "尚未启动独立发布";
174
+ }
175
+
176
+ function applyFilters() {
177
+ const query = elements["search-input"].value.trim().toLowerCase();
178
+ const status = elements["status-filter"].value;
179
+ const type = elements["type-filter"].value;
180
+ const node = elements["node-filter"].value;
181
+ const changed = elements["changed-filter"].checked;
182
+ const iteration = currentIteration();
183
+ state.selectedNode = node;
184
+ state.filtered = state.documents.filter((document) => {
185
+ const haystack = [
186
+ document.title, document.path, document.artifact_id, document.type,
187
+ nodeNames.get(document.node_id), ...document.standards,
188
+ ].join(" ").toLowerCase();
189
+ return (!iteration || !document.iteration_id || document.iteration_id === iteration)
190
+ && (!query || haystack.includes(query))
191
+ && (!status || document.status.key === status)
192
+ && (!type || document.type === type)
193
+ && (!node || document.node_id === node)
194
+ && (!changed || Boolean(document.git_status));
195
+ });
196
+ renderTable();
197
+ renderNodes();
198
+ renderProgress();
199
+ }
200
+
201
+ function renderTable() {
202
+ elements["document-rows"].replaceChildren();
203
+ elements["filter-count"].textContent = `${state.filtered.length} 项物料`;
204
+ elements["empty-state"].hidden = state.filtered.length > 0;
205
+ for (const item of state.filtered) {
206
+ const row = document.createElement("tr");
207
+ row.tabIndex = 0;
208
+ row.className = state.selectedPath === item.path ? "selected" : "";
209
+ row.dataset.path = item.path;
210
+ row.innerHTML = `
211
+ <td>
212
+ <div class="document-cell">
213
+ <span class="file-icon">${icon("file")}</span>
214
+ <span class="document-names">
215
+ <strong>${escapeHtml(item.title)}</strong>
216
+ <span>${escapeHtml(item.path)}</span>
217
+ </span>
218
+ </div>
219
+ </td>
220
+ <td>${escapeHtml(nodeNames.get(item.node_id) || item.node_id)}</td>
221
+ <td>${escapeHtml(item.type)}</td>
222
+ <td><span class="status-label ${item.status.tone}">${escapeHtml(item.status.label)}</span></td>
223
+ <td>${escapeHtml(item.version || "--")}</td>
224
+ <td>${escapeHtml(formatDate(item.modified_at, true))}</td>`;
225
+ const select = () => selectDocument(item.path);
226
+ row.addEventListener("click", select);
227
+ row.addEventListener("keydown", (event) => {
228
+ if (event.key === "Enter" || event.key === " ") select();
229
+ });
230
+ elements["document-rows"].append(row);
231
+ }
232
+ }
233
+
234
+ function relationshipList(container, paths) {
235
+ container.replaceChildren();
236
+ if (!paths.length) {
237
+ const empty = document.createElement("span");
238
+ empty.className = "relationship-empty";
239
+ empty.textContent = "暂无可解析的文件引用";
240
+ container.append(empty);
241
+ return;
242
+ }
243
+ for (const targetPath of paths.slice(0, 8)) {
244
+ const target = state.documents.find((document) => document.path === targetPath);
245
+ const button = document.createElement("button");
246
+ button.type = "button";
247
+ button.className = "relationship-link";
248
+ button.innerHTML = `${icon("link")}<span>${escapeHtml(target?.title || targetPath)}</span>`;
249
+ button.title = targetPath;
250
+ button.addEventListener("click", () => selectDocument(targetPath));
251
+ container.append(button);
252
+ }
253
+ }
254
+
255
+ function metadataMarkup(item) {
256
+ const values = [
257
+ ["节点", nodeNames.get(item.node_id) || item.node_id],
258
+ ["类型", item.type],
259
+ ["版本", item.version || "--"],
260
+ ["物料 ID", item.artifact_id || "--"],
261
+ ["更新时间", formatDate(item.modified_at, true)],
262
+ ["大小", formatBytes(item.size_bytes)],
263
+ ["Git 状态", item.git_status || "已同步"],
264
+ ["迭代", item.iteration_id || "长期基线"],
265
+ ];
266
+ return values.map(([term, value]) => `<dt>${escapeHtml(term)}</dt><dd title="${escapeHtml(value)}">${escapeHtml(value)}</dd>`).join("");
267
+ }
268
+
269
+ async function selectDocument(pathValue) {
270
+ const item = state.documents.find((document) => document.path === pathValue);
271
+ if (!item) return;
272
+ state.selectedPath = item.path;
273
+ state.preview = null;
274
+ elements["inspector-empty"].hidden = true;
275
+ elements["inspector-content"].hidden = false;
276
+ document.querySelector(".inspector").classList.add("open");
277
+ elements["detail-status"].className = `status-label ${item.status.tone}`;
278
+ elements["detail-status"].textContent = item.status.label;
279
+ elements["detail-title"].textContent = item.title;
280
+ elements["detail-path"].textContent = item.path;
281
+ elements["detail-metadata"].innerHTML = metadataMarkup(item);
282
+ elements["inbound-count"].textContent = item.inbound.length;
283
+ elements["outbound-count"].textContent = item.outbound.length;
284
+ relationshipList(elements["inbound-list"], item.inbound);
285
+ relationshipList(elements["outbound-list"], item.outbound);
286
+ elements["detail-checksum"].textContent = item.checksum;
287
+ elements["detail-checksum"].title = item.checksum;
288
+ elements["standards-list"].replaceChildren();
289
+ for (const standard of item.standards) {
290
+ const tag = document.createElement("span");
291
+ tag.className = "standard-tag";
292
+ tag.textContent = standard;
293
+ elements["standards-list"].append(tag);
294
+ }
295
+ if (!item.standards.length) {
296
+ const empty = document.createElement("span");
297
+ empty.className = "relationship-empty";
298
+ empty.textContent = "未在正文中识别到规范 ID";
299
+ elements["standards-list"].append(empty);
300
+ }
301
+ renderTable();
302
+ elements["preview-loading"].hidden = false;
303
+ elements["preview-loading"].textContent = item.previewable ? "正在加载预览..." : "该文件不支持文本预览";
304
+ elements["preview-content"].replaceChildren();
305
+ if (!item.previewable) return;
306
+ if (item.preview_kind === "image") {
307
+ state.preview = {
308
+ path: item.path,
309
+ extension: item.extension,
310
+ imageUrl: `/api/file?path=${encodeURIComponent(item.path)}`,
311
+ };
312
+ renderPreview();
313
+ return;
314
+ }
315
+ try {
316
+ const response = await fetch(`/api/document?path=${encodeURIComponent(item.path)}`);
317
+ const payload = await response.json();
318
+ if (!response.ok) throw new Error(payload.error || "预览加载失败");
319
+ if (state.selectedPath !== item.path) return;
320
+ state.preview = payload;
321
+ renderPreview();
322
+ } catch (error) {
323
+ elements["preview-loading"].hidden = false;
324
+ elements["preview-loading"].textContent = error.message;
325
+ }
326
+ }
327
+
328
+ function parseCsv(source) {
329
+ const rows = [];
330
+ let row = [];
331
+ let cell = "";
332
+ let quoted = false;
333
+ for (let index = 0; index < source.length; index += 1) {
334
+ const character = source[index];
335
+ if (character === '"') {
336
+ if (quoted && source[index + 1] === '"') {
337
+ cell += '"';
338
+ index += 1;
339
+ } else quoted = !quoted;
340
+ } else if (character === "," && !quoted) {
341
+ row.push(cell);
342
+ cell = "";
343
+ } else if ((character === "\n" || character === "\r") && !quoted) {
344
+ if (character === "\r" && source[index + 1] === "\n") index += 1;
345
+ row.push(cell);
346
+ if (row.some((value) => value.length)) rows.push(row);
347
+ row = [];
348
+ cell = "";
349
+ } else cell += character;
350
+ }
351
+ if (cell.length || row.length) {
352
+ row.push(cell);
353
+ rows.push(row);
354
+ }
355
+ return rows;
356
+ }
357
+
358
+ function markdown(source) {
359
+ const escaped = escapeHtml(source);
360
+ const lines = escaped.split(/\r?\n/);
361
+ const output = [];
362
+ let list = null;
363
+ const closeList = () => {
364
+ if (list) output.push(`</${list}>`);
365
+ list = null;
366
+ };
367
+ for (const line of lines) {
368
+ if (/^###\s/.test(line)) { closeList(); output.push(`<h3>${inlineMarkdown(line.slice(4))}</h3>`); }
369
+ else if (/^##\s/.test(line)) { closeList(); output.push(`<h2>${inlineMarkdown(line.slice(3))}</h2>`); }
370
+ else if (/^#\s/.test(line)) { closeList(); output.push(`<h1>${inlineMarkdown(line.slice(2))}</h1>`); }
371
+ else if (/^\s*[-*]\s+/.test(line)) {
372
+ if (list !== "ul") { closeList(); output.push("<ul>"); list = "ul"; }
373
+ output.push(`<li>${inlineMarkdown(line.replace(/^\s*[-*]\s+/, ""))}</li>`);
374
+ } else if (/^\s*\d+\.\s+/.test(line)) {
375
+ if (list !== "ol") { closeList(); output.push("<ol>"); list = "ol"; }
376
+ output.push(`<li>${inlineMarkdown(line.replace(/^\s*\d+\.\s+/, ""))}</li>`);
377
+ } else if (!line.trim()) { closeList(); }
378
+ else { closeList(); output.push(`<p>${inlineMarkdown(line)}</p>`); }
379
+ }
380
+ closeList();
381
+ return output.join("");
382
+ }
383
+
384
+ function inlineMarkdown(value) {
385
+ return value
386
+ .replace(/`([^`]+)`/g, "<code>$1</code>")
387
+ .replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>")
388
+ .replace(/\[([^\]]+)]\(([^)]+)\)/g, "$1");
389
+ }
390
+
391
+ function csvTable(source) {
392
+ const rows = parseCsv(source).slice(0, 200);
393
+ if (!rows.length) return "<p>空 CSV 文件</p>";
394
+ return `<table><thead><tr>${rows[0].map((cell) => `<th>${escapeHtml(cell)}</th>`).join("")}</tr></thead><tbody>${
395
+ rows.slice(1).map((row) => `<tr>${row.map((cell) => `<td>${escapeHtml(cell)}</td>`).join("")}</tr>`).join("")
396
+ }</tbody></table>`;
397
+ }
398
+
399
+ function renderPreview() {
400
+ if (!state.preview) return;
401
+ elements["preview-loading"].hidden = true;
402
+ const { content, extension } = state.preview;
403
+ if (state.preview.imageUrl) {
404
+ elements["preview-content"].innerHTML = `<img src="${escapeHtml(state.preview.imageUrl)}" alt="${escapeHtml(state.preview.path)}">`;
405
+ } else if (state.previewMode === "source") {
406
+ elements["preview-content"].innerHTML = `<pre>${escapeHtml(content)}</pre>`;
407
+ } else if (extension === ".md" || extension === ".mdx") {
408
+ elements["preview-content"].innerHTML = markdown(content);
409
+ } else if (extension === ".csv") {
410
+ elements["preview-content"].innerHTML = csvTable(content);
411
+ } else if (extension === ".json") {
412
+ let formatted = content;
413
+ try { formatted = JSON.stringify(JSON.parse(content), null, 2); } catch { /* Show original content. */ }
414
+ elements["preview-content"].innerHTML = `<pre>${escapeHtml(formatted)}</pre>`;
415
+ } else {
416
+ elements["preview-content"].innerHTML = `<pre>${escapeHtml(content)}</pre>`;
417
+ }
418
+ }
419
+
420
+ function closeInspector() {
421
+ state.selectedPath = null;
422
+ state.preview = null;
423
+ document.querySelector(".inspector").classList.remove("open");
424
+ elements["inspector-content"].hidden = true;
425
+ elements["inspector-empty"].hidden = false;
426
+ renderTable();
427
+ }
428
+
429
+ function initializeCatalog(catalog) {
430
+ state.catalog = catalog;
431
+ state.documents = catalog.documents;
432
+ elements["project-name"].textContent = `项目:${catalog.project.name}`;
433
+ elements["project-root"].textContent = `项目目录:${catalog.project.root}`;
434
+ elements["project-root"].title = catalog.project.root;
435
+ elements["scan-time"].textContent = formatDate(catalog.scanned_at);
436
+ elements["success-count"].textContent = catalog.counts.success || 0;
437
+ elements["warning-count"].textContent = catalog.counts.warning || 0;
438
+ elements["danger-count"].textContent = catalog.counts.danger || 0;
439
+ elements["document-count"].textContent = catalog.documents.length;
440
+ elements["production-version"].textContent = catalog.project.production_version_id || "未发布";
441
+ elements["line-version"].textContent = catalog.project.latest_line_version_id || "--";
442
+ elements["ready-count"].textContent = String(catalog.project.release_ready_count || 0);
443
+ elements["release-target"].textContent = catalog.project.selected_target_version || "未选择";
444
+ elements["release-mode"].textContent = ({
445
+ incremental: "增量发布",
446
+ "same-version-redeploy": "同版本重发",
447
+ })[catalog.project.release_mode] || "未启动";
448
+ const workflowGit = catalog.repositories?.workflow || {};
449
+ const codeGit = catalog.repositories?.code || {};
450
+ elements["workflow-git"].textContent = `${workflowGit.branch || "--"} · ${(workflowGit.head || "").slice(0, 8) || "无提交"}`;
451
+ elements["workflow-sync"].textContent = workflowGit.sync_status || "未知";
452
+ elements["workflow-sync"].classList.toggle("warning", ["uncommitted", "unpushed", "behind"].includes(workflowGit.sync_status));
453
+ elements["code-git"].textContent = `${codeGit.branch || "--"} · ${(codeGit.head || "").slice(0, 8) || "无提交"}`;
454
+ elements["code-dirty"].textContent = codeGit.dirty_paths?.length ? `${codeGit.dirty_paths.length} 个未提交路径` : "工作区干净";
455
+ elements["code-dirty"].classList.toggle("warning", Boolean(codeGit.dirty_paths?.length));
456
+ elements["shared-checkpoints"].textContent = String(catalog.shared_state?.checkpoint_count || 0);
457
+ elements["latest-checkpoint"].textContent = catalog.shared_state?.latest_checkpoint || "暂无";
458
+ elements["traceability-count"].textContent = String(catalog.traceability?.length || 0);
459
+ const evolution = catalog.evolution || {};
460
+ elements["evolution-revision"].textContent = `revision ${evolution.revision || 0}`;
461
+ elements["evolution-feedback"].textContent = String(evolution.feedback_count || 0);
462
+ elements["evolution-proposals"].textContent = String(evolution.proposal_count || 0);
463
+ elements["evolution-trials"].textContent = String(evolution.trial_count || 0);
464
+ elements["evolution-active"].textContent = String(evolution.active_count || 0);
465
+ elements["evolution-high-risk"].textContent = String(evolution.high_risk_count || 0);
466
+ elements["evolution-rollbacks"].textContent = String(evolution.rollback_count || 0);
467
+ elements["evolution-hooks"].textContent = String(evolution.hook_result_count || 0);
468
+
469
+ elements["iteration-select"].replaceChildren();
470
+ if (!catalog.iterations.length) elements["iteration-select"].add(new Option("未指定迭代", ""));
471
+ for (const iteration of catalog.iterations) {
472
+ elements["iteration-select"].add(new Option(
473
+ iteration === catalog.project.current_iteration_id ? `${iteration}(当前)` : iteration,
474
+ iteration,
475
+ ));
476
+ }
477
+ elements["iteration-select"].value = catalog.project.current_iteration_id || catalog.iterations[0] || "";
478
+
479
+ for (const node of catalog.nodes) nodeNames.set(node.id, node.label);
480
+ const statusKeys = [...new Set(state.documents.map((item) => item.status.key))].sort();
481
+ populateSelect(elements["status-filter"], statusKeys, (key) =>
482
+ state.documents.find((item) => item.status.key === key)?.status.label || key);
483
+ populateSelect(elements["type-filter"], uniqueValues("type"));
484
+ populateSelect(elements["node-filter"], catalog.nodes.map((node) => node.id), (id) => nodeNames.get(id) || id);
485
+ if (catalog.truncated) showToast("物料超过 10,000 项,当前结果已截断");
486
+ applyFilters();
487
+ }
488
+
489
+ async function loadCatalog({ preserveSelection = false } = {}) {
490
+ elements["refresh-button"].disabled = true;
491
+ elements["refresh-button"].classList.add("loading");
492
+ try {
493
+ const response = await fetch("/api/catalog", { cache: "no-store" });
494
+ const payload = await response.json();
495
+ if (!response.ok) throw new Error(payload.error || "项目扫描失败");
496
+ const selected = preserveSelection ? state.selectedPath : null;
497
+ initializeCatalog(payload);
498
+ if (selected && payload.documents.some((item) => item.path === selected)) await selectDocument(selected);
499
+ } catch (error) {
500
+ showToast(error.message);
501
+ elements["document-rows"].innerHTML = `<tr><td colspan="6">无法读取项目:${escapeHtml(error.message)}</td></tr>`;
502
+ } finally {
503
+ elements["refresh-button"].disabled = false;
504
+ elements["refresh-button"].classList.remove("loading");
505
+ }
506
+ }
507
+
508
+ elements["refresh-button"].addEventListener("click", () => loadCatalog({ preserveSelection: true }));
509
+ elements["iteration-select"].addEventListener("change", applyFilters);
510
+ elements["search-input"].addEventListener("input", applyFilters);
511
+ elements["status-filter"].addEventListener("change", applyFilters);
512
+ elements["type-filter"].addEventListener("change", applyFilters);
513
+ elements["node-filter"].addEventListener("change", applyFilters);
514
+ elements["changed-filter"].addEventListener("change", applyFilters);
515
+ elements["reset-filters"].addEventListener("click", () => {
516
+ elements["search-input"].value = "";
517
+ elements["status-filter"].value = "";
518
+ elements["type-filter"].value = "";
519
+ elements["node-filter"].value = "";
520
+ elements["changed-filter"].checked = false;
521
+ state.selectedNode = "";
522
+ applyFilters();
523
+ });
524
+ elements["close-inspector"].addEventListener("click", closeInspector);
525
+ elements["copy-checksum"].addEventListener("click", async () => {
526
+ const value = elements["detail-checksum"].textContent;
527
+ if (!value) return;
528
+ await navigator.clipboard.writeText(value);
529
+ showToast("SHA-256 已复制");
530
+ });
531
+ document.querySelectorAll(".preview-tab").forEach((button) => {
532
+ button.addEventListener("click", () => {
533
+ state.previewMode = button.dataset.mode;
534
+ document.querySelectorAll(".preview-tab").forEach((item) => item.classList.toggle("active", item === button));
535
+ renderPreview();
536
+ });
537
+ });
538
+ document.addEventListener("keydown", (event) => {
539
+ if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === "k") {
540
+ event.preventDefault();
541
+ elements["search-input"].focus();
542
+ }
543
+ if (event.key === "Escape" && state.selectedPath) closeInspector();
544
+ });
545
+
546
+ loadCatalog();