@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
@@ -38,7 +38,7 @@ description: 按 Microi 系统真实业务逻辑进行 Playwright 全自动化
38
38
  不适合把 Playwright 用来替代 V8 单函数调试。单个接口引擎的入参输出优先用 VS Code 插件远程执行、MCP `microi_run_engine` 或后端单元测试。
39
39
 
40
40
  <!-- /microi-progressive:chunk -->
41
- <!-- microi-progressive:chunk id=playwright-e2e-002 sha256=2e92ff117b1c3b450e988776204ccecd9dd4d6a83bb08d86d32032b467abc174 -->
41
+ <!-- microi-progressive:chunk id=playwright-e2e-002 sha256=5874137c92e16fd5cc729fd9c23d051caa3e64438fc7bee1d717ae9c5a0f2a90 -->
42
42
  ## 临时文件与产物放置规则(必须遵守)
43
43
 
44
44
  AI 在工作区内生成的**一次性临时脚本、诊断文件、截图、测试报告**等,**绝对不能放在工作区根目录**,必须放在以下指定位置:
@@ -54,7 +54,7 @@ AI 在工作区内生成的**一次性临时脚本、诊断文件、截图、测
54
54
 
55
55
  **禁止在根目录创建的文件类型**:`*.mjs`、`*.py`、`*.ps1`、`*.sh`(一次性脚本)、`*.png`、`screenshots/`、`node_modules/`(无 package.json 时)、`obj/`(非 .NET 项目文件)、`dark-mode-*/`、`test-*/` 等临时目录。
56
56
 
57
- `.tmp/` 目录已在 `.gitignore` 中排除,AI 可以在此自由创建临时文件。任务完成后如无价值可以不清理,也可以整体删除该目录。
57
+ `.tmp/` 目录已在 `.gitignore` 中排除,AI 可以在此创建临时文件。收尾清理只允许针对已确认归属的本任务子目录;删除前核对解析后的绝对路径、所有权和保留价值,不得整体删除共享 `.tmp/` 或其它任务的产物。临时文件保留不等于允许保留测试进程或容器。
58
58
 
59
59
  **2026-06 强制补充**:AI 手写的一次性 Playwright 验证脚本、运行日志、截图和报告只能写到工作区根目录 `.tmp/`。不要写到 `Microi.Client/`、`Microi.Server/`、`microi.doc/` 或其它子项目目录,即使这些目录已有 `tests/` 目录也不例外。只有 Microi.Agent 插件正式初始化的可复用 E2E 工程,才使用 `.microi-e2e/`。
60
60
 
@@ -140,20 +140,21 @@ PW_HOME_PATH=/#/pages/index/index
140
140
  不要只说“代码已编译”或“需要用户自己重启后端”;除非用户明确要求不要中断当前服务,否则 AI 要主动完成重启。
141
141
 
142
142
  <!-- /microi-progressive:chunk -->
143
- <!-- microi-progressive:chunk id=playwright-e2e-007 sha256=6d285a719a85ce3f2c887b292c08cc69d9632964c341269f0f61bb2fdc403fac -->
143
+ <!-- microi-progressive:chunk id=playwright-e2e-007 sha256=ee40e4f023e1da07269f9ebb82522720c4f014cfc9500885db09778dd819b9a6 -->
144
144
  ## 服务自启动纪律(必做)
145
145
 
146
146
  执行自动化测试、截图巡检、接口引擎回读、`/apiengine/{key}` 验收时,如果本地后端或前端不可达,不能把 `fetch failed`、`ECONNREFUSED`、`000 Failed to connect`、端口无人监听当作任务终点。必须先自动启动所需服务,再继续完整验证。
147
147
 
148
148
  同一工作区有多个 AI 对话时,服务自启动还必须遵守共享生命周期:
149
149
 
150
+ - 先执行 `../workspace-conventions/SKILL.md` 的“本地测试程序与依赖生命周期”统一规则:优先复用健康的本地源码程序、隔离目录中的编译产物和同组既有依赖,不按测试任务重复创建 Docker/Compose。需要双节点时启动两个本机程序进程连接同组依赖,不等于建立两套 Docker 环境。
150
151
  - API 服务启动前执行 `node Microi.Server/tools/release-lock.mjs assert api <工作区根>`,PC 服务使用 `assert pc`。Playwright `webServer`、自愈脚本或 AI 只在对应范围发布时等待;官网/Agent 发布不阻塞 `61500/61501`。同时启动前后端时分别检查两项。旧锁仅在真实进程证明所属范围后缩小阻塞范围,未知锁继续保护,不得删除活跃锁。
151
152
  - 端口健康且 PID/命令行属于当前工作区时直接复用,不得每个对话都无条件“先杀再启动”。
152
153
  必须重载源码时才串行重启,并在状态播报中说明旧/新 PID。
153
154
  - 本地长期后端使用项目目录中的 `dotnet run --launch-profile Microi.net.Api`,禁止把
154
155
  `bin/Release/net10.0` 或 `bin/Release/publish` 的 DLL 作为长期测试服务,否则会锁住
155
156
  一键发布的 Release 输出。
156
- - 一次性测试要关闭自己创建的 browser/context 和临时 webServer。VS Code 持有的
157
+ - 每次测试成功、失败、取消或因资源不足暂停时,都必须结束本次创建的 browser/context、临时 webServer 及其它自有测试进程;按统一规则收回新建临时容器,并独立回读进程、监听端口和容器状态确认无残留。复用的既有共享服务保持原状。VS Code 持有的
157
158
  `playwright test-server` 与用户 Edge/Chrome 不属于孤儿清理;禁止按进程名结束所有
158
159
  `node/dotnet/chrome/msedge`。
159
160
  - 只读盘点或精确停止使用 `Microi.Server/tools/Microi.LocalProcessManager.ps1` 的
@@ -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
+