@bovinphang/craft-common 0.0.0-stage → 1.0.0

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 (143) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +12 -2
  3. package/dist/content/catalog.d.ts +4 -0
  4. package/dist/content/resources.d.ts +2 -0
  5. package/dist/content/types.d.ts +21 -0
  6. package/dist/index.d.ts +7 -0
  7. package/dist/index.js +668 -0
  8. package/dist/install/identity.d.ts +5 -0
  9. package/dist/install/planner.d.ts +6 -0
  10. package/dist/install/transaction.d.ts +7 -0
  11. package/dist/install/types.d.ts +44 -0
  12. package/docs/consuming-common.md +33 -0
  13. package/localized/zh-CN/methods/debug.md +13 -0
  14. package/localized/zh-CN/methods/doc-sync.md +12 -0
  15. package/localized/zh-CN/methods/refactoring.md +16 -0
  16. package/localized/zh-CN/methods/source-driven-development.md +19 -0
  17. package/localized/zh-CN/methods/tdd.md +13 -0
  18. package/localized/zh-CN/skills/cc-debug-methodology/SKILL.md +8 -0
  19. package/localized/zh-CN/skills/cc-debug-methodology/references/method.md +13 -0
  20. package/localized/zh-CN/skills/cc-doc-sync-core/SKILL.md +8 -0
  21. package/localized/zh-CN/skills/cc-doc-sync-core/references/method.md +12 -0
  22. package/localized/zh-CN/skills/cc-drawio-studio/SKILL.md +73 -0
  23. package/localized/zh-CN/skills/cc-drawio-studio/data/THIRD_PARTY_NOTICES.md +10 -0
  24. package/localized/zh-CN/skills/cc-drawio-studio/data/brand-icons.json +878 -0
  25. package/localized/zh-CN/skills/cc-drawio-studio/data/shape-index.json +83570 -0
  26. package/localized/zh-CN/skills/cc-drawio-studio/references/data-residency.md +27 -0
  27. package/localized/zh-CN/skills/cc-drawio-studio/references/diagram-patterns.md +60 -0
  28. package/localized/zh-CN/skills/cc-drawio-studio/references/flowchart-quality.md +49 -0
  29. package/localized/zh-CN/skills/cc-drawio-studio/references/layout-and-export.md +72 -0
  30. package/localized/zh-CN/skills/cc-drawio-studio/references/troubleshooting.md +29 -0
  31. package/localized/zh-CN/skills/cc-drawio-studio/references/xml-and-mermaid.md +58 -0
  32. package/localized/zh-CN/skills/cc-drawio-studio/scripts/brand-symbols.mjs +99 -0
  33. package/localized/zh-CN/skills/cc-drawio-studio/scripts/diagram-lint.mjs +205 -0
  34. package/localized/zh-CN/skills/cc-drawio-studio/scripts/diagram-url.mjs +102 -0
  35. package/localized/zh-CN/skills/cc-drawio-studio/scripts/drawio-export.mjs +134 -0
  36. package/localized/zh-CN/skills/cc-drawio-studio/scripts/layout-graph.mjs +107 -0
  37. package/localized/zh-CN/skills/cc-drawio-studio/scripts/png-embed-fix.mjs +26 -0
  38. package/localized/zh-CN/skills/cc-drawio-studio/scripts/scan-go-packages.mjs +36 -0
  39. package/localized/zh-CN/skills/cc-drawio-studio/scripts/scan-js-modules.mjs +52 -0
  40. package/localized/zh-CN/skills/cc-drawio-studio/scripts/scan-python-classes.mjs +30 -0
  41. package/localized/zh-CN/skills/cc-drawio-studio/scripts/scan-python-modules.mjs +55 -0
  42. package/localized/zh-CN/skills/cc-drawio-studio/scripts/scan-rust-modules.mjs +35 -0
  43. package/localized/zh-CN/skills/cc-drawio-studio/scripts/scan-ts-modules.mjs +7 -0
  44. package/localized/zh-CN/skills/cc-drawio-studio/scripts/shape-query.mjs +121 -0
  45. package/localized/zh-CN/skills/cc-drawio-studio/scripts/studio-core.mjs +139 -0
  46. package/localized/zh-CN/skills/cc-drawio-studio/styles/built-in/corporate.json +49 -0
  47. package/localized/zh-CN/skills/cc-drawio-studio/styles/built-in/default.json +49 -0
  48. package/localized/zh-CN/skills/cc-drawio-studio/styles/built-in/handdrawn.json +49 -0
  49. package/localized/zh-CN/skills/cc-drawio-studio/styles/schema.json +112 -0
  50. package/localized/zh-CN/skills/cc-refactoring-core/SKILL.md +8 -0
  51. package/localized/zh-CN/skills/cc-refactoring-core/references/method.md +16 -0
  52. package/localized/zh-CN/skills/cc-source-driven-development/SKILL.md +8 -0
  53. package/localized/zh-CN/skills/cc-source-driven-development/references/method.md +19 -0
  54. package/localized/zh-CN/skills/cc-tdd-methodology/SKILL.md +8 -0
  55. package/localized/zh-CN/skills/cc-tdd-methodology/references/method.md +13 -0
  56. package/localized/zh-CN/skills/cc-technical-diagrams/SKILL.md +34 -0
  57. package/localized/zh-CN/skills/cc-technical-diagrams/assets/interactive-diagram.html +371 -0
  58. package/localized/zh-CN/skills/cc-technical-diagrams/assets/quality-examples/README.md +14 -0
  59. package/localized/zh-CN/skills/cc-technical-diagrams/assets/quality-examples/architecture.json +121 -0
  60. package/localized/zh-CN/skills/cc-technical-diagrams/assets/quality-examples/invalid-explicit.json +41 -0
  61. package/localized/zh-CN/skills/cc-technical-diagrams/assets/quality-examples/sequence.json +53 -0
  62. package/localized/zh-CN/skills/cc-technical-diagrams/assets/quality-examples/sequence.mmd +21 -0
  63. package/localized/zh-CN/skills/cc-technical-diagrams/assets/quality-examples/workflow.json +98 -0
  64. package/localized/zh-CN/skills/cc-technical-diagrams/assets/quality-examples/workflow.mmd +7 -0
  65. package/localized/zh-CN/skills/cc-technical-diagrams/references/diagram-workflows.md +81 -0
  66. package/localized/zh-CN/skills/cc-technical-diagrams/references/html-technical-diagrams.md +243 -0
  67. package/localized/zh-CN/skills/cc-technical-diagrams/references/png-qa-autofix.md +37 -0
  68. package/localized/zh-CN/skills/cc-technical-diagrams/scripts/diagram-browser.mjs +153 -0
  69. package/localized/zh-CN/skills/cc-technical-diagrams/scripts/diagram-layout.mjs +176 -0
  70. package/localized/zh-CN/skills/cc-technical-diagrams/scripts/export-diagram.mjs +332 -0
  71. package/localized/zh-CN/skills/cc-technical-diagrams/scripts/interactive-diagram-server.mjs +654 -0
  72. package/localized/zh-CN/skills/cc-technical-diagrams/scripts/mermaid-render.mjs +64 -0
  73. package/localized/zh-CN/skills/cc-technical-diagrams/scripts/png-qa.mjs +462 -0
  74. package/localized/zh-CN/skills/cc-technical-diagrams/scripts/tech-diagram-render.mjs +1537 -0
  75. package/localized/zh-CN/skills/eval_queries.json +160 -0
  76. package/localized/zh-CN/skills/metadata.json +223 -0
  77. package/localized/zh-CN/skills/relations.json +53 -0
  78. package/methods/debug.md +13 -0
  79. package/methods/doc-sync.md +12 -0
  80. package/methods/refactoring.md +16 -0
  81. package/methods/source-driven-development.md +19 -0
  82. package/methods/tdd.md +13 -0
  83. package/package.json +51 -4
  84. package/skills/cc-debug-methodology/SKILL.md +8 -0
  85. package/skills/cc-debug-methodology/references/method.md +13 -0
  86. package/skills/cc-doc-sync-core/SKILL.md +8 -0
  87. package/skills/cc-doc-sync-core/references/method.md +12 -0
  88. package/skills/cc-drawio-studio/SKILL.md +73 -0
  89. package/skills/cc-drawio-studio/data/THIRD_PARTY_NOTICES.md +10 -0
  90. package/skills/cc-drawio-studio/data/brand-icons.json +878 -0
  91. package/skills/cc-drawio-studio/data/shape-index.json +83570 -0
  92. package/skills/cc-drawio-studio/references/data-residency.md +27 -0
  93. package/skills/cc-drawio-studio/references/diagram-patterns.md +60 -0
  94. package/skills/cc-drawio-studio/references/flowchart-quality.md +49 -0
  95. package/skills/cc-drawio-studio/references/layout-and-export.md +72 -0
  96. package/skills/cc-drawio-studio/references/troubleshooting.md +29 -0
  97. package/skills/cc-drawio-studio/references/xml-and-mermaid.md +58 -0
  98. package/skills/cc-drawio-studio/scripts/brand-symbols.mjs +99 -0
  99. package/skills/cc-drawio-studio/scripts/diagram-lint.mjs +205 -0
  100. package/skills/cc-drawio-studio/scripts/diagram-url.mjs +102 -0
  101. package/skills/cc-drawio-studio/scripts/drawio-export.mjs +134 -0
  102. package/skills/cc-drawio-studio/scripts/layout-graph.mjs +107 -0
  103. package/skills/cc-drawio-studio/scripts/png-embed-fix.mjs +26 -0
  104. package/skills/cc-drawio-studio/scripts/scan-go-packages.mjs +36 -0
  105. package/skills/cc-drawio-studio/scripts/scan-js-modules.mjs +52 -0
  106. package/skills/cc-drawio-studio/scripts/scan-python-classes.mjs +30 -0
  107. package/skills/cc-drawio-studio/scripts/scan-python-modules.mjs +55 -0
  108. package/skills/cc-drawio-studio/scripts/scan-rust-modules.mjs +35 -0
  109. package/skills/cc-drawio-studio/scripts/scan-ts-modules.mjs +7 -0
  110. package/skills/cc-drawio-studio/scripts/shape-query.mjs +121 -0
  111. package/skills/cc-drawio-studio/scripts/studio-core.mjs +139 -0
  112. package/skills/cc-drawio-studio/styles/built-in/corporate.json +49 -0
  113. package/skills/cc-drawio-studio/styles/built-in/default.json +49 -0
  114. package/skills/cc-drawio-studio/styles/built-in/handdrawn.json +49 -0
  115. package/skills/cc-drawio-studio/styles/schema.json +112 -0
  116. package/skills/cc-refactoring-core/SKILL.md +8 -0
  117. package/skills/cc-refactoring-core/references/method.md +16 -0
  118. package/skills/cc-source-driven-development/SKILL.md +8 -0
  119. package/skills/cc-source-driven-development/references/method.md +19 -0
  120. package/skills/cc-tdd-methodology/SKILL.md +8 -0
  121. package/skills/cc-tdd-methodology/references/method.md +13 -0
  122. package/skills/cc-technical-diagrams/SKILL.md +34 -0
  123. package/skills/cc-technical-diagrams/assets/interactive-diagram.html +371 -0
  124. package/skills/cc-technical-diagrams/assets/quality-examples/README.md +14 -0
  125. package/skills/cc-technical-diagrams/assets/quality-examples/architecture.json +121 -0
  126. package/skills/cc-technical-diagrams/assets/quality-examples/invalid-explicit.json +41 -0
  127. package/skills/cc-technical-diagrams/assets/quality-examples/sequence.json +53 -0
  128. package/skills/cc-technical-diagrams/assets/quality-examples/sequence.mmd +21 -0
  129. package/skills/cc-technical-diagrams/assets/quality-examples/workflow.json +98 -0
  130. package/skills/cc-technical-diagrams/assets/quality-examples/workflow.mmd +7 -0
  131. package/skills/cc-technical-diagrams/references/diagram-workflows.md +81 -0
  132. package/skills/cc-technical-diagrams/references/html-technical-diagrams.md +243 -0
  133. package/skills/cc-technical-diagrams/references/png-qa-autofix.md +37 -0
  134. package/skills/cc-technical-diagrams/scripts/diagram-browser.mjs +153 -0
  135. package/skills/cc-technical-diagrams/scripts/diagram-layout.mjs +176 -0
  136. package/skills/cc-technical-diagrams/scripts/export-diagram.mjs +332 -0
  137. package/skills/cc-technical-diagrams/scripts/interactive-diagram-server.mjs +654 -0
  138. package/skills/cc-technical-diagrams/scripts/mermaid-render.mjs +64 -0
  139. package/skills/cc-technical-diagrams/scripts/png-qa.mjs +462 -0
  140. package/skills/cc-technical-diagrams/scripts/tech-diagram-render.mjs +1537 -0
  141. package/skills/eval_queries.json +160 -0
  142. package/skills/metadata.json +223 -0
  143. package/skills/relations.json +53 -0
@@ -0,0 +1,27 @@
1
+ # 数据驻留
2
+
3
+ 当图表包含私有架构、未发布产品细节、客户数据或受监管信息时,使用此清单。
4
+
5
+ ## 本地优先
6
+
7
+ - 写入 workspace 的 `.drawio` 文件是最安全的默认产物,因为它可以本地检查、版本化和打开。
8
+ - 安装 CLI 后,draw.io Desktop 导出会在本机运行。若缺少 CLI,保留源文件并提供 URL 兜底,而不是将图表发送到云端转换器。
9
+ - `diagram-lint.mjs`、`diagram-url.mjs`、`layout-graph.mjs`、`png-embed-fix.mjs` 和代码扫描器都在本地运行。Graphviz 是可选项;安装后也在本地运行。
10
+
11
+ ## URL 模式
12
+
13
+ - viewer/editor `#R` URL 和 `#create=` URL 会把压缩后的图表内容放在 URL fragment 中。
14
+ - URL fragment 不会作为请求路径发送给普通 HTTP 服务器,但浏览器仍会从所选 diagrams.net host 加载应用代码。
15
+ - 超大型图表可能超过浏览器实际可用的 URL 长度限制。URL 变得难以处理时,回退到本地 `.drawio` 文件。
16
+ - 在 Windows 上,通过 `.url` 快捷方式文件打开长图表 URL,或使用 `diagram-url.mjs` 输出的 JSON `windowsShortcut`;直接通过 `cmd start` 传递 URL 可能丢失 fragment。
17
+
18
+ ## 外部请求
19
+
20
+ - 品牌 CDN 符号在渲染时需要网络访问,除非已嵌入为 data URI。
21
+ - diagrams.net web editor/viewer 会从配置的 host 加载编辑器代码和资源。
22
+ - 当环境要求受控端点时,可通过 `--base-url` 传入自托管 diagrams.net base URL。
23
+ - 现有 draw.io MCP App 或 Tool server 可作为预览界面使用,但它们是可选项,并受用户配置的 host 和模型提供方约束。
24
+
25
+ ## 交接规则
26
+
27
+ 对于敏感图表,优先交付本地 `.drawio` 源文件和 lint 结果。仅当用户明确需要且数据边界可接受时,再补充 PNG/SVG/PDF 或 URL 输出。
@@ -0,0 +1,60 @@
1
+ # 图表模式
2
+
3
+ 当用户要求某类具名技术图时,使用这些约定。
4
+
5
+ ## 架构图
6
+
7
+ - 按层级分组节点:client、edge、service、data、external。
8
+ - 边界重要时,使用泳道或分组容器。
9
+ - 分层块状架构图优先使用水平层、左侧层标签、按内容收紧的容器,以及用于 security、monitoring 或 governance 的可选横切侧边栏。
10
+ - 将 bus 或 event-stream 节点放在 publisher/subscriber 行的中心附近。
11
+ - 数据库使用圆柱,服务使用圆角矩形,外部系统使用虚线外框。
12
+ - 只有当 data flow 或 dependency direction 重要时才使用箭头;纯能力栈优先用空间分组表达。
13
+
14
+ ## ERD
15
+
16
+ - 将表建模为容器,列以行展示。
17
+ - 在标签中标记主键和外键。
18
+ - 基数关系不完全明确时,使用虚线关系边。
19
+ - 包含多个关系簇的 schema 优先使用自上而下布局。
20
+ - 使用稳定的表宽和行高,让长列名换行,而不是撑开相邻表格。
21
+
22
+ ## UML 类图
23
+
24
+ - 使用拆分为名称、字段和方法的类框。
25
+ - 继承使用空心三角箭头,接口实现使用虚线空心三角箭头。
26
+ - 接口或抽象基类放在具体类上方。
27
+ - 关联关系保持正交布线,并将 multiplicity 标签放在其描述的边段附近。
28
+
29
+ ## 序列图
30
+
31
+ - 参与者从左到右排列,时间从上到下推进。
32
+ - 请求、响应和异步消息的样式保持可视化区分。
33
+ - 只有能澄清嵌套工作时才添加 activation box。
34
+ - 为消息标签留出足够水平间距;如果标签碰撞,换行或将交互拆成多个阶段。
35
+
36
+ ## 机器学习 / 深度学习
37
+
38
+ - tensor shape 重要时,在第二行展示。
39
+ - 对 encoder、decoder、embedding、attention 和 output 阶段分组。
40
+ - skip connection 绕开主流程布线。
41
+
42
+ ## 流程图
43
+
44
+ - 开始/结束使用椭圆,过程使用矩形,决策使用菱形。
45
+ - 为决策分支添加标签。
46
+ - 尽可能将分支合并回中心路径。
47
+ - 主路径保持纵向。备选或错误分支横向绕出,再用正交边回到主路径。
48
+ - 可编辑 draw.io 流程图最终交付前遵循 [flowchart-quality.md](flowchart-quality.md)。
49
+
50
+ ## 思维导图
51
+
52
+ - 将中心概念放在画布中央,一层分支分布到不同象限。
53
+ - 使用无箭头曲线边,并保持每条分支从父节点到叶子的一致配色。
54
+ - 叶子节点从中心向外展开;不要让分支穿过中心标签。
55
+
56
+ ## 网络拓扑
57
+
58
+ - 使用 internet、edge、switching/routing 和 devices 等层级行。
59
+ - 有线和无线连接应有可视区分,通常实线表示有线,虚线表示无线。
60
+ - 终端设备按行或列对齐,让连接路径易于扫读。
@@ -0,0 +1,49 @@
1
+ # 流程图质量
2
+
3
+ 当 draw.io / diagrams.net 交付物是流程、工作流、审批路径、发布路径、事故响应、入职流程或其他需要打开后依然可读的流程图时,使用本参考。
4
+
5
+ ## 可读性约定
6
+
7
+ - 手工编写的流程图保持窄画布。优先使用 600 到 800 的 `pageWidth`;除非图表明确是横向跨职能表,否则不要超过 800。
8
+ - 将 `dx` 和 `dy` 设置为接近页面尺寸,避免 diagrams.net 初始缩放后文字变得很小。
9
+ - 主路径自上而下推进。备选、失败、重试和异常分支可以横向绕出,但需要用正交连接线回到主路径。
10
+ - 超过五个密集阶段时,优先拆成多张图或多个页面,而不是做成一个超长超宽画布。
11
+
12
+ ## 阶段分组
13
+
14
+ - 真实阶段、角色、阶段组或边界上下文使用 `swimlane` 容器;当标题和包含关系重要时,不要用松散矩形代替。
15
+ - 阶段容器保持 10px 网格对齐,通常保留 20px 页面外边距,阶段之间保留 30-40px 垂直呼吸空间。
16
+ - 容器标题建立清晰层级:阶段标签约 14-16px、加粗,并使用明确边框色。
17
+ - 阶段容器设置 `connectable="0"` 和 `collapsible=0`,避免连接线误连容器本身,也避免出现折叠控件。
18
+ - 容器尺寸应包住子节点并保留至少 15px 底部留白。单侧大面积空白通常意味着需要重新布局。
19
+
20
+ ## 标签
21
+
22
+ - 过程框文字默认居中;只有表格类内容确实需要行对齐时才改用其他对齐。
23
+ - 过程节点使用 `whiteSpace=wrap;html=1`,普通正文不小于 12px。
24
+ - 节点标题放在最前,并比正文更醒目。标签保持简洁,把过长实现细节放进备注或 metadata。
25
+ - 图表语言与用户请求一致,保存前转义 XML 属性内容。
26
+
27
+ ## 连接线
28
+
29
+ - 常规流程图连接线使用 `edgeStyle=orthogonalEdgeStyle`。
30
+ - 阶段到阶段的主路径应比次要边更醒目,例如使用更粗线宽和一致强调色。
31
+ - 决策分支和异常路径需要标注,标签应靠近它描述的边段。
32
+ - 一个节点扇出到多个同级节点时,使用明确的 `entryX`、`entryY`、`exitX` 和 `exitY` 锚点,避免分支互相穿越。
33
+ - 重试、回滚和循环连接线通过左侧或右侧边距及显式 waypoint 绕行,不要斜穿内容。
34
+
35
+ ## 视觉系统
36
+
37
+ - 使用小型色板,每种阶段类型或角色组一个颜色。过程节点填充保持中性,由边框或标题承载阶段色。
38
+ - 警告、失败、待定和未来状态只用于异常节点;不要把状态色当装饰。
39
+ - 结构性阶段边界使用方角容器;普通过程节点可以使用圆角。
40
+
41
+ ## 最终检查
42
+
43
+ 交付前运行:
44
+
45
+ ```bash
46
+ node skills/cc-drawio-studio/scripts/diagram-lint.mjs diagram.drawio --format markdown --strict
47
+ ```
48
+
49
+ 然后手动打开图表,检查主路径连线没有交叉、标签没有截断、阶段没有像折叠态一样不可读,并且 diagrams.net 默认缩放下文字仍然清晰。
@@ -0,0 +1,72 @@
1
+ # 布局与导出
2
+
3
+ ## 自动布局
4
+
5
+ 依赖图、代码结构图和超过约 15 个节点的图表使用 `layout-graph.mjs`。较小图表如果坐标、容器和边锚点可以直接审查,也可以手工编写 XML。
6
+
7
+ ```bash
8
+ node skills/cc-drawio-studio/scripts/layout-graph.mjs graph.json --output diagram.drawio --manifest layout.json
9
+ ```
10
+
11
+ 输入 graph:
12
+
13
+ ```json
14
+ {
15
+ "direction": "TB",
16
+ "nodes": [
17
+ { "id": "client", "label": "Web Client", "group": "Client" },
18
+ { "id": "api", "label": "API", "group": "Service" }
19
+ ],
20
+ "edges": [
21
+ { "source": "client", "target": "api", "label": "HTTPS" }
22
+ ]
23
+ }
24
+ ```
25
+
26
+ Graphviz `dot` 是可选项。若缺失,可手动摆放图表,或交付 XML 加 diagrams.net URL。
27
+
28
+ ## 手工布局
29
+
30
+ - 将可见顶点的 `x` 和 `y` 对齐到 10px 网格。
31
+ - 页面外边距至少保留 60px,并留出足够空间,避免连接线穿过标签。
32
+ - 同一行或同一列的同级节点使用一致尺寸和一致间距,除非内容明确需要不同尺寸。
33
+ - 显式标签换行使用 `
`;不要在 draw.io `value` 属性中写 literal `\n`。
34
+ - 标签包含 `whiteSpace=wrap;html=1`,默认使用 `fontSize=14`,12 是最小可读例外。
35
+ - 容器、侧边栏和泳道应按内容加均衡 padding 后收紧。最后一行不满时,将其居中或拆分分组,不要留下大面积空尾。
36
+
37
+ ## 导出
38
+
39
+ 预览:
40
+
41
+ ```bash
42
+ node skills/cc-drawio-studio/scripts/drawio-export.mjs diagram.drawio --format png --output diagram.png --width 2000
43
+ ```
44
+
45
+ 最终可编辑 PNG:
46
+
47
+ ```bash
48
+ node skills/cc-drawio-studio/scripts/drawio-export.mjs diagram.drawio --format png --output diagram.drawio.png --scale 2 --embed
49
+ node skills/cc-drawio-studio/scripts/png-embed-fix.mjs diagram.drawio.png
50
+ ```
51
+
52
+ 可在不实际调用 draw.io 导出的情况下检查命令:
53
+
54
+ ```bash
55
+ node skills/cc-drawio-studio/scripts/drawio-export.mjs diagram.drawio --format png --output diagram.png --scale 2 --width 2000 --dry-run --json
56
+ ```
57
+
58
+ 兜底 URL:
59
+
60
+ ```bash
61
+ node skills/cc-drawio-studio/scripts/diagram-url.mjs diagram.drawio --edit
62
+ ```
63
+
64
+ 为 XML、Mermaid 或 CSV 创建/导入 URL:
65
+
66
+ ```bash
67
+ node skills/cc-drawio-studio/scripts/diagram-url.mjs diagram.drawio --create --type xml --json
68
+ node skills/cc-drawio-studio/scripts/diagram-url.mjs flow.mmd --create --type mermaid
69
+ node skills/cc-drawio-studio/scripts/diagram-url.mjs org.csv --create --type csv --lightbox
70
+ ```
71
+
72
+ 自托管 diagrams.net 端点使用 `--base-url`。通过 Windows shell 工具打开 URL 时,使用 `--shortcut` 或 JSON `windowsShortcut` 字段。
@@ -0,0 +1,29 @@
1
+ # 故障排查
2
+
3
+ ## 缺少 draw.io CLI
4
+
5
+ 生成 `.drawio` 文件,并使用 `diagram-url.mjs --edit` 在浏览器中打开 diagrams.net。
6
+
7
+ ## 缺少 Graphviz
8
+
9
+ `layout-graph.mjs` 会输出清晰的 Graphviz 提示并退出。保留 graph JSON,然后安装 Graphviz,或手动摆放较小的图表。
10
+
11
+ ## 供应商图形为空
12
+
13
+ 运行 `shape-query.mjs` 并使用返回的 style 字符串。猜测的 `shape=mxgraph.*` 经常会渲染成通用方框。
14
+
15
+ ## 品牌图标未渲染
16
+
17
+ 引用 CDN 的符号在渲染时需要网络访问。需要可移植性时,使用 `brand-symbols.mjs --embed` 生成自包含 data URI。
18
+
19
+ ## 嵌入 PNG 看起来损坏
20
+
21
+ 最终执行 `drawio -e` 导出 PNG 后,运行 `png-embed-fix.mjs`。该命令是幂等的。
22
+
23
+ ## Windows 上浏览器 URL 打开为空白
24
+
25
+ 使用 `diagram-url.mjs --json`,并将输出的 `windowsShortcut` 内容作为 `.url` 文件打开;或运行 `diagram-url.mjs --shortcut`。这样可以保留携带压缩图表的 `#R` 或 `#create=` fragment。
26
+
27
+ ## Mermaid 或 CSV URL 失败
28
+
29
+ Mermaid 和 CSV 必须使用 `diagram-url.mjs --create --type mermaid|csv`。旧的 viewer/editor `#R` 模式仅适用于 XML。
@@ -0,0 +1,58 @@
1
+ # XML 与 Mermaid
2
+
3
+ 使用本参考选择最轻量、且仍满足用户维护性和保真需求的可编辑图表源。
4
+
5
+ ## 路线选择
6
+
7
+ - 对会保存在 Markdown 中的常见流程图、序列图、类图、状态图、ERD、时间线、思维导图和其他文本优先图表,使用 Mermaid。
8
+ - 当图表需要精确形状、供应商符号、嵌套容器、图层、标签、元数据、自定义颜色或持久 `.drawio` 源文件时,使用 draw.io XML。
9
+ - CSV 只作为组织结构图或简单关系图等表格结构的短导入路径;尽可能保存生成后的可编辑源文件。
10
+ - URL 创建用于预览和交接,不要作为重要图表的唯一产物。
11
+
12
+ ## XML 结构
13
+
14
+ - 每个图表都包含根单元 `id="0"` 和 `id="1"`。
15
+ - 为每个可见顶点和边提供稳定唯一 id。
16
+ - 将子节点放入视觉容器内:把 `parent` 设为容器 id,并使用相对该容器的坐标。
17
+ - 跨容器边界的边放在 `parent="1"` 下,避免被泳道或分组裁剪。
18
+ - 每条边都必须包含 `<mxGeometry relative="1" as="geometry"/>`;不要使用自闭合边单元。
19
+ - 在 `value` 属性中强制换行时使用 `&#xa;`。literal `\n` 会在 draw.io 中渲染成文本,应视为质量问题。
20
+ - 手工摆放的顶点保持 10px 网格对齐,位于 `pageWidth` / `pageHeight` 内,并使用稳定尺寸,避免 hover、标签和导出造成布局位移。
21
+ - 手工流程图的 `pageWidth` 保持不超过 800,除非图表明确是宽矩阵或跨职能表。
22
+
23
+ ## 容器与流程布局
24
+
25
+ - actor、tier 或 bounded context 分组使用带标题的泳道。
26
+ - region、VPC、availability zone 和 workload 等真实层级使用嵌套泳道。
27
+ - 当 actor 行和阶段列都重要时,跨职能流程使用表格布局。
28
+ - 仅在不需要视觉边界且分组不应捕获连接线时,使用普通分组。
29
+
30
+ ## 图层、标签与元数据
31
+
32
+ - 逻辑视图、物理视图、注释或迁移阶段等主要可见性集合使用图层。
33
+ - critical、deprecated、external、v2 或 owner 名称等横切过滤条件使用标签。
34
+ - 当图形表示带有 owner、status、IP、service tier 或 version 等字段的记录时,使用对象元数据和占位符。
35
+ - 可见标签保持简洁;如果次要事实会让图表难以扫读,将它们放入元数据或备注。
36
+
37
+ ## 标签与暗色模式
38
+
39
+ - 图表标签语言应与用户语言一致。
40
+ - 标签包含 HTML 标记或转义 `<br>` 格式时,添加 `html=1`。
41
+ - 普通形状标签包含 `whiteSpace=wrap;html=1`,让长文本稳定换行。
42
+ - 转义 XML 属性值:`&`、`<`、`>` 和 `"` 必须写成实体。
43
+ - 优先使用自适应颜色和简单色板;仅当自动反色导致标签或形状不清晰时,才指定暗色模式覆盖。
44
+
45
+ ## Mermaid 导入说明
46
+
47
+ - 有意识地选择 Mermaid header:`flowchart`、`sequenceDiagram`、`classDiagram`、`stateDiagram-v2`、`erDiagram`、`timeline`、`mindmap`、`gantt`、`C4Context` 等 header 会选择不同解析器。
48
+ - 包含标点、空格、括号或非 ASCII 文本的标签需要加引号。
49
+ - 节点 id 保持简单,将展示文本放在 label 中。
50
+ - 对大型、高度样式化或供应商专属图表,最终交付前转为 XML。
51
+
52
+ ## XML 良构性
53
+
54
+ - 不要包含 XML 注释。
55
+ - 不要在属性值中留下未经转义的 `&` 或尖括号。
56
+ - 不要使用重复 id。
57
+ - 不要留下指向缺失 source 或 target 单元的边。
58
+ - 最终交接前运行 `diagram-lint.mjs`。当导出产物是最终交付件时使用 `--strict`,让重叠、literal line break、缺少 wrapping 样式、字号过小、网格偏移和页面越界等 warning 阻断交付。
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env node
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { parseArgs, skillDir } from "./studio-core.mjs";
5
+
6
+ const MANIFEST = path.join(skillDir, "data", "brand-icons.json");
7
+ const STYLE_PREFIX = "shape=image;html=1;imageAspect=0;aspect=fixed;verticalLabelPosition=bottom;verticalAlign=top;image=";
8
+ const SIMPLE = new Map([
9
+ ["qdrant", "qdrant"], ["milvus", "milvus"], ["supabase", "supabase"], ["redis", "redis"],
10
+ ["postgresql", "postgresql"], ["postgres", "postgresql"], ["mongodb", "mongodb"],
11
+ ["elasticsearch", "elasticsearch"], ["neo4j", "neo4j"], ["kafka", "apachekafka"],
12
+ ["clickhouse", "clickhouse"], ["duckdb", "duckdb"], ["mysql", "mysql"], ["sqlite", "sqlite"],
13
+ ]);
14
+
15
+ export function loadFamilies() {
16
+ const manifest = JSON.parse(fs.readFileSync(MANIFEST, "utf8"));
17
+ const families = new Map();
18
+ for (const icon of manifest.icons) {
19
+ const base = icon.replace(/-(color|text)$/, "");
20
+ if (!families.has(base)) families.set(base, new Set());
21
+ families.get(base).add(icon);
22
+ }
23
+ return { cdn: manifest.cdn, families };
24
+ }
25
+
26
+ export async function queryBrand(query, options = {}) {
27
+ const { cdn, families } = loadFamilies();
28
+ const hits = rankBrands(families, query).slice(0, Number(options.limit ?? 8));
29
+ const size = Number(options.size ?? 48);
30
+ const variant = options.variant ?? "color";
31
+ const results = [];
32
+ for (const brand of hits) {
33
+ const file = pickVariant(brand, families.get(brand), variant);
34
+ const url = `${cdn}${file}.svg`;
35
+ results.push({ brand, file, w: size, h: size, style: STYLE_PREFIX + (options.embed ? await dataUri(url) : url) });
36
+ }
37
+ if (results.length === 0) {
38
+ const slug = SIMPLE.get(squish(query));
39
+ if (slug) {
40
+ const url = `https://cdn.simpleicons.org/${slug}`;
41
+ results.push({ brand: squish(query), file: `simpleicons:${slug}`, w: size, h: size, style: STYLE_PREFIX + (options.embed ? await dataUri(url) : url) });
42
+ }
43
+ }
44
+ return results;
45
+ }
46
+
47
+ function rankBrands(families, query) {
48
+ const needle = squish(query);
49
+ const tokens = String(query).toLowerCase().match(/[a-z0-9]+/g) ?? [];
50
+ const scored = [];
51
+ for (const brand of families.keys()) {
52
+ const base = squish(brand);
53
+ let score = 0;
54
+ if (base === needle) score = 100;
55
+ else if (base.startsWith(needle)) score = 70;
56
+ else if (base.includes(needle) || needle.includes(base)) score = 45;
57
+ for (const token of tokens) {
58
+ if (base === token) score = Math.max(score, 90);
59
+ else if (token.length >= 3 && base.includes(token)) score = Math.max(score, 35);
60
+ }
61
+ if (score > 0) scored.push([brand, score]);
62
+ }
63
+ return scored.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).map(([brand]) => brand);
64
+ }
65
+
66
+ function pickVariant(base, variants, preference) {
67
+ const order = preference === "mono" ? ["", "-color", "-text"] : preference === "text" ? ["-text", "-color", ""] : ["-color", "", "-text"];
68
+ for (const suffix of order) if (variants.has(base + suffix)) return base + suffix;
69
+ return [...variants].sort()[0];
70
+ }
71
+
72
+ function squish(value) {
73
+ return String(value ?? "").toLowerCase().replace(/[^a-z0-9]/g, "");
74
+ }
75
+
76
+ async function dataUri(url) {
77
+ const response = await fetch(url);
78
+ if (!response.ok) throw new Error(`failed to fetch ${url}: ${response.status}`);
79
+ const svg = Buffer.from(await response.arrayBuffer()).toString("base64");
80
+ return `data:image/svg+xml;base64,${svg}`;
81
+ }
82
+
83
+ const args = parseArgs(process.argv.slice(2));
84
+ const query = args._.join(" ");
85
+ if (!query && !args.list) {
86
+ console.error("usage: node brand-symbols.mjs <brand> [--variant color|mono|text] [--embed] [--json]");
87
+ process.exit(2);
88
+ }
89
+ if (args.list) {
90
+ process.stdout.write([...loadFamilies().families.keys()].sort().join("\n") + "\n");
91
+ } else {
92
+ const results = await queryBrand(query, args);
93
+ if (results.length === 0) {
94
+ console.error(`no brand symbol matched ${JSON.stringify(query)}`);
95
+ process.exit(1);
96
+ }
97
+ if (args.json) process.stdout.write(`${JSON.stringify(results, null, 2)}\n`);
98
+ else for (const item of results) process.stdout.write(`${item.brand} (${item.file}, ${item.w}x${item.h})\n ${item.style.slice(0, 180)}${item.style.length > 180 ? "..." : ""}\n`);
99
+ }
@@ -0,0 +1,205 @@
1
+ #!/usr/bin/env node
2
+ import { formatResult, parseArgs, readText } from "./studio-core.mjs";
3
+
4
+ export function lintDrawio(xml) {
5
+ const errors = [];
6
+ const warnings = [];
7
+ if (/<!--/.test(xml) || /-->/.test(xml)) errors.push("XML comments are not allowed in draw.io diagram sources");
8
+ if (!/<mx(Cell|file|GraphModel)\b/.test(xml)) warnings.push("No draw.io graph markers found");
9
+ if (/<mxCell\b[^>]*\bedge="1"[^>]*\/>/.test(xml)) errors.push("edge mxCell must not be self-closing; add mxGeometry relative=\"1\"");
10
+
11
+ const graphModel = attrsOf(xml.match(/<mxGraphModel\b([^>]*)>/)?.[1] ?? "");
12
+ const pageWidth = Number(graphModel.pageWidth);
13
+ const pageHeight = Number(graphModel.pageHeight);
14
+ const viewportWidth = Number(graphModel.dx);
15
+ const viewportHeight = Number(graphModel.dy);
16
+ if (Number.isFinite(pageWidth) && pageWidth > 800) warnings.push(`pageWidth ${pageWidth} is above the readable manual-flowchart maximum of 800`);
17
+ if (Number.isFinite(pageWidth) && Number.isFinite(viewportWidth) && viewportWidth < pageWidth * 0.75) {
18
+ warnings.push(`dx ${viewportWidth} is much smaller than pageWidth ${pageWidth}; diagrams.net may initially zoom labels too small`);
19
+ }
20
+ if (Number.isFinite(pageHeight) && Number.isFinite(viewportHeight) && viewportHeight < pageHeight * 0.5) {
21
+ warnings.push(`dy ${viewportHeight} is much smaller than pageHeight ${pageHeight}; diagrams.net may initially zoom labels too small`);
22
+ }
23
+ const cellRe = /<mxCell\b([^>]*?)(?:\/>|>([\s\S]*?)<\/mxCell>)/g;
24
+ const objectByCellIndex = objectsByCellIndex(xml);
25
+ const cells = [];
26
+ const ids = new Map();
27
+ for (const match of xml.matchAll(cellRe)) {
28
+ const objectAttrs = objectByCellIndex.get(match.index) ?? {};
29
+ const attrs = attrsOf(match[1]);
30
+ if (!attrs.id && objectAttrs.id) attrs.id = objectAttrs.id;
31
+ if (!attrs.value && objectAttrs.label) attrs.value = objectAttrs.label;
32
+ const body = match[2] ?? "";
33
+ const id = attrs.id;
34
+ if (!id) {
35
+ errors.push("mxCell without id");
36
+ continue;
37
+ }
38
+ if (ids.has(id)) errors.push(`duplicate id ${id}`);
39
+ ids.set(id, attrs);
40
+ for (const [name, value] of rawAttrsOf(match[1])) {
41
+ if (hasUnescapedAmpersand(value)) errors.push(`cell ${id} attribute ${name} contains an unescaped ampersand`);
42
+ if (/[<>]/.test(value)) errors.push(`cell ${id} attribute ${name} contains an unescaped angle bracket`);
43
+ if (name === "value" && /\\n/.test(value)) warnings.push(`cell ${id} value contains literal \\n; use &#xa; for draw.io line breaks`);
44
+ }
45
+ const geom = body.match(/<mxGeometry\b([^>]*?)(?:\/>|>)/)?.[1];
46
+ cells.push({ id, attrs, body, geom: geom ? attrsOf(geom) : null });
47
+ }
48
+
49
+ if (cells.length > 0) {
50
+ if (!ids.has("0")) errors.push("missing root cell id 0");
51
+ if (!ids.has("1")) errors.push("missing default layer cell id 1");
52
+ }
53
+
54
+ for (const cell of cells) {
55
+ const { attrs, id, geom } = cell;
56
+ if ((attrs.vertex === "1" || attrs.edge === "1") && (id === "0" || id === "1")) {
57
+ errors.push(`reserved id used by graph cell ${id}`);
58
+ }
59
+ if (attrs.parent && !ids.has(attrs.parent)) errors.push(`cell ${id} parent ${attrs.parent} does not exist`);
60
+ for (const end of ["source", "target"]) {
61
+ if (attrs[end] && !ids.has(attrs[end])) errors.push(`edge ${id} ${end} ${attrs[end]} does not exist`);
62
+ }
63
+ if (attrs.edge === "1" && !geom) errors.push(`edge ${id} is missing mxGeometry`);
64
+ if (attrs.edge === "1" && (!attrs.source || !attrs.target)) warnings.push(`edge ${id} has no source or target`);
65
+ if (attrs.edge === "1" && attrs.style && !styleHas(attrs.style, "edgeStyle", "orthogonalEdgeStyle")) {
66
+ warnings.push(`edge ${id} should use orthogonalEdgeStyle for readable technical diagram routing`);
67
+ }
68
+ if (attrs.vertex === "1") {
69
+ if (looksLikeHtml(attrs.value) && !styleHas(attrs.style, "html", "1")) warnings.push(`cell ${id} label contains HTML but style does not include html=1`);
70
+ if (!styleHas(attrs.style, "whiteSpace", "wrap")) warnings.push(`cell ${id} style should include whiteSpace=wrap for label wrapping`);
71
+ if (!styleHas(attrs.style, "html", "1")) warnings.push(`cell ${id} style should include html=1 for predictable label rendering`);
72
+ const fontSize = styleNumber(attrs.style, "fontSize");
73
+ if (Number.isFinite(fontSize) && fontSize < 12) warnings.push(`cell ${id} fontSize ${fontSize} is below the readable minimum of 12`);
74
+ if (isSwimlane(attrs.style)) {
75
+ if (attrs.connectable !== "0") warnings.push(`swimlane ${id} should set connectable="0" so connectors target intentional nodes`);
76
+ if (styleValue(attrs.style, "collapsible") !== "0") warnings.push(`swimlane ${id} should set collapsible=0 to avoid collapse controls in deliverables`);
77
+ if (styleValue(attrs.style, "rounded") !== "0") warnings.push(`swimlane ${id} should use rounded=0 for clear structural stage boundaries`);
78
+ if (Number.isFinite(fontSize) && fontSize < 14) warnings.push(`swimlane ${id} title fontSize ${fontSize} is below the recommended flowchart stage range of 14-16`);
79
+ } else if (isLikelyProcessNode(attrs.style)) {
80
+ const align = styleValue(attrs.style, "align");
81
+ if (align && align !== "center") warnings.push(`process node ${id} should use align=center for flowchart readability`);
82
+ }
83
+ if (!geom) errors.push(`vertex ${id} is missing mxGeometry`);
84
+ else {
85
+ const rect = rectOf(geom);
86
+ if (!Number.isFinite(rect.w) || !Number.isFinite(rect.h)) errors.push(`vertex ${id} has invalid size`);
87
+ else if (rect.w <= 0 || rect.h <= 0) warnings.push(`vertex ${id} has non-positive size`);
88
+ if (rect.x < 0 || rect.y < 0) warnings.push(`vertex ${id} has negative position`);
89
+ if (!onGrid(rect.x) || !onGrid(rect.y)) warnings.push(`vertex ${id} position should align to the 10px grid`);
90
+ if (Number.isFinite(pageWidth) && rect.x + rect.w > pageWidth) warnings.push(`vertex ${id} extends beyond pageWidth ${pageWidth}`);
91
+ if (Number.isFinite(pageHeight) && rect.y + rect.h > pageHeight) warnings.push(`vertex ${id} extends beyond pageHeight ${pageHeight}`);
92
+ }
93
+ }
94
+ }
95
+
96
+ const leafVertices = cells
97
+ .filter((cell) => cell.attrs.vertex === "1" && cell.geom)
98
+ .filter((cell) => !cells.some((candidate) => candidate.attrs.parent === cell.id))
99
+ .map((cell) => ({ id: cell.id, parent: cell.attrs.parent ?? "1", rect: rectOf(cell.geom) }))
100
+ .filter((item) => Number.isFinite(item.rect.w) && Number.isFinite(item.rect.h));
101
+ for (let i = 0; i < leafVertices.length; i += 1) {
102
+ for (let j = i + 1; j < leafVertices.length; j += 1) {
103
+ if (leafVertices[i].parent === leafVertices[j].parent && overlaps(leafVertices[i].rect, leafVertices[j].rect)) {
104
+ warnings.push(`vertices ${leafVertices[i].id} and ${leafVertices[j].id} overlap`);
105
+ }
106
+ }
107
+ }
108
+ if (cells.filter((cell) => cell.attrs.vertex === "1").length === 0) warnings.push("diagram has no vertex cells");
109
+ return { title: "Diagram Lint", errors, warnings, ok: errors.length === 0 };
110
+ }
111
+
112
+ function objectsByCellIndex(xml) {
113
+ const out = new Map();
114
+ const objectRe = /<object\b([^>]*)>([\s\S]*?)<\/object>/g;
115
+ for (const match of xml.matchAll(objectRe)) {
116
+ const inner = match[2] ?? "";
117
+ const cellOffset = inner.search(/<mxCell\b/);
118
+ if (cellOffset < 0) continue;
119
+ const index = match.index + match[0].indexOf("<mxCell");
120
+ out.set(index, attrsOf(match[1]));
121
+ }
122
+ return out;
123
+ }
124
+
125
+ function attrsOf(raw) {
126
+ const attrs = {};
127
+ for (const match of raw.matchAll(/([A-Za-z_:][-A-Za-z0-9_:.]*)="([^"]*)"/g)) attrs[match[1]] = decode(match[2]);
128
+ return attrs;
129
+ }
130
+
131
+ function rawAttrsOf(raw) {
132
+ return [...raw.matchAll(/([A-Za-z_:][-A-Za-z0-9_:.]*)="([^"]*)"/g)].map((match) => [match[1], match[2]]);
133
+ }
134
+
135
+ function decode(value) {
136
+ return value.replaceAll("&quot;", '"').replaceAll("&gt;", ">").replaceAll("&lt;", "<").replaceAll("&amp;", "&");
137
+ }
138
+
139
+ function hasUnescapedAmpersand(value) {
140
+ return /&(?!amp;|lt;|gt;|quot;|apos;|#[0-9]+;|#x[0-9a-fA-F]+;)/.test(value);
141
+ }
142
+
143
+ function looksLikeHtml(value = "") {
144
+ return /<\/?[A-Za-z][^>]*>/.test(value);
145
+ }
146
+
147
+ function styleHas(style = "", key, expected) {
148
+ return style.split(";").some((part) => {
149
+ const [partKey, value] = part.split("=", 2);
150
+ return partKey === key && value === expected;
151
+ });
152
+ }
153
+
154
+ function styleNumber(style = "", key) {
155
+ for (const part of style.split(";")) {
156
+ const [partKey, value] = part.split("=", 2);
157
+ if (partKey === key) return Number(value);
158
+ }
159
+ return NaN;
160
+ }
161
+
162
+ function styleValue(style = "", key) {
163
+ for (const part of style.split(";")) {
164
+ const [partKey, value] = part.split("=", 2);
165
+ if (partKey === key) return value ?? "";
166
+ }
167
+ return undefined;
168
+ }
169
+
170
+ function isSwimlane(style = "") {
171
+ return style.split(";").some((part) => part === "swimlane" || part === "shape=swimlane");
172
+ }
173
+
174
+ function isLikelyProcessNode(style = "") {
175
+ const shape = styleValue(style, "shape");
176
+ if (shape) return ["process", "rectangle", "rhombus", "terminator"].includes(shape);
177
+ return style.includes("rounded=1") || style.includes("whiteSpace=wrap");
178
+ }
179
+
180
+ function rectOf(geom) {
181
+ return {
182
+ x: Number(geom.x ?? 0),
183
+ y: Number(geom.y ?? 0),
184
+ w: Number(geom.width),
185
+ h: Number(geom.height),
186
+ };
187
+ }
188
+
189
+ function onGrid(value, grid = 10) {
190
+ return Number.isFinite(value) && Math.abs(value / grid - Math.round(value / grid)) < 1e-9;
191
+ }
192
+
193
+ function overlaps(a, b) {
194
+ return a.x < b.x + b.w && b.x < a.x + a.w && a.y < b.y + b.h && b.y < a.y + a.h;
195
+ }
196
+
197
+ const args = parseArgs(process.argv.slice(2));
198
+ const input = args._[0];
199
+ if (!input) {
200
+ console.error("usage: node diagram-lint.mjs <file.drawio> [--format markdown|json] [--strict]");
201
+ process.exit(2);
202
+ }
203
+ const result = lintDrawio(readText(input));
204
+ process.stdout.write(formatResult(result, args.format ?? (args.json ? "json" : "markdown")));
205
+ if (!result.ok || (args.strict && result.warnings.length > 0)) process.exit(1);