@kienntpixon/dieu-phoi 0.7.0 → 0.7.2

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.
package/lib/aggregate.cjs CHANGED
@@ -125,6 +125,7 @@ function buildAgents(events) {
125
125
  const userVerdicts = [];
126
126
  const autoVerdicts = [];
127
127
  const overrides = [];
128
+ const implicitOverrides = []; // core 0.7.2 (K4): deviations the hook logged; not the user's overrides
128
129
  const steers = [];
129
130
  const get = (id) => {
130
131
  if (!map.has(id)) map.set(id, { agent_id: id, dispatch: null, completes: [], verdicts: [], steers: [] });
@@ -135,7 +136,7 @@ function buildAgents(events) {
135
136
  if (!ev || typeof ev !== 'object') return;
136
137
  if (isInternalComplete(ev, dispatched)) return;
137
138
  const e = Object.assign({ _idx: idx }, ev);
138
- if (ev.kind === 'override') { overrides.push(e); return; }
139
+ if (ev.kind === 'override') { (ev.source === 'implicit' ? implicitOverrides : overrides).push(e); return; }
139
140
  if (ev.kind === 'verdict') {
140
141
  const v = Object.assign(e, normalizeVerdict(ev));
141
142
  if (v.by === 'user') { userVerdicts.push(v); return; }
@@ -173,6 +174,8 @@ function buildAgents(events) {
173
174
  task_id: pick('task_id') || (verdict && verdict.task_id) || (a.steers[0] && a.steers[0].task_id) || null,
174
175
  try: Number(pick('try')) || 1,
175
176
  role: pick('role') || 'exec',
177
+ // core 0.7.1 (C5): the one batched verifier of a fast request (tag `|batch`, dispatch `batch:true`)
178
+ batch: pick('batch') === true,
176
179
  score: d.score !== undefined ? d.score : null,
177
180
  urgency: d.urgency || null,
178
181
  model: core.normalizeModel(cl.model) || core.normalizeModel(d.model) || null,
@@ -200,7 +203,7 @@ function buildAgents(events) {
200
203
  last_complete_ts: a.completes.length ? Math.max(...a.completes.map(tsOf)) : 0,
201
204
  });
202
205
  }
203
- return { agents, userVerdicts, autoVerdicts, overrides, steers };
206
+ return { agents, userVerdicts, autoVerdicts, overrides, implicitOverrides, steers };
204
207
  }
205
208
 
206
209
  /** Group agents into tasks (sample unit = task_id; agents without task_id are their own task). */
@@ -284,7 +287,9 @@ function buildTasks(agents, userVerdicts, autoVerdicts) {
284
287
  const autoOverturned = autoAccept && user.some((v) => v.outcome === 'reject' && tsOf(v) > Math.min(...autoTs));
285
288
  const lastUser = user[user.length - 1] || null;
286
289
  const accepted = lastUser ? lastUser.outcome === 'accept' : attempts.some((a) => a.outcome === 'pass');
287
- const costed = t.agents.filter((a) => a.completed || a.outcome);
290
+ // core 0.7.1 (C5): the batched verifier checks every fast step of a request; it is billed to its own cell
291
+ // (batchVerifyCells), never to the task_type x tier of the fast step whose task_id it carries.
292
+ const costed = t.agents.filter((a) => (a.completed || a.outcome) && !isBatchVerify(a));
288
293
  tasks.push({
289
294
  key: t.key,
290
295
  task_id: t.task_id,
@@ -315,6 +320,43 @@ function buildTasks(agents, userVerdicts, autoVerdicts) {
315
320
  return tasks;
316
321
  }
317
322
 
323
+ /** core 0.7.1 (C5): a role=verify agent dispatched with `batch:true`. */
324
+ function isBatchVerify(a) {
325
+ return !!a && a.role === 'verify' && a.batch === true;
326
+ }
327
+
328
+ /**
329
+ * core 0.7.1 (C5), CLI only: batched verifiers grouped by their own model x effort. Agents with a complete or a
330
+ * verdict; cost/time per agent = sum over its complete segments (null when unknown, left out of the percentiles).
331
+ */
332
+ function batchVerifyCells(agents) {
333
+ const groups = new Map();
334
+ for (const a of agents || []) {
335
+ if (!isBatchVerify(a) || !(a.completed || a.outcome)) continue;
336
+ const key = `${a.model || '?'}|${a.effort || '?'}`;
337
+ if (!groups.has(key)) groups.set(key, { model: a.model, effort: a.effort, agents: [] });
338
+ groups.get(key).agents.push(a);
339
+ }
340
+ const out = [];
341
+ for (const g of groups.values()) {
342
+ const costs = g.agents.map((a) => a.cost_usd);
343
+ const times = g.agents.map((a) => (a.duration_s === null ? null : a.duration_s / 60));
344
+ out.push({
345
+ model: g.model,
346
+ effort: g.effort,
347
+ n: g.agents.length,
348
+ tasks: [...new Set(g.agents.map((a) => a.task_id).filter(Boolean))].length,
349
+ cost_total_usd: round6(costs.reduce((s, c) => s + (typeof c === 'number' && isFinite(c) ? c : 0), 0)),
350
+ cost_mean_usd: round6(mean(costs)),
351
+ cost_p50_usd: round6(percentile(costs, 0.5)),
352
+ time_mean_min: round6(mean(times)),
353
+ time_p50_min: round6(percentile(times, 0.5)),
354
+ });
355
+ }
356
+ out.sort((a, b) => String(a.model).localeCompare(String(b.model)) || String(a.effort).localeCompare(String(b.effort)));
357
+ return out;
358
+ }
359
+
318
360
  function cellKey(type, model, effort) {
319
361
  return `${type}|${model || '?'}|${effort || '?'}`;
320
362
  }
@@ -522,18 +564,19 @@ function orchestrationSummary(events) {
522
564
  }
523
565
 
524
566
  function analyze(events, opts) {
525
- const { agents, userVerdicts, autoVerdicts, overrides, steers } = buildAgents(withoutDiscarded(events));
567
+ const { agents, userVerdicts, autoVerdicts, overrides, implicitOverrides, steers } = buildAgents(withoutDiscarded(events));
526
568
  const tasks = buildTasks(agents, userVerdicts, autoVerdicts);
527
569
  const cells = buildCells(tasks, opts);
528
570
  return {
529
- agents, tasks, cells, overrides, userVerdicts, autoVerdicts, steerEvents: steers, steers: buildSteers(tasks, steers, agents),
571
+ agents, tasks, cells, overrides, implicitOverrides, userVerdicts, autoVerdicts, steerEvents: steers, steers: buildSteers(tasks, steers, agents),
530
572
  orchestration: orchestrationSummary(events),
573
+ batch_verify: batchVerifyCells(agents),
531
574
  };
532
575
  }
533
576
 
534
577
  module.exports = {
535
578
  MIN_N, FAIL_OUTCOMES, HEAVY_STEERS, ABORTED, QUOTA_REASON_RE, HANDOFF_REASON_RE, discardedTaskIds, withoutDiscarded, CANONICAL_CELL_FIELDS, normalizeVerdict, isSelf, isInternalComplete, buildAgents, buildTasks,
536
- buildCells, buildSteers, canonicalCells, orchestrationSummary, analyze, cellKey, mean, percentile, tsOf, steerIdOf,
579
+ buildCells, buildSteers, canonicalCells, isBatchVerify, batchVerifyCells, orchestrationSummary, analyze, cellKey, mean, percentile, tsOf, steerIdOf,
537
580
  };
538
581
 
539
582
  // ------------------------------------------------------------------ core 0.7.0 (s2): stats split by mode
package/lib/args.cjs CHANGED
@@ -4,7 +4,7 @@ const fs = require('fs');
4
4
  const path = require('path');
5
5
  const core = require('../hook.cjs');
6
6
 
7
- const BOOLEAN_FLAGS = new Set(['json', 'help', 'h', 'purge', 'version', 'v', 'dry-run', 'no-sync', 'approve', 'yes', 'force', 'follow', 'remove', 'dirty', 'no-cache', 'untagged', 'no-knowledge', 'net', 'with-codebase-memory', 'cbm', 'allow-custom-source', 'auto']);
7
+ const BOOLEAN_FLAGS = new Set(['json', 'help', 'h', 'purge', 'version', 'v', 'dry-run', 'no-sync', 'approve', 'yes', 'force', 'follow', 'remove', 'dirty', 'no-cache', 'untagged', 'no-knowledge', 'net', 'with-codebase-memory', 'cbm', 'allow-custom-source', 'auto', 'include-local']);
8
8
 
9
9
  /** Minimal argv parser: positionals, --k v, --k=v, boolean flags. */
10
10
  function parseArgs(argv) {
package/lib/choose.cjs CHANGED
@@ -132,9 +132,19 @@ function loadProfile(skillDir) {
132
132
  const v = Number(r.orch_ctx_soft_cap);
133
133
  if (isFinite(v) && v > 0) softCap = Math.min(DEFAULT_ORCH_CTX_SOFT_CAP, Math.floor(v));
134
134
  }
135
+ // core 0.7.1 (retro 2026-10-26, C4): per-type tier floor from the user's preference (e.g. art-gen -> opus/medium).
136
+ // Only tightens: choose takes max(floor, safety floors, escalation); an entry off the ladder is ignored.
137
+ const tierFloor = {};
138
+ if (r.tier_floor && typeof r.tier_floor === 'object' && !Array.isArray(r.tier_floor)) {
139
+ for (const [k, v] of Object.entries(r.tier_floor)) {
140
+ if (k.startsWith('_')) continue;
141
+ const idx = parsePrior(v);
142
+ if (idx !== null) tierFloor[String(k).toLowerCase()] = idx;
143
+ }
144
+ }
135
145
  return {
136
146
  source, dept: r.dept || null, priors, lambda, explore_rate: explore, time_cap_min: timeCap, extra_task_types: extra,
137
- orch_ctx_soft_cap: softCap,
147
+ orch_ctx_soft_cap: softCap, tier_floor: tierFloor,
138
148
  };
139
149
  }
140
150
 
@@ -302,20 +312,8 @@ function estimateFor(idx, cell, pricing) {
302
312
 
303
313
  // ------------------------------------------------------------------ task type guard
304
314
 
305
- // Ad-hoc labels seen in real logs → the closest fixed type (core 0.4.0). A label maps only when its target
306
- // is known (fixed list or the profile's extra_task_types), e.g. art-fix → art-gen needs art-gen in the profile.
307
- const TYPE_ALIASES = {
308
- 'bug-fix': 'debug', bugfix: 'debug', fix: 'debug', bug: 'debug',
309
- config: 'mechanical-edit', shell: 'mechanical-edit', deploy: 'mechanical-edit', ops: 'mechanical-edit', edit: 'mechanical-edit',
310
- docs: 'content-write', doc: 'content-write', documentation: 'content-write', writing: 'content-write', copy: 'content-write',
311
- design: 'plan-design', plan: 'plan-design', planning: 'plan-design', architecture: 'plan-design',
312
- research: 'research-synth', synth: 'research-synth',
313
- test: 'test-write', tests: 'test-write',
314
- analysis: 'data-analysis', data: 'data-analysis',
315
- impl: 'feature-impl', feature: 'feature-impl', implement: 'feature-impl',
316
- 'code-review': 'review',
317
- 'art-fix': 'art-gen', art: 'art-gen',
318
- };
315
+ // Ad-hoc labels → the closest fixed type: the table lives in hook.cjs (core 0.7.1) so dispatch tags map the same way.
316
+ const TYPE_ALIASES = core.TYPE_ALIASES;
319
317
 
320
318
  /**
321
319
  * { type, known, mapped_from, warning }: a fixed/extra type passes; an ad-hoc label with a known alias target
@@ -451,6 +449,11 @@ function choose(opts) {
451
449
  if (!verifiable) { safetyFloor = Math.max(safetyFloor, OPUS_MEDIUM); floorWhy.push('không kiểm chứng bằng máy → tối thiểu opus/medium'); }
452
450
  if (!reversible && role === 'exec') { safetyFloor = Math.max(safetyFloor, OPUS_MEDIUM); floorWhy.push('khó hoàn tác → tối thiểu opus/medium'); }
453
451
 
452
+ // Preference floor from the profile (tier_floor): never lowers a safety floor (max), never above the ceiling.
453
+ const prefFloor = known && profile.tier_floor && Number.isInteger(profile.tier_floor[type])
454
+ ? Math.min(TOP, profile.tier_floor[type]) : 0;
455
+ const prefWhy = prefFloor > 0 ? `tier_floor của profile: ${type} tối thiểu ${tierName(prefFloor)}` : null;
456
+
454
457
  const noExploreWhy = [];
455
458
  if (score >= 9) noExploreWhy.push(`score ${score}≥9`);
456
459
  if (!verifiable) noExploreWhy.push('không kiểm chứng bằng máy');
@@ -489,6 +492,7 @@ function choose(opts) {
489
492
  tier: tierName(idx),
490
493
  role,
491
494
  task_type: type,
495
+ score,
492
496
  mode,
493
497
  verify_plan: vp ? vp.verify_plan : null,
494
498
  verify_reason: vp ? vp.verify_reason : null,
@@ -501,7 +505,7 @@ function choose(opts) {
501
505
 
502
506
  // Unknown task type (not fixed, not in the profile, no alias): its stats would never pool — ask first.
503
507
  if (!known) {
504
- return finish(Math.max(prior, safetyFloor), rt.warning, false, { action: 'ask_user', unknown_type: true });
508
+ return finish(Math.max(prior, safetyFloor), rt.warning, false, { action: 'ask_user', unknown_type: true, reason_code: 'unknown-type' });
505
509
  }
506
510
 
507
511
  if (overrideIdx !== null && overrideIdx < safetyFloor) {
@@ -509,34 +513,37 @@ function choose(opts) {
509
513
  }
510
514
  // Escalation beats override/approval: the user must decide first.
511
515
  if (esc.ask) {
512
- const userIdx = overrideIdx !== null ? overrideIdx : (approvedIdx !== null ? approvedIdx : 0);
516
+ const userIdx = overrideIdx !== null ? overrideIdx : (approvedIdx !== null ? approvedIdx : prefFloor);
513
517
  const idx = Math.max(userIdx, esc.floor, safetyFloor);
514
- return finish(idx, esc.note, false, { action: 'ask_user', fails: esc.fails, tried: esc.tried });
518
+ return finish(idx, esc.note, false, { action: 'ask_user', fails: esc.fails, tried: esc.tried, reason_code: 'ask' });
515
519
  }
516
520
 
517
521
  // Tier approved by the user in the plan table: no Thompson/exploration; only floors can raise it.
518
522
  if (approvedIdx !== null) {
519
523
  const forced = Math.max(safetyFloor, esc.floor);
520
524
  if (approvedIdx >= forced) {
521
- const n = esc.note ? `; ${esc.note}` : '';
522
- return finish(approvedIdx, `người dùng đã duyệt ${tierName(approvedIdx)} trong bảng phân công${n}`, false, { deviation: false });
525
+ let n = esc.note ? `; ${esc.note}` : '';
526
+ if (approvedIdx < prefFloor) n += `; dưới ${prefWhy} (người dùng duyệt nên giữ)`;
527
+ return finish(approvedIdx, `người dùng đã duyệt ${tierName(approvedIdx)} trong bảng phân công${n}`, false, { deviation: false, reason_code: 'plan' });
523
528
  }
524
529
  const why = [];
525
530
  if (safetyFloor > approvedIdx) why.push(`sàn an toàn ${tierName(safetyFloor)} (${floorWhy.join(', ')})`);
526
531
  if (esc.floor > approvedIdx) why.push(esc.note || `leo thang → ${tierName(esc.floor)}`);
527
- return finish(forced, `lệch khỏi tier đã duyệt ${tierName(approvedIdx)} → ${tierName(forced)}: ${why.join('; ')}`, false, { deviation: true, approved_tier: tierName(approvedIdx) });
532
+ return finish(forced, `lệch khỏi tier đã duyệt ${tierName(approvedIdx)} → ${tierName(forced)}: ${why.join('; ')}`, false, { deviation: true, approved_tier: tierName(approvedIdx), reason_code: 'plan' });
528
533
  }
529
534
  if (esc.note && !esc.ask) notes.push(esc.note);
530
535
 
531
- const floor = Math.max(safetyFloor, esc.ask ? 0 : esc.floor);
536
+ const floor = Math.max(safetyFloor, esc.ask ? 0 : esc.floor, prefFloor);
532
537
  const base = Math.min(TOP, Math.max(prior, floor));
533
538
  if (base > prior) {
534
539
  const why = floorWhy.slice();
535
540
  if (esc.floor > prior && esc.floor >= safetyFloor && !esc.ask) why.push(`leo thang → ${tierName(esc.floor)}`);
541
+ if (prefFloor > prior) why.push(prefWhy);
536
542
  notes.unshift(`sàn ${tierName(base)} cao hơn ${priorWhy} ${tierName(prior)}: ${why.join(', ') || 'luật an toàn'}`);
537
543
  } else if (floorWhy.length) {
538
544
  notes.push(`sàn an toàn: ${floorWhy.join(', ')}`);
539
545
  }
546
+ if (prefFloor > 0 && base <= prior) notes.push(prefWhy);
540
547
 
541
548
  const minIdx = noExplore ? base : floor;
542
549
  const candidates = [];
@@ -552,10 +559,15 @@ function choose(opts) {
552
559
 
553
560
  let chosen = base;
554
561
  let why;
562
+ // core 0.7.1 (C3): why this tier, as a code — thompson | floor | escalation | prior | user-prior | explore | plan | override | ask | unknown-type
563
+ let code;
564
+ let tsScore = null;
555
565
  const baseCell = cellAt(base);
556
566
  const scored = pool.filter((x) => x.cell && x.cell.enough);
557
567
  if (!baseCell || !baseCell.enough || !scored.length) {
558
568
  why = `${priorWhy} ${tierName(base)} (${baseCell ? `n=${baseCell.n}` : 'n=0'}, chưa đủ dữ liệu)`;
569
+ if (base > prior) code = esc.floor === base && esc.floor > Math.max(safetyFloor, prefFloor) ? 'escalation' : 'floor';
570
+ else code = priorWhy === 'prior' ? 'prior' : 'user-prior';
559
571
  } else {
560
572
  let best = null;
561
573
  for (const x of scored) {
@@ -568,6 +580,8 @@ function choose(opts) {
568
580
  if (!best || s < best.s) best = { i: x.i, s, p };
569
581
  }
570
582
  chosen = best.i;
583
+ code = 'thompson';
584
+ tsScore = Math.round(best.s * 1000) / 1000;
571
585
  why = `Thompson: ${tierName(chosen)} điểm ${best.s.toFixed(3)} (p≈${best.p.toFixed(2)}, ${scored.length} ô đủ dữ liệu)`;
572
586
  }
573
587
 
@@ -576,17 +590,19 @@ function choose(opts) {
576
590
  if (!noExplore && chosen - 1 >= floor && u < profile.explore_rate) {
577
591
  chosen -= 1;
578
592
  explored = true;
593
+ code = 'explore';
579
594
  why = `thăm dò xuống ${tierName(chosen)} (xác suất ${profile.explore_rate}); ${why}`;
580
595
  }
581
596
 
582
597
  if (overrideIdx !== null) {
583
598
  const sugg = tierName(chosen);
584
- return finish(overrideIdx, `người dùng chọn ${tierName(overrideIdx)} (đề xuất là ${sugg}: ${why})`, false, {
585
- overridden: overrideIdx !== chosen, suggested_tier: sugg,
599
+ const below = overrideIdx < prefFloor ? `; dưới ${prefWhy} (người dùng chọn nên giữ)` : '';
600
+ return finish(overrideIdx, `người dùng chọn ${tierName(overrideIdx)} (đề xuất là ${sugg}: ${why})${below}`, false, {
601
+ overridden: overrideIdx !== chosen, suggested_tier: sugg, reason_code: 'override', suggested_reason_code: code, thompson_score: tsScore,
586
602
  });
587
603
  }
588
604
  if (noExplore) notes.push(`không thăm dò: ${noExploreWhy.join(', ')}`);
589
- return finish(chosen, [why].concat(notes).join('; '), explored);
605
+ return finish(chosen, [why].concat(notes).join('; '), explored, { reason_code: code, thompson_score: tsScore });
590
606
  }
591
607
 
592
608
  module.exports = {