@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/commands.cjs
CHANGED
|
@@ -15,7 +15,7 @@ const AGENT_OUTCOMES = ['pass', 'fail', 'timeout', 'aborted'];
|
|
|
15
15
|
const USER_OUTCOMES = ['accept', 'reject'];
|
|
16
16
|
const CAUSES = ['quality', 'scope'];
|
|
17
17
|
const STEER_KINDS = ['minor', 'plan', 'redirect'];
|
|
18
|
-
const VERIFY = ['tests', 'lint', 'review', 'judgment', 'none'];
|
|
18
|
+
const VERIFY = ['tests', 'lint', 'review', 'judgment', 'self', 'none'];
|
|
19
19
|
const INVALID_ID_WINDOW_DAYS = 7;
|
|
20
20
|
const NEW_ID_MAX = 1000;
|
|
21
21
|
|
|
@@ -295,8 +295,13 @@ async function checkSinkLive(ctx) {
|
|
|
295
295
|
*/
|
|
296
296
|
function cmdStart(opts) {
|
|
297
297
|
const ctx = ctxOf(opts);
|
|
298
|
+
const wantMode = modeOpt(opts); // validate before the run dir is created
|
|
298
299
|
const reg = setupLib.registration(claudeDirOf(opts));
|
|
299
300
|
const info = core.startInfo(ctx, repoOf(opts));
|
|
301
|
+
if (wantMode) {
|
|
302
|
+
core.appendEvent(ctx, modeEvent(ctx, info.id, wantMode, 'start', opts.reason));
|
|
303
|
+
info.mode = wantMode;
|
|
304
|
+
}
|
|
300
305
|
const h = info.health;
|
|
301
306
|
if (!fs.existsSync(ctx.paths.hook)) h.problems.unshift(`chưa cài hook (${ctx.paths.hook}) — chạy \`dieu-phoi setup --dept <phòng>\`, log không được ghi`);
|
|
302
307
|
else if (!(reg.PostToolUse && reg.SubagentStop && reg.SessionStart)) h.problems.unshift('hook chưa đăng ký đủ trong settings.json — chạy lại `dieu-phoi setup`, log/trạng thái không được ghi');
|
|
@@ -314,12 +319,58 @@ function cmdStart(opts) {
|
|
|
314
319
|
`step_ids: ${info.step_ids.join(' ')}`,
|
|
315
320
|
`health: ${core.healthLine(h)}`,
|
|
316
321
|
`scout_brief: ${info.scout_brief || '(không thấy — dùng references/brief-template.md mục E)'}`,
|
|
322
|
+
core.modeLine(info),
|
|
317
323
|
...core.orchLessonLines(info),
|
|
318
324
|
...core.knowledgeLines(info),
|
|
319
325
|
];
|
|
320
326
|
return { code: 0, out: out.join('\n') };
|
|
321
327
|
}
|
|
322
328
|
|
|
329
|
+
// ------------------------------------------------------------------ mode
|
|
330
|
+
|
|
331
|
+
/** One local-only mode event for a request (root_task_id). source: start | cmd | plan. */
|
|
332
|
+
function modeEvent(ctx, root, mode, source, reason, session) {
|
|
333
|
+
const sid = typeof session === 'string' && session ? session : (process.env.CLAUDE_CODE_SESSION_ID || null);
|
|
334
|
+
return Object.assign({
|
|
335
|
+
schema: core.SCHEMA,
|
|
336
|
+
kind: 'mode',
|
|
337
|
+
ts: core.nowIso(),
|
|
338
|
+
agent_id: `root:${root}`,
|
|
339
|
+
task_id: null,
|
|
340
|
+
root_task_id: root,
|
|
341
|
+
mode,
|
|
342
|
+
source,
|
|
343
|
+
reason_code: kebab(reason),
|
|
344
|
+
session_id: sid,
|
|
345
|
+
}, core.commonFields(ctx));
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* `dieu-phoi mode --root <root_task_id> [fast|normal|strict] [--reason R] [--session S]`: show or change the mode of a
|
|
350
|
+
* request. One event per change; the same mode again writes nothing. Steps not yet dispatched follow the new mode,
|
|
351
|
+
* dispatched steps keep the mode they were given. Never changes a mode by itself.
|
|
352
|
+
*/
|
|
353
|
+
function cmdMode(opts) {
|
|
354
|
+
const usage = 'Cú pháp: dieu-phoi mode --root <root_task_id> [fast|normal|strict] [--reason R] [--session S] [--json]';
|
|
355
|
+
if (typeof opts.root !== 'string') throw new Error(usage);
|
|
356
|
+
const root = requireTaskId(opts.root);
|
|
357
|
+
const ctx = ctxOf(opts);
|
|
358
|
+
const cur = core.rootModeFromEvents(core.readEvents(ctx), root);
|
|
359
|
+
const was = cur ? cur.mode : core.DEFAULT_MODE;
|
|
360
|
+
const want = opts._[0];
|
|
361
|
+
const show = (extra) => (opts.json ? JSON.stringify(Object.assign({ root_task_id: root, mode: was, previous: was, changed: false }, extra)) : null);
|
|
362
|
+
if (want === undefined) {
|
|
363
|
+
return { code: 0, out: show({}) || `root ${root}: mode=${was}${cur ? ` (từ ${cur.ts}, nguồn ${cur.source || '?'})` : ' (mặc định, chưa đặt)'}` };
|
|
364
|
+
}
|
|
365
|
+
if (!core.isMode(want)) throw new Error(`mode phải là ${core.MODES.join('|')} (nhận "${want}"). ${usage}`);
|
|
366
|
+
if (cur && was === want) {
|
|
367
|
+
return { code: 0, out: show({}) || `root ${root}: vẫn ${want} (không đổi, không ghi sự kiện)` };
|
|
368
|
+
}
|
|
369
|
+
core.appendEvent(ctx, modeEvent(ctx, root, want, 'cmd', opts.reason, opts.session));
|
|
370
|
+
if (opts.json) return { code: 0, out: JSON.stringify({ root_task_id: root, mode: want, previous: was, changed: true }) };
|
|
371
|
+
return { code: 0, out: `root ${root}: ${was} → ${want} (áp cho bước chưa giao; bước đã giao giữ mode lúc giao)` };
|
|
372
|
+
}
|
|
373
|
+
|
|
323
374
|
// ------------------------------------------------------------------ hook / sync
|
|
324
375
|
|
|
325
376
|
function cmdHook(opts) {
|
|
@@ -426,6 +477,7 @@ function verdictUser(opts, ctx, events) {
|
|
|
426
477
|
if (typeof taskId !== 'string' || !taskId) throw new Error('--task cần task_id');
|
|
427
478
|
requireTaskId(taskId);
|
|
428
479
|
if (!USER_OUTCOMES.includes(outcome)) throw new Error(`verdict --task: outcome phải là ${USER_OUTCOMES.join('|')}`);
|
|
480
|
+
if (opts.auto && outcome !== 'accept') throw new Error('--auto chỉ dùng với accept (tự nghiệm thu bước fast); reject luôn là của người dùng: verdict --task <task_id> reject --cause quality|scope');
|
|
429
481
|
let cause = typeof opts.cause === 'string' ? opts.cause : null;
|
|
430
482
|
if (outcome === 'reject' && !CAUSES.includes(cause)) throw new Error(`reject cần --cause ${CAUSES.join('|')} (scope = yêu cầu đổi/thiếu, không phải lỗi model)`);
|
|
431
483
|
if (outcome === 'accept') cause = null;
|
|
@@ -433,6 +485,7 @@ function verdictUser(opts, ctx, events) {
|
|
|
433
485
|
if (!known) throw new Error(`Không thấy task "${taskId}" trong log.`);
|
|
434
486
|
const rounds = events.filter((e) => e.kind === 'verdict' && e.task_id === taskId
|
|
435
487
|
&& agg.normalizeVerdict(e).by === 'user').map((e) => Number(e.round) || 1);
|
|
488
|
+
if (opts.auto) return verdictAuto(opts, ctx, events, taskId, rounds);
|
|
436
489
|
let round = (rounds.length ? Math.max(...rounds) : 0) + 1;
|
|
437
490
|
if (opts.round !== undefined) {
|
|
438
491
|
round = Number(opts.round);
|
|
@@ -453,6 +506,33 @@ function verdictUser(opts, ctx, events) {
|
|
|
453
506
|
}, core.commonFields(ctx));
|
|
454
507
|
}
|
|
455
508
|
|
|
509
|
+
/**
|
|
510
|
+
* `verdict --task <task_id> accept --auto` (core 0.7.0): a fast-mode step that passed the batch verifier is accepted
|
|
511
|
+
* automatically (`by=auto`). Not a user verdict: never counts in accept_rate/user_outcomes/rounds, never merges
|
|
512
|
+
* knowledge; the user's final `verdict --root … accept` (by=user) or a later reject (wins) still decides.
|
|
513
|
+
*/
|
|
514
|
+
function verdictAuto(opts, ctx, events, taskId, userRounds) {
|
|
515
|
+
if (opts.round !== undefined || opts.cause !== undefined) throw new Error('--auto không nhận --round/--cause');
|
|
516
|
+
if (userRounds.length) throw new Error(`Task ${taskId} đã có verdict người dùng; --auto chỉ dùng trước khi người dùng nghiệm thu.`);
|
|
517
|
+
const mode = core.taskModeInfo(ctx, taskId, events).mode;
|
|
518
|
+
if (mode !== 'fast') throw new Error(`--auto chỉ cho bước mode fast (task ${taskId} đang ${mode}); bước khác chờ người dùng nghiệm thu.`);
|
|
519
|
+
const exec = events.find((e) => e.kind === 'dispatch' && e.task_id === taskId && (e.role || 'exec') === 'exec');
|
|
520
|
+
if (exec && typeof exec.score === 'number' && exec.score > 6) throw new Error(`--auto chỉ cho bước score ≤ 6 (task ${taskId} score ${exec.score}).`);
|
|
521
|
+
return Object.assign({
|
|
522
|
+
schema: core.SCHEMA,
|
|
523
|
+
kind: 'verdict',
|
|
524
|
+
ts: core.nowIso(),
|
|
525
|
+
agent_id: `task:${taskId}`,
|
|
526
|
+
task_id: taskId,
|
|
527
|
+
by: 'auto',
|
|
528
|
+
outcome: 'accept',
|
|
529
|
+
cause: null,
|
|
530
|
+
round: null,
|
|
531
|
+
verify: 'tests',
|
|
532
|
+
reason_code: kebab(opts.reason),
|
|
533
|
+
}, core.commonFields(ctx));
|
|
534
|
+
}
|
|
535
|
+
|
|
456
536
|
/**
|
|
457
537
|
* `verdict --root <root> accept [--steps a,b]` (core 0.4.0): the user accepts the steps of the latest approved plan
|
|
458
538
|
* in one call. Covers every step whose task has an exec dispatch and no user verdict yet (a step already rejected
|
|
@@ -460,6 +540,7 @@ function verdictUser(opts, ctx, events) {
|
|
|
460
540
|
*/
|
|
461
541
|
function verdictRoot(opts, ctx, events) {
|
|
462
542
|
const root = requireTaskId(String(opts.root));
|
|
543
|
+
if (opts.auto) throw new Error('verdict --root là người dùng duyệt cuối (by=user), không nhận --auto; tự nghiệm thu từng bước: verdict --task <task_id> accept --auto');
|
|
463
544
|
if (opts._[0] !== 'accept') {
|
|
464
545
|
throw new Error('verdict --root chỉ nhận accept; bước không đạt → dieu-phoi verdict --task <task_id> reject --cause quality|scope');
|
|
465
546
|
}
|
|
@@ -552,6 +633,9 @@ function cmdVerdict(opts) {
|
|
|
552
633
|
if (kn) process.stderr.write(`${kn.out}\n`);
|
|
553
634
|
return { code: 0, out: JSON.stringify(ev) };
|
|
554
635
|
}
|
|
636
|
+
if (ev.by === 'auto') {
|
|
637
|
+
return { code: 0, out: `Đã ghi tự nghiệm thu (by=auto) cho task ${ev.task_id}: không tính vào accept_rate; người dùng vẫn duyệt cuối (verdict --root <root> accept) hoặc reject (thắng auto).` };
|
|
638
|
+
}
|
|
555
639
|
if (ev.by === 'user') {
|
|
556
640
|
const msg = `Đã ghi người dùng ${ev.outcome}${ev.cause ? ` (${ev.cause})` : ''} cho task ${ev.task_id}, vòng ${ev.round}.`;
|
|
557
641
|
return { code: 0, out: kn ? `${msg}\n${kn.out}` : msg };
|
|
@@ -747,12 +831,14 @@ function pct(v) {
|
|
|
747
831
|
function cmdStats(opts) {
|
|
748
832
|
const ctx = ctxOf(opts);
|
|
749
833
|
const { lambda } = lambdaOf(opts, ctx);
|
|
750
|
-
const
|
|
834
|
+
const events = core.readEvents(ctx);
|
|
835
|
+
const { cells, steers, orchestration, tasks } = agg.analyze(events, { lambda });
|
|
836
|
+
const modes = agg.modeSummary(events, tasks);
|
|
751
837
|
const type = typeof opts.type === 'string' ? opts.type : null;
|
|
752
838
|
const rows = type ? cells.filter((c) => c.task_type === type) : cells;
|
|
753
839
|
const steerRows = type ? steers.filter((s) => s.task_type === type) : steers;
|
|
754
840
|
if (opts.json) {
|
|
755
|
-
return { code: 0, out: JSON.stringify({ lambda_usd_per_min: lambda, min_n: agg.MIN_N, cells: rows, steers: steerRows, orchestration }, null, 2) };
|
|
841
|
+
return { code: 0, out: JSON.stringify({ lambda_usd_per_min: lambda, min_n: agg.MIN_N, cells: rows, steers: steerRows, orchestration, modes }, null, 2) };
|
|
756
842
|
}
|
|
757
843
|
if (!rows.length) {
|
|
758
844
|
return { code: 0, out: orchestration.sessions ? `Chưa có dữ liệu theo ô.\n${orchLine(orchestration)}` : 'Chưa có dữ liệu.' };
|
|
@@ -779,13 +865,40 @@ function cmdStats(opts) {
|
|
|
779
865
|
}
|
|
780
866
|
return {
|
|
781
867
|
code: 0,
|
|
782
|
-
out: `${table(t)}\n\n${table(s)}\n\n${orchLine(orchestration)}\n\n`
|
|
868
|
+
out: `${table(t)}\n\n${table(s)}\n\n${modeTable(modes)}${orchLine(orchestration)}\n\n`
|
|
783
869
|
+ 'n = task tính tỉ lệ (/số đã giao, (-k) = bị loại: reject-scope hoặc lượt exec đầu bị steer plan/redirect). '
|
|
784
870
|
+ 'accept% = thành công theo verdict người dùng đầu tiên, chưa có thì verdict agent; agent_pass% = chỉ tầng agent. '
|
|
785
871
|
+ `E[cost], E[time] tính trọn mọi lượt + review của task. score = E[cost] + λ·p50[time] (trung vị, không dùng TB: thời gian chờ thổi phồng TB), λ=${lambda} USD/phút.`,
|
|
786
872
|
};
|
|
787
873
|
}
|
|
788
874
|
|
|
875
|
+
/** Stats by mode (core 0.7.0): one row per mode with tasks, then a blank line; '' when only normal has data. */
|
|
876
|
+
function modeTable(modes) {
|
|
877
|
+
const keys = agg.MODE_KEYS.filter((m) => modes[m] && modes[m].tasks);
|
|
878
|
+
if (!keys.length || (keys.length === 1 && keys[0] === 'normal')) return '';
|
|
879
|
+
const t = [['mode', 'task', 'có verifier', 'catch%', 'reject%', 'phút verify', 'test_s', 'test_s/task', 'lần cả suite', 'verdict self', 'fail verify gộp']];
|
|
880
|
+
for (const m of keys) {
|
|
881
|
+
const r = modes[m];
|
|
882
|
+
t.push([
|
|
883
|
+
m, r.tasks, r.verified_tasks,
|
|
884
|
+
r.checked_verdicts ? `${pct(r.catch_rate)} (${r.checked_verdicts})` : '-',
|
|
885
|
+
r.user_verdict_tasks ? `${pct(r.reject_rate)} (${r.user_verdict_tasks})` : '-',
|
|
886
|
+
fmtNum(r.verify_min), fmtNum(r.test_s), r.test_s_per_task === null ? '-' : fmtNum(r.test_s_per_task),
|
|
887
|
+
r.full_suite_runs, r.self_verdicts, r.batch_verify_fails,
|
|
888
|
+
]);
|
|
889
|
+
}
|
|
890
|
+
return `${table(t)}\n\n`;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
/** One retro line: per mode with tasks. */
|
|
894
|
+
function modeRetroLine(modes) {
|
|
895
|
+
const parts = agg.MODE_KEYS.filter((m) => modes[m].tasks).map((m) => {
|
|
896
|
+
const r = modes[m];
|
|
897
|
+
return `${m} ${r.tasks} task, catch ${pct(r.catch_rate)}, reject ${pct(r.reject_rate)}, verify ${fmtNum(r.verify_min)}m, test ${fmtNum(r.test_s)}s, cả suite ×${r.full_suite_runs}`;
|
|
898
|
+
});
|
|
899
|
+
return `Theo mode: ${parts.join(' · ') || '-'}`;
|
|
900
|
+
}
|
|
901
|
+
|
|
789
902
|
const HIGH_STEER_RATE = 0.3;
|
|
790
903
|
const HIGH_STEER_MIN_TASKS = 3;
|
|
791
904
|
|
|
@@ -794,6 +907,7 @@ function cmdRetro(opts) {
|
|
|
794
907
|
const { prof, lambda } = lambdaOf(opts, ctx);
|
|
795
908
|
const events = core.readEvents(ctx);
|
|
796
909
|
const { agents, tasks, cells, steers, userVerdicts, overrides, orchestration } = agg.analyze(events, { lambda });
|
|
910
|
+
const modes = agg.modeSummary(events, tasks);
|
|
797
911
|
const reasons = {};
|
|
798
912
|
const outcomes = {};
|
|
799
913
|
const userOutcomes = {};
|
|
@@ -833,6 +947,10 @@ function cmdRetro(opts) {
|
|
|
833
947
|
multi_attempt_tasks: escalations,
|
|
834
948
|
discarded_tasks: agg.discardedTaskIds(events).size,
|
|
835
949
|
overrides: overrides.length,
|
|
950
|
+
// core 0.7.0: fast-mode evidence kept apart from the learning data (CONTRACT "Mode theo yêu cầu")
|
|
951
|
+
self_verdicts: agents.reduce((s, a) => s + (a.self_verdicts || 0), 0),
|
|
952
|
+
auto_accept: tasks.filter((t) => t.auto_accept).length,
|
|
953
|
+
auto_overturned: tasks.filter((t) => t.auto_overturned).length,
|
|
836
954
|
cost_usd: agents.reduce((s, a) => s + (a.cost_usd || 0), 0),
|
|
837
955
|
},
|
|
838
956
|
lambda_usd_per_min: prof.lambda,
|
|
@@ -845,6 +963,7 @@ function cmdRetro(opts) {
|
|
|
845
963
|
high_steer_task_types: highSteer,
|
|
846
964
|
overrides: overrides.map((o) => ({ ts: o.ts, task_type: o.task_type, suggested_tier: o.suggested_tier, chosen_tier: o.chosen_tier })),
|
|
847
965
|
orchestration,
|
|
966
|
+
modes,
|
|
848
967
|
cells,
|
|
849
968
|
};
|
|
850
969
|
if (opts.json) return { code: 0, out: JSON.stringify(summary, null, 2) };
|
|
@@ -852,10 +971,12 @@ function cmdRetro(opts) {
|
|
|
852
971
|
`Retro ${summary.dept || ''} — ${summary.period ? `${summary.period.from.slice(0, 10)} → ${summary.period.to.slice(0, 10)}` : 'chưa có dữ liệu'}`,
|
|
853
972
|
`${summary.totals.tasks} task, ${summary.totals.agents} agent, chi phí ≈ ${fmtUsd(summary.totals.cost_usd)}, ${summary.totals.untagged_agents} agent không thẻ, ${summary.totals.no_user_verdict_tasks} task chưa có verdict người dùng, ${escalations} task nhiều lượt, ${overrides.length} override.`,
|
|
854
973
|
`Người dùng: ${Object.entries(userOutcomes).map(([k, v]) => `${k}=${v}`).join(', ') || '-'} · Agent: ${Object.entries(outcomes).map(([k, v]) => `${k}=${v}`).join(', ') || '-'}`,
|
|
974
|
+
`Fast: verdict self=${summary.totals.self_verdicts} (không vào agent_pass_rate), tự nghiệm thu auto_accept=${summary.totals.auto_accept}, người dùng lật auto_overturned=${summary.totals.auto_overturned}`,
|
|
855
975
|
`Lý do hay gặp: ${summary.reason_codes.slice(0, 8).map((r) => `${r.code}×${r.count}`).join(', ') || '-'}`,
|
|
856
976
|
`Loại task: ${summary.task_types.map((t) => `${t.task_type}(${t.n})`).join(', ') || '-'}`,
|
|
857
977
|
`Steer nhiều (brainstorm hỏi quá ít): ${highSteer.map((s) => `${s.task_type} ${pct(s.steer_rate)}`).join(', ') || '-'}`,
|
|
858
978
|
orchLine(orchestration),
|
|
979
|
+
modeRetroLine(modes),
|
|
859
980
|
'Chi tiết theo ô: `dieu-phoi stats`; đưa cho LLM: `dieu-phoi retro --json`.',
|
|
860
981
|
];
|
|
861
982
|
return { code: 0, out: lines.join('\n') };
|
|
@@ -863,6 +984,13 @@ function cmdRetro(opts) {
|
|
|
863
984
|
|
|
864
985
|
// ------------------------------------------------------------------ choose
|
|
865
986
|
|
|
987
|
+
/** --mode value (validated) or undefined; a bare --mode is an error. */
|
|
988
|
+
function modeOpt(opts) {
|
|
989
|
+
if (opts.mode === undefined) return undefined;
|
|
990
|
+
if (typeof opts.mode !== 'string' || !core.isMode(opts.mode)) throw new Error(`--mode phải là ${core.MODES.join('|')}`);
|
|
991
|
+
return opts.mode;
|
|
992
|
+
}
|
|
993
|
+
|
|
866
994
|
function chooseInput(opts, ctx, events) {
|
|
867
995
|
return {
|
|
868
996
|
type: opts.type,
|
|
@@ -870,6 +998,9 @@ function chooseInput(opts, ctx, events) {
|
|
|
870
998
|
urgency: typeof opts.urgency === 'string' ? opts.urgency : 'normal',
|
|
871
999
|
reversible: yesNo(opts.reversible, true),
|
|
872
1000
|
verifiable: yesNo(opts.verifiable, true),
|
|
1001
|
+
publish: yesNo(opts.publish, false),
|
|
1002
|
+
dependents: yesNo(opts.dependents, false),
|
|
1003
|
+
mode: modeOpt(opts),
|
|
873
1004
|
role: typeof opts.role === 'string' ? opts.role : 'exec',
|
|
874
1005
|
seed: opts.seed,
|
|
875
1006
|
taskId: typeof opts.task === 'string' ? opts.task : null,
|
|
@@ -894,6 +1025,18 @@ function overrideEvent(ctx, taskId, taskType, suggested, chosen) {
|
|
|
894
1025
|
}, core.commonFields(ctx));
|
|
895
1026
|
}
|
|
896
1027
|
|
|
1028
|
+
/**
|
|
1029
|
+
* core 0.7.0 (s2): mode_suggest for choose (exec only). The root's CURRENT mode decides (a dispatched step keeps its
|
|
1030
|
+
* fast mode, but once the user moved the root back there is nothing to suggest). Never changes the mode.
|
|
1031
|
+
*/
|
|
1032
|
+
function chooseModeSuggest(ctx, input, res, mi) {
|
|
1033
|
+
const root = mi && mi.root ? mi.root : null;
|
|
1034
|
+
const rootEv = root ? core.rootModeFromEvents(input.events, root) : null;
|
|
1035
|
+
const mode = rootEv ? rootEv.mode : res.mode;
|
|
1036
|
+
const sug = core.modeSuggestion(input.events, root, { mode, taskIds: core.rootTaskIds(core.loadPlans(ctx.paths.plans), root) });
|
|
1037
|
+
return { mode_suggest: sug.mode_suggest, mode_suggest_reason: sug.mode_suggest_reason, mode_suggest_root: sug.mode_suggest ? root : null };
|
|
1038
|
+
}
|
|
1039
|
+
|
|
897
1040
|
function cmdChoose(opts) {
|
|
898
1041
|
const ctx = ctxOf(opts);
|
|
899
1042
|
if (opts.override !== undefined && typeof opts.task !== 'string') {
|
|
@@ -901,7 +1044,20 @@ function cmdChoose(opts) {
|
|
|
901
1044
|
}
|
|
902
1045
|
if (opts.task !== undefined) requireTaskId(typeof opts.task === 'string' ? opts.task : '');
|
|
903
1046
|
const input = chooseInput(opts, ctx, core.readEvents(ctx));
|
|
1047
|
+
let modeSource = 'flag';
|
|
1048
|
+
const mi = input.taskId ? core.taskModeInfo(ctx, input.taskId, input.events) : null;
|
|
1049
|
+
if (input.mode === undefined) {
|
|
1050
|
+
if (mi) {
|
|
1051
|
+
input.mode = mi.mode;
|
|
1052
|
+
modeSource = mi.source;
|
|
1053
|
+
} else {
|
|
1054
|
+
input.mode = core.DEFAULT_MODE;
|
|
1055
|
+
modeSource = 'default';
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
904
1058
|
const res = chooser.choose(input);
|
|
1059
|
+
res.mode_source = modeSource;
|
|
1060
|
+
if (res.verify_plan) Object.assign(res, chooseModeSuggest(ctx, input, res, mi));
|
|
905
1061
|
const orch = measureOrch(opts, ctx, input.profile);
|
|
906
1062
|
res.orch_ctx_tokens = orch.ctx_tokens;
|
|
907
1063
|
res.orch_ctx_growth = orch.ctx_growth;
|
|
@@ -920,14 +1076,15 @@ function cmdChoose(opts) {
|
|
|
920
1076
|
}
|
|
921
1077
|
if (opts.json) return { code: 0, out: JSON.stringify(res) };
|
|
922
1078
|
const typeWarn = res.type_warning && !String(res.reason).includes(res.type_warning) ? `\nCẢNH BÁO: ${res.type_warning}` : '';
|
|
923
|
-
const
|
|
1079
|
+
const sugLine = res.mode_suggest ? `\n${core.modeSuggestLine(res.mode_suggest_root, 'fast', res.mode_suggest_reason)}` : '';
|
|
1080
|
+
const warn = `${typeWarn}${res.orch_ctx_warn ? `\nCẢNH BÁO: ${orch.advice}` : ''}${sugLine}`;
|
|
924
1081
|
const est =`~${fmtUsd(res.est_cost_usd)} · ~${fmtNum(res.est_time_min)} phút (${res.est_source === 'data' ? 'dữ liệu' : 'ước lượng thô'})`;
|
|
925
1082
|
if (res.action === 'ask_user') {
|
|
926
1083
|
return { code: 0, out: `HỎI NGƯỜI DÙNG — ${res.reason}${warn}` };
|
|
927
1084
|
}
|
|
928
1085
|
return {
|
|
929
1086
|
code: 0,
|
|
930
|
-
out: `model=${res.model} effort=${res.effort}${res.explored ? ' (thăm dò)' : ''}${res.overridden ? ' (override)' : ''}${res.deviation ? ' (LỆCH khỏi tier đã duyệt)' : ''} ${est} — ${res.reason}${warn}`,
|
|
1087
|
+
out: `model=${res.model} effort=${res.effort}${res.explored ? ' (thăm dò)' : ''}${res.overridden ? ' (override)' : ''}${res.deviation ? ' (LỆCH khỏi tier đã duyệt)' : ''} ${est} — ${res.reason}${res.verify_plan ? ` | mode=${res.mode} kiểm=${res.verify_plan} (${res.test_scope === 'selective' ? 'test chọn lọc' : 'cả suite'})` : ''}${warn}`,
|
|
931
1088
|
};
|
|
932
1089
|
}
|
|
933
1090
|
|
|
@@ -974,6 +1131,8 @@ function buildPlanTable(rawSteps, base) {
|
|
|
974
1131
|
score: s.score,
|
|
975
1132
|
verifiable: s.verifiable === undefined ? true : yesNo(s.verifiable, true),
|
|
976
1133
|
reversible: s.reversible === undefined ? true : yesNo(s.reversible, true),
|
|
1134
|
+
publish: s.publish === undefined ? false : yesNo(s.publish, false),
|
|
1135
|
+
mode: s.mode === undefined || s.mode === null ? null : s.mode,
|
|
977
1136
|
urgency: s.urgency,
|
|
978
1137
|
deps: deps.map(String),
|
|
979
1138
|
};
|
|
@@ -990,6 +1149,9 @@ function buildPlanTable(rawSteps, base) {
|
|
|
990
1149
|
verifiable: s.verifiable,
|
|
991
1150
|
reversible: s.reversible,
|
|
992
1151
|
urgency: s.urgency || base.urgency,
|
|
1152
|
+
publish: s.publish === true || base.publish === true,
|
|
1153
|
+
dependents: steps.some((o) => o !== s && o.deps.includes(s.step)),
|
|
1154
|
+
mode: s.mode || base.mode || core.DEFAULT_MODE,
|
|
993
1155
|
taskId: s.task || base.taskId,
|
|
994
1156
|
override: s.override || base.override,
|
|
995
1157
|
role: s.role || base.role,
|
|
@@ -999,6 +1161,7 @@ function buildPlanTable(rawSteps, base) {
|
|
|
999
1161
|
const head = {
|
|
1000
1162
|
step: s.step, title: s.title, type: s.type, task_id: s.task || null, score: Number(s.score),
|
|
1001
1163
|
verifiable: s.verifiable, reversible: s.reversible, depends_on: s.deps,
|
|
1164
|
+
mode: s.mode || base.mode || core.DEFAULT_MODE, mode_override: s.mode || null,
|
|
1002
1165
|
};
|
|
1003
1166
|
try {
|
|
1004
1167
|
return Object.assign(head, call());
|
|
@@ -1015,7 +1178,10 @@ function buildPlanTable(rawSteps, base) {
|
|
|
1015
1178
|
const ok = rows.filter((r) => r.action !== 'refused');
|
|
1016
1179
|
const costs = ok.map((r) => r.est_cost_usd);
|
|
1017
1180
|
const totalCost = costs.every((c) => typeof c === 'number') ? costs.reduce((a, b) => a + b, 0) : null;
|
|
1181
|
+
const selfBatch = rows.filter((r) => r.verify_plan === 'self+batch').length;
|
|
1018
1182
|
return {
|
|
1183
|
+
mode: base.mode || core.DEFAULT_MODE,
|
|
1184
|
+
self_batch_steps: selfBatch,
|
|
1019
1185
|
steps: rows,
|
|
1020
1186
|
refused: rows.filter((r) => r.action === 'refused').map((r) => ({ step: r.step, error: r.error })),
|
|
1021
1187
|
total_cost_usd: totalCost === null ? null : Math.round(totalCost * 1000) / 1000,
|
|
@@ -1111,10 +1277,10 @@ function recordApprovedPlan(ctx, root, stepsFile, plan) {
|
|
|
1111
1277
|
steps_file: stepsFile.split(path.sep).join('/'),
|
|
1112
1278
|
approved_at: core.nowIso(),
|
|
1113
1279
|
approval: prevApproval(prev) + 1,
|
|
1114
|
-
steps: plan.steps.map((r) => ({
|
|
1280
|
+
steps: plan.steps.map((r) => Object.assign({
|
|
1115
1281
|
id: r.step, task_id: r.task_id || null, title: r.title === undefined ? null : r.title, type: r.task_type || r.type || null,
|
|
1116
1282
|
tier: r.action === 'refused' ? null : (r.tier || null), depends_on: r.depends_on,
|
|
1117
|
-
})),
|
|
1283
|
+
}, r.mode_override ? { mode: r.mode_override } : {})),
|
|
1118
1284
|
history,
|
|
1119
1285
|
};
|
|
1120
1286
|
core.writeFileAtomic(file, `${JSON.stringify(rec, null, 2)}\n`);
|
|
@@ -1151,8 +1317,19 @@ function cmdPlanTable(opts) {
|
|
|
1151
1317
|
throw new Error(`plan-table: không đọc được ${opts.file}: ${e.message}`);
|
|
1152
1318
|
}
|
|
1153
1319
|
const ctx = ctxOf(opts);
|
|
1320
|
+
// steps.json: array (old) or { mode?, steps: [...] }; root mode = --mode > current mode event of --root > file.mode > normal
|
|
1321
|
+
let fileMode;
|
|
1322
|
+
if (raw && !Array.isArray(raw) && typeof raw === 'object' && Array.isArray(raw.steps)) {
|
|
1323
|
+
if (raw.mode !== undefined && raw.mode !== null) {
|
|
1324
|
+
if (!core.isMode(raw.mode)) throw new Error(`plan-table: "mode" trong file phải là ${core.MODES.join('|')} (nhận "${raw.mode}")`);
|
|
1325
|
+
fileMode = raw.mode;
|
|
1326
|
+
}
|
|
1327
|
+
raw = raw.steps;
|
|
1328
|
+
}
|
|
1154
1329
|
const base = chooseInput(Object.assign({}, opts, { type: 'x', score: 0, override: undefined }), ctx, core.readEvents(ctx));
|
|
1155
1330
|
delete base.override;
|
|
1331
|
+
const rootEv = typeof opts.root === 'string' && core.isValidTaskId(opts.root) ? core.rootModeFromEvents(base.events, opts.root) : null;
|
|
1332
|
+
base.mode = base.mode || (rootEv ? rootEv.mode : null) || fileMode || core.DEFAULT_MODE;
|
|
1156
1333
|
const plan = buildPlanTable(raw, base);
|
|
1157
1334
|
const problems = planTaskIdProblems(plan, opts.approve ? String(opts.root) : null);
|
|
1158
1335
|
if (opts.approve && problems.length) {
|
|
@@ -1162,26 +1339,33 @@ function cmdPlanTable(opts) {
|
|
|
1162
1339
|
const typeNotes = planTypeNotes(plan);
|
|
1163
1340
|
if (typeNotes.length) process.stderr.write(`dieu-phoi plan-table: cảnh báo — ${typeNotes.join('; ')}\n`);
|
|
1164
1341
|
const rec = opts.approve ? recordApprovedPlan(ctx, String(opts.root), path.resolve(opts.file), plan) : null;
|
|
1342
|
+
let modeLogged = false;
|
|
1343
|
+
if (rec && (rootEv ? rootEv.mode !== plan.mode : plan.mode !== core.DEFAULT_MODE)) {
|
|
1344
|
+
core.appendEvent(ctx, modeEvent(ctx, rec.root_task_id, plan.mode, 'plan', opts.reason));
|
|
1345
|
+
modeLogged = true;
|
|
1346
|
+
}
|
|
1165
1347
|
const ovs = rec ? planOverrideEvents(ctx, plan, base.events) : [];
|
|
1166
1348
|
for (const ev of ovs) core.appendEvent(ctx, ev);
|
|
1167
1349
|
if (rec) recordPlanSuggestions(ctx, plan);
|
|
1168
1350
|
if (opts.json) {
|
|
1169
1351
|
const out = rec ? Object.assign({}, plan, {
|
|
1170
|
-
approved: { root_task_id: rec.root_task_id, approval: rec.approval, record: rec.file, overrides_logged: ovs.length },
|
|
1352
|
+
approved: { root_task_id: rec.root_task_id, approval: rec.approval, record: rec.file, overrides_logged: ovs.length, mode_logged: modeLogged },
|
|
1171
1353
|
}) : plan;
|
|
1172
1354
|
return { code: 0, out: JSON.stringify(out, null, 2) };
|
|
1173
1355
|
}
|
|
1174
|
-
const lines = ['| Bước | Model/effort | Rubric (điểm · kiểm chứng · đảo ngược) | Lý do | Ước tính |', '
|
|
1356
|
+
const lines = ['| Bước | Model/effort | Rubric (điểm · kiểm chứng · đảo ngược) | Lý do | Ước tính | Kiểm |', '|---|---|---|---|---|---|'];
|
|
1175
1357
|
for (const r of plan.steps) {
|
|
1176
1358
|
const tierCell = r.action === 'refused' ? '—' : (r.action === 'ask_user' ? `${r.tier} (hỏi người dùng)` : r.tier);
|
|
1177
1359
|
const yn = (v) => (v ? 'y' : 'n');
|
|
1178
1360
|
const rubric = `${Number.isFinite(r.score) ? r.score : '?'} · ${yn(r.verifiable)} · ${yn(r.reversible)}`;
|
|
1179
1361
|
const why = `${r.type}${r.depends_on.length ? `, sau ${r.depends_on.join(', ')}` : ''} — ${r.reason.replace(/; profile: .*$/, '')}`;
|
|
1180
1362
|
const est = r.action === 'refused' ? '-' : `~${fmtUsd(r.est_cost_usd)} · ~${fmtNum(r.est_time_min)} phút${r.est_source === 'rough' ? ' (thô)' : ''}`;
|
|
1181
|
-
|
|
1363
|
+
const kiem = r.action === 'refused' || !r.verify_plan ? '—' : `${r.verify_plan}${r.mode_override ? ` (mode ${r.mode_override})` : ''}`;
|
|
1364
|
+
lines.push(`| ${mdCell(r.title ? `${r.step}. ${r.title}` : r.step)} | ${mdCell(tierCell)} | ${mdCell(rubric)} | ${mdCell(why)} | ${mdCell(est)} | ${mdCell(kiem)} |`);
|
|
1182
1365
|
}
|
|
1183
1366
|
lines.push('');
|
|
1184
1367
|
lines.push(`**Tổng chi phí ước tính:** ~${fmtUsd(plan.total_cost_usd)} · **Thời gian (đường găng, chạy song song khi không phụ thuộc):** ~${fmtNum(plan.critical_path_min)} phút (cộng dồn ${fmtNum(plan.sum_time_min)} phút)`);
|
|
1368
|
+
lines.push(`**Mode:** ${plan.mode}${plan.self_batch_steps ? ` · Kiểm (fast): bỏ ${plan.self_batch_steps} verifier riêng, 1 verify gộp (cả suite một lần) trước khi trình người dùng` : ''}`);
|
|
1185
1369
|
if (plan.rough) lines.push('', '_(thô) = ô chưa đủ 5 task, ước lượng theo giả định token của từng tier._');
|
|
1186
1370
|
if (plan.refused.length) {
|
|
1187
1371
|
lines.push('', `**Cảnh báo:** ${plan.refused.length} bước bị từ chối (${plan.refused.map((x) => x.step).join(', ')}), không tính vào tổng — sửa rồi chạy lại.`);
|
|
@@ -1189,11 +1373,11 @@ function cmdPlanTable(opts) {
|
|
|
1189
1373
|
`);
|
|
1190
1374
|
}
|
|
1191
1375
|
if (typeNotes.length) lines.push('', `**Loại task:** ${typeNotes.join('; ')}`);
|
|
1192
|
-
if (rec) lines.push('', `Đã ghi kế hoạch đã duyệt (root ${rec.root_task_id}, lần duyệt ${rec.approval})${ovs.length ? `; ghi ${ovs.length} override` : ''}`);
|
|
1376
|
+
if (rec) lines.push('', `Đã ghi kế hoạch đã duyệt (root ${rec.root_task_id}, lần duyệt ${rec.approval})${ovs.length ? `; ghi ${ovs.length} override` : ''}${modeLogged ? `; ghi mode ${plan.mode}` : ''}`);
|
|
1193
1377
|
return { code: 0, out: lines.join('\n') };
|
|
1194
1378
|
}
|
|
1195
1379
|
|
|
1196
1380
|
module.exports = {
|
|
1197
|
-
skillDirOf, cmdSetup, cmdUninstall, cmdDoctor, cmdStart, checkSinkLive, cmdHook, cmdSync, cmdVerdict, cmdSteer, cmdDiscard, ctxWarnOnce, ctxWarnLevel, sweepStaleTmp, planOverrideEvents, recordPlanSuggestions, cmdStats, cmdRetro, cmdPrune: (o) => require('./prune.cjs').cmdPrune(o), cmdChoose, cmdCtx, cmdState, measureOrch,
|
|
1381
|
+
skillDirOf, cmdSetup, cmdUninstall, cmdDoctor, cmdStart, checkSinkLive, cmdHook, cmdSync, cmdVerdict, cmdSteer, cmdDiscard, ctxWarnOnce, ctxWarnLevel, sweepStaleTmp, planOverrideEvents, recordPlanSuggestions, cmdStats, cmdRetro, cmdPrune: (o) => require('./prune.cjs').cmdPrune(o), cmdChoose, cmdMode, modeEvent, cmdCtx, cmdState, measureOrch,
|
|
1198
1382
|
cmdPlanTable, cmdNewId, requireTaskId, buildPlanTable, recordApprovedPlan, planTaskIdProblems, criticalPath, resolveAgentId, AGENT_OUTCOMES, USER_OUTCOMES, VERIFY,
|
|
1199
1383
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kienntpixon/dieu-phoi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Hook + CLI ghi log subagent của Claude Code và chọn model/effort theo chi phí + thời gian tới lúc nghiệm thu.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "kiennt <kiennt@pixon.games>",
|