deveco_hmigbot 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/LICENSE +26 -0
  2. package/README.md +50 -0
  3. package/agents/hmigbot-worker.md +61 -0
  4. package/agents/hmigbot.md +22 -0
  5. package/agents/workflow-subagent.md +55 -0
  6. package/commands/hmigbot.md +17 -0
  7. package/dist/index.js +1 -0
  8. package/manifest.json +19 -0
  9. package/package.json +29 -0
  10. package/skills/migrate-core/FILES.md +26 -0
  11. package/skills/migrate-core/SKILL.md +484 -0
  12. package/skills/migrate-core/references/README.md +64 -0
  13. package/skills/migrate-core/references/flow/arkts-vector-gate.md +55 -0
  14. package/skills/migrate-core/references/flow/build-error-patterns.md +52 -0
  15. package/skills/migrate-core/references/flow/conventions-template.md +244 -0
  16. package/skills/migrate-core/references/flow/navigation-migration.md +42 -0
  17. package/skills/migrate-core/references/flow/platform-api-guards.md +59 -0
  18. package/skills/migrate-core/references/flow/platform-model-gaps.md +43 -0
  19. package/skills/migrate-core/references/flow/resource-conversion.md +46 -0
  20. package/skills/migrate-core/references/flow/ui-layout-semantics.md +124 -0
  21. package/skills/migrate-core/references/flow/unit-breakdown.md +42 -0
  22. package/skills/migrate-core/references/host-capabilities.md +24 -0
  23. package/skills/migrate-core/references/topics/app-identity.md +214 -0
  24. package/skills/migrate-core/references/topics/env-doctor.md +245 -0
  25. package/skills/migrate-core/references/topics/i18n/README.md +458 -0
  26. package/skills/migrate-core/references/topics/i18n/references/code-examples.md +304 -0
  27. package/skills/migrate-core/references/topics/i18n/references/common-pitfalls.md +354 -0
  28. package/skills/migrate-core/references/topics/i18n/references/dynamic-language-switch.md +464 -0
  29. package/skills/migrate-core/references/topics/i18n/references/language-codes.md +104 -0
  30. package/skills/migrate-core/references/topics/icon-sizing.md +98 -0
  31. package/skills/migrate-core/references/topics/library-migration/README.md +234 -0
  32. package/skills/migrate-core/references/topics/library-migration/closed-source-sdk.md +128 -0
  33. package/skills/migrate-core/references/topics/library-migration/download-api-decision.md +84 -0
  34. package/skills/migrate-core/references/topics/library-migration/library-mapping-table.md +100 -0
  35. package/skills/migrate-core/references/topics/library-migration/napi-compile-guide.md +84 -0
  36. package/skills/migrate-core/references/topics/library-migration/ohpm-search-guide.md +73 -0
  37. package/skills/migrate-core/references/topics/library-migration/stdlib-mapping-table.md +34 -0
  38. package/skills/migrate-core/references/topics/resources/aar-decompile.md +25 -0
  39. package/skills/migrate-core/references/topics/resources/conversion-rules.md +625 -0
  40. package/skills/migrate-core/references/topics/resources/dependency-analysis-rules.md +328 -0
  41. package/skills/migrate-core/references/topics/resources/material-design-icons.md +173 -0
  42. package/skills/migrate-core/references/topics/resources/svg-fix-patterns.md +175 -0
  43. package/skills/migrate-core/references/topics/resources/xml-drawable-to-svg-rules.md +513 -0
  44. package/skills/migrate-core/references/topics/system-capabilities/README.md +331 -0
  45. package/skills/migrate-core/references/topics/system-capabilities/avplayer-guide.md +161 -0
  46. package/skills/migrate-core/references/topics/system-capabilities/background-tasks.md +403 -0
  47. package/skills/migrate-core/references/topics/system-capabilities/browser-intent.md +121 -0
  48. package/skills/migrate-core/references/topics/system-capabilities/camera-picker.md +118 -0
  49. package/skills/migrate-core/references/topics/system-capabilities/document-picker.md +246 -0
  50. package/skills/migrate-core/references/topics/system-capabilities/file-utils.md +131 -0
  51. package/skills/migrate-core/references/topics/system-capabilities/permission-helper.md +112 -0
  52. package/skills/migrate-core/references/topics/system-capabilities/photo-access-helper.md +208 -0
  53. package/skills/migrate-core/references/topics/system-capabilities/print-management.md +213 -0
  54. package/skills/migrate-core/references/topics/system-capabilities/share-panel.md +177 -0
  55. package/skills/migrate-core/references/topics/system-capabilities/system-settings.md +322 -0
  56. package/skills/migrate-core/references/topics/system-capabilities/telephony-dial.md +49 -0
  57. package/skills/migrate-core/references/topics/system-capabilities/video-playback.md +42 -0
  58. package/skills/migrate-core/references/topics/system-capabilities/webview-patterns.md +38 -0
  59. package/skills/migrate-core/references/topics/ui-alignment/README.md +344 -0
  60. package/skills/migrate-core/references/topics/ui-alignment/references/dark-mode.md +47 -0
  61. package/skills/migrate-core/references/topics/ui-alignment/references/layout-mapping.md +301 -0
  62. package/skills/migrate-core/references/topics/ui-alignment/references/visual-patterns.md +411 -0
  63. package/skills/migrate-core/scripts/closure/check-anchors.mjs +186 -0
  64. package/skills/migrate-core/scripts/closure/check-api-guards.mjs +175 -0
  65. package/skills/migrate-core/scripts/closure/check-consumers.mjs +301 -0
  66. package/skills/migrate-core/scripts/closure/check-permissions.mjs +165 -0
  67. package/skills/migrate-core/scripts/closure/check-resources.mjs +130 -0
  68. package/skills/migrate-core/scripts/closure/check-routes.mjs +527 -0
  69. package/skills/migrate-core/scripts/closure/check-safearea.mjs +122 -0
  70. package/skills/migrate-core/scripts/closure/check-stubs.mjs +69 -0
  71. package/skills/migrate-core/scripts/closure/closure-suite.mjs +256 -0
  72. package/skills/migrate-core/scripts/closure/idioms.json +105 -0
  73. package/skills/migrate-core/scripts/convert/convert-resources.mjs +437 -0
  74. package/skills/migrate-core/scripts/feasibility/feasibility.mjs +235 -0
  75. package/skills/migrate-core/scripts/feasibility/tables/cross-platform.json +11 -0
  76. package/skills/migrate-core/scripts/feasibility/tables/deprecated-api.json +10 -0
  77. package/skills/migrate-core/scripts/feasibility/tables/imported-arkts-core.json +425 -0
  78. package/skills/migrate-core/scripts/feasibility/tables/lib-equivalence.json +206 -0
  79. package/skills/migrate-core/scripts/feasibility/tables/system-capabilities.json +22 -0
  80. package/skills/migrate-core/scripts/front.mjs +107 -0
  81. package/skills/migrate-core/scripts/interface/ark-extract.mjs +172 -0
  82. package/skills/migrate-core/scripts/interface/interface.mjs +152 -0
  83. package/skills/migrate-core/scripts/ledger/ledger.mjs +383 -0
  84. package/skills/migrate-core/scripts/ledger/parse-cards.mjs +98 -0
  85. package/skills/migrate-core/scripts/lib/literals.mjs +37 -0
  86. package/skills/migrate-core/scripts/lib/scan.mjs +315 -0
  87. package/skills/migrate-core/scripts/smoke/align-sdk.mjs +118 -0
  88. package/skills/migrate-core/scripts/smoke/ensure-sign.mjs +53 -0
  89. package/skills/migrate-core/scripts/smoke/smoke.mjs +238 -0
  90. package/skills/migrate-core/scripts/smoke/verdict.mjs +31 -0
  91. package/skills/migrate-core/scripts/smoke/walk.mjs +480 -0
  92. package/skills/migrate-core/scripts/transpile/mapping.json +76 -0
  93. package/skills/migrate-core/scripts/transpile/transpile-layout.mjs +404 -0
  94. package/skills/migrate-core/scripts/vectors/run-arkts-vectors.mjs +107 -0
  95. package/skills/migrate-core/scripts/vectors/setup-arkts-test.mjs +90 -0
  96. package/skills/migrate-core/scripts/wire/extractors.mjs +258 -0
  97. package/skills/migrate-core/scripts/wire/wire-routes.mjs +507 -0
  98. package/skills/migrate-core/templates/acceptance.js +365 -0
  99. package/skills/migrate-core/templates/explore.js +86 -0
  100. package/skills/migrate-core/templates/implement.js +211 -0
  101. package/skills/migrate-core/templates/mig_slices.js +491 -0
@@ -0,0 +1,211 @@
1
+ export const meta = { name: 'implement', description: 'Wave-parallel unit implementation with shared context pack, retry, integration step, and closing gate check' }
2
+
3
+ const fail = (msg) => { throw new Error('workflow args: ' + msg) }
4
+ const got = (v) => v === undefined ? 'missing' : v === null ? 'null' : Array.isArray(v) ? 'an array of ' + v.length : typeof v === 'string' ? 'the string ' + JSON.stringify(v.length > 80 ? v.slice(0, 80) + '…' : v) : typeof v
5
+ const isStr = (v) => typeof v === 'string' && v.trim().length > 0
6
+ const asArr = (v) => {
7
+ if (Array.isArray(v)) return v
8
+ if (typeof v === 'string') {
9
+ const s = v.trim()
10
+ if (s[0] === '[' || s[0] === '{') { try { const p = JSON.parse(s); if (Array.isArray(p)) return p; if (p && typeof p === 'object') return [p] } catch (e) {} }
11
+ return s ? [s] : []
12
+ }
13
+ return v && typeof v === 'object' ? [v] : []
14
+ }
15
+ const skillStep = (...vs) => {
16
+ const sk = []
17
+ for (const v of vs) for (const s of asArr(v)) if (isStr(s) && !sk.includes(s)) sk.push(s)
18
+ return sk.length ? 'Before starting, use the skill tool to load and follow these skills: ' + sk.join(', ') + '.' : ''
19
+ }
20
+ const norm = (r, strictPass) => {
21
+ if (r === null || r === undefined) return { ok: false, summary: '(agent failed)' }
22
+ if (typeof r === 'string') return strictPass ? { ok: false, summary: r.slice(0, 2000) } : { ok: true, unverified: true, summary: r.slice(0, 2000) }
23
+ if (typeof r === 'object') {
24
+ const okFlag = r.ok === undefined ? r.pass : r.ok
25
+ return { ...r, ok: strictPass ? okFlag === true : okFlag !== false, summary: typeof r.summary === 'string' ? r.summary : '' }
26
+ }
27
+ return { ok: !strictPass, summary: String(r) }
28
+ }
29
+ // '$ ' 前缀 = 字面 shell 命令:引擎 check() 直跑,退出码即结论(比模型转述更硬的证据,
30
+ // 还省一个子会话);无前缀 = 交给检查代理执行并评判(携带判定标准的文字检查走这条)。
31
+ const isCmd = (v) => isStr(v) && v.trim().indexOf('$ ') === 0
32
+ const cmdOf = (v) => v.trim().slice(2)
33
+ const CHECK_ERRORS_SCHEMA = { type: 'object', required: ['errors'], properties: { errors: { type: 'array', items: { type: 'object', required: ['message'], properties: { file: { type: 'string' }, message: { type: 'string' } } } } } }
34
+
35
+ if (!args || typeof args !== 'object') fail("implement requires args: { goal, units: [{ id, ... }] } — got " + got(args) + '. Pass args as a real JSON object (not a quoted string); for large args, Write a JSON file and pass argsFile.')
36
+ if (!isStr(args.goal)) fail('implement requires args.goal — got ' + got(args.goal))
37
+ let units = asArr(args.units).filter((u) => u && isStr(u.id))
38
+ if (!units.length) fail('implement requires args.units: [{ id, title?, label?, section?, detail?, targetPath?, wave?, contextFiles? }] — got ' + got(args.units))
39
+ const ids = new Set()
40
+ for (const u of units) { if (ids.has(u.id)) fail('duplicate unit id: ' + u.id); ids.add(u.id) }
41
+ if (asArr(args.only).length) {
42
+ const only = new Set(args.only)
43
+ units = units.filter((u) => only.has(u.id))
44
+ if (!units.length) fail('args.only matched no unit ids')
45
+ log('re-running only: ' + units.map((u) => u.id).join(', '))
46
+ }
47
+ if (isStr(args.instructionsFile) === false && units.some((u) => isStr(u.section))) fail('units use section markers but args.instructionsFile is missing')
48
+
49
+ // 规格按段投递:派发前用 readSection 抽出「公共前导 + 该单元自己的段」内联进提示词 ——
50
+ // 否则每个单元把整个 instructions 文件读进上下文,之后每个 step 都为无关段落重复付费。
51
+ // 缺段在任何代理起跑前快失败;readSection 不可用(移植宿主未注入)时回退旧的读文件路径。
52
+ let sectionOf = null
53
+ if (isStr(args.instructionsFile) && units.some((u) => isStr(u.section))) {
54
+ try {
55
+ const m = new Map()
56
+ for (const u of units) if (isStr(u.section)) m.set(u.id, readSection(args.instructionsFile, u.section.indexOf('SECTION') === 0 ? u.section : 'SECTION: ' + u.section))
57
+ sectionOf = m
58
+ } catch (e) {
59
+ const msg = e && e.message ? String(e.message) : String(e)
60
+ if (msg.indexOf('not available') !== -1) log('readSection unavailable — units read the instructions file themselves')
61
+ else fail('instructionsFile sections: ' + msg)
62
+ }
63
+ }
64
+ // 体积 lint:conventions 是每个单元每个 step 的固定开销,肥大是全局隐形税。
65
+ if (isStr(args.conventionsFile)) {
66
+ try {
67
+ const t = readSection(args.conventionsFile)
68
+ if (t.length > 30000) log('lint: conventions file is ' + Math.round(t.length / 1024) + 'KB — every unit ingests it on every step; keep a lean core and move domain detail into per-unit contextFiles')
69
+ } catch (e) {
70
+ if (String(e && e.message ? e.message : e).indexOf('exceeds') !== -1) log('lint: conventions file exceeds 50KB — every unit ingests it on every step; keep a lean core and move domain detail into per-unit contextFiles')
71
+ }
72
+ }
73
+
74
+ const envNotes = []
75
+ // 检查集中原则:检查跑在集中点(每波一次的 waveGate、收尾一次的 gate),不撒进单元。
76
+ // 旧的 unitCheck 让 N 个并行单元各自跑同一条检查(最多各两遍;配置漏了 {targetPath}
77
+ // 还会变成 N 次全项目扫描),同一批代码被反复查。参数保留兼容:收到只提示并忽略。
78
+ if (isStr(args.unitCheck) || units.some((u) => isStr(u.unitCheck))) log('unitCheck is ignored — configure checks on waveGate/gate instead')
79
+ const unitPrompt = (u) => {
80
+ const parts = []
81
+ parts.push('You are implementing ONE independent unit of: ' + args.goal)
82
+ parts.push('Unit ' + u.id + (isStr(u.title) ? ': ' + u.title : '') + (isStr(u.targetPath) ? '\nTarget path: ' + u.targetPath : ''))
83
+ if (isStr(args.conventionsFile)) parts.push('MANDATORY first step: READ the conventions file at ' + args.conventionsFile + ' (Read tool) and follow it exactly — the naming and coding rules, the API/library choices, and the known pitfalls. Consistency across parallel units depends on it.')
84
+ const skills = skillStep(args.skills, u.skills)
85
+ if (skills) parts.push(skills)
86
+ for (const f of asArr(u.contextFiles)) parts.push('Also read before starting: ' + f)
87
+ if (isStr(args.instructionsFile) && isStr(u.section)) {
88
+ if (sectionOf && sectionOf.has(u.id)) parts.push('Your unit specification (the common rules plus your section, extracted from ' + args.instructionsFile + ') — follow ALL of it:\n\n' + sectionOf.get(u.id))
89
+ else parts.push('READ the instructions file at ' + args.instructionsFile + ' (Read tool), find the section marked "' + u.section + '", and follow ALL of it: the common rules plus your section (sources to read, what to produce, output paths).')
90
+ }
91
+ if (isStr(u.detail)) parts.push('What this unit must do:\n' + u.detail)
92
+ parts.push('Fidelity rules: the SOURCE of truth (real source code / real APIs) wins over this brief — if they conflict, follow the source and record the conflict in deviations. NO silent degradation: a stub, mock, or placeholder is allowed only where your brief explicitly authorizes one, and every one you ship must be listed in deviations. If you change a shared contract (an exported signature/interface a sibling unit consumes), record exactly what changed in deviations.')
93
+ parts.push('Stay within THIS unit: do not create or edit other units\' files.' + (asArr(args.sharedFiles).length ? ' The following files are SHARED and off-limits to direct edits — instead, list the exact changes they need in sharedEdits: ' + asArr(args.sharedFiles).join(', ') : ''))
94
+ parts.push('Verify what you can cheaply (re-read your edits; run a fast static check only if one exists scoped to just your files — once, no fix-and-re-run loops). Do NOT run whole-project builds or checks — the gate phase owns those — and do NOT take any exclusive external resource (a device or emulator, a fixed port, a shared test database): sibling units run IN PARALLEL and will collide on it. In envNotes report environment quirks siblings will hit (proxy/registry issues, broken SDK declarations, tool workarounds). In unresolved list anything a later step MUST still do — INCLUDING anything you could not verify and why (no device, no data, a precondition another unit owns). An unverified claim silently reported as done is worse than an honest gap.')
95
+ return parts.join('\n\n')
96
+ }
97
+ const UNIT_SCHEMA = { type: 'object', required: ['ok', 'summary'], properties: { ok: { type: 'boolean' }, summary: { type: 'string' }, files: { type: 'array', items: { type: 'string' } }, sharedEdits: { type: 'array', items: { type: 'string' } }, deviations: { type: 'array', items: { type: 'string' } }, unresolved: { type: 'array', items: { type: 'string' } }, envNotes: { type: 'array', items: { type: 'string' } } } }
98
+
99
+ const byWave = new Map()
100
+ for (const u of units) { const w = Number.isFinite(u.wave) ? u.wave : 0; if (!byWave.has(w)) byWave.set(w, []); byWave.get(w).push(u) }
101
+ const waves = [...byWave.keys()].sort((a, b) => a - b)
102
+
103
+ // 死亡重试由引擎原地完成(同 id 不涨分母、不动 resume 哈希)。envNotes 走 context 选项:
104
+ // 它随每波累积而变,混进 prompt 会让 resume 哈希永不命中。检查/集成代理同样吃 context ——
105
+ // 环境坑(代理、镜像、SDK 声明)它们一样会踩,不该重新诊断一遍。
106
+ const envCtx = () => envNotes.length ? 'Environment notes from units that already ran (trust these, do not re-diagnose):\n- ' + envNotes.slice(0, 12).join('\n- ') : undefined
107
+ const runUnit = async (u, phaseTitle) => {
108
+ const label = isStr(u.label) ? u.label : 'impl ' + u.id
109
+ let r = await agent(unitPrompt(u), { label, phase: phaseTitle, schema: UNIT_SCHEMA, context: envCtx() })
110
+ // ok:false = 代理诚实报"没做完"(多为单元过大、一轮没写完交付文件)。据此重派续跑,别放它进 gate 蒙混。
111
+ let att = 0
112
+ while (r !== null && r.ok === false && att < 2) {
113
+ att++
114
+ const p = norm(r, false)
115
+ r = await agent(unitPrompt(u) + '\n\n【续跑 ' + att + '】上一轮 ok:false 未完成。已做:' + String(p.summary || '').slice(0, 500) + '\n仍需落地(务必把交付文件真正写到盘上、写完再返回 ok:true;一轮写不完就先落地能完成的最小部分并如实 ok:false):\n- ' + asArr(p.unresolved).slice(0, 12).join('\n- '), { label: label + ' 续' + att, phase: phaseTitle, schema: UNIT_SCHEMA, context: envCtx(), cache: false })
116
+ }
117
+ return { id: u.id, title: isStr(u.title) ? u.title : u.id, ...norm(r, false) }
118
+ }
119
+
120
+ const GATE_SCHEMA = { type: 'object', required: ['ok'], properties: { ok: { type: 'boolean' }, errors: { type: 'array', items: { type: 'object', required: ['message'], properties: { file: { type: 'string' }, message: { type: 'string' } } } }, evidence: { type: 'string' } } }
121
+ const gates = asArr(args.gate).filter(isStr)
122
+ const waveGates = []
123
+ // 检查执行器:'$ ' 前缀走 check() 直跑(退出码即结论,失败才 spawn 一个只读代理把输出
124
+ // 解析成 {file,message} 清单);无前缀走检查代理执行并评判。两条路输出同构。
125
+ const parseFailure = async (cmd, code, output, label, phaseTitle) => {
126
+ const r = await agent('A verification command for: ' + args.goal + ' FAILED.\n\nCommand: ' + cmd + '\nExit status: ' + code + '\nOutput (may be truncated):\n' + output + '\n\nReturn errors as { file, message } entries — one per distinct error, file as an absolute path when the output names one, deduplicated, max 40. If the output alone is not informative enough, you may re-run the command or read the files it names — but fix NOTHING.', { label, phase: phaseTitle, schema: CHECK_ERRORS_SCHEMA, cache: false, context: envCtx() })
127
+ const errs = r === null ? [{ message: '(error-parse agent failed — the raw check output is in the run log)' }] : asArr(r.errors).filter((e) => e && typeof e.message === 'string')
128
+ return errs.slice(0, 40)
129
+ }
130
+ const runGateCheck = async (text, judgedIntro, label, phaseTitle) => {
131
+ if (isCmd(text)) {
132
+ const cmd = cmdOf(text)
133
+ const c = await check(cmd)
134
+ if (c.ok) return { ok: true, evidence: 'exit 0: ' + cmd.slice(0, 200), errors: [] }
135
+ return { ok: false, evidence: 'exit ' + c.exitCode + ': ' + cmd.slice(0, 200), errors: await parseFailure(cmd, c.exitCode, c.output, label, phaseTitle) }
136
+ }
137
+ const g = norm(await agent(judgedIntro + '\n\nRun EXACTLY this check: ' + text + '\n\nReport ok=true ONLY if it genuinely passes, and put the exact command(s) you ran plus their exit status / artifact path in evidence — ok without evidence is invalid. On failure return errors as { file, message } entries (deduplicated, max 40). Fix NOTHING yourself.', { label, phase: phaseTitle, schema: GATE_SCHEMA, cache: false, context: envCtx() }), true)
138
+ return { ok: g.ok === true, evidence: isStr(g.evidence) ? g.evidence : '', errors: asArr(g.errors).slice(0, 40) }
139
+ }
140
+ const all = []
141
+ for (const w of waves) {
142
+ const batch = byWave.get(w)
143
+ const title = waves.length > 1 ? 'Wave ' + w + ' (' + batch.length + ' units)' : 'Implement (' + batch.length + ' units)'
144
+ phase(title)
145
+ const results = await parallel(batch.map((u) => () => runUnit(u, title)))
146
+ const done = results.filter((r) => r !== null)
147
+ all.push(...done)
148
+ for (const r of done) for (const n of asArr(r.envNotes)) if (isStr(n) && !envNotes.includes(n)) envNotes.push(n)
149
+ const shared = done.filter((r) => asArr(r.sharedEdits).length).map((r) => '- ' + r.id + ': ' + asArr(r.sharedEdits).join('; '))
150
+ if (args.integration !== false && shared.length) {
151
+ // integration 死了 = 整波共享文件接线丢失,代价远高于一个普通单元 —— 引擎的原地重试
152
+ // 兜第一层;仍然失败必须大声报出来,编排才知道要手工收口。
153
+ const igPrompt = 'You are the INTEGRATION agent for one implementation wave of: ' + args.goal + (isStr(args.conventionsFile) ? '\nFirst read the conventions file at ' + args.conventionsFile + '.' : '') + '\n\nParallel units could not edit shared files directly. Apply their requested shared-file changes now, resolving conflicts coherently (routes, barrel exports, resource tables, package/module config):\n' + shared.join('\n') + '\n\nMake the edits, then RETURN a compact summary of what you changed.'
154
+ const ig = await agent(igPrompt, { label: 'integrate wave ' + w, phase: title, context: envCtx() })
155
+ log('integration wave ' + w + ': ' + (ig === null ? 'FAILED — the shared-file changes above are NOT applied' : 'done'))
156
+ }
157
+ // 波内集中检查(可选):波与波之间有依赖,等到收尾 gate 才发现坏波,代价是后面每一波
158
+ // 都建立在坏地基上。waveGate 每波结束后集中跑一次 —— 一个检查代理管整波,而不是每个
159
+ // 单元各查各的;{files} 替换为该波单元上报文件的并集,检查只扫这一波改过的东西。
160
+ // 配了收尾 gate 时最后一波跳过:gate 马上要重查同一批代码,连跑两遍是纯重复。
161
+ const isLastWave = w === waves[waves.length - 1]
162
+ if (isStr(args.waveGate) && isLastWave && gates.length) {
163
+ log('waveGate skipped on the final wave — the closing gate covers it')
164
+ } else if (isStr(args.waveGate)) {
165
+ const waveFiles = [...new Set(done.flatMap((r) => asArr(r.files).filter(isStr)))]
166
+ if (args.waveGate.includes('{files}') && !waveFiles.length) {
167
+ log('wave ' + w + ' reported no files — skipping the {files}-scoped wave gate')
168
+ } else {
169
+ const wgText = args.waveGate.split('{files}').join(waveFiles.join(' '))
170
+ const wg = await runGateCheck(wgText, 'You are the per-wave gate after wave ' + w + ' of: ' + args.goal, 'gate wave ' + w, title)
171
+ waveGates.push({ wave: w, ok: wg.ok, evidence: wg.evidence, errors: wg.errors })
172
+ if (!wg.ok) log('wave ' + w + ' gate FAILED — later waves build on this one; fix before relying on them: ' + wg.errors.slice(0, 5).map((e) => e.message).join(' | '))
173
+ }
174
+ }
175
+ }
176
+
177
+ let gate = null
178
+ if (gates.length) {
179
+ phase(gates.length > 1 ? 'Gate (' + gates.length + ' checks)' : 'Gate')
180
+ // 逐条串行是有意的:gate 检查通常共享构建状态(构建守护进程、锁、输出目录),并行会
181
+ // 互相踩 —— 能并行的才并行,这里不能。省时间靠配置端克制(一条真实 build 通常足够,
182
+ // 后续检查点还会全面复查),不靠并行跑一堆重复检查。
183
+ const checks = []
184
+ for (let i = 0; i < gates.length; i++) {
185
+ const one = gates[i]
186
+ const intro = gates.length > 1
187
+ ? 'You are gate check ' + (i + 1) + ' of ' + gates.length + ' for an implementation phase of: ' + args.goal + '. Each check is judged SEPARATELY; the phase passes only if EVERY check passes, so do not consider or report on the other checks.'
188
+ : 'You are the closing GATE for an implementation phase of: ' + args.goal
189
+ const g = await runGateCheck(one, intro, gates.length > 1 ? 'gate ' + (i + 1) : 'gate check', undefined)
190
+ checks.push({ check: one.slice(0, 160), ok: g.ok, evidence: g.evidence, errors: g.errors })
191
+ }
192
+ const failedChecks = checks.filter((c) => !c.ok)
193
+ gate = {
194
+ ok: failedChecks.length === 0,
195
+ checks,
196
+ evidence: checks.map((c, i) => '[' + (i + 1) + '] ' + (c.ok ? 'PASS' : 'FAIL') + ' ' + c.check + (c.evidence ? '\n' + c.evidence : '')).join('\n'),
197
+ errors: checks.flatMap((c) => c.errors).slice(0, 40),
198
+ }
199
+ if (gate.ok) log('gate: PASS (' + checks.length + '/' + checks.length + ')')
200
+ else log('gate: FAIL — ' + failedChecks.length + '/' + checks.length + ' check(s) failed: ' + failedChecks.map((c) => c.check).join(' | ') + ' — fix these errors before the next checkpoint')
201
+ } else {
202
+ log('no gate configured (args.gate) — build-check this work at the next checkpoint')
203
+ }
204
+
205
+ const failed = all.filter((r) => !r.ok).map((r) => r.id)
206
+ const debt = all.flatMap((r) => [...asArr(r.deviations), ...asArr(r.unresolved)].map((d) => r.id + ': ' + d))
207
+ if (failed.length) log('failed units — re-run with args.only = ' + JSON.stringify(failed))
208
+ // 交了散文没交结构化结果的单元计 done 但标 unverified:报告没丢,但 gate/verify 应先核这些。
209
+ const unverified = all.filter((r) => r.unverified === true).map((r) => r.id)
210
+ if (unverified.length) log('units counted done from prose (no structured result) — verify these FIRST: ' + unverified.join(', '))
211
+ return { goal: args.goal, waves: waves.length, units: all.map((r) => ({ id: r.id, ok: r.ok, unverified: r.unverified === true ? true : undefined, summary: (r.summary || '').slice(0, 400), files: asArr(r.files) })), failedUnits: failed, unverifiedUnits: unverified, debt, envNotes, waveGates, gate }