@microi.net/cli 5.9.3 → 5.9.5

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 (56) 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/.workbuddy-plugin/marketplace.json +2 -2
  5. package/.workbuddy-plugin/plugin.json +1 -1
  6. package/assets/build-meta.json +4 -4
  7. package/cordis.patch.yml +1 -1
  8. package/package.json +1 -1
  9. package/scripts/mcp-server.js +81 -81
  10. package/scripts/microi-skills.meta.json +243 -243
  11. package/skills/.microi-skills-version.json +2 -2
  12. package/skills/.progressive-disclosure-manifest.json +42 -33
  13. package/skills/ai-engine/agents/openai.yaml +4 -4
  14. package/skills/app-store/agents/openai.yaml +4 -4
  15. package/skills/datasource-engine/agents/openai.yaml +4 -4
  16. package/skills/job-engine/agents/openai.yaml +4 -4
  17. package/skills/message-notification/agents/openai.yaml +5 -5
  18. package/skills/microi-ai-app-auth.js +652 -652
  19. package/skills/microi-ai-application/agents/openai.yaml +4 -4
  20. package/skills/microi-codex/SKILL.md +11 -6
  21. package/skills/microi-codex-installer/agents/openai.yaml +7 -7
  22. package/skills/microi-db-schema/agents/openai.yaml +4 -4
  23. package/skills/microi-deployment/SKILL.md +6 -4
  24. package/skills/microi-docs-coverage/references/capability-map.md +1 -1
  25. package/skills/microi-docs-coverage/scripts/audit-doc-skill-coverage.mjs +894 -894
  26. package/skills/microi-microservice/SKILL.md +11 -4
  27. package/skills/microi-microservice/references/runtime-delivery.md +9 -5
  28. package/skills/microi-solution-quotation/agents/openai.yaml +4 -4
  29. package/skills/microi-solution-quotation/scripts/build_solution_quote.py +296 -296
  30. package/skills/microi-system-delivery/SKILL.md +2 -2
  31. 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 +3 -1
  32. package/skills/microi-uniapp-frontend/SKILL.md +3 -1
  33. package/skills/microi.v8.js +1921 -1921
  34. package/skills/ocr-engine/agents/openai.yaml +4 -4
  35. package/skills/page-engine/examples/compact-dashboard.json +1444 -1444
  36. package/skills/playwright-e2e/SKILL.md +5 -4
  37. package/skills/report-engine/agents/openai.yaml +4 -4
  38. package/skills/scripts/optimize-progressive-disclosure.mjs +204 -204
  39. package/skills/scripts/refresh-progressive-disclosure.mjs +64 -64
  40. package/skills/scripts/sync-embedded-skills.mjs +46 -46
  41. package/skills/scripts/validate-progressive-disclosure.mjs +57 -57
  42. package/skills/search-engine/agents/openai.yaml +4 -4
  43. package/skills/translate-engine/agents/openai.yaml +4 -4
  44. package/skills/ui-design/assets/pattern-showcase/app.js +54 -54
  45. package/skills/ui-design/assets/pattern-showcase/index.html +163 -163
  46. package/skills/ui-design/assets/pattern-showcase/styles.css +311 -311
  47. package/skills/unity-integration/agents/openai.yaml +4 -4
  48. package/skills/v8-file-upload/SKILL.md +2 -0
  49. package/skills/v8-image-processing/agents/openai.yaml +4 -4
  50. package/skills/v8-mongodb/SKILL.md +2 -1
  51. package/skills/v8-mq-mqtt/scripts/check-mqtt-skill-coverage.mjs +203 -203
  52. package/skills/v8-security/SKILL.md +6 -0
  53. package/skills/v8-tcp-integration/agents/openai.yaml +4 -4
  54. package/skills/vision-engine/agents/openai.yaml +4 -4
  55. package/skills/workspace-conventions/SKILL.md +4 -3
  56. package/skills/workspace-conventions/references/progressive-02-microi-net-api-/346/234/254/345/234/260/345/220/257/345/212/250/347/272/246/345/256/232.md +13 -0
@@ -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()
@@ -35,14 +35,14 @@ Manifest 使用 `tables[].formBanner`;未显式配置时仍按字段类型选
35
35
  模块引擎或 `sys_menu`。逐步建模在字段完成后调用 `microi_configure_form_banner` 回读验收。
36
36
 
37
37
  <!-- microi-progressive:begin -->
38
- <!-- microi-progressive:chunk id=microi-system-delivery-000 sha256=bdbc78e88e50c53a3bd29eeb76ce34e3fed8eec5f4ba16371417d2aeee9c2fbe -->
38
+ <!-- microi-progressive:chunk id=microi-system-delivery-000 sha256=04f08fdc316d253f167e26923066e134763ab129dd830f833c762214613c607f -->
39
39
  ## 交付总原则
40
40
 
41
41
  1. **先事实源,后建模**:先读需求文档、截图、现有蓝图、数据库结构和菜单结构,形成业务蓝图;不要边猜边建表、边猜边写接口。
42
42
  2. **先平台能力,后临时绕路**:能用 MCP/平台 API 修改元数据、字段、菜单、事件、接口引擎、数据源、任务调度,就不要新建临时 `_fix_*`、`_mcp_*` 接口引擎绕过平台能力。MCP 缺能力时优先补 MCP/后端,再补 Skill。
43
43
  3. **平台通用层禁止项目定制**:Microi.Server、Microi.Client、MCP、VS Code 插件、microi.skills 中不得写某个项目专属文案、专属上传限制、专属业务规则。项目规则应在租户表、接口引擎、业务配置、前端项目或业务蓝图中。
44
44
  4. **本地改完必须远端验收**:V8 文件、表单事件、菜单按钮、数据源、工作流、页面配置等不能只改本地文件。必须写入远端数据库,并通过 HTTP 或平台工具回读确认。
45
- 5. **测试不等于接口能通**:交付前必须跑构建、接口契约、真实登录、核心写流程、截图、人眼复核、网络 404/5xx 守卫、远端同步状态。
45
+ 5. **测试不等于接口能通**:交付前必须跑构建、接口契约、真实登录、核心写流程、截图、人眼复核、网络 404/5xx 守卫、远端同步状态。依赖默认按 `workspace-conventions` 的“平台回归与独立应用测试归属”使用当前 MCP 实际 MongoDB 版本与 HDFS 提供商;明确的兼容任务才扩展矩阵,未执行项如实记录。
46
46
  6. **生产数据谨慎优先**:涉及真实会员、余额、积分、订单、库存、卡券、收益、佣金、分润等资金/资产数据时,先确认服务器、租户、目标表、筛选条件和影响范围,再执行 MCP、接口引擎或 SQL 写操作。除用户明确要求清理/重置或必须修复错误数据外,不得擅自批量改真实数据。
47
47
  7. **默认按分布式交付**:任何后端能力都假设会有至少两个节点连接同一数据库和 Redis,并经历滚动升级、重复投递与节点硬重启。定时任务/消费者使用分布式租约且业务本身幂等;会话、票据和任务状态使用共享存储;新旧版本并存时采用“先扩展、后迁移、再收缩”。只在单节点验证通过不能视为完整交付。
48
48
  8. **业务逻辑默认接口引擎、元数据升级默认应用商城**:先用低代码 CRUD/事件,再用接口引擎;只缺底层原子能力时先扩展 V8,只有协议/鉴权/密钥隔离/运行时内核才进入 C#。表、字段、Tab、菜单、权限、引擎、页面和任务等可打包资源不得新增 `Microi.Upgrade` 定制类;官方开发者通过 `microi_itdos` 发布官方应用后再由目标租户 MCP 更新,无官方权限时只升级自己的租户。
@@ -2,7 +2,7 @@
2
2
 
3
3
  > 按需读取;本文件由 SKILL.md 的原章节无损拆分。
4
4
 
5
- <!-- microi-progressive:chunk id=microi-system-delivery-006 sha256=4b999e29e04c192319690a886ba25bb8952d5cc1910db8fa151b583d599b2be5 -->
5
+ <!-- microi-progressive:chunk id=microi-system-delivery-006 sha256=1dd7c9face286616c392ece855acac7d1a3358afbad904a4ff6da5f10c0a8e01 -->
6
6
  ## 自动化测试必须覆盖的坑
7
7
 
8
8
  ### 后端统一测试与发布门禁
@@ -24,6 +24,7 @@
24
24
  `MICROI_TEST_ALLOW_WRITES=YES`。只有用户明确授权时才可对真实租户执行;
25
25
  真实租户默认只做登录和只读烟测,写入必须限定到名称/说明明确为自动化
26
26
  测试用途的专用表,并使用唯一前缀、`finally` 清理和清理回读。
27
+ - Full 的本地程序与依赖按 `../../workspace-conventions/SKILL.md` 的“本地测试程序与依赖生命周期”执行:优先复用健康的本地源码程序、隔离目录中的编译产物和同组既有依赖,以租户、数据库或命名空间隔离测试,不按任务重复创建 Docker/Compose。每次测试成功、失败、取消或因资源不足暂停时,都结束本次自有测试进程并收回新建临时容器,独立回读进程、端口和容器状态;既有共享服务保持原状。这些生命周期要求不减少 Full 的用例、断言或发布门禁。
27
28
  - Full 至少覆盖完整 Release 构建、FormEngine 单条/批量/按条件 CRUD、
28
29
  查询和计数、ApiEngine GET/JSON POST、API 实际启动与健康检查、真实登录、
29
30
  匿名系统配置脱敏、清理回读、NuGet 漏洞/弃用审计。NuGet 全面升级后不能
@@ -38,6 +39,7 @@
38
39
  - 即使 Full 通过,涉及定时任务、消息消费、共享缓存、迁移或滚动发布时,
39
40
  仍需两个节点连接同一 Redis/数据库,覆盖重复投递、锁持有者退出、
40
41
  Redis/MongoDB 短暂故障、响应前重启和新旧版本共存。
42
+ 两节点默认是两个本机源码或编译产物程序进程连接同组依赖,不等于两套 Docker 环境;破坏性故障测试仅作用于明确归属的隔离资源,不能停止复用的共享数据库或缓存。
41
43
 
42
44
  ### 登录与账号密码
43
45