@kienntpixon/dieu-phoi 0.7.3 → 0.7.4
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 +15 -0
- package/agents/dp-exec.md +6 -1
- package/agents/dp-helper.md +6 -0
- package/agents/dp-scout.md +7 -1
- package/agents/dp-verify.md +6 -0
- package/bin/dieu-phoi.cjs +8 -8
- package/hook.cjs +196 -129
- package/lib/aggregate.cjs +20 -2
- package/lib/choose.cjs +27 -22
- package/lib/commands.cjs +195 -34
- package/lib/knowledge.cjs +47 -4
- package/lib/prune.cjs +81 -7
- package/package.json +1 -1
- package/profile/lessons.md +0 -1
package/lib/prune.cjs
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
|
-
// `dieu-phoi prune --after-retro <file> [--yes]` (core 0.5.2): post-retro cleanup of closed run dirs,
|
|
3
|
-
// files and events.jsonl (moved to a gz archive, never deleted). Dry-run unless --yes.
|
|
2
|
+
// `dieu-phoi prune --after-retro <file> [--yes] [--delta-days N]` (core 0.5.2): post-retro cleanup of closed run dirs,
|
|
3
|
+
// bad plan files and events.jsonl (moved to a gz archive, never deleted). Dry-run unless --yes. 0.7.4 (s6): with
|
|
4
|
+
// --delta-days N (default 30) rejected unmerged deltas of runs idle > N days are deleted; accepted ones never.
|
|
5
|
+
// s9: `--delta-days N` alone (no --after-retro) runs only that delta step (cmdPruneDeltas).
|
|
4
6
|
// Only touches: <runs dir>/<id>/, <data>/plans, <data>/events.jsonl, <data>/archive, <data>/sync-offset.json.
|
|
5
7
|
|
|
6
8
|
const fs = require('fs');
|
|
@@ -58,6 +60,8 @@ function human(n) {
|
|
|
58
60
|
// contract/steps/plan/handoff files of a run dir: the record of what was planned and handed over (never pruned)
|
|
59
61
|
const KEEP_RE = /^(?:contract-r.*\.md|steps-r.*\.json|plan.*\.md|handoff-.*\.md)$/;
|
|
60
62
|
const DELTA_RE = /^knowledge-delta.*\.md$/i;
|
|
63
|
+
const DAY_MS = 86400000;
|
|
64
|
+
const DELTA_DAYS_DEFAULT = 30;
|
|
61
65
|
|
|
62
66
|
/**
|
|
63
67
|
* Files to keep in a run dir: retro-*.md + per task/role the highest-try result AND the newest-by-mtime result
|
|
@@ -123,6 +127,7 @@ function taskIdsOfDir(id, names, plansDir) {
|
|
|
123
127
|
function planRuns(ctx, events, cutoffMs, protect, popts) {
|
|
124
128
|
const po = popts || {};
|
|
125
129
|
const nowMs = typeof po.now === 'number' ? po.now : Date.now();
|
|
130
|
+
const deltaDays = typeof po.deltaDays === 'number' ? po.deltaDays : 0;
|
|
126
131
|
const cls = core.deltaClassifier(events, ctx.paths.plans);
|
|
127
132
|
const runs = core.runsDir();
|
|
128
133
|
const byTask = new Map();
|
|
@@ -149,6 +154,19 @@ function planRuns(ctx, events, cutoffMs, protect, popts) {
|
|
|
149
154
|
if (!inside(runs, dir)) continue;
|
|
150
155
|
const names = fs.readdirSync(dir);
|
|
151
156
|
const tasks = Array.from(taskIdsOfDir(id, names, ctx.paths.plans));
|
|
157
|
+
if (po.deltaOnly) { // s9 `prune --delta-days N` alone: only the rejected unmerged deltas of an idle run, nothing else
|
|
158
|
+
let runMs = 0;
|
|
159
|
+
for (const n of names) { try { runMs = Math.max(runMs, fs.lstatSync(path.join(dir, n)).mtimeMs); } catch (_) { /* gone */ } }
|
|
160
|
+
if (nowMs - runMs <= deltaDays * DAY_MS) continue;
|
|
161
|
+
const drops = names.filter((n) => {
|
|
162
|
+
if (!DELTA_RE.test(n) || cls.deltaState(id, core.deltaTagOf(n)) !== 'reject') return false;
|
|
163
|
+
let text = null;
|
|
164
|
+
try { text = fs.readFileSync(path.join(dir, n), 'utf8'); } catch (_) { return false; }
|
|
165
|
+
return !appliedSet().has(core.deltaHash(text));
|
|
166
|
+
});
|
|
167
|
+
if (drops.length) out.prune.push({ id, dir, tasks, keep: [], keep_deltas: 0, archive: [], drop_deltas: drops, del: [], delete_bytes: 0, keep_bytes: 0 });
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
152
170
|
let anyVerdict = false; let reason = null; let latest = 0;
|
|
153
171
|
for (const t of tasks) {
|
|
154
172
|
const evs = byTask.get(t) || [];
|
|
@@ -189,6 +207,21 @@ function planRuns(ctx, events, cutoffMs, protect, popts) {
|
|
|
189
207
|
if (nowMs - m > core.DELTA_WINDOW_MS && cls.deltaState(id, core.deltaTagOf(n)) === null) archive.push(n);
|
|
190
208
|
}
|
|
191
209
|
for (const n of archive) keep.delete(n);
|
|
210
|
+
// s6 --delta-days N: a delta the user REJECTED, unmerged, in a run idle for more than N days, is no work any more:
|
|
211
|
+
// deleted with --yes. Accepted-but-unmerged (K7) and no-verdict deltas (K1: archived above, never deleted) stay.
|
|
212
|
+
const dropDeltas = [];
|
|
213
|
+
if (deltaDays > 0) {
|
|
214
|
+
let runMs = 0;
|
|
215
|
+
for (const n of names) { try { runMs = Math.max(runMs, fs.lstatSync(path.join(dir, n)).mtimeMs); } catch (_) { /* gone */ } }
|
|
216
|
+
if (nowMs - runMs > deltaDays * DAY_MS) {
|
|
217
|
+
for (const n of names) {
|
|
218
|
+
if (!DELTA_RE.test(n) || !keep.has(n) || archive.includes(n)) continue;
|
|
219
|
+
if (cls.deltaState(id, core.deltaTagOf(n)) !== 'reject' || !unmerged(n)) continue;
|
|
220
|
+
keep.delete(n);
|
|
221
|
+
dropDeltas.push(n);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
192
225
|
const keepDeltas = names.filter((n) => DELTA_RE.test(n) && keep.has(n)).length;
|
|
193
226
|
const del = [];
|
|
194
227
|
let delBytes = 0; let keepBytes = 0;
|
|
@@ -200,7 +233,7 @@ function planRuns(ctx, events, cutoffMs, protect, popts) {
|
|
|
200
233
|
del.push({ name: n, bytes: b });
|
|
201
234
|
delBytes += b;
|
|
202
235
|
}
|
|
203
|
-
out.prune.push({ id, dir, tasks, keep: Array.from(keep).sort(), keep_deltas: keepDeltas, archive, del, delete_bytes: delBytes, keep_bytes: keepBytes });
|
|
236
|
+
out.prune.push({ id, dir, tasks, keep: Array.from(keep).sort(), keep_deltas: keepDeltas, archive, drop_deltas: dropDeltas, del, delete_bytes: delBytes, keep_bytes: keepBytes });
|
|
204
237
|
}
|
|
205
238
|
return out;
|
|
206
239
|
}
|
|
@@ -275,10 +308,46 @@ function otherSessionActive(ctx, opts, events) {
|
|
|
275
308
|
return count ? { count, sessions: Array.from(sessions), last: new Date(last).toISOString(), ownKnown: !!own } : null;
|
|
276
309
|
}
|
|
277
310
|
|
|
311
|
+
/**
|
|
312
|
+
* s9 `prune --delta-days N` without --after-retro: deletes (with --yes) only the rejected, unmerged deltas of runs idle
|
|
313
|
+
* for more than N days. No run dir is archived, no event moves, no bad plan goes. Dry-run otherwise.
|
|
314
|
+
*/
|
|
315
|
+
function cmdPruneDeltas(opts, ctx) {
|
|
316
|
+
const dd = opts['delta-days'];
|
|
317
|
+
const deltaDays = dd === true ? DELTA_DAYS_DEFAULT : Number(dd);
|
|
318
|
+
if (!Number.isFinite(deltaDays) || deltaDays < 0) return { code: 2, out: `prune: --delta-days phải là số ngày ≥ 0 (nhận: ${dd})` };
|
|
319
|
+
const runs = core.runsDir();
|
|
320
|
+
if (!path.isAbsolute(runs) || runs === path.parse(runs).root) return { code: 2, out: `prune: từ chối đường dẫn runs không an toàn: ${runs}` };
|
|
321
|
+
const yes = opts.yes === true || opts.yes === 'y' || opts.yes === 'yes';
|
|
322
|
+
const rp = planRuns(ctx, core.readEvents(ctx), 0, null, { now: typeof opts.now === 'number' ? opts.now : undefined, deltaDays, deltaOnly: true });
|
|
323
|
+
let removed = 0;
|
|
324
|
+
if (yes) {
|
|
325
|
+
for (const r of rp.prune) {
|
|
326
|
+
for (const n of r.drop_deltas) {
|
|
327
|
+
const p = path.join(r.dir, n);
|
|
328
|
+
if (!inside(r.dir, p) || !inside(runs, p)) continue;
|
|
329
|
+
fs.rmSync(p, { force: true });
|
|
330
|
+
removed++;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
const total = rp.prune.reduce((n, r) => n + r.drop_deltas.length, 0);
|
|
335
|
+
if (opts.json) {
|
|
336
|
+
return { code: 0, out: JSON.stringify({ mode: yes ? 'apply' : 'dry-run', delta_only: true, delta_days: deltaDays,
|
|
337
|
+
runs: rp.prune.map((r) => ({ id: r.id, drop_deltas: r.drop_deltas })), totals: { drop_deltas: total, removed } }, null, 2) };
|
|
338
|
+
}
|
|
339
|
+
const L = [];
|
|
340
|
+
L.push(`prune --delta-days ${deltaDays} ${yes ? '(ĐÃ THỰC HIỆN)' : '(dry-run — thêm --yes để thực hiện)'}: chỉ delta đã từ chối, chưa gộp, của run rảnh > ${deltaDays} ngày`);
|
|
341
|
+
for (const r of rp.prune) L.push(` ${r.id}: ${yes ? 'xoá' : 'sẽ xoá'} ${r.drop_deltas.length} delta (${r.drop_deltas.join(', ')})`);
|
|
342
|
+
L.push(`Tổng: ${total} delta ${yes ? `đã xoá ${removed}` : 'sẽ xoá'}. Không đụng run dir khác, events.jsonl, plan hay archive.`);
|
|
343
|
+
return { code: 0, out: L.join('\n') };
|
|
344
|
+
}
|
|
345
|
+
|
|
278
346
|
function cmdPrune(opts) {
|
|
279
347
|
const ctx = ctxOf(opts);
|
|
280
348
|
const retro = typeof opts['after-retro'] === 'string' ? path.resolve(opts['after-retro']) : null;
|
|
281
|
-
if (!retro
|
|
349
|
+
if (!retro && opts['delta-days'] !== undefined) return cmdPruneDeltas(opts, ctx);
|
|
350
|
+
if (!retro) return { code: 2, out: 'prune: thiếu --after-retro <file retro> (hoặc --delta-days N để chỉ dọn delta đã từ chối)' };
|
|
282
351
|
let st; try { st = fs.statSync(retro); } catch (_) { st = null; }
|
|
283
352
|
if (!st || !st.isFile()) return { code: 2, out: `prune: không đọc được file retro: ${retro}` };
|
|
284
353
|
const runs = core.runsDir();
|
|
@@ -287,10 +356,14 @@ function cmdPrune(opts) {
|
|
|
287
356
|
if (!path.isAbsolute(p) || p === path.parse(p).root) return { code: 2, out: `prune: từ chối đường dẫn ${name} không an toàn: ${p}` };
|
|
288
357
|
}
|
|
289
358
|
const yes = opts.yes === true || opts.yes === 'y' || opts.yes === 'yes';
|
|
359
|
+
// --delta-days N (default 30; 0 = off): rejected unmerged deltas of runs idle > N days are deleted with --yes
|
|
360
|
+
const dd = opts['delta-days'];
|
|
361
|
+
const deltaDays = dd === undefined ? DELTA_DAYS_DEFAULT : (dd === true ? DELTA_DAYS_DEFAULT : Number(dd));
|
|
362
|
+
if (!Number.isFinite(deltaDays) || deltaDays < 0) return { code: 2, out: `prune: --delta-days phải là số ngày ≥ 0 (nhận: ${dd})` };
|
|
290
363
|
const cut = cutoffOf(retro);
|
|
291
364
|
const { lines } = fs.existsSync(ctx.paths.events) ? eventLines(ctx) : { lines: [] };
|
|
292
365
|
const allEvents = core.readEvents(ctx);
|
|
293
|
-
const rp = planRuns(ctx, allEvents, cut.ms, retro, { now: typeof opts.now === 'number' ? opts.now : undefined });
|
|
366
|
+
const rp = planRuns(ctx, allEvents, cut.ms, retro, { now: typeof opts.now === 'number' ? opts.now : undefined, deltaDays });
|
|
294
367
|
const prunedTasks = new Set();
|
|
295
368
|
for (const r of rp.prune) for (const t of r.tasks) prunedTasks.add(t);
|
|
296
369
|
const plans = badPlans(ctx);
|
|
@@ -308,7 +381,7 @@ function cmdPrune(opts) {
|
|
|
308
381
|
|
|
309
382
|
const report = {
|
|
310
383
|
mode: yes ? 'apply' : 'dry-run', retro, cutoff: new Date(cut.ms).toISOString(), cutoff_source: cut.source,
|
|
311
|
-
runs: rp.prune.map((r) => ({ id: r.id, delete_files: r.del.length, delete_bytes: r.delete_bytes, keep: r.keep, keep_deltas: r.keep_deltas, archive_deltas: r.archive.length, keep_bytes: r.keep_bytes })),
|
|
384
|
+
runs: rp.prune.map((r) => ({ id: r.id, delete_files: r.del.length, delete_bytes: r.delete_bytes, keep: r.keep, keep_deltas: r.keep_deltas, archive_deltas: r.archive.length, drop_deltas: r.drop_deltas.length, keep_bytes: r.keep_bytes })),
|
|
312
385
|
runs_skipped: rp.skipped.length,
|
|
313
386
|
skipped_reasons: rp.skipped.reduce((a, s) => { const k = s.reason.replace(/task \S+/, 'task *').replace(/\(task \S+\)/, ''); a[k] = (a[k] || 0) + 1; return a; }, {}),
|
|
314
387
|
bad_plans: plans.map((p) => ({ name: p.name, bytes: p.bytes })),
|
|
@@ -317,6 +390,7 @@ function cmdPrune(opts) {
|
|
|
317
390
|
report.totals = {
|
|
318
391
|
run_dirs: rp.prune.length,
|
|
319
392
|
archive_deltas: rp.prune.reduce((n, r) => n + r.archive.length, 0),
|
|
393
|
+
drop_deltas: rp.prune.reduce((n, r) => n + r.drop_deltas.length, 0),
|
|
320
394
|
files: rp.prune.reduce((n, r) => n + r.del.length, 0),
|
|
321
395
|
bytes: rp.prune.reduce((n, r) => n + r.delete_bytes, 0) + plans.reduce((n, p) => n + p.bytes, 0),
|
|
322
396
|
};
|
|
@@ -402,7 +476,7 @@ function cmdPrune(opts) {
|
|
|
402
476
|
const L = [];
|
|
403
477
|
L.push(`prune ${yes ? '(ĐÃ THỰC HIỆN)' : '(dry-run — thêm --yes để thực hiện)'}: mốc cắt ${report.cutoff} (${cut.source}) từ ${retro}`);
|
|
404
478
|
L.push(`Run dir: ${rp.prune.length} sẽ dọn, ${rp.skipped.length} giữ nguyên (còn mở / sau mốc)`);
|
|
405
|
-
for (const r of rp.prune) L.push(` ${r.id}: xoá ${r.del.length} mục (${human(r.delete_bytes)}), giữ ${r.keep.length} (${human(r.keep_bytes)}), trong đó delta chưa gộp ${r.keep_deltas}${r.archive.length ? `; chuyển ${r.archive.length} delta >30 ngày chưa có verdict sang _archive/deltas/` : ''}`);
|
|
479
|
+
for (const r of rp.prune) L.push(` ${r.id}: xoá ${r.del.length} mục (${human(r.delete_bytes)}), giữ ${r.keep.length} (${human(r.keep_bytes)}), trong đó delta chưa gộp ${r.keep_deltas}${r.archive.length ? `; chuyển ${r.archive.length} delta >30 ngày chưa có verdict sang _archive/deltas/` : ''}${r.drop_deltas.length ? `; xoá ${r.drop_deltas.length} delta đã từ chối (run nằm yên >${deltaDays} ngày)` : ''}`);
|
|
406
480
|
L.push(`Plan hỏng: ${plans.length} (${human(plans.reduce((n, p) => n + p.bytes, 0))})${plans.length ? ` — ${plans.map((p) => p.name).join(', ')}` : ''}`);
|
|
407
481
|
const evTxt = yes
|
|
408
482
|
? `đã chuyển ${removedLines}/${lines.length} dòng vào archive và xoá khỏi events.jsonl (${freshCount} dòng ghi mới${freshCount ? ` vào archive/events-${archiveToday()}.jsonl.gz` : ''}; ${human(evBytes)})`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kienntpixon/dieu-phoi",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.4",
|
|
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>",
|
package/profile/lessons.md
CHANGED
|
@@ -20,7 +20,6 @@ Nhiều máy: file trong skill đã publish là bản đã duyệt (một nguồ
|
|
|
20
20
|
|
|
21
21
|
Câu hỏi bắt buộc hỏi ở cổng 1 cho từng loại task (retro thêm khi tỉ lệ steer cao). Định dạng: `- <task_type>: <câu hỏi>`
|
|
22
22
|
|
|
23
|
-
- *: (chỉ khi kế hoạch có ≥ 4 bước s≤6) Chọn mode? fast (bước s≤6 không verifier riêng, test chọn lọc, một verifier gộp chạy cả suite trước khi trình, tự nghiệm thu rồi bạn duyệt cuối) | normal (mặc định) | strict (luôn cả suite + kiểm ngẫu nhiên 1/3). Kế hoạch nhỏ hơn: dùng normal, không hỏi (retro 11-05: 46/46 lần chọn normal, fast 0 lần qua 2 retro). Sàn an toàn giữ ở mọi mode; đổi được giữa chừng.
|
|
24
23
|
- extract: Cần đúng những trường nào, đầu ra định dạng gì (bảng/JSON/cột Sheet), và khi các nguồn lệch nhau thì nguồn nào là chuẩn? (retro 11-05: steer_rate 0,3, n10)
|
|
25
24
|
|
|
26
25
|
## Bài học
|