@kienntpixon/dieu-phoi 0.6.0 → 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 +69 -0
- package/README.md +11 -2
- package/agents/dp-exec.md +1 -1
- package/agents/dp-helper.md +1 -0
- package/agents/dp-scout.md +1 -1
- package/agents/dp-verify.md +1 -0
- package/bin/dieu-phoi.cjs +28 -8
- package/hook.cjs +397 -8
- package/lib/aggregate.cjs +137 -14
- package/lib/args.cjs +1 -1
- package/lib/cbm.cjs +490 -0
- package/lib/choose.cjs +60 -2
- package/lib/commands.cjs +260 -19
- package/lib/knowledge.cjs +1 -1
- package/lib/profile.cjs +1 -1
- package/package.json +1 -1
package/lib/commands.cjs
CHANGED
|
@@ -8,13 +8,14 @@ const core = require('../hook.cjs');
|
|
|
8
8
|
const agg = require('./aggregate.cjs');
|
|
9
9
|
const chooser = require('./choose.cjs');
|
|
10
10
|
const setupLib = require('./setup.cjs');
|
|
11
|
+
const cbm = require('./cbm.cjs');
|
|
11
12
|
const { claudeDirOf, ctxOf, yesNo, repoOf } = require('./args.cjs');
|
|
12
13
|
|
|
13
14
|
const AGENT_OUTCOMES = ['pass', 'fail', 'timeout', 'aborted'];
|
|
14
15
|
const USER_OUTCOMES = ['accept', 'reject'];
|
|
15
16
|
const CAUSES = ['quality', 'scope'];
|
|
16
17
|
const STEER_KINDS = ['minor', 'plan', 'redirect'];
|
|
17
|
-
const VERIFY = ['tests', 'lint', 'review', 'judgment', 'none'];
|
|
18
|
+
const VERIFY = ['tests', 'lint', 'review', 'judgment', 'self', 'none'];
|
|
18
19
|
const INVALID_ID_WINDOW_DAYS = 7;
|
|
19
20
|
const NEW_ID_MAX = 1000;
|
|
20
21
|
|
|
@@ -48,6 +49,7 @@ function table(rows) {
|
|
|
48
49
|
// ------------------------------------------------------------------ setup / uninstall
|
|
49
50
|
|
|
50
51
|
function cmdSetup(opts) {
|
|
52
|
+
if (opts['with-codebase-memory']) return cmdSetupWithCbm(opts);
|
|
51
53
|
const res = setupLib.setup({
|
|
52
54
|
claudeDir: claudeDirOf(opts),
|
|
53
55
|
dept: typeof opts.dept === 'string' ? opts.dept : undefined,
|
|
@@ -75,6 +77,30 @@ function cmdSetup(opts) {
|
|
|
75
77
|
return { code: 0, out: lines.join('\n') };
|
|
76
78
|
}
|
|
77
79
|
|
|
80
|
+
/** `setup --with-codebase-memory [--force] [--cbm-dir D]`: normal setup, then install + register codebase-memory-mcp. */
|
|
81
|
+
async function cmdSetupWithCbm(opts) {
|
|
82
|
+
const base = cmdSetup(Object.assign({}, opts, { 'with-codebase-memory': false, json: false }));
|
|
83
|
+
let res;
|
|
84
|
+
let err = null;
|
|
85
|
+
try {
|
|
86
|
+
res = await cbm.install({ claudeDir: claudeDirOf(opts), force: !!opts.force, dir: typeof opts['cbm-dir'] === 'string' ? opts['cbm-dir'] : undefined, allowCustomSource: !!opts['allow-custom-source'] });
|
|
87
|
+
} catch (e) {
|
|
88
|
+
err = e.message;
|
|
89
|
+
}
|
|
90
|
+
if (opts.json) return { code: err ? 1 : 0, out: JSON.stringify({ setup: 'ok', codebase_memory: err ? { ok: false, error: err } : res }, null, 2) };
|
|
91
|
+
const lines = [base.out];
|
|
92
|
+
if (err) {
|
|
93
|
+
lines.push(`codebase-memory-mcp: LỖI — ${err} (setup dieu-phoi vẫn xong; không có gì bị ghi đè)`);
|
|
94
|
+
} else {
|
|
95
|
+
const act = { kept: 'đã có, giữ nguyên (--force để cài lại)', installed: 'đã cài', replaced: 'đã thay' }[res.action];
|
|
96
|
+
lines.push(`codebase-memory-mcp: ${act} v${res.version || '?'} tại ${res.binary.replace(/\\/g, '/')}${res.backup ? ` (bản cũ: ${path.basename(res.backup)})` : ''}`);
|
|
97
|
+
lines.push(`MCP (user scope): ${{ kept: 'đã đăng ký, giữ nguyên', 'kept-other': 'đã đăng ký (binary khác), giữ nguyên', added: 'đã đăng ký bằng `claude mcp add`', replaced: 'đã đăng ký lại', restored: 'đăng ký mới lỗi — đã khôi phục đăng ký cũ', manual: 'chưa đăng ký (xem ghi chú)' }[res.mcp]}`);
|
|
98
|
+
for (const n of res.notes) lines.push(` - ${n}`);
|
|
99
|
+
if (res.action !== 'kept' || res.mcp === 'added' || res.mcp === 'replaced') lines.push('Khởi động lại các phiên Claude Code để nạp MCP; kiểm bằng `dieu-phoi doctor`.');
|
|
100
|
+
}
|
|
101
|
+
return { code: err ? 1 : 0, out: lines.join('\n') };
|
|
102
|
+
}
|
|
103
|
+
|
|
78
104
|
function cmdUninstall(opts) {
|
|
79
105
|
const res = setupLib.uninstall({ claudeDir: claudeDirOf(opts), purge: !!opts.purge });
|
|
80
106
|
if (opts.json) return { code: 0, out: JSON.stringify(res, null, 2) };
|
|
@@ -116,6 +142,14 @@ async function cmdDoctor(opts) {
|
|
|
116
142
|
const sd = ctx.config.skill_dir;
|
|
117
143
|
add(!!sd && fs.existsSync(path.join(sd, 'profile', 'profile.json')), 'profile',
|
|
118
144
|
sd ? path.join(sd, 'profile', 'profile.json') : 'không có skill_dir — choose dùng priors mặc định', 'CẢNH BÁO');
|
|
145
|
+
// codebase-memory-mcp (optional): started here so its `daemon status` (~2.7 s, only with --cbm/--net; else a 10-minute
|
|
146
|
+
// cache) overlaps the hook-run check below
|
|
147
|
+
let cbmP = Promise.resolve([]);
|
|
148
|
+
try {
|
|
149
|
+
cbmP = cbm.doctorChecks({ claudeDir, repo: repoOf(opts), net: !!opts.net, deep: !!opts.cbm, cacheFile: path.join(ctx.paths.data, 'cbm-daemon.json') }).catch((e) => [{ ok: true, level: 'OK', name: 'codebase-memory', detail: `bỏ qua kiểm (${e.message})` }]);
|
|
150
|
+
} catch (e) {
|
|
151
|
+
cbmP = Promise.resolve([{ ok: true, level: 'OK', name: 'codebase-memory', detail: `bỏ qua kiểm (${e.message})` }]);
|
|
152
|
+
}
|
|
119
153
|
if (reg.commands.length) {
|
|
120
154
|
const cmd = reg.commands[0];
|
|
121
155
|
const t0 = Date.now();
|
|
@@ -168,13 +202,17 @@ async function cmdDoctor(opts) {
|
|
|
168
202
|
const cached = opts.net ? null : core.sinkStatusCached(ctx);
|
|
169
203
|
if (cached && (cached.state === 'ok' || cached.state === 'fail')) {
|
|
170
204
|
add(cached.state === 'ok', 'sink', `${ctx.config.sink_url} → ${cached.state === 'ok' ? 'ok' : `lỗi: ${cached.detail}`} (bộ nhớ đệm: ${cached.source} lúc ${cached.ts}; \`doctor --net\` để kiểm trực tiếp)`);
|
|
205
|
+
} else if (!opts.net && recentSinkTimeout(ctx)) {
|
|
206
|
+
const t = recentSinkTimeout(ctx);
|
|
207
|
+
add(false, 'sink', `${ctx.config.sink_url} → chậm/không xác định: lần kiểm lúc ${t.ts} không phản hồi kịp (chưa coi là lỗi); \`doctor --net\` để thử lại`, 'CẢNH BÁO');
|
|
171
208
|
} else {
|
|
172
209
|
const live = await checkSinkLive(ctx);
|
|
173
|
-
add(live.ok, 'sink', `${ctx.config.sink_url} → ${live.detail}
|
|
210
|
+
add(live.ok, 'sink', `${ctx.config.sink_url} → ${live.detail}`, live.slow ? 'CẢNH BÁO' : undefined);
|
|
174
211
|
}
|
|
175
212
|
} else {
|
|
176
213
|
add(true, 'sink', 'chưa cấu hình (chỉ lưu cục bộ)');
|
|
177
214
|
}
|
|
215
|
+
for (const c of await cbmP) checks.push(c);
|
|
178
216
|
const failed = checks.some((c) => c.level === 'LỖI');
|
|
179
217
|
if (opts.json) return { code: failed ? 1 : 0, out: JSON.stringify({ ok: !failed, runs_dir: reg.runs_dir, checks }, null, 2) };
|
|
180
218
|
const out = checks.map((c) => `[${c.level}] ${c.name}: ${c.detail}`).join('\n');
|
|
@@ -208,26 +246,45 @@ function sweepStaleTmp(ctx) {
|
|
|
208
246
|
return { removed, left };
|
|
209
247
|
}
|
|
210
248
|
|
|
211
|
-
/**
|
|
249
|
+
/** The doctor's own timed-out sink check for the current sink, if less than 1 h old (else null): plain `doctor` does not retry a 20 s wait each time. */
|
|
250
|
+
function recentSinkTimeout(ctx) {
|
|
251
|
+
const chk = core.readJsonFile(ctx.paths.sinkCheck, null);
|
|
252
|
+
if (!chk || !chk.timeout || chk.url !== core.sinkUrlKey(ctx.config)) return null;
|
|
253
|
+
const t = Date.parse(chk.ts);
|
|
254
|
+
return t && Date.now() - t < 3600 * 1000 && t <= Date.now() + 60000 ? { ts: chk.ts } : null;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Live sink check (view=stats); result cached in data/sink-check.json for 24 h. Apps Script often needs 8-15 s, so the
|
|
259
|
+
* timeout is 20 s (DIEU_PHOI_SINK_CHECK_TIMEOUT_MS overrides, for tests). A timeout is not a hard failure: it is
|
|
260
|
+
* reported as slow/unknown (CẢNH BÁO) and cached with `timeout: true`, which the cache readers ignore.
|
|
261
|
+
*/
|
|
212
262
|
async function checkSinkLive(ctx) {
|
|
213
263
|
let ok = false;
|
|
264
|
+
let slow = false;
|
|
214
265
|
let detail;
|
|
266
|
+
const ms = Number(process.env.DIEU_PHOI_SINK_CHECK_TIMEOUT_MS) > 0 ? Number(process.env.DIEU_PHOI_SINK_CHECK_TIMEOUT_MS) : 20000;
|
|
215
267
|
try {
|
|
216
268
|
const u = new URL(ctx.config.sink_url);
|
|
217
269
|
u.searchParams.set('token', ctx.config.sink_token);
|
|
218
270
|
u.searchParams.set('view', 'stats');
|
|
219
|
-
const res = await fetch(u, { redirect: 'follow', signal: AbortSignal.timeout(
|
|
271
|
+
const res = await fetch(u, { redirect: 'follow', signal: AbortSignal.timeout(ms) });
|
|
220
272
|
const txt = await res.text();
|
|
221
273
|
ok = res.ok;
|
|
222
274
|
try { const b = JSON.parse(txt); if (b && b.ok === false) ok = false; } catch (_) { ok = false; }
|
|
223
275
|
detail = `HTTP ${res.status}`;
|
|
224
276
|
} catch (e) {
|
|
225
|
-
|
|
277
|
+
if (e && (e.name === 'TimeoutError' || e.name === 'AbortError')) {
|
|
278
|
+
slow = true;
|
|
279
|
+
detail = `chậm/không xác định: không phản hồi trong ${Math.round(ms / 1000)} s (Apps Script thường mất 8-15 s; chạy lại \`doctor --net\`, hoặc xem kết quả sync nền)`;
|
|
280
|
+
} else {
|
|
281
|
+
detail = e.message;
|
|
282
|
+
}
|
|
226
283
|
}
|
|
227
284
|
try {
|
|
228
|
-
core.writeFileAtomic(ctx.paths.sinkCheck, `${JSON.stringify({ ts: core.nowIso(), ok, detail, url: core.sinkUrlKey(ctx.config) })}\n`);
|
|
285
|
+
core.writeFileAtomic(ctx.paths.sinkCheck, `${JSON.stringify({ ts: core.nowIso(), ok, detail, url: core.sinkUrlKey(ctx.config), ...(slow ? { timeout: true } : {}) })}\n`);
|
|
229
286
|
} catch (_) { /* cache is best effort */ }
|
|
230
|
-
return { ok, detail };
|
|
287
|
+
return { ok, detail, slow };
|
|
231
288
|
}
|
|
232
289
|
|
|
233
290
|
// ------------------------------------------------------------------ start
|
|
@@ -238,8 +295,13 @@ async function checkSinkLive(ctx) {
|
|
|
238
295
|
*/
|
|
239
296
|
function cmdStart(opts) {
|
|
240
297
|
const ctx = ctxOf(opts);
|
|
298
|
+
const wantMode = modeOpt(opts); // validate before the run dir is created
|
|
241
299
|
const reg = setupLib.registration(claudeDirOf(opts));
|
|
242
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
|
+
}
|
|
243
305
|
const h = info.health;
|
|
244
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`);
|
|
245
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');
|
|
@@ -257,12 +319,58 @@ function cmdStart(opts) {
|
|
|
257
319
|
`step_ids: ${info.step_ids.join(' ')}`,
|
|
258
320
|
`health: ${core.healthLine(h)}`,
|
|
259
321
|
`scout_brief: ${info.scout_brief || '(không thấy — dùng references/brief-template.md mục E)'}`,
|
|
322
|
+
core.modeLine(info),
|
|
260
323
|
...core.orchLessonLines(info),
|
|
261
324
|
...core.knowledgeLines(info),
|
|
262
325
|
];
|
|
263
326
|
return { code: 0, out: out.join('\n') };
|
|
264
327
|
}
|
|
265
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
|
+
|
|
266
374
|
// ------------------------------------------------------------------ hook / sync
|
|
267
375
|
|
|
268
376
|
function cmdHook(opts) {
|
|
@@ -369,6 +477,7 @@ function verdictUser(opts, ctx, events) {
|
|
|
369
477
|
if (typeof taskId !== 'string' || !taskId) throw new Error('--task cần task_id');
|
|
370
478
|
requireTaskId(taskId);
|
|
371
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');
|
|
372
481
|
let cause = typeof opts.cause === 'string' ? opts.cause : null;
|
|
373
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)`);
|
|
374
483
|
if (outcome === 'accept') cause = null;
|
|
@@ -376,6 +485,7 @@ function verdictUser(opts, ctx, events) {
|
|
|
376
485
|
if (!known) throw new Error(`Không thấy task "${taskId}" trong log.`);
|
|
377
486
|
const rounds = events.filter((e) => e.kind === 'verdict' && e.task_id === taskId
|
|
378
487
|
&& agg.normalizeVerdict(e).by === 'user').map((e) => Number(e.round) || 1);
|
|
488
|
+
if (opts.auto) return verdictAuto(opts, ctx, events, taskId, rounds);
|
|
379
489
|
let round = (rounds.length ? Math.max(...rounds) : 0) + 1;
|
|
380
490
|
if (opts.round !== undefined) {
|
|
381
491
|
round = Number(opts.round);
|
|
@@ -396,6 +506,33 @@ function verdictUser(opts, ctx, events) {
|
|
|
396
506
|
}, core.commonFields(ctx));
|
|
397
507
|
}
|
|
398
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
|
+
|
|
399
536
|
/**
|
|
400
537
|
* `verdict --root <root> accept [--steps a,b]` (core 0.4.0): the user accepts the steps of the latest approved plan
|
|
401
538
|
* in one call. Covers every step whose task has an exec dispatch and no user verdict yet (a step already rejected
|
|
@@ -403,6 +540,7 @@ function verdictUser(opts, ctx, events) {
|
|
|
403
540
|
*/
|
|
404
541
|
function verdictRoot(opts, ctx, events) {
|
|
405
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');
|
|
406
544
|
if (opts._[0] !== 'accept') {
|
|
407
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');
|
|
408
546
|
}
|
|
@@ -495,6 +633,9 @@ function cmdVerdict(opts) {
|
|
|
495
633
|
if (kn) process.stderr.write(`${kn.out}\n`);
|
|
496
634
|
return { code: 0, out: JSON.stringify(ev) };
|
|
497
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
|
+
}
|
|
498
639
|
if (ev.by === 'user') {
|
|
499
640
|
const msg = `Đã ghi người dùng ${ev.outcome}${ev.cause ? ` (${ev.cause})` : ''} cho task ${ev.task_id}, vòng ${ev.round}.`;
|
|
500
641
|
return { code: 0, out: kn ? `${msg}\n${kn.out}` : msg };
|
|
@@ -690,12 +831,14 @@ function pct(v) {
|
|
|
690
831
|
function cmdStats(opts) {
|
|
691
832
|
const ctx = ctxOf(opts);
|
|
692
833
|
const { lambda } = lambdaOf(opts, ctx);
|
|
693
|
-
const
|
|
834
|
+
const events = core.readEvents(ctx);
|
|
835
|
+
const { cells, steers, orchestration, tasks } = agg.analyze(events, { lambda });
|
|
836
|
+
const modes = agg.modeSummary(events, tasks);
|
|
694
837
|
const type = typeof opts.type === 'string' ? opts.type : null;
|
|
695
838
|
const rows = type ? cells.filter((c) => c.task_type === type) : cells;
|
|
696
839
|
const steerRows = type ? steers.filter((s) => s.task_type === type) : steers;
|
|
697
840
|
if (opts.json) {
|
|
698
|
-
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) };
|
|
699
842
|
}
|
|
700
843
|
if (!rows.length) {
|
|
701
844
|
return { code: 0, out: orchestration.sessions ? `Chưa có dữ liệu theo ô.\n${orchLine(orchestration)}` : 'Chưa có dữ liệu.' };
|
|
@@ -722,13 +865,40 @@ function cmdStats(opts) {
|
|
|
722
865
|
}
|
|
723
866
|
return {
|
|
724
867
|
code: 0,
|
|
725
|
-
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`
|
|
726
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). '
|
|
727
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. '
|
|
728
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.`,
|
|
729
872
|
};
|
|
730
873
|
}
|
|
731
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
|
+
|
|
732
902
|
const HIGH_STEER_RATE = 0.3;
|
|
733
903
|
const HIGH_STEER_MIN_TASKS = 3;
|
|
734
904
|
|
|
@@ -737,6 +907,7 @@ function cmdRetro(opts) {
|
|
|
737
907
|
const { prof, lambda } = lambdaOf(opts, ctx);
|
|
738
908
|
const events = core.readEvents(ctx);
|
|
739
909
|
const { agents, tasks, cells, steers, userVerdicts, overrides, orchestration } = agg.analyze(events, { lambda });
|
|
910
|
+
const modes = agg.modeSummary(events, tasks);
|
|
740
911
|
const reasons = {};
|
|
741
912
|
const outcomes = {};
|
|
742
913
|
const userOutcomes = {};
|
|
@@ -776,6 +947,10 @@ function cmdRetro(opts) {
|
|
|
776
947
|
multi_attempt_tasks: escalations,
|
|
777
948
|
discarded_tasks: agg.discardedTaskIds(events).size,
|
|
778
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,
|
|
779
954
|
cost_usd: agents.reduce((s, a) => s + (a.cost_usd || 0), 0),
|
|
780
955
|
},
|
|
781
956
|
lambda_usd_per_min: prof.lambda,
|
|
@@ -788,6 +963,7 @@ function cmdRetro(opts) {
|
|
|
788
963
|
high_steer_task_types: highSteer,
|
|
789
964
|
overrides: overrides.map((o) => ({ ts: o.ts, task_type: o.task_type, suggested_tier: o.suggested_tier, chosen_tier: o.chosen_tier })),
|
|
790
965
|
orchestration,
|
|
966
|
+
modes,
|
|
791
967
|
cells,
|
|
792
968
|
};
|
|
793
969
|
if (opts.json) return { code: 0, out: JSON.stringify(summary, null, 2) };
|
|
@@ -795,10 +971,12 @@ function cmdRetro(opts) {
|
|
|
795
971
|
`Retro ${summary.dept || ''} — ${summary.period ? `${summary.period.from.slice(0, 10)} → ${summary.period.to.slice(0, 10)}` : 'chưa có dữ liệu'}`,
|
|
796
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.`,
|
|
797
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}`,
|
|
798
975
|
`Lý do hay gặp: ${summary.reason_codes.slice(0, 8).map((r) => `${r.code}×${r.count}`).join(', ') || '-'}`,
|
|
799
976
|
`Loại task: ${summary.task_types.map((t) => `${t.task_type}(${t.n})`).join(', ') || '-'}`,
|
|
800
977
|
`Steer nhiều (brainstorm hỏi quá ít): ${highSteer.map((s) => `${s.task_type} ${pct(s.steer_rate)}`).join(', ') || '-'}`,
|
|
801
978
|
orchLine(orchestration),
|
|
979
|
+
modeRetroLine(modes),
|
|
802
980
|
'Chi tiết theo ô: `dieu-phoi stats`; đưa cho LLM: `dieu-phoi retro --json`.',
|
|
803
981
|
];
|
|
804
982
|
return { code: 0, out: lines.join('\n') };
|
|
@@ -806,6 +984,13 @@ function cmdRetro(opts) {
|
|
|
806
984
|
|
|
807
985
|
// ------------------------------------------------------------------ choose
|
|
808
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
|
+
|
|
809
994
|
function chooseInput(opts, ctx, events) {
|
|
810
995
|
return {
|
|
811
996
|
type: opts.type,
|
|
@@ -813,6 +998,9 @@ function chooseInput(opts, ctx, events) {
|
|
|
813
998
|
urgency: typeof opts.urgency === 'string' ? opts.urgency : 'normal',
|
|
814
999
|
reversible: yesNo(opts.reversible, true),
|
|
815
1000
|
verifiable: yesNo(opts.verifiable, true),
|
|
1001
|
+
publish: yesNo(opts.publish, false),
|
|
1002
|
+
dependents: yesNo(opts.dependents, false),
|
|
1003
|
+
mode: modeOpt(opts),
|
|
816
1004
|
role: typeof opts.role === 'string' ? opts.role : 'exec',
|
|
817
1005
|
seed: opts.seed,
|
|
818
1006
|
taskId: typeof opts.task === 'string' ? opts.task : null,
|
|
@@ -837,6 +1025,18 @@ function overrideEvent(ctx, taskId, taskType, suggested, chosen) {
|
|
|
837
1025
|
}, core.commonFields(ctx));
|
|
838
1026
|
}
|
|
839
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
|
+
|
|
840
1040
|
function cmdChoose(opts) {
|
|
841
1041
|
const ctx = ctxOf(opts);
|
|
842
1042
|
if (opts.override !== undefined && typeof opts.task !== 'string') {
|
|
@@ -844,7 +1044,20 @@ function cmdChoose(opts) {
|
|
|
844
1044
|
}
|
|
845
1045
|
if (opts.task !== undefined) requireTaskId(typeof opts.task === 'string' ? opts.task : '');
|
|
846
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
|
+
}
|
|
847
1058
|
const res = chooser.choose(input);
|
|
1059
|
+
res.mode_source = modeSource;
|
|
1060
|
+
if (res.verify_plan) Object.assign(res, chooseModeSuggest(ctx, input, res, mi));
|
|
848
1061
|
const orch = measureOrch(opts, ctx, input.profile);
|
|
849
1062
|
res.orch_ctx_tokens = orch.ctx_tokens;
|
|
850
1063
|
res.orch_ctx_growth = orch.ctx_growth;
|
|
@@ -863,14 +1076,15 @@ function cmdChoose(opts) {
|
|
|
863
1076
|
}
|
|
864
1077
|
if (opts.json) return { code: 0, out: JSON.stringify(res) };
|
|
865
1078
|
const typeWarn = res.type_warning && !String(res.reason).includes(res.type_warning) ? `\nCẢNH BÁO: ${res.type_warning}` : '';
|
|
866
|
-
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}`;
|
|
867
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ô'})`;
|
|
868
1082
|
if (res.action === 'ask_user') {
|
|
869
1083
|
return { code: 0, out: `HỎI NGƯỜI DÙNG — ${res.reason}${warn}` };
|
|
870
1084
|
}
|
|
871
1085
|
return {
|
|
872
1086
|
code: 0,
|
|
873
|
-
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}`,
|
|
874
1088
|
};
|
|
875
1089
|
}
|
|
876
1090
|
|
|
@@ -917,6 +1131,8 @@ function buildPlanTable(rawSteps, base) {
|
|
|
917
1131
|
score: s.score,
|
|
918
1132
|
verifiable: s.verifiable === undefined ? true : yesNo(s.verifiable, true),
|
|
919
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,
|
|
920
1136
|
urgency: s.urgency,
|
|
921
1137
|
deps: deps.map(String),
|
|
922
1138
|
};
|
|
@@ -933,6 +1149,9 @@ function buildPlanTable(rawSteps, base) {
|
|
|
933
1149
|
verifiable: s.verifiable,
|
|
934
1150
|
reversible: s.reversible,
|
|
935
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,
|
|
936
1155
|
taskId: s.task || base.taskId,
|
|
937
1156
|
override: s.override || base.override,
|
|
938
1157
|
role: s.role || base.role,
|
|
@@ -942,6 +1161,7 @@ function buildPlanTable(rawSteps, base) {
|
|
|
942
1161
|
const head = {
|
|
943
1162
|
step: s.step, title: s.title, type: s.type, task_id: s.task || null, score: Number(s.score),
|
|
944
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,
|
|
945
1165
|
};
|
|
946
1166
|
try {
|
|
947
1167
|
return Object.assign(head, call());
|
|
@@ -958,7 +1178,10 @@ function buildPlanTable(rawSteps, base) {
|
|
|
958
1178
|
const ok = rows.filter((r) => r.action !== 'refused');
|
|
959
1179
|
const costs = ok.map((r) => r.est_cost_usd);
|
|
960
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;
|
|
961
1182
|
return {
|
|
1183
|
+
mode: base.mode || core.DEFAULT_MODE,
|
|
1184
|
+
self_batch_steps: selfBatch,
|
|
962
1185
|
steps: rows,
|
|
963
1186
|
refused: rows.filter((r) => r.action === 'refused').map((r) => ({ step: r.step, error: r.error })),
|
|
964
1187
|
total_cost_usd: totalCost === null ? null : Math.round(totalCost * 1000) / 1000,
|
|
@@ -1054,10 +1277,10 @@ function recordApprovedPlan(ctx, root, stepsFile, plan) {
|
|
|
1054
1277
|
steps_file: stepsFile.split(path.sep).join('/'),
|
|
1055
1278
|
approved_at: core.nowIso(),
|
|
1056
1279
|
approval: prevApproval(prev) + 1,
|
|
1057
|
-
steps: plan.steps.map((r) => ({
|
|
1280
|
+
steps: plan.steps.map((r) => Object.assign({
|
|
1058
1281
|
id: r.step, task_id: r.task_id || null, title: r.title === undefined ? null : r.title, type: r.task_type || r.type || null,
|
|
1059
1282
|
tier: r.action === 'refused' ? null : (r.tier || null), depends_on: r.depends_on,
|
|
1060
|
-
})),
|
|
1283
|
+
}, r.mode_override ? { mode: r.mode_override } : {})),
|
|
1061
1284
|
history,
|
|
1062
1285
|
};
|
|
1063
1286
|
core.writeFileAtomic(file, `${JSON.stringify(rec, null, 2)}\n`);
|
|
@@ -1094,8 +1317,19 @@ function cmdPlanTable(opts) {
|
|
|
1094
1317
|
throw new Error(`plan-table: không đọc được ${opts.file}: ${e.message}`);
|
|
1095
1318
|
}
|
|
1096
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
|
+
}
|
|
1097
1329
|
const base = chooseInput(Object.assign({}, opts, { type: 'x', score: 0, override: undefined }), ctx, core.readEvents(ctx));
|
|
1098
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;
|
|
1099
1333
|
const plan = buildPlanTable(raw, base);
|
|
1100
1334
|
const problems = planTaskIdProblems(plan, opts.approve ? String(opts.root) : null);
|
|
1101
1335
|
if (opts.approve && problems.length) {
|
|
@@ -1105,26 +1339,33 @@ function cmdPlanTable(opts) {
|
|
|
1105
1339
|
const typeNotes = planTypeNotes(plan);
|
|
1106
1340
|
if (typeNotes.length) process.stderr.write(`dieu-phoi plan-table: cảnh báo — ${typeNotes.join('; ')}\n`);
|
|
1107
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
|
+
}
|
|
1108
1347
|
const ovs = rec ? planOverrideEvents(ctx, plan, base.events) : [];
|
|
1109
1348
|
for (const ev of ovs) core.appendEvent(ctx, ev);
|
|
1110
1349
|
if (rec) recordPlanSuggestions(ctx, plan);
|
|
1111
1350
|
if (opts.json) {
|
|
1112
1351
|
const out = rec ? Object.assign({}, plan, {
|
|
1113
|
-
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 },
|
|
1114
1353
|
}) : plan;
|
|
1115
1354
|
return { code: 0, out: JSON.stringify(out, null, 2) };
|
|
1116
1355
|
}
|
|
1117
|
-
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 |', '|---|---|---|---|---|---|'];
|
|
1118
1357
|
for (const r of plan.steps) {
|
|
1119
1358
|
const tierCell = r.action === 'refused' ? '—' : (r.action === 'ask_user' ? `${r.tier} (hỏi người dùng)` : r.tier);
|
|
1120
1359
|
const yn = (v) => (v ? 'y' : 'n');
|
|
1121
1360
|
const rubric = `${Number.isFinite(r.score) ? r.score : '?'} · ${yn(r.verifiable)} · ${yn(r.reversible)}`;
|
|
1122
1361
|
const why = `${r.type}${r.depends_on.length ? `, sau ${r.depends_on.join(', ')}` : ''} — ${r.reason.replace(/; profile: .*$/, '')}`;
|
|
1123
1362
|
const est = r.action === 'refused' ? '-' : `~${fmtUsd(r.est_cost_usd)} · ~${fmtNum(r.est_time_min)} phút${r.est_source === 'rough' ? ' (thô)' : ''}`;
|
|
1124
|
-
|
|
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)} |`);
|
|
1125
1365
|
}
|
|
1126
1366
|
lines.push('');
|
|
1127
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` : ''}`);
|
|
1128
1369
|
if (plan.rough) lines.push('', '_(thô) = ô chưa đủ 5 task, ước lượng theo giả định token của từng tier._');
|
|
1129
1370
|
if (plan.refused.length) {
|
|
1130
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.`);
|
|
@@ -1132,11 +1373,11 @@ function cmdPlanTable(opts) {
|
|
|
1132
1373
|
`);
|
|
1133
1374
|
}
|
|
1134
1375
|
if (typeNotes.length) lines.push('', `**Loại task:** ${typeNotes.join('; ')}`);
|
|
1135
|
-
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}` : ''}`);
|
|
1136
1377
|
return { code: 0, out: lines.join('\n') };
|
|
1137
1378
|
}
|
|
1138
1379
|
|
|
1139
1380
|
module.exports = {
|
|
1140
|
-
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,
|
|
1141
1382
|
cmdPlanTable, cmdNewId, requireTaskId, buildPlanTable, recordApprovedPlan, planTaskIdProblems, criticalPath, resolveAgentId, AGENT_OUTCOMES, USER_OUTCOMES, VERIFY,
|
|
1142
1383
|
};
|
package/lib/knowledge.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
|
-
// `dieu-phoi knowledge ...` — per-project knowledge store (core 0.6.0
|
|
2
|
+
// `dieu-phoi knowledge ...` — per-project knowledge store (core 0.6.0).
|
|
3
3
|
// s1: project identity (path, rekey). s2: the store itself — index.json + sections/<id>.md + history/, read with
|
|
4
4
|
// get/list, written only by `merge --run <id>` (lock, tmp+rename, history, 40-line cap, sha/rev conflicts), and
|
|
5
5
|
// cross-repo links (related: child|parent) for a parent repo whose sessions work in its child repos.
|
package/lib/profile.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
|
-
// `dieu-phoi profile base|apply` (core 0.6.0
|
|
2
|
+
// `dieu-phoi profile base|apply` (core 0.6.0): apply an approved retro to <skill>/profile/lessons.md and
|
|
3
3
|
// profile.json without overwriting what other sessions wrote meanwhile.
|
|
4
4
|
//
|
|
5
5
|
// profile base --save <dir> snapshot both files into <dir>/profile-base/ (+ base.json with their sha256) before the
|
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>",
|