@kienntpixon/dieu-phoi 0.6.1 → 0.7.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.
- package/CHANGELOG.md +36 -0
- package/bin/dieu-phoi.cjs +20 -7
- package/hook.cjs +393 -5
- package/lib/aggregate.cjs +137 -14
- package/lib/args.cjs +1 -1
- package/lib/choose.cjs +60 -2
- package/lib/commands.cjs +197 -13
- package/package.json +1 -1
package/lib/aggregate.cjs
CHANGED
|
@@ -56,13 +56,14 @@ function round6(v) {
|
|
|
56
56
|
|
|
57
57
|
/**
|
|
58
58
|
* Normalize a verdict to the two-layer model. Legacy `rework` (no `by`) = user reject/quality;
|
|
59
|
-
* missing `by` otherwise = agent layer.
|
|
59
|
+
* missing `by` otherwise = agent layer. Core 0.7.0: `by=auto` (fast-mode self-acceptance) is its own layer —
|
|
60
|
+
* never a user verdict, never an agent verdict (CONTRACT "Mode theo yêu cầu").
|
|
60
61
|
*/
|
|
61
62
|
function normalizeVerdict(ev) {
|
|
62
63
|
if (ev.outcome === 'rework' && !ev.by) {
|
|
63
64
|
return { by: 'user', outcome: 'reject', cause: 'quality', round: Number(ev.round) || null };
|
|
64
65
|
}
|
|
65
|
-
const by = ev.by === 'user' ? 'user' : 'agent';
|
|
66
|
+
const by = ev.by === 'user' ? 'user' : (ev.by === 'auto' ? 'auto' : 'agent');
|
|
66
67
|
let cause = ev.cause || null;
|
|
67
68
|
if (by === 'user' && ev.outcome === 'reject' && cause !== 'scope') cause = 'quality';
|
|
68
69
|
let outcome = ev.outcome;
|
|
@@ -111,12 +112,18 @@ function steerIdOf(ev) {
|
|
|
111
112
|
return typeof ev.steer_id === 'string' && ev.steer_id ? ev.steer_id : `${ev.agent_id}|${ev.ts}`;
|
|
112
113
|
}
|
|
113
114
|
|
|
115
|
+
/** core 0.7.0: an exec's own selective-test verdict (`verify=self`) is not evidence for the agent layer. */
|
|
116
|
+
function isSelf(v) {
|
|
117
|
+
return !!v && v.verify === 'self';
|
|
118
|
+
}
|
|
119
|
+
|
|
114
120
|
/**
|
|
115
|
-
* Join events. Returns agents (one per real agent), userVerdicts (task layer), overrides, steers.
|
|
121
|
+
* Join events. Returns agents (one per real agent), userVerdicts (task layer), autoVerdicts (by=auto), overrides, steers.
|
|
116
122
|
*/
|
|
117
123
|
function buildAgents(events) {
|
|
118
124
|
const map = new Map();
|
|
119
125
|
const userVerdicts = [];
|
|
126
|
+
const autoVerdicts = [];
|
|
120
127
|
const overrides = [];
|
|
121
128
|
const steers = [];
|
|
122
129
|
const get = (id) => {
|
|
@@ -132,6 +139,7 @@ function buildAgents(events) {
|
|
|
132
139
|
if (ev.kind === 'verdict') {
|
|
133
140
|
const v = Object.assign(e, normalizeVerdict(ev));
|
|
134
141
|
if (v.by === 'user') { userVerdicts.push(v); return; }
|
|
142
|
+
if (v.by === 'auto') { autoVerdicts.push(v); return; }
|
|
135
143
|
}
|
|
136
144
|
if (ev.kind === 'steer') steers.push(e);
|
|
137
145
|
if (typeof ev.agent_id !== 'string' || !ev.agent_id) return;
|
|
@@ -149,6 +157,8 @@ function buildAgents(events) {
|
|
|
149
157
|
const vAll = a.verdicts.slice().sort((x, y) => tsOf(x) - tsOf(y) || x._idx - y._idx);
|
|
150
158
|
// `aborted` (quota pause) is neither pass nor fail: it never sets the outcome nor counts as a result.
|
|
151
159
|
const v = vAll.filter((x) => x.outcome !== ABORTED);
|
|
160
|
+
const checked = v.filter((x) => !isSelf(x));
|
|
161
|
+
const selfs = v.filter(isSelf);
|
|
152
162
|
const verdict = v.length ? v[v.length - 1] : null;
|
|
153
163
|
const firstVerdictTs = v.length ? tsOf(v[0]) : Infinity;
|
|
154
164
|
const pick = (k) => (d[k] !== undefined && d[k] !== null ? d[k] : (c0[k] !== undefined && c0[k] !== null ? c0[k] : null));
|
|
@@ -173,7 +183,10 @@ function buildAgents(events) {
|
|
|
173
183
|
duration_s: a.completes.length ? sumOrNull(a.completes.map((x) => x.duration_s)) : null,
|
|
174
184
|
tool_uses: a.completes.length ? sumOrNull(a.completes.map((x) => x.tool_uses)) : null,
|
|
175
185
|
outcome: verdict ? verdict.outcome : null,
|
|
176
|
-
|
|
186
|
+
// agent layer = first verdict that is not the exec's own selective tests (verify=self, core 0.7.0)
|
|
187
|
+
first_outcome: checked.length ? checked[0].outcome : null,
|
|
188
|
+
self_first: selfs.length ? selfs[0].outcome : null,
|
|
189
|
+
self_verdicts: selfs.length,
|
|
177
190
|
// every non-aborted agent verdict in time order: a retry by SendMessage to the same agent is a new result
|
|
178
191
|
verdict_seq: v.map((x) => ({ outcome: x.outcome, ts: tsOf(x), idx: x._idx })),
|
|
179
192
|
aborted: vAll.length > 0 && vAll[vAll.length - 1].outcome === ABORTED,
|
|
@@ -187,24 +200,28 @@ function buildAgents(events) {
|
|
|
187
200
|
last_complete_ts: a.completes.length ? Math.max(...a.completes.map(tsOf)) : 0,
|
|
188
201
|
});
|
|
189
202
|
}
|
|
190
|
-
return { agents, userVerdicts, overrides, steers };
|
|
203
|
+
return { agents, userVerdicts, autoVerdicts, overrides, steers };
|
|
191
204
|
}
|
|
192
205
|
|
|
193
206
|
/** Group agents into tasks (sample unit = task_id; agents without task_id are their own task). */
|
|
194
|
-
function buildTasks(agents, userVerdicts) {
|
|
207
|
+
function buildTasks(agents, userVerdicts, autoVerdicts) {
|
|
195
208
|
const map = new Map();
|
|
196
209
|
const byAgent = new Map();
|
|
197
210
|
for (const a of agents) {
|
|
198
211
|
const key = a.task_id ? `task:${a.task_id}` : `agent:${a.agent_id}`;
|
|
199
|
-
if (!map.has(key)) map.set(key, { key, task_id: a.task_id, agents: [], user: [] });
|
|
212
|
+
if (!map.has(key)) map.set(key, { key, task_id: a.task_id, agents: [], user: [], auto: [] });
|
|
200
213
|
map.get(key).agents.push(a);
|
|
201
214
|
byAgent.set(a.agent_id, key);
|
|
202
215
|
}
|
|
216
|
+
for (const v of autoVerdicts || []) {
|
|
217
|
+
const t = v.task_id ? map.get(`task:${v.task_id}`) : null;
|
|
218
|
+
if (t) t.auto.push(v);
|
|
219
|
+
}
|
|
203
220
|
for (const v of userVerdicts || []) {
|
|
204
221
|
let key = v.task_id ? `task:${v.task_id}` : null;
|
|
205
222
|
if (!key && v.agent_id) key = byAgent.get(v.agent_id) || null; // legacy rework without task_id
|
|
206
223
|
if (!key) continue;
|
|
207
|
-
if (!map.has(key)) map.set(key, { key, task_id: v.task_id, agents: [], user: [] });
|
|
224
|
+
if (!map.has(key)) map.set(key, { key, task_id: v.task_id, agents: [], user: [], auto: [] });
|
|
208
225
|
map.get(key).user.push(v);
|
|
209
226
|
}
|
|
210
227
|
const tasks = [];
|
|
@@ -261,6 +278,10 @@ function buildTasks(agents, userVerdicts) {
|
|
|
261
278
|
if (userOutcome === 'accept') { success = true; source = 'user'; } else if (userOutcome === 'reject_quality') { success = false; source = 'user'; } else if (!userOutcome && agentFirst !== null) {
|
|
262
279
|
success = agentFirst; source = 'agent';
|
|
263
280
|
}
|
|
281
|
+
// core 0.7.0: by=auto never decides success; a user reject after the first auto accept overturns it.
|
|
282
|
+
const autoTs = t.auto.filter((v) => v.outcome === 'accept').map(tsOf);
|
|
283
|
+
const autoAccept = autoTs.length > 0;
|
|
284
|
+
const autoOverturned = autoAccept && user.some((v) => v.outcome === 'reject' && tsOf(v) > Math.min(...autoTs));
|
|
264
285
|
const lastUser = user[user.length - 1] || null;
|
|
265
286
|
const accepted = lastUser ? lastUser.outcome === 'accept' : attempts.some((a) => a.outcome === 'pass');
|
|
266
287
|
const costed = t.agents.filter((a) => a.completed || a.outcome);
|
|
@@ -283,6 +304,9 @@ function buildTasks(agents, userVerdicts) {
|
|
|
283
304
|
excluded: !!(first && first.steered) || userOutcome === 'reject_scope',
|
|
284
305
|
accepted,
|
|
285
306
|
agent_first_pass: agentFirst,
|
|
307
|
+
self_first: firstRes ? firstRes.self_first : null,
|
|
308
|
+
auto_accept: autoAccept,
|
|
309
|
+
auto_overturned: autoOverturned,
|
|
286
310
|
// agents with a verdict but no complete event (hook missed) make cost/time unknown (null)
|
|
287
311
|
total_cost: sumOrNull(costed.map((a) => a.cost_usd)),
|
|
288
312
|
total_time_s: sumOrNull(costed.map((a) => a.duration_s)),
|
|
@@ -309,11 +333,17 @@ function buildCells(tasks, opts) {
|
|
|
309
333
|
if (!f) continue;
|
|
310
334
|
const key = cellKey(t.task_type, f.model, f.effort);
|
|
311
335
|
if (!cells.has(key)) {
|
|
312
|
-
cells.set(key, {
|
|
336
|
+
cells.set(key, {
|
|
337
|
+
task_type: t.task_type, model: f.model, effort: f.effort, dispatched: 0, excluded: 0, tasks: [], agentTasks: [], selfTasks: [], verdicted: 0, auto: 0, overturned: 0,
|
|
338
|
+
});
|
|
313
339
|
}
|
|
314
340
|
const c = cells.get(key);
|
|
315
341
|
c.dispatched++;
|
|
342
|
+
if (t.auto_accept) c.auto++;
|
|
343
|
+
if (t.auto_overturned) c.overturned++;
|
|
316
344
|
if (t.excluded) { c.excluded++; continue; }
|
|
345
|
+
if (t.self_first) c.selfTasks.push(t);
|
|
346
|
+
if (t.self_first || t.agent_first_pass !== null) c.verdicted++;
|
|
317
347
|
if (t.success !== null) c.tasks.push(t);
|
|
318
348
|
if (t.agent_first_pass !== null) c.agentTasks.push(t);
|
|
319
349
|
}
|
|
@@ -331,6 +361,9 @@ function buildCells(tasks, opts) {
|
|
|
331
361
|
// score uses the time median, not the mean: waits for the user (gate 3, quota pauses) inflate the mean
|
|
332
362
|
// 3-13x (retro 2026-10-18: art-gen sonnet mean 483 vs p50 37 min) and would flip priors to slower tiers.
|
|
333
363
|
const timeP50 = percentile(timesMin, 0.5);
|
|
364
|
+
const notes = [];
|
|
365
|
+
if (n < MIN_N) notes.push('chưa đủ dữ liệu');
|
|
366
|
+
if (c.verdicted && c.selfTasks.length / c.verdicted > 0.5) notes.push('ít bằng chứng');
|
|
334
367
|
out.push({
|
|
335
368
|
task_type: c.task_type,
|
|
336
369
|
model: c.model,
|
|
@@ -358,8 +391,12 @@ function buildCells(tasks, opts) {
|
|
|
358
391
|
attempt_p50_min: percentile(attemptTimes, 0.5),
|
|
359
392
|
attempt_p75_min: percentile(attemptTimes, 0.75),
|
|
360
393
|
score: costMean === null && timeP50 === null ? null : round6((costMean || 0) + lambda * (timeP50 || 0)),
|
|
394
|
+
n_self: c.selfTasks.length,
|
|
395
|
+
self_pass: c.selfTasks.filter((t) => t.self_first === 'pass').length,
|
|
396
|
+
auto_accept: c.auto,
|
|
397
|
+
auto_overturned: c.overturned,
|
|
361
398
|
enough: n >= MIN_N,
|
|
362
|
-
note:
|
|
399
|
+
note: notes.join('; '),
|
|
363
400
|
});
|
|
364
401
|
}
|
|
365
402
|
out.sort((a, b) => a.task_type.localeCompare(b.task_type)
|
|
@@ -485,16 +522,102 @@ function orchestrationSummary(events) {
|
|
|
485
522
|
}
|
|
486
523
|
|
|
487
524
|
function analyze(events, opts) {
|
|
488
|
-
const { agents, userVerdicts, overrides, steers } = buildAgents(withoutDiscarded(events));
|
|
489
|
-
const tasks = buildTasks(agents, userVerdicts);
|
|
525
|
+
const { agents, userVerdicts, autoVerdicts, overrides, steers } = buildAgents(withoutDiscarded(events));
|
|
526
|
+
const tasks = buildTasks(agents, userVerdicts, autoVerdicts);
|
|
490
527
|
const cells = buildCells(tasks, opts);
|
|
491
528
|
return {
|
|
492
|
-
agents, tasks, cells, overrides, userVerdicts, steerEvents: steers, steers: buildSteers(tasks, steers, agents),
|
|
529
|
+
agents, tasks, cells, overrides, userVerdicts, autoVerdicts, steerEvents: steers, steers: buildSteers(tasks, steers, agents),
|
|
493
530
|
orchestration: orchestrationSummary(events),
|
|
494
531
|
};
|
|
495
532
|
}
|
|
496
533
|
|
|
497
534
|
module.exports = {
|
|
498
|
-
MIN_N, FAIL_OUTCOMES, HEAVY_STEERS, ABORTED, QUOTA_REASON_RE, HANDOFF_REASON_RE, discardedTaskIds, withoutDiscarded, CANONICAL_CELL_FIELDS, normalizeVerdict, isInternalComplete, buildAgents, buildTasks,
|
|
535
|
+
MIN_N, FAIL_OUTCOMES, HEAVY_STEERS, ABORTED, QUOTA_REASON_RE, HANDOFF_REASON_RE, discardedTaskIds, withoutDiscarded, CANONICAL_CELL_FIELDS, normalizeVerdict, isSelf, isInternalComplete, buildAgents, buildTasks,
|
|
499
536
|
buildCells, buildSteers, canonicalCells, orchestrationSummary, analyze, cellKey, mean, percentile, tsOf, steerIdOf,
|
|
500
537
|
};
|
|
538
|
+
|
|
539
|
+
// ------------------------------------------------------------------ core 0.7.0 (s2): stats split by mode
|
|
540
|
+
//
|
|
541
|
+
// Kept apart from the canonical cells (not part of the CLI <-> sink parity yet). A task's mode = `mode` of its first
|
|
542
|
+
// exec dispatch; tasks dispatched before modes existed (no `mode`) count as normal.
|
|
543
|
+
|
|
544
|
+
const MODE_KEYS = ['fast', 'normal', 'strict'];
|
|
545
|
+
const NO_CATCH_VERIFY = new Set(['self', 'none']);
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* Per mode: tasks, verified_tasks (a verify agent ran), catch_rate (share of exec agent verdicts checked by something
|
|
549
|
+
* other than self/none that came back fail), self_verdicts, batch_verify_fails, user_verdict_tasks, rejects (quality),
|
|
550
|
+
* reject_rate, verify_min (minutes of verify agents), test_s (sum of complete.test_s over the task's agents),
|
|
551
|
+
* test_s_per_task, full_suite_runs. `events` = the same events given to analyze(); `tasks` = analyze().tasks.
|
|
552
|
+
*/
|
|
553
|
+
function modeSummary(events, tasks) {
|
|
554
|
+
const evs = withoutDiscarded(events).filter(Boolean);
|
|
555
|
+
const dispatchMode = new Map();
|
|
556
|
+
const tests = new Map();
|
|
557
|
+
const verdicts = new Map();
|
|
558
|
+
for (const ev of evs) {
|
|
559
|
+
if (ev.kind === 'dispatch' && typeof ev.agent_id === 'string' && !dispatchMode.has(ev.agent_id)) dispatchMode.set(ev.agent_id, ev.mode);
|
|
560
|
+
if (ev.kind === 'complete' && typeof ev.agent_id === 'string') {
|
|
561
|
+
const t = tests.get(ev.agent_id) || { s: 0, timed: 0, full: 0 };
|
|
562
|
+
const s = finiteNum(ev.test_s);
|
|
563
|
+
if (s !== null) { t.s += s; t.timed++; }
|
|
564
|
+
t.full += finiteNum(ev.full_suite_runs) || 0;
|
|
565
|
+
tests.set(ev.agent_id, t);
|
|
566
|
+
}
|
|
567
|
+
if (ev.kind === 'verdict' && ev.by !== 'user' && typeof ev.agent_id === 'string') {
|
|
568
|
+
if (!verdicts.has(ev.agent_id)) verdicts.set(ev.agent_id, []);
|
|
569
|
+
verdicts.get(ev.agent_id).push(ev);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
const out = {};
|
|
573
|
+
for (const m of MODE_KEYS) {
|
|
574
|
+
out[m] = {
|
|
575
|
+
tasks: 0, verified_tasks: 0, checked_verdicts: 0, caught: 0, catch_rate: null, self_verdicts: 0, batch_verify_fails: 0,
|
|
576
|
+
user_verdict_tasks: 0, rejects: 0, reject_rate: null, verify_min: 0, test_s: 0, test_timed_tasks: 0, test_s_per_task: null, full_suite_runs: 0,
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
for (const t of tasks || []) {
|
|
580
|
+
if (!t.first) continue;
|
|
581
|
+
const raw = dispatchMode.get(t.first.agent_id);
|
|
582
|
+
const r = out[MODE_KEYS.includes(raw) ? raw : 'normal'];
|
|
583
|
+
r.tasks++;
|
|
584
|
+
if (t.verifies.length) r.verified_tasks++;
|
|
585
|
+
for (const v of t.verifies) r.verify_min += (finiteNum(v.duration_s) || 0) / 60;
|
|
586
|
+
let ts = 0;
|
|
587
|
+
let timed = false;
|
|
588
|
+
for (const a of t.attempts.concat(t.verifies)) {
|
|
589
|
+
const x = tests.get(a.agent_id);
|
|
590
|
+
if (!x) continue;
|
|
591
|
+
if (x.timed) { ts += x.s; timed = true; }
|
|
592
|
+
r.full_suite_runs += x.full;
|
|
593
|
+
}
|
|
594
|
+
if (timed) { r.test_s += ts; r.test_timed_tasks++; }
|
|
595
|
+
for (const a of t.attempts) {
|
|
596
|
+
if (a.role !== 'exec') continue;
|
|
597
|
+
for (const v of verdicts.get(a.agent_id) || []) {
|
|
598
|
+
if (v.outcome === 'fail' && /^batch-verify(?:-|$)/.test(String(v.reason_code || ''))) r.batch_verify_fails++;
|
|
599
|
+
if (v.verify === 'self') r.self_verdicts++;
|
|
600
|
+
if (v.outcome !== 'pass' && v.outcome !== 'fail') continue;
|
|
601
|
+
if (NO_CATCH_VERIFY.has(v.verify || 'none')) continue;
|
|
602
|
+
r.checked_verdicts++;
|
|
603
|
+
if (v.outcome === 'fail') r.caught++;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
if (t.user_verdicts.length) {
|
|
607
|
+
r.user_verdict_tasks++;
|
|
608
|
+
if (t.user_verdicts.some((v) => v.outcome === 'reject' && v.cause !== 'scope')) r.rejects++;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
for (const m of MODE_KEYS) {
|
|
612
|
+
const r = out[m];
|
|
613
|
+
r.catch_rate = r.checked_verdicts ? round6(r.caught / r.checked_verdicts) : null;
|
|
614
|
+
r.reject_rate = r.user_verdict_tasks ? round6(r.rejects / r.user_verdict_tasks) : null;
|
|
615
|
+
r.verify_min = round6(r.verify_min);
|
|
616
|
+
r.test_s = round6(r.test_s);
|
|
617
|
+
r.test_s_per_task = r.test_timed_tasks ? round6(r.test_s / r.test_timed_tasks) : null;
|
|
618
|
+
}
|
|
619
|
+
return out;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
module.exports.MODE_KEYS = MODE_KEYS;
|
|
623
|
+
module.exports.modeSummary = modeSummary;
|
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']);
|
|
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']);
|
|
8
8
|
|
|
9
9
|
/** Minimal argv parser: positionals, --k v, --k=v, boolean flags. */
|
|
10
10
|
function parseArgs(argv) {
|
package/lib/choose.cjs
CHANGED
|
@@ -334,10 +334,56 @@ function resolveTaskType(raw, profile) {
|
|
|
334
334
|
return { type: t, known: false, mapped_from: null, warning: `loại "${t}" không có trong danh sách cố định (${list}) — chọn loại gần nhất hoặc hỏi người dùng` };
|
|
335
335
|
}
|
|
336
336
|
|
|
337
|
+
// ------------------------------------------------------------------ verify plan (core 0.7.0, mode per request)
|
|
338
|
+
|
|
339
|
+
const VERIFY_PLANS = ['independent-blocking', 'independent-nonblocking', 'self+batch', 'spot'];
|
|
340
|
+
/** fast: highest score still allowed to drop its own blocking verifier (s<=6 self+batch, s7-8 independent-nonblocking). */
|
|
341
|
+
const FAST_SELF_MAX = 6;
|
|
342
|
+
const FAST_SOFT_MAX = 8;
|
|
343
|
+
/** strict: share of steps that also get a random Opus reviewer spot check. */
|
|
344
|
+
const STRICT_SPOT_RATE = 1 / 3;
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* How one exec step is checked, from the request mode and the step's rubric inputs.
|
|
348
|
+
* The safety floor is evaluated FIRST and wins in every mode: reversible=n, publish/deploy, verifiable=n, score>=9
|
|
349
|
+
* always get an independent blocking verifier. Only after that does the mode loosen (fast) or tighten (strict).
|
|
350
|
+
* opts: { mode, score, verifiable, reversible, publish, dependents, seed }
|
|
351
|
+
* Returns { verify_plan, verify_reason, verify_floor: string[], test_scope: 'selective'|'full' }.
|
|
352
|
+
*/
|
|
353
|
+
function verifyPlanFor(opts) {
|
|
354
|
+
const mode = opts.mode || core.DEFAULT_MODE;
|
|
355
|
+
if (!core.MODES.includes(mode)) throw new Error(`--mode phải là ${core.MODES.join('|')}`);
|
|
356
|
+
const score = Number(opts.score);
|
|
357
|
+
const floor = [];
|
|
358
|
+
if (opts.reversible === false) floor.push('khó hoàn tác');
|
|
359
|
+
if (opts.publish === true) floor.push('publish/deploy');
|
|
360
|
+
if (opts.verifiable === false) floor.push('không kiểm chứng bằng máy');
|
|
361
|
+
if (score >= 9) floor.push(`score ${score}≥9`);
|
|
362
|
+
const out = (plan, reason, scope) => ({ verify_plan: plan, verify_reason: reason, verify_floor: floor, test_scope: scope });
|
|
363
|
+
if (floor.length) return out('independent-blocking', `sàn an toàn (${floor.join(', ')}) → verifier độc lập chặn bước, mọi mode`, 'full');
|
|
364
|
+
if (mode === 'fast') {
|
|
365
|
+
if (score <= FAST_SELF_MAX) {
|
|
366
|
+
return out('self+batch', `fast, s${score}≤${FAST_SELF_MAX}: exec tự kiểm (test chọn lọc theo file đổi, --verify self); verifier gộp chạy cả suite một lần trước cổng 3`, 'selective');
|
|
367
|
+
}
|
|
368
|
+
if (score <= FAST_SOFT_MAX) {
|
|
369
|
+
return opts.dependents === true
|
|
370
|
+
? out('independent-blocking', `fast, s${score}: bước sau phụ thuộc (depends_on) vào bước này → verifier độc lập vẫn chặn`, 'full')
|
|
371
|
+
: out('independent-nonblocking', `fast, s${score}: verifier độc lập nhưng không chặn bước sau (không bước nào depends_on)`, 'full');
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
if (mode === 'strict') {
|
|
375
|
+
const u = makeRng(opts.seed === undefined || opts.seed === null || opts.seed === true ? undefined : `spot:${opts.seed}`)();
|
|
376
|
+
if (u < STRICT_SPOT_RATE) return out('spot', 'strict: verifier độc lập chặn + reviewer Opus kiểm ngẫu nhiên (1/3), luôn chạy cả suite', 'full');
|
|
377
|
+
return out('independent-blocking', 'strict: verifier độc lập chặn, luôn chạy cả suite', 'full');
|
|
378
|
+
}
|
|
379
|
+
return out('independent-blocking', `${mode}: verifier độc lập chặn bước`, 'full');
|
|
380
|
+
}
|
|
381
|
+
|
|
337
382
|
// ------------------------------------------------------------------ choose
|
|
338
383
|
|
|
339
384
|
/**
|
|
340
|
-
* opts: { type, score, urgency, reversible, verifiable, role, seed, events, profile, taskId, override, pricing
|
|
385
|
+
* opts: { type, score, urgency, reversible, verifiable, role, seed, events, profile, taskId, override, pricing,
|
|
386
|
+
* mode, publish, dependents } (mode/publish/dependents only drive verify_plan, never the tier)
|
|
341
387
|
* Returns { action, model, model_id, effort, reason, explored, tier, budget_s, est_cost_usd,
|
|
342
388
|
* est_time_min, est_source, profile_source, suggested_tier?, overridden? , fails?, tried? }.
|
|
343
389
|
* Throws when an override goes below a safety floor or off the ladder.
|
|
@@ -368,6 +414,13 @@ function choose(opts) {
|
|
|
368
414
|
}
|
|
369
415
|
const reversible = opts.reversible !== false;
|
|
370
416
|
const verifiable = opts.verifiable !== false;
|
|
417
|
+
if (opts.mode !== undefined && opts.mode !== null && !core.MODES.includes(opts.mode)) {
|
|
418
|
+
throw new Error(`--mode phải là ${core.MODES.join('|')} (nhận "${opts.mode}")`);
|
|
419
|
+
}
|
|
420
|
+
const mode = opts.mode || core.DEFAULT_MODE;
|
|
421
|
+
const vp = role === 'exec'
|
|
422
|
+
? verifyPlanFor({ mode, score, verifiable, reversible, publish: opts.publish === true, dependents: opts.dependents === true, seed: opts.seed })
|
|
423
|
+
: null;
|
|
371
424
|
const profile = opts.profile || loadProfile(null);
|
|
372
425
|
const lambda = profile.lambda[urgency];
|
|
373
426
|
const rng = makeRng(opts.seed);
|
|
@@ -436,6 +489,11 @@ function choose(opts) {
|
|
|
436
489
|
tier: tierName(idx),
|
|
437
490
|
role,
|
|
438
491
|
task_type: type,
|
|
492
|
+
mode,
|
|
493
|
+
verify_plan: vp ? vp.verify_plan : null,
|
|
494
|
+
verify_reason: vp ? vp.verify_reason : null,
|
|
495
|
+
verify_floor: vp ? vp.verify_floor : null,
|
|
496
|
+
test_scope: vp ? vp.test_scope : null,
|
|
439
497
|
budget_s: budgetFor(cell),
|
|
440
498
|
}, estimateFor(idx, cell, opts.pricing), { profile_source: src },
|
|
441
499
|
rt.mapped_from ? { mapped_from: rt.mapped_from } : {}, rt.warning ? { type_warning: rt.warning } : {}, extra || {});
|
|
@@ -535,5 +593,5 @@ module.exports = {
|
|
|
535
593
|
DEFAULT_ORCH_CTX_SOFT_CAP,
|
|
536
594
|
LADDER, DEFAULT_PRIORS, DEFAULT_LAMBDA, DEFAULT_TIME_CAP_MIN, ROUGH, tierIndex, tierName, parsePrior,
|
|
537
595
|
parseTier, loadProfile, makeRng, betaSample, escalation, overridePrior, roughEstimate, choose, shortModel,
|
|
538
|
-
TYPE_ALIASES, resolveTaskType,
|
|
596
|
+
TYPE_ALIASES, resolveTaskType, verifyPlanFor, VERIFY_PLANS,
|
|
539
597
|
};
|