@microi.net/cli 5.9.6 → 5.9.8

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 (62) hide show
  1. package/.codebuddy-plugin/marketplace.json +2 -2
  2. package/.codebuddy-plugin/plugin.json +1 -1
  3. package/.codex-plugin/plugin.json +1 -1
  4. package/.mcp.json +1 -1
  5. package/.workbuddy-plugin/marketplace.json +2 -2
  6. package/.workbuddy-plugin/plugin.json +1 -1
  7. package/LICENSE +21 -21
  8. package/README.md +71 -71
  9. package/assets/build-meta.json +7 -7
  10. package/assets/feature-matrix.json +138 -138
  11. package/assets/logo.svg +4 -4
  12. package/cordis.patch.yml +1 -1
  13. package/package.json +1 -1
  14. package/scripts/codex-marketplace.json +20 -20
  15. package/scripts/mcp-codex-stdio-adapter.js +189 -189
  16. package/scripts/mcp-server.js +165 -436
  17. package/scripts/mcp-trae-windows-launcher.cmd +21 -21
  18. package/scripts/microi-cli-mcp.js +7 -7
  19. package/scripts/microi-cli.js +61 -665
  20. package/scripts/microi-codex-broker.js +466 -466
  21. package/scripts/microi-codex-router.js +635 -635
  22. package/scripts/microi-skills.meta.json +237 -237
  23. package/skills/.microi-skills-version.json +2 -2
  24. package/skills/.progressive-disclosure-manifest.json +24 -24
  25. package/skills/ai-engine/agents/openai.yaml +4 -4
  26. package/skills/app-store/agents/openai.yaml +4 -4
  27. package/skills/datasource-engine/agents/openai.yaml +4 -4
  28. package/skills/job-engine/agents/openai.yaml +4 -4
  29. package/skills/message-notification/agents/openai.yaml +5 -5
  30. package/skills/microi-ai-app-auth.js +652 -652
  31. package/skills/microi-ai-application/agents/openai.yaml +4 -4
  32. package/skills/microi-client-frontend/SKILL.md +8 -2
  33. package/skills/microi-codex-installer/agents/openai.yaml +7 -7
  34. package/skills/microi-db-schema/agents/openai.yaml +4 -4
  35. package/skills/microi-deployment/SKILL.md +6 -0
  36. package/skills/microi-docs-coverage/references/capability-map.md +1 -1
  37. package/skills/microi-docs-coverage/scripts/audit-doc-skill-coverage.mjs +894 -894
  38. package/skills/microi-solution-quotation/agents/openai.yaml +4 -4
  39. package/skills/microi-solution-quotation/scripts/build_solution_quote.py +296 -296
  40. package/skills/microi-system-delivery/references/progressive-02-/350/207/252/345/212/250/345/214/226/346/265/213/350/257/225/345/277/205/351/241/273/350/246/206/347/233/226/347/232/204/345/235/221.md +20 -2
  41. package/skills/microi-uniapp-frontend/SKILL.md +5 -1
  42. package/skills/microi.v8.js +1921 -1921
  43. package/skills/module-engine/SKILL.md +10 -0
  44. package/skills/ocr-engine/agents/openai.yaml +4 -4
  45. package/skills/page-engine/examples/compact-dashboard.json +1444 -1444
  46. package/skills/playwright-e2e/SKILL.md +2 -0
  47. package/skills/report-engine/agents/openai.yaml +4 -4
  48. package/skills/scripts/optimize-progressive-disclosure.mjs +204 -204
  49. package/skills/scripts/refresh-progressive-disclosure.mjs +64 -64
  50. package/skills/scripts/sync-embedded-skills.mjs +46 -46
  51. package/skills/scripts/validate-progressive-disclosure.mjs +57 -57
  52. package/skills/search-engine/agents/openai.yaml +4 -4
  53. package/skills/translate-engine/agents/openai.yaml +4 -4
  54. package/skills/ui-design/assets/pattern-showcase/app.js +54 -54
  55. package/skills/ui-design/assets/pattern-showcase/index.html +163 -163
  56. package/skills/ui-design/assets/pattern-showcase/styles.css +311 -311
  57. package/skills/unity-integration/agents/openai.yaml +4 -4
  58. package/skills/v8-image-processing/agents/openai.yaml +4 -4
  59. package/skills/v8-mq-mqtt/scripts/check-mqtt-skill-coverage.mjs +203 -203
  60. package/skills/v8-tcp-integration/agents/openai.yaml +4 -4
  61. package/skills/vision-engine/agents/openai.yaml +4 -4
  62. package/skills/workspace-conventions/SKILL.md +4 -4
@@ -1,4 +1,4 @@
1
- interface:
2
- display_name: "Microi吾码解决方案与报价"
3
- short_description: "生成紧凑专业的吾码解决方案、周期与总价报价 Word"
4
- default_prompt: "使用 $microi-solution-quotation 生成一份紧凑的一页 Microi吾码系统解决方案与报价 Word。"
1
+ interface:
2
+ display_name: "Microi吾码解决方案与报价"
3
+ short_description: "生成紧凑专业的吾码解决方案、周期与总价报价 Word"
4
+ default_prompt: "使用 $microi-solution-quotation 生成一份紧凑的一页 Microi吾码系统解决方案与报价 Word。"
@@ -1,296 +1,296 @@
1
- import argparse
2
- import json
3
- from datetime import date
4
- from pathlib import Path
5
-
6
- from docx import Document
7
- from docx.enum.table import WD_CELL_VERTICAL_ALIGNMENT, WD_TABLE_ALIGNMENT
8
- from docx.enum.text import WD_ALIGN_PARAGRAPH
9
- from docx.oxml import OxmlElement
10
- from docx.oxml.ns import qn
11
- from docx.shared import Inches, Pt, RGBColor
12
-
13
-
14
- NAVY = "17365D"
15
- BLUE = "2F75B5"
16
- PALE_BLUE = "EAF3F9"
17
- PALE_GRAY = "F4F6F8"
18
- PALE_GOLD = "FFF3D6"
19
- GOLD = "B45309"
20
- INK = "1F2937"
21
- MUTED = "64748B"
22
- WHITE = "FFFFFF"
23
- BORDER = "CBD5E1"
24
- FONT = "Microsoft YaHei"
25
- PAGE_WIDTH_DXA = 11909
26
- MARGIN_DXA = 720
27
- TABLE_INDENT_DXA = 120
28
- TABLE_WIDTH_DXA = PAGE_WIDTH_DXA - MARGIN_DXA * 2 - TABLE_INDENT_DXA
29
-
30
-
31
- def set_run_font(run, size=9.2, bold=False, color=INK):
32
- run.font.name = FONT
33
- rfonts = run._element.get_or_add_rPr().get_or_add_rFonts()
34
- for key in ("w:eastAsia", "w:ascii", "w:hAnsi"):
35
- rfonts.set(qn(key), FONT)
36
- run.font.size = Pt(size)
37
- run.font.bold = bold
38
- run.font.color.rgb = RGBColor.from_string(color)
39
-
40
-
41
- def format_paragraph(paragraph, before=0, after=0, line=1.05, align=None):
42
- fmt = paragraph.paragraph_format
43
- fmt.space_before = Pt(before)
44
- fmt.space_after = Pt(after)
45
- fmt.line_spacing = line
46
- if align is not None:
47
- paragraph.alignment = align
48
-
49
-
50
- def add_paragraph(doc, text, size=9.2, bold=False, color=INK, before=0, after=2, line=1.05, align=None):
51
- paragraph = doc.add_paragraph()
52
- format_paragraph(paragraph, before, after, line, align)
53
- set_run_font(paragraph.add_run(text), size, bold, color)
54
- return paragraph
55
-
56
-
57
- def set_cell_shading(cell, fill):
58
- tc_pr = cell._tc.get_or_add_tcPr()
59
- shd = tc_pr.find(qn("w:shd"))
60
- if shd is None:
61
- shd = OxmlElement("w:shd")
62
- tc_pr.append(shd)
63
- shd.set(qn("w:fill"), fill)
64
-
65
-
66
- def set_cell_margins(cell, top=70, start=110, bottom=70, end=110):
67
- tc_pr = cell._tc.get_or_add_tcPr()
68
- tc_mar = tc_pr.first_child_found_in("w:tcMar")
69
- if tc_mar is None:
70
- tc_mar = OxmlElement("w:tcMar")
71
- tc_pr.append(tc_mar)
72
- for name, value in (("top", top), ("start", start), ("bottom", bottom), ("end", end)):
73
- element = tc_mar.find(qn(f"w:{name}"))
74
- if element is None:
75
- element = OxmlElement(f"w:{name}")
76
- tc_mar.append(element)
77
- element.set(qn("w:w"), str(value))
78
- element.set(qn("w:type"), "dxa")
79
-
80
-
81
- def set_cell_width(cell, width):
82
- tc_pr = cell._tc.get_or_add_tcPr()
83
- tc_w = tc_pr.find(qn("w:tcW"))
84
- if tc_w is None:
85
- tc_w = OxmlElement("w:tcW")
86
- tc_pr.append(tc_w)
87
- tc_w.set(qn("w:w"), str(width))
88
- tc_w.set(qn("w:type"), "dxa")
89
-
90
-
91
- def set_table_geometry(table, widths, indent=TABLE_INDENT_DXA):
92
- total = sum(widths)
93
- table.autofit = False
94
- table.alignment = WD_TABLE_ALIGNMENT.LEFT
95
- tbl_pr = table._tbl.tblPr
96
- tbl_w = tbl_pr.find(qn("w:tblW"))
97
- if tbl_w is None:
98
- tbl_w = OxmlElement("w:tblW")
99
- tbl_pr.append(tbl_w)
100
- tbl_w.set(qn("w:w"), str(total))
101
- tbl_w.set(qn("w:type"), "dxa")
102
- tbl_ind = tbl_pr.find(qn("w:tblInd"))
103
- if tbl_ind is None:
104
- tbl_ind = OxmlElement("w:tblInd")
105
- tbl_pr.append(tbl_ind)
106
- tbl_ind.set(qn("w:w"), str(indent))
107
- tbl_ind.set(qn("w:type"), "dxa")
108
- grid = table._tbl.tblGrid
109
- for child in list(grid):
110
- grid.remove(child)
111
- for width in widths:
112
- col = OxmlElement("w:gridCol")
113
- col.set(qn("w:w"), str(width))
114
- grid.append(col)
115
- for row in table.rows:
116
- for index, cell in enumerate(row.cells):
117
- set_cell_width(cell, widths[index])
118
- set_cell_margins(cell)
119
- cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
120
-
121
-
122
- def set_table_borders(table, color=BORDER, size="6"):
123
- tbl_pr = table._tbl.tblPr
124
- borders = tbl_pr.first_child_found_in("w:tblBorders")
125
- if borders is None:
126
- borders = OxmlElement("w:tblBorders")
127
- tbl_pr.append(borders)
128
- for edge in ("top", "left", "bottom", "right", "insideH", "insideV"):
129
- tag = f"w:{edge}"
130
- element = borders.find(qn(tag))
131
- if element is None:
132
- element = OxmlElement(tag)
133
- borders.append(element)
134
- element.set(qn("w:val"), "single")
135
- element.set(qn("w:sz"), size)
136
- element.set(qn("w:space"), "0")
137
- element.set(qn("w:color"), color)
138
-
139
-
140
- def add_section_heading(doc, text):
141
- return add_paragraph(doc, text, size=10.8, bold=True, color=BLUE, before=4, after=1, line=1.0)
142
-
143
-
144
- def add_bullet(doc, label, detail):
145
- paragraph = doc.add_paragraph(style="List Bullet")
146
- format_paragraph(paragraph, before=0, after=1, line=1.03)
147
- set_run_font(paragraph.add_run(f"{label}:"), 8.8, True, NAVY)
148
- set_run_font(paragraph.add_run(detail), 8.8, False, INK)
149
-
150
-
151
- def add_metadata_table(doc, config):
152
- table = doc.add_table(rows=1, cols=4)
153
- widths = [TABLE_WIDTH_DXA // 4] * 4
154
- widths[-1] += TABLE_WIDTH_DXA - sum(widths)
155
- values = [
156
- ("版本", config["version"]),
157
- ("日期", config["date"]),
158
- ("开发周期", config["development_cycle"]),
159
- ("报价有效期", f'{config["validity_days"]} 天'),
160
- ]
161
- for index, (label, value) in enumerate(values):
162
- cell = table.cell(0, index)
163
- set_cell_shading(cell, PALE_GRAY)
164
- paragraph = cell.paragraphs[0]
165
- format_paragraph(paragraph, after=0, line=1.0, align=WD_ALIGN_PARAGRAPH.CENTER)
166
- set_run_font(paragraph.add_run(f"{label}:"), 8.0, True, MUTED)
167
- set_run_font(paragraph.add_run(str(value)), 8.2, True, NAVY)
168
- set_table_geometry(table, widths)
169
- set_table_borders(table, "E2E8F0", "4")
170
-
171
-
172
- def add_callout(doc, label, detail, fill=PALE_BLUE):
173
- table = doc.add_table(rows=1, cols=1)
174
- set_table_geometry(table, [TABLE_WIDTH_DXA])
175
- set_table_borders(table, "9CC2E5", "7")
176
- cell = table.cell(0, 0)
177
- set_cell_shading(cell, fill)
178
- paragraph = cell.paragraphs[0]
179
- format_paragraph(paragraph, after=0, line=1.03)
180
- set_run_font(paragraph.add_run(f"{label}:"), 8.8, True, NAVY)
181
- set_run_font(paragraph.add_run(detail), 8.8, False, INK)
182
-
183
-
184
- def add_quote_table(doc, config):
185
- table = doc.add_table(rows=2, cols=3)
186
- widths = [TABLE_WIDTH_DXA // 3] * 3
187
- widths[-1] += TABLE_WIDTH_DXA - sum(widths)
188
- headers = ["市场预估价(总价)", "吾码优惠报价(总价)", "开发周期"]
189
- values = [config["market_price"], config["preferential_price"], config["development_cycle"]]
190
- for index, header in enumerate(headers):
191
- cell = table.cell(0, index)
192
- set_cell_shading(cell, NAVY)
193
- paragraph = cell.paragraphs[0]
194
- format_paragraph(paragraph, after=0, line=1.0, align=WD_ALIGN_PARAGRAPH.CENTER)
195
- set_run_font(paragraph.add_run(header), 8.7, True, WHITE)
196
- for index, value in enumerate(values):
197
- cell = table.cell(1, index)
198
- set_cell_shading(cell, PALE_GOLD if index == 1 else PALE_GRAY)
199
- paragraph = cell.paragraphs[0]
200
- format_paragraph(paragraph, after=0, line=1.0, align=WD_ALIGN_PARAGRAPH.CENTER)
201
- set_run_font(paragraph.add_run(str(value)), 12.0 if index == 1 else 10.5, True, GOLD if index == 1 else NAVY)
202
- set_table_geometry(table, widths)
203
- set_table_borders(table)
204
-
205
-
206
- def normalize_config(raw):
207
- required = [
208
- "system_name", "project_goal", "solution_items", "technical_route",
209
- "deliverables", "market_price", "preferential_price",
210
- "development_cycle", "boundary_note",
211
- ]
212
- missing = [name for name in required if not raw.get(name)]
213
- if missing:
214
- raise ValueError("Missing required fields: " + ", ".join(missing))
215
- if not isinstance(raw["solution_items"], list) or not raw["solution_items"]:
216
- raise ValueError("solution_items must be a non-empty list")
217
- raw.setdefault("version", "v1.0.0")
218
- raw.setdefault("date", date.today().isoformat())
219
- raw.setdefault("validity_days", 15)
220
- raw.setdefault("subtitle", "AI 深度融合 V8引擎 的低代码平台,官网:https://microi.net")
221
- raw.setdefault("project_scope", "")
222
- return raw
223
-
224
-
225
- def build_document(config, output):
226
- doc = Document()
227
- section = doc.sections[0]
228
- section.page_width = Inches(8.27)
229
- section.page_height = Inches(11.69)
230
- section.top_margin = Inches(0.5)
231
- section.bottom_margin = Inches(0.5)
232
- section.left_margin = Inches(0.5)
233
- section.right_margin = Inches(0.5)
234
- section.header_distance = Inches(0.2)
235
- section.footer_distance = Inches(0.2)
236
-
237
- normal = doc.styles["Normal"]
238
- normal.font.name = FONT
239
- normal._element.rPr.rFonts.set(qn("w:eastAsia"), FONT)
240
- normal.font.size = Pt(9.2)
241
- normal.font.color.rgb = RGBColor.from_string(INK)
242
-
243
- title = f'Microi吾码 {config["system_name"]} 解决方案与报价'
244
- add_paragraph(doc, title, size=20.0, bold=True, color=NAVY, after=1, line=1.0, align=WD_ALIGN_PARAGRAPH.CENTER)
245
- add_paragraph(doc, config["subtitle"], size=9.3, color=MUTED, after=4, line=1.0, align=WD_ALIGN_PARAGRAPH.CENTER)
246
- add_metadata_table(doc, config)
247
-
248
- add_section_heading(doc, "一、项目目标")
249
- add_callout(doc, "目标", config["project_goal"])
250
-
251
- add_section_heading(doc, "二、解决方案")
252
- for item in config["solution_items"]:
253
- if isinstance(item, str):
254
- add_bullet(doc, "方案", item)
255
- else:
256
- add_bullet(doc, item.get("label", "方案"), item.get("detail", ""))
257
-
258
- add_section_heading(doc, "三、核心技术路线")
259
- add_callout(doc, "链路", config["technical_route"], fill=PALE_GRAY)
260
-
261
- add_section_heading(doc, "四、交付范围")
262
- add_paragraph(doc, config["deliverables"], size=8.7, after=1, line=1.03)
263
- if config["project_scope"]:
264
- add_paragraph(doc, config["project_scope"], size=8.3, color=MUTED, after=1, line=1.0)
265
-
266
- add_section_heading(doc, "五、项目总价与周期")
267
- add_quote_table(doc, config)
268
-
269
- add_section_heading(doc, "六、说明与边界")
270
- add_paragraph(doc, config["boundary_note"], size=8.1, color=MUTED, after=0, line=1.0)
271
-
272
- footer = section.footer.paragraphs[0]
273
- format_paragraph(footer, after=0, line=1.0, align=WD_ALIGN_PARAGRAPH.RIGHT)
274
- set_run_font(footer.add_run(f'Microi吾码 | {config["version"]} | {config["date"]} | https://microi.net'), 7.2, False, MUTED)
275
-
276
- doc.core_properties.title = title
277
- doc.core_properties.subject = "Microi吾码系统解决方案与报价"
278
- doc.core_properties.author = "Microi吾码"
279
- output.parent.mkdir(parents=True, exist_ok=True)
280
- doc.save(output)
281
-
282
-
283
- def main():
284
- parser = argparse.ArgumentParser(description="Build a compact Microi solution quotation DOCX")
285
- parser.add_argument("--input", required=True, help="UTF-8 JSON configuration file")
286
- parser.add_argument("--output", required=True, help="Output DOCX path")
287
- args = parser.parse_args()
288
- with open(args.input, "r", encoding="utf-8") as handle:
289
- config = normalize_config(json.load(handle))
290
- output = Path(args.output).resolve()
291
- build_document(config, output)
292
- print(output)
293
-
294
-
295
- if __name__ == "__main__":
296
- main()
1
+ import argparse
2
+ import json
3
+ from datetime import date
4
+ from pathlib import Path
5
+
6
+ from docx import Document
7
+ from docx.enum.table import WD_CELL_VERTICAL_ALIGNMENT, WD_TABLE_ALIGNMENT
8
+ from docx.enum.text import WD_ALIGN_PARAGRAPH
9
+ from docx.oxml import OxmlElement
10
+ from docx.oxml.ns import qn
11
+ from docx.shared import Inches, Pt, RGBColor
12
+
13
+
14
+ NAVY = "17365D"
15
+ BLUE = "2F75B5"
16
+ PALE_BLUE = "EAF3F9"
17
+ PALE_GRAY = "F4F6F8"
18
+ PALE_GOLD = "FFF3D6"
19
+ GOLD = "B45309"
20
+ INK = "1F2937"
21
+ MUTED = "64748B"
22
+ WHITE = "FFFFFF"
23
+ BORDER = "CBD5E1"
24
+ FONT = "Microsoft YaHei"
25
+ PAGE_WIDTH_DXA = 11909
26
+ MARGIN_DXA = 720
27
+ TABLE_INDENT_DXA = 120
28
+ TABLE_WIDTH_DXA = PAGE_WIDTH_DXA - MARGIN_DXA * 2 - TABLE_INDENT_DXA
29
+
30
+
31
+ def set_run_font(run, size=9.2, bold=False, color=INK):
32
+ run.font.name = FONT
33
+ rfonts = run._element.get_or_add_rPr().get_or_add_rFonts()
34
+ for key in ("w:eastAsia", "w:ascii", "w:hAnsi"):
35
+ rfonts.set(qn(key), FONT)
36
+ run.font.size = Pt(size)
37
+ run.font.bold = bold
38
+ run.font.color.rgb = RGBColor.from_string(color)
39
+
40
+
41
+ def format_paragraph(paragraph, before=0, after=0, line=1.05, align=None):
42
+ fmt = paragraph.paragraph_format
43
+ fmt.space_before = Pt(before)
44
+ fmt.space_after = Pt(after)
45
+ fmt.line_spacing = line
46
+ if align is not None:
47
+ paragraph.alignment = align
48
+
49
+
50
+ def add_paragraph(doc, text, size=9.2, bold=False, color=INK, before=0, after=2, line=1.05, align=None):
51
+ paragraph = doc.add_paragraph()
52
+ format_paragraph(paragraph, before, after, line, align)
53
+ set_run_font(paragraph.add_run(text), size, bold, color)
54
+ return paragraph
55
+
56
+
57
+ def set_cell_shading(cell, fill):
58
+ tc_pr = cell._tc.get_or_add_tcPr()
59
+ shd = tc_pr.find(qn("w:shd"))
60
+ if shd is None:
61
+ shd = OxmlElement("w:shd")
62
+ tc_pr.append(shd)
63
+ shd.set(qn("w:fill"), fill)
64
+
65
+
66
+ def set_cell_margins(cell, top=70, start=110, bottom=70, end=110):
67
+ tc_pr = cell._tc.get_or_add_tcPr()
68
+ tc_mar = tc_pr.first_child_found_in("w:tcMar")
69
+ if tc_mar is None:
70
+ tc_mar = OxmlElement("w:tcMar")
71
+ tc_pr.append(tc_mar)
72
+ for name, value in (("top", top), ("start", start), ("bottom", bottom), ("end", end)):
73
+ element = tc_mar.find(qn(f"w:{name}"))
74
+ if element is None:
75
+ element = OxmlElement(f"w:{name}")
76
+ tc_mar.append(element)
77
+ element.set(qn("w:w"), str(value))
78
+ element.set(qn("w:type"), "dxa")
79
+
80
+
81
+ def set_cell_width(cell, width):
82
+ tc_pr = cell._tc.get_or_add_tcPr()
83
+ tc_w = tc_pr.find(qn("w:tcW"))
84
+ if tc_w is None:
85
+ tc_w = OxmlElement("w:tcW")
86
+ tc_pr.append(tc_w)
87
+ tc_w.set(qn("w:w"), str(width))
88
+ tc_w.set(qn("w:type"), "dxa")
89
+
90
+
91
+ def set_table_geometry(table, widths, indent=TABLE_INDENT_DXA):
92
+ total = sum(widths)
93
+ table.autofit = False
94
+ table.alignment = WD_TABLE_ALIGNMENT.LEFT
95
+ tbl_pr = table._tbl.tblPr
96
+ tbl_w = tbl_pr.find(qn("w:tblW"))
97
+ if tbl_w is None:
98
+ tbl_w = OxmlElement("w:tblW")
99
+ tbl_pr.append(tbl_w)
100
+ tbl_w.set(qn("w:w"), str(total))
101
+ tbl_w.set(qn("w:type"), "dxa")
102
+ tbl_ind = tbl_pr.find(qn("w:tblInd"))
103
+ if tbl_ind is None:
104
+ tbl_ind = OxmlElement("w:tblInd")
105
+ tbl_pr.append(tbl_ind)
106
+ tbl_ind.set(qn("w:w"), str(indent))
107
+ tbl_ind.set(qn("w:type"), "dxa")
108
+ grid = table._tbl.tblGrid
109
+ for child in list(grid):
110
+ grid.remove(child)
111
+ for width in widths:
112
+ col = OxmlElement("w:gridCol")
113
+ col.set(qn("w:w"), str(width))
114
+ grid.append(col)
115
+ for row in table.rows:
116
+ for index, cell in enumerate(row.cells):
117
+ set_cell_width(cell, widths[index])
118
+ set_cell_margins(cell)
119
+ cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
120
+
121
+
122
+ def set_table_borders(table, color=BORDER, size="6"):
123
+ tbl_pr = table._tbl.tblPr
124
+ borders = tbl_pr.first_child_found_in("w:tblBorders")
125
+ if borders is None:
126
+ borders = OxmlElement("w:tblBorders")
127
+ tbl_pr.append(borders)
128
+ for edge in ("top", "left", "bottom", "right", "insideH", "insideV"):
129
+ tag = f"w:{edge}"
130
+ element = borders.find(qn(tag))
131
+ if element is None:
132
+ element = OxmlElement(tag)
133
+ borders.append(element)
134
+ element.set(qn("w:val"), "single")
135
+ element.set(qn("w:sz"), size)
136
+ element.set(qn("w:space"), "0")
137
+ element.set(qn("w:color"), color)
138
+
139
+
140
+ def add_section_heading(doc, text):
141
+ return add_paragraph(doc, text, size=10.8, bold=True, color=BLUE, before=4, after=1, line=1.0)
142
+
143
+
144
+ def add_bullet(doc, label, detail):
145
+ paragraph = doc.add_paragraph(style="List Bullet")
146
+ format_paragraph(paragraph, before=0, after=1, line=1.03)
147
+ set_run_font(paragraph.add_run(f"{label}:"), 8.8, True, NAVY)
148
+ set_run_font(paragraph.add_run(detail), 8.8, False, INK)
149
+
150
+
151
+ def add_metadata_table(doc, config):
152
+ table = doc.add_table(rows=1, cols=4)
153
+ widths = [TABLE_WIDTH_DXA // 4] * 4
154
+ widths[-1] += TABLE_WIDTH_DXA - sum(widths)
155
+ values = [
156
+ ("版本", config["version"]),
157
+ ("日期", config["date"]),
158
+ ("开发周期", config["development_cycle"]),
159
+ ("报价有效期", f'{config["validity_days"]} 天'),
160
+ ]
161
+ for index, (label, value) in enumerate(values):
162
+ cell = table.cell(0, index)
163
+ set_cell_shading(cell, PALE_GRAY)
164
+ paragraph = cell.paragraphs[0]
165
+ format_paragraph(paragraph, after=0, line=1.0, align=WD_ALIGN_PARAGRAPH.CENTER)
166
+ set_run_font(paragraph.add_run(f"{label}:"), 8.0, True, MUTED)
167
+ set_run_font(paragraph.add_run(str(value)), 8.2, True, NAVY)
168
+ set_table_geometry(table, widths)
169
+ set_table_borders(table, "E2E8F0", "4")
170
+
171
+
172
+ def add_callout(doc, label, detail, fill=PALE_BLUE):
173
+ table = doc.add_table(rows=1, cols=1)
174
+ set_table_geometry(table, [TABLE_WIDTH_DXA])
175
+ set_table_borders(table, "9CC2E5", "7")
176
+ cell = table.cell(0, 0)
177
+ set_cell_shading(cell, fill)
178
+ paragraph = cell.paragraphs[0]
179
+ format_paragraph(paragraph, after=0, line=1.03)
180
+ set_run_font(paragraph.add_run(f"{label}:"), 8.8, True, NAVY)
181
+ set_run_font(paragraph.add_run(detail), 8.8, False, INK)
182
+
183
+
184
+ def add_quote_table(doc, config):
185
+ table = doc.add_table(rows=2, cols=3)
186
+ widths = [TABLE_WIDTH_DXA // 3] * 3
187
+ widths[-1] += TABLE_WIDTH_DXA - sum(widths)
188
+ headers = ["市场预估价(总价)", "吾码优惠报价(总价)", "开发周期"]
189
+ values = [config["market_price"], config["preferential_price"], config["development_cycle"]]
190
+ for index, header in enumerate(headers):
191
+ cell = table.cell(0, index)
192
+ set_cell_shading(cell, NAVY)
193
+ paragraph = cell.paragraphs[0]
194
+ format_paragraph(paragraph, after=0, line=1.0, align=WD_ALIGN_PARAGRAPH.CENTER)
195
+ set_run_font(paragraph.add_run(header), 8.7, True, WHITE)
196
+ for index, value in enumerate(values):
197
+ cell = table.cell(1, index)
198
+ set_cell_shading(cell, PALE_GOLD if index == 1 else PALE_GRAY)
199
+ paragraph = cell.paragraphs[0]
200
+ format_paragraph(paragraph, after=0, line=1.0, align=WD_ALIGN_PARAGRAPH.CENTER)
201
+ set_run_font(paragraph.add_run(str(value)), 12.0 if index == 1 else 10.5, True, GOLD if index == 1 else NAVY)
202
+ set_table_geometry(table, widths)
203
+ set_table_borders(table)
204
+
205
+
206
+ def normalize_config(raw):
207
+ required = [
208
+ "system_name", "project_goal", "solution_items", "technical_route",
209
+ "deliverables", "market_price", "preferential_price",
210
+ "development_cycle", "boundary_note",
211
+ ]
212
+ missing = [name for name in required if not raw.get(name)]
213
+ if missing:
214
+ raise ValueError("Missing required fields: " + ", ".join(missing))
215
+ if not isinstance(raw["solution_items"], list) or not raw["solution_items"]:
216
+ raise ValueError("solution_items must be a non-empty list")
217
+ raw.setdefault("version", "v1.0.0")
218
+ raw.setdefault("date", date.today().isoformat())
219
+ raw.setdefault("validity_days", 15)
220
+ raw.setdefault("subtitle", "AI 深度融合 V8引擎 的低代码平台,官网:https://microi.net")
221
+ raw.setdefault("project_scope", "")
222
+ return raw
223
+
224
+
225
+ def build_document(config, output):
226
+ doc = Document()
227
+ section = doc.sections[0]
228
+ section.page_width = Inches(8.27)
229
+ section.page_height = Inches(11.69)
230
+ section.top_margin = Inches(0.5)
231
+ section.bottom_margin = Inches(0.5)
232
+ section.left_margin = Inches(0.5)
233
+ section.right_margin = Inches(0.5)
234
+ section.header_distance = Inches(0.2)
235
+ section.footer_distance = Inches(0.2)
236
+
237
+ normal = doc.styles["Normal"]
238
+ normal.font.name = FONT
239
+ normal._element.rPr.rFonts.set(qn("w:eastAsia"), FONT)
240
+ normal.font.size = Pt(9.2)
241
+ normal.font.color.rgb = RGBColor.from_string(INK)
242
+
243
+ title = f'Microi吾码 {config["system_name"]} 解决方案与报价'
244
+ add_paragraph(doc, title, size=20.0, bold=True, color=NAVY, after=1, line=1.0, align=WD_ALIGN_PARAGRAPH.CENTER)
245
+ add_paragraph(doc, config["subtitle"], size=9.3, color=MUTED, after=4, line=1.0, align=WD_ALIGN_PARAGRAPH.CENTER)
246
+ add_metadata_table(doc, config)
247
+
248
+ add_section_heading(doc, "一、项目目标")
249
+ add_callout(doc, "目标", config["project_goal"])
250
+
251
+ add_section_heading(doc, "二、解决方案")
252
+ for item in config["solution_items"]:
253
+ if isinstance(item, str):
254
+ add_bullet(doc, "方案", item)
255
+ else:
256
+ add_bullet(doc, item.get("label", "方案"), item.get("detail", ""))
257
+
258
+ add_section_heading(doc, "三、核心技术路线")
259
+ add_callout(doc, "链路", config["technical_route"], fill=PALE_GRAY)
260
+
261
+ add_section_heading(doc, "四、交付范围")
262
+ add_paragraph(doc, config["deliverables"], size=8.7, after=1, line=1.03)
263
+ if config["project_scope"]:
264
+ add_paragraph(doc, config["project_scope"], size=8.3, color=MUTED, after=1, line=1.0)
265
+
266
+ add_section_heading(doc, "五、项目总价与周期")
267
+ add_quote_table(doc, config)
268
+
269
+ add_section_heading(doc, "六、说明与边界")
270
+ add_paragraph(doc, config["boundary_note"], size=8.1, color=MUTED, after=0, line=1.0)
271
+
272
+ footer = section.footer.paragraphs[0]
273
+ format_paragraph(footer, after=0, line=1.0, align=WD_ALIGN_PARAGRAPH.RIGHT)
274
+ set_run_font(footer.add_run(f'Microi吾码 | {config["version"]} | {config["date"]} | https://microi.net'), 7.2, False, MUTED)
275
+
276
+ doc.core_properties.title = title
277
+ doc.core_properties.subject = "Microi吾码系统解决方案与报价"
278
+ doc.core_properties.author = "Microi吾码"
279
+ output.parent.mkdir(parents=True, exist_ok=True)
280
+ doc.save(output)
281
+
282
+
283
+ def main():
284
+ parser = argparse.ArgumentParser(description="Build a compact Microi solution quotation DOCX")
285
+ parser.add_argument("--input", required=True, help="UTF-8 JSON configuration file")
286
+ parser.add_argument("--output", required=True, help="Output DOCX path")
287
+ args = parser.parse_args()
288
+ with open(args.input, "r", encoding="utf-8") as handle:
289
+ config = normalize_config(json.load(handle))
290
+ output = Path(args.output).resolve()
291
+ build_document(config, output)
292
+ print(output)
293
+
294
+
295
+ if __name__ == "__main__":
296
+ main()
@@ -2,7 +2,7 @@
2
2
 
3
3
  > 按需读取;本文件由 SKILL.md 的原章节无损拆分。
4
4
 
5
- <!-- microi-progressive:chunk id=microi-system-delivery-006 sha256=bc1fbdecb428b0a94d04d55d22e5c14f61354097cbaa2a059574f47fb41f5bdc -->
5
+ <!-- microi-progressive:chunk id=microi-system-delivery-006 sha256=3c429fa01c7c8b077ccb8bceca59ad0274f7b83e7989e2f21a861dd0e54f11aa -->
6
6
  ## 自动化测试必须覆盖的坑
7
7
 
8
8
  ### 后端统一测试与发布门禁
@@ -24,7 +24,7 @@
24
24
  `MICROI_TEST_ALLOW_WRITES=YES`。只有用户明确授权时才可对真实租户执行;
25
25
  真实租户默认只做登录和只读烟测,写入必须限定到名称/说明明确为自动化
26
26
  测试用途的专用表,并使用唯一前缀、`finally` 清理和清理回读。
27
- - Full 的本地程序与依赖按 `../../workspace-conventions/SKILL.md` 的“本地测试程序与依赖生命周期”执行:优先复用健康的本地源码程序、隔离目录中的编译产物和同组既有依赖,以租户、数据库或命名空间隔离测试,不按任务重复创建 Docker/Compose。每次测试成功、失败、取消或因资源不足暂停时,都结束本次自有测试进程并收回新建临时容器,独立回读进程、端口和容器状态;既有共享服务保持原状。这些生命周期要求不减少 Full 的用例、断言或发布门禁。
27
+ - Full 的本地程序与依赖按 `../../workspace-conventions/SKILL.md` 的“本地测试程序与依赖生命周期”执行:优先复用健康的本地源码程序、隔离目录中的编译产物和同组既有依赖,以租户、数据库或命名空间隔离测试,不按任务重复创建 Docker/Compose。每次测试成功、失败、取消或因资源不足暂停时,都结束本次自有测试进程并收回新建临时容器,独立回读进程、端口和容器状态;既有共享服务保持原状。这些生命周期要求不减少当前选定 Full 矩阵的用例、断言或发布门禁。
28
28
  - Full 至少覆盖完整 Release 构建、FormEngine 单条/批量/按条件 CRUD、
29
29
  查询和计数、ApiEngine GET/JSON POST、API 实际启动与健康检查、真实登录、
30
30
  匿名系统配置脱敏、清理回读、NuGet 漏洞/弃用审计。NuGet 全面升级后不能
@@ -41,6 +41,24 @@
41
41
  Redis/MongoDB 短暂故障、响应前重启和新旧版本共存。
42
42
  两节点默认是两个本机源码或编译产物程序进程连接同组依赖,不等于两套 Docker 环境;破坏性故障测试仅作用于明确归属的隔离资源,不能停止复用的共享数据库或缓存。
43
43
 
44
+ ### 按指定 MCP 选择发布测试矩阵
45
+
46
+ 测试范围以 `workspace-conventions/SKILL.md` 的“平台回归与独立应用测试归属”为准。常规发布先核实用户指定的 Profile、ApiBase、OsClient 和实际依赖,再冻结本次必测清单;不能从本机恰好存在的连接或历史 Full 清单推导额外兼容矩阵。
47
+
48
+ | 依赖 | 常规发布范围 | 验收依据 |
49
+ |---|---|---|
50
+ | MySQL | 指定 MCP 实际使用的服务与版本 | 隔离数据的 CRUD、事务、升级兼容及清理回读 |
51
+ | MongoDB | 指定 MCP 实际使用的版本 | 实际读写、索引及本次功能相关持久化;不默认同时要求旧版和新版 |
52
+ | Redis | 指定 MCP 对应的实例与隔离命名空间 | 会话、缓存、TTL、锁或任务等本次链路,以及清理回读 |
53
+ | MinIO 或 OSS/HDFS | 该租户当前生效的提供商 | 实际上传、下载字节/哈希、公开/私有访问权限与自有测试对象清理;路由单测不能代替真实文件闭环 |
54
+ | SQL Server | 仅用户明确指定排查、修复或验收 SQL Server 问题时加入 | 对应版本、隔离连接与问题回归;常规发布标“不适用(未执行)” |
55
+
56
+ - 通用编译、离线单元/组件、安全、登录、业务闭环和候选源码/产物哈希校验仍按变更范围执行;指定依赖测试不能替代这些质量要求。
57
+ - 只有所选矩阵中的必测项才参与环境完整性与零失败、零跳过门禁。没有选择的 SQL Server 专项不属于失败、待办或需要再次申请的免测;不得报告为已通过。其他提供商或跨版本矩阵也只按明确的专项需求扩展。
58
+ - 混合 MySQL/SQL Server 用例必须按提供商独立选择夹具与断言;不能要求两种连接同时存在才运行 MySQL,也不能按整类排除而漏掉仍必测的 MySQL 行。
59
+ - 旧脚本若仍硬编码 SQL Server 必填、MongoDB 多版本齐备或全提供商清单,先按本规则适配矩阵选择和结果统计,再使用正式发布入口。禁止每轮要求用户重新批准 SQL Server 免测、只删除必填变量、吞掉 skipped/失败或绕过一键发布门禁。
60
+ - 记录目标 MCP、所选依赖/版本、用例数量、实际结果、不适用边界、候选哈希及资源回收证据;已选必测环境不可用时仍如实停止。生产故障注入或新增 Docker 依赖继续需要对应授权。
61
+
44
62
  ### 登录与账号密码
45
63
 
46
64
  - E2E 必须支持提前录入测试账号密码:`PW_TEST_ACCOUNT`、`PW_TEST_PASSWORD`、`PW_LOGIN_ENGINE`。