@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
@@ -15,6 +15,8 @@ description: 按 Microi 系统真实业务逻辑进行 Playwright 全自动化
15
15
 
16
16
  `E2E` 是 `End-to-End`,中文通常叫"端到端测试"。它强调从用户入口开始,穿过前端页面、接口引擎、表单引擎、权限、缓存、数据库等真实链路,验证一条业务路径是否真的可交付。
17
17
 
18
+ 真实依赖与发布范围沿用 `../workspace-conventions/SKILL.md` 的“平台回归与独立应用测试归属”:常规验证指定 MCP 实际使用的 MySQL、MongoDB、Redis 和 MinIO 或 OSS/HDFS,不要求额外 SQL Server 环境;仅用户明确指定 SQL Server 问题时才做其专项。按 [发布测试矩阵](../microi-system-delivery/references/progressive-02-自动化测试必须覆盖的坑.md#按指定-mcp-选择发布测试矩阵) 记录实际覆盖,保留正常登录、业务闭环与资源收尾,不以连接成功代替真实读写验收。
19
+
18
20
  <!-- microi-progressive:begin -->
19
21
  <!-- microi-progressive:chunk id=playwright-e2e-000 sha256=a6ef528a2a940fd0f58efaee6b73eb5dbc159db93d95afedf41c7b8642536d18 -->
20
22
  ## 是否需要 `-e2e` 后缀
@@ -1,4 +1,4 @@
1
- interface:
2
- display_name: "报表引擎"
3
- short_description: "设计遵守菜单与行级权限的报表、聚合接口、写操作与导出验收"
4
- default_prompt: "使用 $report-engine 设计并验收当前 Microi 报表。"
1
+ interface:
2
+ display_name: "报表引擎"
3
+ short_description: "设计遵守菜单与行级权限的报表、聚合接口、写操作与导出验收"
4
+ default_prompt: "使用 $report-engine 设计并验收当前 Microi 报表。"
@@ -1,204 +1,204 @@
1
- import crypto from 'node:crypto'
2
- import fs from 'node:fs'
3
- import path from 'node:path'
4
- import { fileURLToPath } from 'node:url'
5
-
6
- const skillsRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
7
- const manifestPath = path.join(skillsRoot, '.progressive-disclosure-manifest.json')
8
- const maxEntryLines = 285
9
- const keepBudgetLines = 170
10
- const referenceBudgetLines = 245
11
-
12
- const hash = (value) => crypto.createHash('sha256').update(value).digest('hex')
13
- const lineCount = (value) => value.length === 0 ? 0 : value.split(/\r?\n/).length
14
- const slug = (value) => value
15
- .replace(/^#+\s*/, '')
16
- .replace(/[`*_]/g, '')
17
- .trim()
18
- .toLowerCase()
19
- .replace(/[^\p{L}\p{N}]+/gu, '-')
20
- .replace(/^-|-$/g, '')
21
- .slice(0, 48) || 'reference'
22
-
23
- const splitAtHeading = (value, headingPattern) => {
24
- const matches = [...value.matchAll(headingPattern)]
25
- if (matches.length <= 1) return [value]
26
- const parts = []
27
- if (matches[0].index > 0) parts.push(value.slice(0, matches[0].index))
28
- for (let index = 0; index < matches.length; index += 1) {
29
- parts.push(value.slice(matches[index].index, matches[index + 1]?.index ?? value.length))
30
- }
31
- return parts.filter(Boolean)
32
- }
33
-
34
- const splitOversized = (value, eol) => {
35
- if (lineCount(value) <= referenceBudgetLines) return [value]
36
- let parts = splitAtHeading(value, /^###\s+.+$/gm)
37
- if (parts.length === 1) parts = splitAtHeading(value, /^####\s+.+$/gm)
38
- const output = []
39
- for (const part of parts) {
40
- if (lineCount(part) <= referenceBudgetLines) {
41
- output.push(part)
42
- continue
43
- }
44
- const lines = part.split(/\r?\n/)
45
- let start = 0
46
- while (start < lines.length) {
47
- let end = Math.min(start + referenceBudgetLines - 8, lines.length)
48
- if (end < lines.length) {
49
- for (let cursor = end; cursor > start + 40; cursor -= 1) {
50
- if (lines[cursor - 1].trim() === '') {
51
- end = cursor
52
- break
53
- }
54
- }
55
- }
56
- const hasTrailingBreak = end < lines.length || part.endsWith(eol)
57
- output.push(lines.slice(start, end).join(eol) + (hasTrailingBreak ? eol : ''))
58
- start = end
59
- }
60
- }
61
- return output
62
- }
63
-
64
- const headingOf = (value, fallback) => value.match(/^#{2,4}\s+(.+)$/m)?.[1]?.trim() || fallback
65
-
66
- const skillDirectories = fs.readdirSync(skillsRoot, { withFileTypes: true })
67
- .filter((entry) => entry.isDirectory() && fs.existsSync(path.join(skillsRoot, entry.name, 'SKILL.md')))
68
- .map((entry) => entry.name)
69
- .sort()
70
-
71
- const manifest = {
72
- version: 1,
73
- generatedAt: new Date().toISOString(),
74
- policy: {
75
- maxEntryLines,
76
- referenceBudgetLines,
77
- note: 'Original prefix and ordered chunks reconstruct the exact pre-split SKILL.md bytes.'
78
- },
79
- skills: {}
80
- }
81
-
82
- for (const skillName of skillDirectories) {
83
- const skillPath = path.join(skillsRoot, skillName, 'SKILL.md')
84
- const original = fs.readFileSync(skillPath, 'utf8')
85
- if (lineCount(original) <= 300) continue
86
- if (original.includes('<!-- microi-progressive:begin -->')) {
87
- throw new Error(`${skillName} is already optimized; validate instead of running twice`)
88
- }
89
-
90
- const eol = original.includes('\r\n') ? '\r\n' : '\n'
91
- const sectionMatches = [...original.matchAll(/^##\s+.+$/gm)]
92
- if (sectionMatches.length < 2) {
93
- throw new Error(`${skillName} needs at least two level-2 sections for safe semantic splitting`)
94
- }
95
- const prefix = original.slice(0, sectionMatches[0].index)
96
- const sections = sectionMatches.map((match, index) => ({
97
- heading: match[0].replace(/^##\s+/, '').trim(),
98
- raw: original.slice(match.index, sectionMatches[index + 1]?.index ?? original.length),
99
- }))
100
-
101
- const kept = []
102
- const moved = []
103
- let keptLines = lineCount(prefix)
104
- for (const section of sections) {
105
- // Keep one contiguous prefix only. Picking a later short section after an
106
- // earlier section was moved would reorder the original knowledge stream.
107
- if (moved.length === 0 && (kept.length === 0 || keptLines + lineCount(section.raw) <= keepBudgetLines)) {
108
- kept.push(section)
109
- keptLines += lineCount(section.raw)
110
- } else {
111
- moved.push(section)
112
- }
113
- }
114
- if (moved.length === 0) {
115
- moved.push(kept.pop())
116
- }
117
-
118
- const fragments = moved.flatMap((section) => splitOversized(section.raw, eol).map((raw, index) => ({
119
- raw,
120
- heading: headingOf(raw, `${section.heading} (${index + 1})`),
121
- })))
122
-
123
- const referenceGroups = []
124
- let current = []
125
- let currentLines = 5
126
- for (const fragment of fragments) {
127
- const cost = lineCount(fragment.raw) + 3
128
- if (current.length && currentLines + cost > referenceBudgetLines) {
129
- referenceGroups.push(current)
130
- current = []
131
- currentLines = 5
132
- }
133
- current.push(fragment)
134
- currentLines += cost
135
- }
136
- if (current.length) referenceGroups.push(current)
137
-
138
- const referencesDirectory = path.join(skillsRoot, skillName, 'references')
139
- fs.mkdirSync(referencesDirectory, { recursive: true })
140
- const chunkRecords = []
141
- let ordinal = 0
142
- const wrapChunk = (raw, destination) => {
143
- const id = `${skillName}-${String(ordinal).padStart(3, '0')}`
144
- const record = {
145
- id,
146
- ordinal,
147
- destination: destination.replaceAll('\\', '/'),
148
- sha256: hash(raw),
149
- lines: lineCount(raw),
150
- heading: headingOf(raw, skillName),
151
- }
152
- chunkRecords.push(record)
153
- ordinal += 1
154
- return `<!-- microi-progressive:chunk id=${id} sha256=${record.sha256} -->${eol}${raw}<!-- /microi-progressive:chunk -->${eol}`
155
- }
156
-
157
- const entryChunks = kept.map((section) => wrapChunk(section.raw, `${skillName}/SKILL.md`)).join('')
158
- const referenceRecords = []
159
- referenceGroups.forEach((group, index) => {
160
- const fileName = `progressive-${String(index + 1).padStart(2, '0')}-${slug(group[0].heading)}.md`
161
- const relative = `references/${fileName}`
162
- const destination = `${skillName}/${relative}`
163
- const body = group.map((fragment) => wrapChunk(fragment.raw, destination)).join('')
164
- const title = `# ${skillName} 详细参考 ${index + 1}${eol}${eol}`
165
- const note = `> 按需读取;本文件由 SKILL.md 的原章节无损拆分。${eol}${eol}`
166
- fs.writeFileSync(path.join(referencesDirectory, fileName), title + note + body, 'utf8')
167
- referenceRecords.push({
168
- path: relative,
169
- headings: group.map((fragment) => fragment.heading),
170
- })
171
- })
172
-
173
- const routerLines = [
174
- '## 详细参考路由(渐进披露)',
175
- '',
176
- '仅在当前任务涉及对应主题时读取;下列文件合计保留了原 SKILL.md 的全部详细知识。',
177
- '',
178
- ...referenceRecords.map((reference) =>
179
- `- [${reference.path}](${reference.path}):${reference.headings.join(';')}`),
180
- '',
181
- ].join(eol)
182
- const rewritten = prefix
183
- + `<!-- microi-progressive:begin -->${eol}`
184
- + entryChunks
185
- + routerLines
186
- + `<!-- microi-progressive:end -->${eol}`
187
- if (lineCount(rewritten) > maxEntryLines) {
188
- throw new Error(`${skillName} entry remains ${lineCount(rewritten)} lines; lower keepBudgetLines`)
189
- }
190
- fs.writeFileSync(skillPath, rewritten, 'utf8')
191
-
192
- manifest.skills[skillName] = {
193
- originalSha256: hash(original),
194
- originalLines: lineCount(original),
195
- prefixSha256: hash(prefix),
196
- prefixBytes: Buffer.byteLength(prefix),
197
- entryLines: lineCount(rewritten),
198
- chunks: chunkRecords,
199
- references: referenceRecords,
200
- }
201
- }
202
-
203
- fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, 'utf8')
204
- console.log(`Optimized ${Object.keys(manifest.skills).length} skills; manifest: ${manifestPath}`)
1
+ import crypto from 'node:crypto'
2
+ import fs from 'node:fs'
3
+ import path from 'node:path'
4
+ import { fileURLToPath } from 'node:url'
5
+
6
+ const skillsRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
7
+ const manifestPath = path.join(skillsRoot, '.progressive-disclosure-manifest.json')
8
+ const maxEntryLines = 285
9
+ const keepBudgetLines = 170
10
+ const referenceBudgetLines = 245
11
+
12
+ const hash = (value) => crypto.createHash('sha256').update(value).digest('hex')
13
+ const lineCount = (value) => value.length === 0 ? 0 : value.split(/\r?\n/).length
14
+ const slug = (value) => value
15
+ .replace(/^#+\s*/, '')
16
+ .replace(/[`*_]/g, '')
17
+ .trim()
18
+ .toLowerCase()
19
+ .replace(/[^\p{L}\p{N}]+/gu, '-')
20
+ .replace(/^-|-$/g, '')
21
+ .slice(0, 48) || 'reference'
22
+
23
+ const splitAtHeading = (value, headingPattern) => {
24
+ const matches = [...value.matchAll(headingPattern)]
25
+ if (matches.length <= 1) return [value]
26
+ const parts = []
27
+ if (matches[0].index > 0) parts.push(value.slice(0, matches[0].index))
28
+ for (let index = 0; index < matches.length; index += 1) {
29
+ parts.push(value.slice(matches[index].index, matches[index + 1]?.index ?? value.length))
30
+ }
31
+ return parts.filter(Boolean)
32
+ }
33
+
34
+ const splitOversized = (value, eol) => {
35
+ if (lineCount(value) <= referenceBudgetLines) return [value]
36
+ let parts = splitAtHeading(value, /^###\s+.+$/gm)
37
+ if (parts.length === 1) parts = splitAtHeading(value, /^####\s+.+$/gm)
38
+ const output = []
39
+ for (const part of parts) {
40
+ if (lineCount(part) <= referenceBudgetLines) {
41
+ output.push(part)
42
+ continue
43
+ }
44
+ const lines = part.split(/\r?\n/)
45
+ let start = 0
46
+ while (start < lines.length) {
47
+ let end = Math.min(start + referenceBudgetLines - 8, lines.length)
48
+ if (end < lines.length) {
49
+ for (let cursor = end; cursor > start + 40; cursor -= 1) {
50
+ if (lines[cursor - 1].trim() === '') {
51
+ end = cursor
52
+ break
53
+ }
54
+ }
55
+ }
56
+ const hasTrailingBreak = end < lines.length || part.endsWith(eol)
57
+ output.push(lines.slice(start, end).join(eol) + (hasTrailingBreak ? eol : ''))
58
+ start = end
59
+ }
60
+ }
61
+ return output
62
+ }
63
+
64
+ const headingOf = (value, fallback) => value.match(/^#{2,4}\s+(.+)$/m)?.[1]?.trim() || fallback
65
+
66
+ const skillDirectories = fs.readdirSync(skillsRoot, { withFileTypes: true })
67
+ .filter((entry) => entry.isDirectory() && fs.existsSync(path.join(skillsRoot, entry.name, 'SKILL.md')))
68
+ .map((entry) => entry.name)
69
+ .sort()
70
+
71
+ const manifest = {
72
+ version: 1,
73
+ generatedAt: new Date().toISOString(),
74
+ policy: {
75
+ maxEntryLines,
76
+ referenceBudgetLines,
77
+ note: 'Original prefix and ordered chunks reconstruct the exact pre-split SKILL.md bytes.'
78
+ },
79
+ skills: {}
80
+ }
81
+
82
+ for (const skillName of skillDirectories) {
83
+ const skillPath = path.join(skillsRoot, skillName, 'SKILL.md')
84
+ const original = fs.readFileSync(skillPath, 'utf8')
85
+ if (lineCount(original) <= 300) continue
86
+ if (original.includes('<!-- microi-progressive:begin -->')) {
87
+ throw new Error(`${skillName} is already optimized; validate instead of running twice`)
88
+ }
89
+
90
+ const eol = original.includes('\r\n') ? '\r\n' : '\n'
91
+ const sectionMatches = [...original.matchAll(/^##\s+.+$/gm)]
92
+ if (sectionMatches.length < 2) {
93
+ throw new Error(`${skillName} needs at least two level-2 sections for safe semantic splitting`)
94
+ }
95
+ const prefix = original.slice(0, sectionMatches[0].index)
96
+ const sections = sectionMatches.map((match, index) => ({
97
+ heading: match[0].replace(/^##\s+/, '').trim(),
98
+ raw: original.slice(match.index, sectionMatches[index + 1]?.index ?? original.length),
99
+ }))
100
+
101
+ const kept = []
102
+ const moved = []
103
+ let keptLines = lineCount(prefix)
104
+ for (const section of sections) {
105
+ // Keep one contiguous prefix only. Picking a later short section after an
106
+ // earlier section was moved would reorder the original knowledge stream.
107
+ if (moved.length === 0 && (kept.length === 0 || keptLines + lineCount(section.raw) <= keepBudgetLines)) {
108
+ kept.push(section)
109
+ keptLines += lineCount(section.raw)
110
+ } else {
111
+ moved.push(section)
112
+ }
113
+ }
114
+ if (moved.length === 0) {
115
+ moved.push(kept.pop())
116
+ }
117
+
118
+ const fragments = moved.flatMap((section) => splitOversized(section.raw, eol).map((raw, index) => ({
119
+ raw,
120
+ heading: headingOf(raw, `${section.heading} (${index + 1})`),
121
+ })))
122
+
123
+ const referenceGroups = []
124
+ let current = []
125
+ let currentLines = 5
126
+ for (const fragment of fragments) {
127
+ const cost = lineCount(fragment.raw) + 3
128
+ if (current.length && currentLines + cost > referenceBudgetLines) {
129
+ referenceGroups.push(current)
130
+ current = []
131
+ currentLines = 5
132
+ }
133
+ current.push(fragment)
134
+ currentLines += cost
135
+ }
136
+ if (current.length) referenceGroups.push(current)
137
+
138
+ const referencesDirectory = path.join(skillsRoot, skillName, 'references')
139
+ fs.mkdirSync(referencesDirectory, { recursive: true })
140
+ const chunkRecords = []
141
+ let ordinal = 0
142
+ const wrapChunk = (raw, destination) => {
143
+ const id = `${skillName}-${String(ordinal).padStart(3, '0')}`
144
+ const record = {
145
+ id,
146
+ ordinal,
147
+ destination: destination.replaceAll('\\', '/'),
148
+ sha256: hash(raw),
149
+ lines: lineCount(raw),
150
+ heading: headingOf(raw, skillName),
151
+ }
152
+ chunkRecords.push(record)
153
+ ordinal += 1
154
+ return `<!-- microi-progressive:chunk id=${id} sha256=${record.sha256} -->${eol}${raw}<!-- /microi-progressive:chunk -->${eol}`
155
+ }
156
+
157
+ const entryChunks = kept.map((section) => wrapChunk(section.raw, `${skillName}/SKILL.md`)).join('')
158
+ const referenceRecords = []
159
+ referenceGroups.forEach((group, index) => {
160
+ const fileName = `progressive-${String(index + 1).padStart(2, '0')}-${slug(group[0].heading)}.md`
161
+ const relative = `references/${fileName}`
162
+ const destination = `${skillName}/${relative}`
163
+ const body = group.map((fragment) => wrapChunk(fragment.raw, destination)).join('')
164
+ const title = `# ${skillName} 详细参考 ${index + 1}${eol}${eol}`
165
+ const note = `> 按需读取;本文件由 SKILL.md 的原章节无损拆分。${eol}${eol}`
166
+ fs.writeFileSync(path.join(referencesDirectory, fileName), title + note + body, 'utf8')
167
+ referenceRecords.push({
168
+ path: relative,
169
+ headings: group.map((fragment) => fragment.heading),
170
+ })
171
+ })
172
+
173
+ const routerLines = [
174
+ '## 详细参考路由(渐进披露)',
175
+ '',
176
+ '仅在当前任务涉及对应主题时读取;下列文件合计保留了原 SKILL.md 的全部详细知识。',
177
+ '',
178
+ ...referenceRecords.map((reference) =>
179
+ `- [${reference.path}](${reference.path}):${reference.headings.join(';')}`),
180
+ '',
181
+ ].join(eol)
182
+ const rewritten = prefix
183
+ + `<!-- microi-progressive:begin -->${eol}`
184
+ + entryChunks
185
+ + routerLines
186
+ + `<!-- microi-progressive:end -->${eol}`
187
+ if (lineCount(rewritten) > maxEntryLines) {
188
+ throw new Error(`${skillName} entry remains ${lineCount(rewritten)} lines; lower keepBudgetLines`)
189
+ }
190
+ fs.writeFileSync(skillPath, rewritten, 'utf8')
191
+
192
+ manifest.skills[skillName] = {
193
+ originalSha256: hash(original),
194
+ originalLines: lineCount(original),
195
+ prefixSha256: hash(prefix),
196
+ prefixBytes: Buffer.byteLength(prefix),
197
+ entryLines: lineCount(rewritten),
198
+ chunks: chunkRecords,
199
+ references: referenceRecords,
200
+ }
201
+ }
202
+
203
+ fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, 'utf8')
204
+ console.log(`Optimized ${Object.keys(manifest.skills).length} skills; manifest: ${manifestPath}`)
@@ -1,64 +1,64 @@
1
- import crypto from 'node:crypto'
2
- import fs from 'node:fs'
3
- import path from 'node:path'
4
- import { fileURLToPath } from 'node:url'
5
-
6
- const skillsRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
7
- const manifestPath = path.join(skillsRoot, '.progressive-disclosure-manifest.json')
8
- const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'))
9
- const requested = process.argv.slice(2)
10
- if (requested.length === 0) {
11
- throw new Error('Pass each intentionally changed skill name explicitly.')
12
- }
13
- const hash = (value) => crypto.createHash('sha256').update(value).digest('hex')
14
-
15
- for (const skillName of requested) {
16
- const skill = manifest.skills[skillName]
17
- if (!skill) throw new Error(`${skillName} is not a progressive-disclosure skill`)
18
- const sources = new Map()
19
- const entryPath = path.join(skillsRoot, skillName, 'SKILL.md')
20
- sources.set(`${skillName}/SKILL.md`, fs.readFileSync(entryPath, 'utf8'))
21
- for (const reference of skill.references) {
22
- const destination = `${skillName}/${reference.path}`
23
- sources.set(destination, fs.readFileSync(path.join(skillsRoot, destination), 'utf8'))
24
- }
25
-
26
- const ordered = []
27
- for (const chunk of skill.chunks) {
28
- let source = sources.get(chunk.destination)
29
- const openPattern = new RegExp(`<!-- microi-progressive:chunk id=${chunk.id} sha256=[a-f0-9]{64} -->`)
30
- const match = source.match(openPattern)
31
- if (!match) throw new Error(`${skillName}: missing chunk ${chunk.id}`)
32
- const markerStart = match.index
33
- const markerEnd = markerStart + match[0].length
34
- const contentStart = markerEnd + (source.startsWith('\r\n', markerEnd) ? 2 : 1)
35
- const close = '<!-- /microi-progressive:chunk -->'
36
- const contentEnd = source.indexOf(close, contentStart)
37
- const raw = source.slice(contentStart, contentEnd)
38
- const digest = hash(raw)
39
- source = source.slice(0, markerStart)
40
- + `<!-- microi-progressive:chunk id=${chunk.id} sha256=${digest} -->`
41
- + source.slice(markerEnd)
42
- sources.set(chunk.destination, source)
43
- chunk.sha256 = digest
44
- chunk.lines = raw.split(/\r?\n/).length
45
- ordered[chunk.ordinal] = raw
46
- }
47
-
48
- for (const [destination, source] of sources) {
49
- fs.writeFileSync(path.join(skillsRoot, destination), source, 'utf8')
50
- }
51
- const entry = sources.get(`${skillName}/SKILL.md`)
52
- const begin = entry.indexOf('<!-- microi-progressive:begin -->')
53
- const prefix = entry.slice(0, begin)
54
- skill.prefixSha256 = hash(prefix)
55
- skill.prefixBytes = Buffer.byteLength(prefix)
56
- skill.originalSha256 = hash(prefix + ordered.join(''))
57
- skill.originalLines = (prefix + ordered.join('')).split(/\r?\n/).length
58
- skill.entryLines = entry.split(/\r?\n/).length
59
- }
60
-
61
- manifest.generatedAt = new Date().toISOString()
62
- fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, 'utf8')
63
- console.log(`Accepted intentional knowledge updates for: ${requested.join(', ')}`)
64
-
1
+ import crypto from 'node:crypto'
2
+ import fs from 'node:fs'
3
+ import path from 'node:path'
4
+ import { fileURLToPath } from 'node:url'
5
+
6
+ const skillsRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
7
+ const manifestPath = path.join(skillsRoot, '.progressive-disclosure-manifest.json')
8
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'))
9
+ const requested = process.argv.slice(2)
10
+ if (requested.length === 0) {
11
+ throw new Error('Pass each intentionally changed skill name explicitly.')
12
+ }
13
+ const hash = (value) => crypto.createHash('sha256').update(value).digest('hex')
14
+
15
+ for (const skillName of requested) {
16
+ const skill = manifest.skills[skillName]
17
+ if (!skill) throw new Error(`${skillName} is not a progressive-disclosure skill`)
18
+ const sources = new Map()
19
+ const entryPath = path.join(skillsRoot, skillName, 'SKILL.md')
20
+ sources.set(`${skillName}/SKILL.md`, fs.readFileSync(entryPath, 'utf8'))
21
+ for (const reference of skill.references) {
22
+ const destination = `${skillName}/${reference.path}`
23
+ sources.set(destination, fs.readFileSync(path.join(skillsRoot, destination), 'utf8'))
24
+ }
25
+
26
+ const ordered = []
27
+ for (const chunk of skill.chunks) {
28
+ let source = sources.get(chunk.destination)
29
+ const openPattern = new RegExp(`<!-- microi-progressive:chunk id=${chunk.id} sha256=[a-f0-9]{64} -->`)
30
+ const match = source.match(openPattern)
31
+ if (!match) throw new Error(`${skillName}: missing chunk ${chunk.id}`)
32
+ const markerStart = match.index
33
+ const markerEnd = markerStart + match[0].length
34
+ const contentStart = markerEnd + (source.startsWith('\r\n', markerEnd) ? 2 : 1)
35
+ const close = '<!-- /microi-progressive:chunk -->'
36
+ const contentEnd = source.indexOf(close, contentStart)
37
+ const raw = source.slice(contentStart, contentEnd)
38
+ const digest = hash(raw)
39
+ source = source.slice(0, markerStart)
40
+ + `<!-- microi-progressive:chunk id=${chunk.id} sha256=${digest} -->`
41
+ + source.slice(markerEnd)
42
+ sources.set(chunk.destination, source)
43
+ chunk.sha256 = digest
44
+ chunk.lines = raw.split(/\r?\n/).length
45
+ ordered[chunk.ordinal] = raw
46
+ }
47
+
48
+ for (const [destination, source] of sources) {
49
+ fs.writeFileSync(path.join(skillsRoot, destination), source, 'utf8')
50
+ }
51
+ const entry = sources.get(`${skillName}/SKILL.md`)
52
+ const begin = entry.indexOf('<!-- microi-progressive:begin -->')
53
+ const prefix = entry.slice(0, begin)
54
+ skill.prefixSha256 = hash(prefix)
55
+ skill.prefixBytes = Buffer.byteLength(prefix)
56
+ skill.originalSha256 = hash(prefix + ordered.join(''))
57
+ skill.originalLines = (prefix + ordered.join('')).split(/\r?\n/).length
58
+ skill.entryLines = entry.split(/\r?\n/).length
59
+ }
60
+
61
+ manifest.generatedAt = new Date().toISOString()
62
+ fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, 'utf8')
63
+ console.log(`Accepted intentional knowledge updates for: ${requested.join(', ')}`)
64
+
@@ -1,46 +1,46 @@
1
- import crypto from 'node:crypto';
2
- import fs from 'node:fs';
3
- import path from 'node:path';
4
- import { fileURLToPath } from 'node:url';
5
-
6
- // 后端只持有自包含的知识快照;规则正文仍以 microi.skills 为唯一可编辑事实源。
7
- const skillsRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
8
- const serverRoot = path.resolve(skillsRoot, '../Microi.Server/Microi.AI');
9
- const project = fs.readFileSync(path.join(serverRoot, 'Microi.AI.csproj'), 'utf8');
10
- const manifest = JSON.parse(fs.readFileSync(path.join(skillsRoot, '.progressive-disclosure-manifest.json'), 'utf8'));
11
- const check = process.argv.includes('--check');
12
- const names = process.argv.slice(2).filter(value => value !== '--check');
13
- if (!names.length) throw new Error('请显式指定本次需要同步的 Skill 名称。');
14
- const stripMarkers = text => text.replace(/<!--\s*\/?microi-progressive:[^>]*-->\r?\n?/g, '');
15
- const digest = text => crypto.createHash('sha256').update(text).digest('hex');
16
- const results = [];
17
-
18
- for (const name of names) {
19
- if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name)) throw new Error(`无效 Skill 名称:${name}`);
20
- const file = `skill-${name}.md`;
21
- // 不擅自新增程序集资源;只有现有项目明确注册的知识快照可以由此脚本刷新。
22
- if (!project.includes(`Resource\\${file}`)) throw new Error(`后端尚未注册 ${file},请先审阅资源归属。`);
23
- let text = fs.readFileSync(path.join(skillsRoot, name, 'SKILL.md'), 'utf8');
24
- const references = manifest.skills[name]?.references || [];
25
- if (references.length) {
26
- const route = text.indexOf('## 详细参考路由(渐进披露)');
27
- const end = text.indexOf('<!-- microi-progressive:end -->', route);
28
- if (route < 0 || end < route) throw new Error(`${name} 缺少受控参考路由,停止覆盖快照。`);
29
- text = text.slice(0, route) + text.slice(end + '<!-- microi-progressive:end -->'.length);
30
- for (const reference of references) {
31
- const source = path.resolve(skillsRoot, name, reference.path);
32
- const relative = path.relative(path.join(skillsRoot, name), source);
33
- if (relative.startsWith('..') || path.isAbsolute(relative)) throw new Error('参考文件越过 Skill 目录。');
34
- // 平台 AI 读取程序集时不能打开工作区相对路径,必须带上渐进参考的完整正文。
35
- text += `\n\n${fs.readFileSync(source, 'utf8')}`;
36
- }
37
- }
38
- text = `${stripMarkers(text).replace(/\r\n/g, '\n').trimEnd()}\n`;
39
- const target = path.join(serverRoot, 'Resource', file);
40
- const before = fs.existsSync(target) ? fs.readFileSync(target, 'utf8') : '';
41
- const changed = before.replace(/\r\n/g, '\n') !== text;
42
- if (changed && !check) fs.writeFileSync(target, text, 'utf8');
43
- results.push({ name, changed, sha256: digest(text) });
44
- }
45
- console.log(JSON.stringify({ check, results }, null, 2));
46
- if (check && results.some(item => item.changed)) process.exitCode = 1;
1
+ import crypto from 'node:crypto';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+
6
+ // 后端只持有自包含的知识快照;规则正文仍以 microi.skills 为唯一可编辑事实源。
7
+ const skillsRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
8
+ const serverRoot = path.resolve(skillsRoot, '../Microi.Server/Microi.AI');
9
+ const project = fs.readFileSync(path.join(serverRoot, 'Microi.AI.csproj'), 'utf8');
10
+ const manifest = JSON.parse(fs.readFileSync(path.join(skillsRoot, '.progressive-disclosure-manifest.json'), 'utf8'));
11
+ const check = process.argv.includes('--check');
12
+ const names = process.argv.slice(2).filter(value => value !== '--check');
13
+ if (!names.length) throw new Error('请显式指定本次需要同步的 Skill 名称。');
14
+ const stripMarkers = text => text.replace(/<!--\s*\/?microi-progressive:[^>]*-->\r?\n?/g, '');
15
+ const digest = text => crypto.createHash('sha256').update(text).digest('hex');
16
+ const results = [];
17
+
18
+ for (const name of names) {
19
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name)) throw new Error(`无效 Skill 名称:${name}`);
20
+ const file = `skill-${name}.md`;
21
+ // 不擅自新增程序集资源;只有现有项目明确注册的知识快照可以由此脚本刷新。
22
+ if (!project.includes(`Resource\\${file}`)) throw new Error(`后端尚未注册 ${file},请先审阅资源归属。`);
23
+ let text = fs.readFileSync(path.join(skillsRoot, name, 'SKILL.md'), 'utf8');
24
+ const references = manifest.skills[name]?.references || [];
25
+ if (references.length) {
26
+ const route = text.indexOf('## 详细参考路由(渐进披露)');
27
+ const end = text.indexOf('<!-- microi-progressive:end -->', route);
28
+ if (route < 0 || end < route) throw new Error(`${name} 缺少受控参考路由,停止覆盖快照。`);
29
+ text = text.slice(0, route) + text.slice(end + '<!-- microi-progressive:end -->'.length);
30
+ for (const reference of references) {
31
+ const source = path.resolve(skillsRoot, name, reference.path);
32
+ const relative = path.relative(path.join(skillsRoot, name), source);
33
+ if (relative.startsWith('..') || path.isAbsolute(relative)) throw new Error('参考文件越过 Skill 目录。');
34
+ // 平台 AI 读取程序集时不能打开工作区相对路径,必须带上渐进参考的完整正文。
35
+ text += `\n\n${fs.readFileSync(source, 'utf8')}`;
36
+ }
37
+ }
38
+ text = `${stripMarkers(text).replace(/\r\n/g, '\n').trimEnd()}\n`;
39
+ const target = path.join(serverRoot, 'Resource', file);
40
+ const before = fs.existsSync(target) ? fs.readFileSync(target, 'utf8') : '';
41
+ const changed = before.replace(/\r\n/g, '\n') !== text;
42
+ if (changed && !check) fs.writeFileSync(target, text, 'utf8');
43
+ results.push({ name, changed, sha256: digest(text) });
44
+ }
45
+ console.log(JSON.stringify({ check, results }, null, 2));
46
+ if (check && results.some(item => item.changed)) process.exitCode = 1;