// hmw.js — HMW P2 (Execute) workflow cho SOLUTION_ERP. CHẠY bởi Workflow runtime (body wrap async → // top-level await/return hợp lệ); KHÔNG node-runnable trực tiếp (`node hmw.js` sẽ lỗi await). // Em main lo P0/P1/P3/P4 NGOÀI workflow; script này CHỈ lo P2 fan-out. // Invoke bằng {scriptPath} (no hot-reload — restart/re-invoke sau khi sửa). Scope = repo SOLUTION_ERP ONLY (S1). // ⚠️ Script chạy JS-sandbox KHÔNG filesystem → KHÔNG tự tạo folder/ghi file. Scaffold run-folder runs// (TRACKED) = EM MAIN @P1 (Harness-10, supersedes Harness 2 B3 wave-folder). export const meta = { name: 'hmw', description: 'HMW P2 execute (SOLUTION_ERP) — fan-out roster ĐẦY-ĐỦ (số vai canonical → docs/STATUS.md §Sub-agents; KHÔNG hardcode số ở đây — lesson S108 comment stale làm worker đọc sai runtime) có MEMORY-PACK slice (qua args vì script không đọc file) + return findings + checklistEvidence + memoryDelta (spawn-record 4-field). 2 MODE (Harness 2, 06-07): (A) DEFAULT return-delta-only — fan-out NHỎ (<3 task VÀ ≤5 phút), sub KHÔNG ghi file, git-diff verify. (B) RUN-TRACE mode (args.run, Harness-10) — 🔴 BẮT BUỘC khi run ≥3 task HOẶC ước >5 phút (sàn H22 S111; script no-fs KHÔNG tự ép — enforcement = em-main scaffold @P1 + set args.run) — workflow DÀI, em main scaffold .claude/workflows/runs// TRACKED FLAT (run.md + sub--.md + -synthesis.md phẳng cùng cấp) @P1, sub ghi full-detail vào CHỈ sub--.md mình (B4/B6), harvest per-turn primary (C4) + H2 gom @session-end = backstop verify-idempotent. taskList thoải mái (queue theo slot, không cap cứng). memoryDelta KHÔNG tự ghi — em main VERIFY + APPEND-only @P3 (no-overwrite-unverified, B3). Model worker-tier-pin (owner-decision S124): role-có-frontmatter = opus+effort:max (worker-tier, KHÔNG inherit) · role-less inherit lead · per-task tier:\'fable\'|\'opus\' escape-hatch (fable qua /fable-real; H23-precedence CHƯA test — restart-để-ăn). Scope = repo SOLUTION_ERP ONLY (S1 — KHÔNG fan-out repo/corpus khác).', phases: [{ title: 'Execute', detail: 'fan-out memory-pack-injected agents, structured return' }], } // ─── args (em main bơm @P2 sau khi P1 chốt) ────────────────────────────────── // args = { // memoryPack: { 'investigator-codebase':'', 'implementer-backend':'...', reviewer:'...', ... }, // SLICE liên quan, KHÔNG full // spec: '', // checkpointApproved: true, // em main set SAU khi BÁO {số agent·vai·task} @inform (S2) // taskList: [ { role:, label:'..', prompt:'..', tier:'fable'|'opus'? }, ... ] // tier = per-task model override (H4.5) // run: { name:'', dir:'.claude/workflows/runs/' } // 🔴 BẮT BUỘC khi run ≥3 task HOẶC ước >5 phút (sàn H22 S111 — chết giữa run còn vật-liệu đĩa cho /tiep; dưới ngưỡng = tùy chọn) — bật RUN-TRACE mode (B, Harness-10 TRACKED). Folder + run.md em main ĐÃ scaffold @P1 (script no-fs). [legacy alias: args.wave] // } const VALID_ROLES = [ 'investigator-codebase', 'investigator-api', 'implementer-backend', 'implementer-frontend', 'test-specialist', 'reviewer', 'cicd-monitor', 'frontend-designer', 'database-agent', // +S57 — S56 dùng 3× qua fail-soft WARN; read-advisory DB lens (DB1-DB11) 'office-document', // +S107 — Office document WRITE specialist (OD1-OD10; docx/xlsx/pptx/pdf + form-engine) 'tooling-auditor', 'harvest-curator', // +S110 anh-directed (H21 roster-parity lệnh-B ensemble; supersede // điểm H6-design "monitor ngoài workflow" — monitor VẪN INFORM-only, lane RETURN-only nên không xung đột) 'lead-view-auditor', 'lead-omission-auditor', // +S121 W2 (H24 lead-self-audit) — 2 monitor MỚI, INFORM-only propose-only. // 🔴 KHÔNG gộp vào tooling-auditor(H1)/harvest-curator(H2): H24 §2(1) soi LEAD (view-* = lead nhìn sai / gap-* = // lead bỏ sót), khác trục H1 (tooling-freshness) và H2 (harvest-integrity). Class-flag = enum ĐÓNG ở // memory-budget.json → lead_self_audit.flag_classes (10 class: 5 view-* + 5 gap-*) — vai KHÔNG tự chế class. // Từ-vựng "view"/"omission" chọn CÓ CHỦ-ĐÍCH tránh va-chạm: governance-detectors.ps1:15 đã sở-hữu // "derived-staleness" ⇒ cấm tên *-stale-role; "GAP" quá-tải (session-end build-gap · memory-selfimprove CRITERIA+GAP). ] // ─── PA-2a — hằng-số kỳ-vọng model tier-2 (H23 model-at-spawn, owner-chốt O1 @S121 W2) ── // 🔴 ĐÂY LÀ HẰNG-SỐ KHAI-BÁO, KHÔNG PHẢI CƠ-CHẾ NEO. Nó KHÔNG ép model lúc spawn. // Vì sao chỉ là hằng-số: neo-model-tại-spawn BẤT-KHẢ-THI ở SE — param `model:` chỉ nhận ALIAS // ('opus'/'fable'/...), không nhận full-id; SE nay pin ALIAS `opus` (KHÔNG full-id) ở 14/14 frontmatter. // ⇒ owner chốt PA-2 = PA-2a (hằng-số này, để audit có thứ ĐỐI-CHIẾU) + PA-2b (scripts/spawn-model-audit.ps1 // so resolved-vs-expected). Đọc bởi: scripts/spawn-model-audit.ps1 (thiếu hằng-số ⇒ script FAIL-LOUD). // 🔸 H23 precedence — NAY TESTABLE (đổi từ DEFER, owner-decision S124): thứ-tự ưu-tiên giữa "tham-số tại spawn" // và "pin ở tệp định-nghĩa" khi CẢ HAI cùng hiện-diện. Ở SE cả hai đều hiện-diện (resolveModel() trả alias // vào agent(...,{model:...}) TRONG KHI frontmatter 14/14 = opus, KHÔNG còn inherit). // 🔴 Vì frontmatter nay = `opus` (KHÁC `fable`): spawn tier:'fable' → nếu spawn THẮNG=fable, nếu frontmatter // THẮNG=opus ⇒ PHÂN-BIỆT ĐƯỢC KỂ CẢ lead=Opus (khác thời all-inherit — inherit→lead làm 2 nhánh trùng). // 🔴 Nếu frontmatter THẮNG ⇒ tier:'fable' của /fable-real = NO-OP ⇒ KHÔNG escalate Fable được (owner dùng // /model chọn lead=Fable tay). PHẢI test phiên kế (sau RESTART kích-hoạt frontmatter mới): spawn 1 lane // tier:'fable' → đọc resolved qua spawn-model-audit.ps1. CHƯA test lượt này (frontmatter mới cần restart). // 🔸 CHƯA THỬ (fix#8a): param `model:` có TỪ-CHỐI full-id không — doc nói enum-alias; chưa ai thử thật. const TIER2_EXPECTED_FULL_ID = 'claude-opus-4-8' // ─── Worker-tier-pin opus (owner-decision S124 — thay thế H8 all-inherit; adopt erratum-h8 floor) ── // MỌI sub-agent (14/14) → frontmatter `model: opus` + `effort: max` = worker-tier gán-cứng (KHÔNG còn // inherit lead). Lead = frontier owner-choice per-session (BƯỚC 0.6 probe); Fable = owner gõ /fable-real // HOẶC /model tay (owner "còn Fable tao sẽ chỉ định qua Fable-real hoặc tự chọn"). // KHÔNG hardcode model hiện-hành ở comment — lesson S108: comment stale làm 2/4 worker đọc sai runtime. // (🔴 restart-CLI để frontmatter mới ăn runtime — EDIT-agent-tồn-tại no-hot-reload.) // hmw KHÔNG set model cho role-có-frontmatter → return undefined, frontmatter (opus) tự lo (= worker-tier). // Role-less (H6.2 governed-ultracode): default = inherit lead-model (top-tier) — KHÔNG pin rẻ làm default. // H8.2 "workflow nhanh nhất" = song song tối đa + xuất nhanh, KHÔNG hạ model làm rẻ. // Escape-hatch per-task tier:'opus' GIỮ cho sweep/cost (ngoại lệ có chủ đích); tier:'fable' CHỈ hợp lệ // trong lượt lệnh engine-đắt anh gõ kèm vai (/fable-real|/fable-clone <đề-bài> — H21 S110); // lead KHÔNG tự ý elevate theo phán-đoán riêng (MTv3 §1.3: đường DUY NHẤT nâng 1 vai lên hạng-nhất = lệnh owner). function resolveModel(role, rawRole, tier, i) { if (tier === 'fable' || tier === 'opus') return tier if (tier) log(`⚠️ hmw: tier "${tier}" lạ (task #${i}) → bỏ qua, dùng mặc định (frontmatter worker-tier opus)`) // Role-less (null) → inherit lead; invalid non-empty role đã THROW up-front (STOP-HARD, DIRECTED REC-3 2026-07-13) // → nhánh này giờ CHỈ đạt bởi role-less thật (rawRole null/''); giữ làm defensive no-op. if (!role && rawRole) return undefined if (!role) { log(`hmw: task #${i} role-less → inherit lead-model (H6.2 governed-ultracode; per-task tier:'opus' = escape-hatch sweep/cost)`); return undefined } return undefined // role có frontmatter: tất cả `opus`+`effort:max` (worker-tier-pin S124) — KHÔNG override } const SCHEMA = { type: 'object', required: ['findings', 'memoryDelta'], properties: { findings: { type: 'string', description: 'Kết quả chính. MỌI claim kèm evidence file:line. KHÔNG narrative suông.' }, checklistEvidence: { type: 'string', description: 'Bằng chứng cho acceptance-checklist P1 (số đo / PASS-FAIL / verdict).' }, subMdPath: { type: 'string', description: 'RUN-TRACE mode FLAT: đường-dẫn sub--.md agent đã ghi (em main/H2 đọc on-demand). DEFAULT-mode: bỏ trống.' }, memoryDelta: { type: 'object', description: 'Spawn-record 4-field — RETURN-only để EM MAIN harvest @P3. Agent KHÔNG tự ghi ký ức (KHÔNG file MEMORY.md, KHÔNG store_memory/RAG). Em main VERIFY + APPEND-only (KHÔNG overwrite entry cũ nếu chưa kiểm tra — B3).', required: ['task', 'verdict', 'learned', 'surprise'], properties: { task: { type: 'string', description: 'việc gì (1 dòng)' }, verdict: { type: 'string', description: 'kết luận / PASS-FAIL / root-cause' }, learned: { type: 'string', description: 'pattern / anti-pattern rút ra' }, surprise: { type: 'string', description: 'edge-case / bất ngờ (chống mất discovery)' }, }, }, }, } // S4b — args có thể tới OBJECT hoặc JSON-STRING (harness đôi khi stringify object args) → normalize defensive const A = (typeof args === 'string') ? JSON.parse(args) : (args || {}) if (!A || !Array.isArray(A.taskList) || A.taskList.length === 0) { throw new Error('hmw: cần args.taskList (mảng {role,label,prompt}) — em main bơm @P1. (đã thử JSON.parse nếu args bị stringify.)') } // S2 mechanize: checkpoint TRƯỚC P2 = gate CỨNG, không chỉ prompt. Em main BÁO {số agent·vai·task} @inform // (KHÔNG chờ confirm) → set args.checkpointApproved=true → fan-out. Flag GIỮ = anti-accidental-fire // (call quên-set-flag → throw, chặn 515K-class accidental). if (A.checkpointApproved !== true) { throw new Error('hmw: checkpointApproved chưa set — em-main BÁO {số agent·vai·task} @inform rồi set args.checkpointApproved=true (guard = anti-accidental-fire).') } // S4 — số task THOẢI MÁI: harness chạy theo slot, dư tự queue, KHÔNG cap cứng (soft notice only). if (A.taskList.length > 16) { log(`hmw: taskList=${A.taskList.length} (>16) → harness queue theo slot (thoải mái, không cap cứng).`) } // ─── STOP-HARD role-whitelist (DIRECTED 6c32df89 REC-3, 2026-07-13 · S115) ──── // Vai ∉ VALID_ROLES (typo / rename-drift) = DỪNG HẲN + báo owner — KHÔNG fail-soft // về default subagent (fail-soft = "đứt âm-thầm" đã gây lỗi thật hub-side; sim 5-ca). // PHÂN-BIỆT (backward-compat H6.2): role null/omitted/'' = HỢP-LỆ role-less (inherit // lead) → KHÔNG throw. CHỈ non-empty-string ∉ whitelist mới throw. Validate UP-FRONT // (trước parallel) — halt KHÔNG phụ-thuộc semantics parallel()/.filter(Boolean) (per-lane // throw có thể bị nuốt thành null = STOP-SILENT, tệ hơn). Mirror checkpointApproved tripwire. const badRoles = A.taskList .map((t, i) => ({ i, raw: t && t.role })) .filter(x => x.raw != null && x.raw !== '' && !VALID_ROLES.includes(x.raw)) if (badRoles.length > 0) { const detail = badRoles.map(x => `#${x.i}="${x.raw}"`).join(', ') throw new Error( `hmw: vai ∉ VALID_ROLES (${detail}) — DỪNG HẲN (STOP-HARD). ` + `Sửa typo HOẶC báo owner thêm vai vào VALID_ROLES [${VALID_ROLES.join(', ')}]. ` + `KHÔNG fail-soft về default subagent (DIRECTED 6c32df89 REC-3 2026-07-13).`) } const memoryPack = A.memoryPack || {} const spec = A.spec || '' // ─── RUN-TRACE mode (Harness-10, supersedes Harness-2 wave-mode B) ──────────── // run = { name, dir }. Folder runs// (TRACKED) + run.md em main ĐÃ scaffold @P1 (script no-fs). Bật → sub ghi full-detail // vào CHỈ sub--.md phẳng mình + return memoryDelta. Containment: tracked-change NGOÀI run-folder + code-disjoint = vi-phạm // (run-folder TRACKED → HIỆN trong git-diff = audit trực-tiếp; em main git-diff post-P2 + chunk-count RAG). [legacy alias args.wave] const wave = (A.run && A.run.dir) ? A.run : ((A.wave && A.wave.dir) ? A.wave : null) if (wave) log(`hmw: RUN-TRACE mode on → dir=${wave.dir} (TRACKED FLAT; sub ghi sub--.md phẳng isolated; em main scaffold @P1; harvest per-turn primary C4, H2 gom @session-end = backstop).`) // ─── SÀN-1 GATE — RUN-TRACE BẮT BUỘC khi ≥3 task (H22 wal-defect-fix, adopt S121 W2) ───── // TRƯỚC S121 sàn này chỉ là CHỮ trong doc ("script no-fs KHÔNG tự ép") ⇒ phụ-thuộc em-main NHỚ set // args.run. Đo thật @S119: `git grep args.run .claude/commands/` = 0 hit ⇒ 4/4 wrapper KHÔNG cờ // ⇒ sàn = danh-nghĩa. Nay CƠ-KHÍ-HOÁ (mirror tripwire checkpointApproved). // Vì sao ≥3: chết giữa run DÀI mà không có run-folder ⇒ /tiep §4 KHÔNG có vật-liệu trên đĩa để cắt-gọt // ⇒ phải chạy lại TỪ ĐẦU (per-turn returns mất theo phiên; chỉ file trên đĩa sống sót). // Bằng-chứng: S118 raw-Agent 7-spawn ⇒ 0 run-folder ⇒ mất trace. // 🔴 THROW chứ KHÔNG fail-soft: fail-soft = "đứt âm-thầm" (cùng lý-lẽ STOP-HARD role-whitelist bên trên). if (!wave && A.taskList.length >= 3) { throw new Error( `hmw: SÀN-1 — ${A.taskList.length} task (≥3) mà THIẾU args.run ⇒ DỪNG HẲN. ` + `RUN-TRACE là BẮT BUỘC ở ngưỡng này (sàn H22 S111): chết giữa run mà không có runs// ` + `thì /tiep §4 không có vật-liệu đĩa để relaunch cắt-gọt, phải chạy lại từ đầu. ` + `SỬA: em-main scaffold .claude/workflows/runs// (run.md + taskList snapshot) @P1 rồi truyền ` + `args.run = { name:'', dir:'.claude/workflows/runs/' }. ` + `(Script chạy JS-sandbox KHÔNG filesystem ⇒ KHÔNG tự tạo folder được — vì thế mới gate ở đây.) ` + `Dưới 3 task = MODE A return-delta-only, args.run tùy chọn.`) } // 🔸 KHAI THẬT — gate này CHỈ phủ đường `hmw.js`. Ngưỡng "hoặc ước >5 phút" KHÔNG cơ-khí-hoá được // (script không đo trước được thời-lượng) ⇒ phần đó vẫn là kỷ-luật người, không phải cổng. // 🔸 KẼ CÒN-LẠI (F2, KHÔNG gate được từ đây): raw `Workflow` tool và raw `Agent` tool đi THẲNG, không // qua file này ⇒ mất luôn writeGuard + run-trace. S72 raw-Workflow ⇒ reviewer ghi MEMORY +2850B over-cap; // S118 raw-Agent 7-spawn ⇒ 0 run-folder; và chính run S119 cũng đi raw Workflow. SE KHÔNG có CLI-launcher // để whitelist ⇒ 3 nguyên-tắc detector của broadcast là N/A **vì không có launcher**, KHÔNG phải vì "hết kẽ". phase('Execute') log(`HMW P2: fan-out ${A.taskList.length} task (${wave ? 'RUN-TRACE' : 'return-delta-only'}, worker-tier-pin opus, memory-pack-injected, scope=SOLUTION_ERP repo only)`) const results = await parallel(A.taskList.map((t, i) => () => { const raw = t && t.role // STOP-HARD validate up-front (DIRECTED REC-3 2026-07-13): raw ở đây CHỈ có thể ∈ VALID_ROLES HOẶC null/'' (role-less H6.2). const role = VALID_ROLES.includes(raw) ? raw : undefined // role-less (null/'') → undefined → inherit lead const mem = role && memoryPack[role] ? memoryPack[role] : '' const subMd = wave ? `${wave.dir}/sub-${role || 'task'}-${i}.md` : null // Harness-10 FLAT (h10-refine 2026-06-18): sub--.md phẳng cùng cấp dưới runs// — KHÔNG sub-md/ subdir // Write-guard TOOL-AWARE theo MODE (B6 isolation). SE read-only sub (KHÔNG Write tool): investigator-codebase/api, // reviewer, cicd-monitor (+ monitor tooling-auditor/harvest-curator). Write sub: implementer-backend/frontend, test-specialist, frontend-designer. const writeGuard = wave ? [ `## ✍️ RUN-TRACE ghi sub--.md FLAT (Harness-10 h10-refine, supersedes subfolder sub-md/) — TOOL-AWARE (chống mojibake G-009):`, `- Full-detail công-việc của mày → ĐÚNG 1 file: \`${subMd}\` (folder đã scaffold sẵn — KHÔNG tạo folder).`, ` • NẾU mày CÓ Write/Edit tool (implementer-backend/frontend, test-specialist, frontend-designer): GHI TRỰC TIẾP via Write/Edit. 🔴 KHÔNG Bash-write MD ($-expansion/mojibake).`, ` • NẾU mày CHỈ có Bash (read-only sub: investigator-codebase/api, reviewer, cicd-monitor — KHÔNG Write tool): 🔴 TUYỆT ĐỐI KHÔNG Bash-write MD → để full-detail trong "findings" + đặt subMdPath="${subMd}"; EM MAIN scribe @P3 (single-writer Write-tool, no-corruption).`, `- 🔴 ISOLATION (B6→Harness-10, AUDIT): CHỈ ghi \`${subMd}\` (+ code-file-disjoint nếu task giao). TUYỆT ĐỐI KHÔNG ghi/sửa: agent-memory/* (MEMORY.md BẤT KỲ sub) · MD canonical (CLAUDE/README/STATUS/agents) · sub-MD agent khác. Em main git-status/diff audit sau P2 — tracked-file đổi NGOÀI run-folder (runs//) + code-disjoint = vi-phạm (run-folder TRACKED → HIỆN trong diff).`, `- LUÔN return: findings (FULL) + checklistEvidence + memoryDelta (4-field) + subMdPath="${subMd}". Harvest per-turn primary (C4); H2 gom @session-end = backstop verify-idempotent → agent-memory/${role || 'sub'}.`, `- 🔴 KHÔNG store_memory/RAG-write · KHÔNG Bash curl/HTTP Qdrant (:6333 = git-diff MÙ, chỉ chunk-count bắt) · KHÔNG ghi file NGOÀI repo/run-folder. RAG single-writer=em main; containment = git-diff(in-repo)+chunk-count(RAG) [G-015].`, ].join('\n') : [ `## OUTPUT write-guard (DEFAULT return-delta-only):`, `- ⚠️ KHÔNG ghi ký ức kênh nào: KHÔNG file MEMORY.md, KHÔNG store_memory/RAG-write (Qdrant). CHỈ return memoryDelta → em main VERIFY + APPEND-only @P3 (B3). KHÔNG overwrite file/chunk của sub khác.`, `- ⚠️ Nếu task có WRITE file: CHỈ file-disjoint được giao (em main git-diff verify sau P2). KHÔNG đụng file ngoài phạm vi. Scope = repo SOLUTION_ERP only (S1).`, ].join('\n') const prompt = [ mem ? `## MEMORY-PACK (${role}) — SLICE ký ức tích lũy của sub này, ĐỌC trước khi làm (KHÔNG phải full memory):\n${mem}\n` : '', spec ? `## SPEC / acceptance-criteria (chung):\n${spec}\n` : '', `## TASK:\n${(t && t.prompt) || '(thiếu prompt — lỗi taskList @P0)'}`, [ '## OUTPUT (structured bắt buộc):', '- findings: kết quả + evidence file:line (KHÔNG narrative suông).', '- checklistEvidence: số đo / verdict cho acceptance-checklist.', '- memoryDelta {task·verdict·learned·surprise}: để EM MAIN harvest @P3.', wave ? '- subMdPath: đường-dẫn sub-MD mày đã ghi.' : '', ].filter(Boolean).join('\n'), writeGuard, ].filter(Boolean).join('\n') return agent(prompt, { agentType: role || undefined, schema: SCHEMA, label: (t && t.label) || `hmw:${role || 'task'}-${i}`, model: resolveModel(role, raw, t && t.tier, i), // worker-tier-pin (undefined = theo frontmatter = opus S124) }) })) // trả mảng kết quả (lọc null nếu agent lỗi/null — invalid-role đã STOP-HARD up-front; .filter(Boolean) chỉ lọc null-lane do agent lỗi runtime) // về em main → P3 VERIFY + harvest + P4 checklist return results.filter(Boolean)