@kienntpixon/dieu-phoi 0.0.0-stage → 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 kiennt (PixOn)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,102 @@
1
- # Temporary Holding Version
1
+ # @kienntpixon/dieu-phoi
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Hook + CLI cho skill **dieu-phoi**: ghi lại mỗi lần Claude Code giao việc cho subagent (model, effort, token, chi phí, thời gian, kết quả nghiệm thu) rồi dùng chính log đó để đề xuất model + effort rẻ nhất mà vẫn qua nghiệm thu.
4
+
5
+ - Node ≥ 18, không có dependency nào ngoài thư viện chuẩn của Node.
6
+ - Không ghi nội dung prompt, đầu ra hay đường dẫn file. Chỉ có số đo và nhãn.
7
+ - Hook không gọi mạng, không in ra stdout, luôn thoát mã 0.
8
+
9
+ ## Cài đặt
10
+
11
+ Khuyến nghị cài toàn cục để skill gọi được `dieu-phoi …` trực tiếp:
12
+
13
+ ```bash
14
+ npm i -g @kienntpixon/dieu-phoi && dieu-phoi setup --dept ai-lab
15
+ # có kho dữ liệu chung của phòng:
16
+ dieu-phoi setup --dept ai-lab --sink-url https://script.google.com/macros/s/…/exec --sink-token dp_…
17
+ ```
18
+
19
+ Không cài toàn cục được thì dùng `npx -y @kienntpixon/dieu-phoi <lệnh>` thay cho `dieu-phoi <lệnh>` (chậm hơn vì phải tải gói).
20
+
21
+ `setup` sẽ:
22
+
23
+ 1. chép `hook.cjs` + `pricing.json` vào `~/.claude/dieu-phoi/bin/`;
24
+ 2. sao lưu `~/.claude/settings.json` thành `settings.json.bak-dieu-phoi-<thời gian>` rồi đăng ký `PostToolUse` (matcher `Agent`) và `SubagentStop` chạy `"<đường dẫn node>" "<…>/dieu-phoi/bin/hook.cjs"` (node tuyệt đối, không phụ thuộc PATH). Các hook và khoá khác giữ nguyên, kể cả mảng/đối tượng rỗng. Chạy lại khi hook đã đúng thì không ghi file, không tạo bản sao lưu. Đổi phiên bản node (nvm…) thì chạy lại `setup`;
25
+ 3. ghi `~/.claude/dieu-phoi/config.json` (phòng ban, sink, đường dẫn skill, mã băm người dùng). Thư mục skill tự dò: `<claude>/skills/dieu-phoi`, hoặc thư mục `<claude>/skills/*` có `profile/profile.json` và `SKILL.md` mang `name: dieu-phoi` (fork phòng ban đổi tên thư mục vẫn tìm được).
26
+
27
+ Khởi động lại Claude Code sau khi cài. Kiểm tra bằng `dieu-phoi doctor` (chạy thử chính lệnh hook đã đăng ký).
28
+
29
+ `CLAUDE_CONFIG_DIR` được tôn trọng (thay cho `~/.claude`). Thử trên thư mục tạm: thêm `--claude-dir <thư mục>` vào mọi lệnh.
30
+
31
+ ## Lệnh
32
+
33
+ | Lệnh | Việc |
34
+ |---|---|
35
+ | `setup [--dept D] [--sink-url U --sink-token T] [--skill-dir P]` | cài hook, đăng ký trong settings, ghi config |
36
+ | `doctor [--json]` | kiểm node, hook, settings, config, profile, sink |
37
+ | `verdict <agent_id\|last> <pass\|fail\|timeout> [--verify tests\|lint\|review\|judgment\|none] [--reason short-kebab]` | nghiệm thu tầng agent (`by=agent`). `last` = agent hoàn thành gần nhất; từ chối (liệt kê ứng viên) nếu trong 30 phút qua có > 1 agent xong mà chưa có verdict. Được viết tắt agent_id nếu không trùng |
38
+ | `verdict --task <task_id> <accept\|reject> [--cause quality\|scope] [--round n] [--reason R]` | nghiệm thu của **người dùng** (`by=user`). `reject` bắt buộc `--cause`: `quality` = làm sai (tính là fail), `scope` = yêu cầu đổi/thiếu (không tính cho model). `--round` mặc định tự tăng |
39
+ | `steer <agent_id> \| --task <task_id> --kind minor\|plan\|redirect [--reason R]` | ghi **một** lần người dùng lái giữa chừng (một `steer_id`). `--task` chỉ đánh dấu agent đang chạy của task; không có agent nào đang chạy → bản ghi cấp task. `plan`/`redirect` trước verdict loại lượt đó khỏi tỉ lệ của tier (chi phí vẫn tính); steer không xoá fail đã ghi |
40
+ | `stats [--type T] [--json]` | các ô `task_type × model/effort` theo "Thước đo" của `CONTRACT.md`: n, n_excluded, accept_rate, agent_pass_rate, chi phí/thời gian mỗi task (TB, p50, p75); bảng steer theo task_type |
41
+ | `choose --type T --score N [--urgency U] [--reversible y\|n] [--verifiable y\|n] [--role R] [--task ID] [--override M/E \| --approved M/E] [--skill-dir P] [--seed S] [--json]` | đề xuất model + effort kèm lý do, `action`, `role`, ước tính chi phí/thời gian. `--override` cần `--task` |
42
+ | `plan-table --file <steps.json> [--json]` | bảng phân công tiếng Việt (Bước \| Model/effort \| Lý do \| Ước tính) + tổng chi phí + thời gian đường găng. Bước lỗi (override sai) hiện \"từ chối: …\", các bước khác vẫn hiện |
43
+ | `sync [--json]` | đẩy bản ghi chưa đồng bộ lên sink, lô ≤ 200, có thử lại. Bản ghi sink từ chối → `data/sync-rejected.jsonl`, `doctor` báo số lượng |
44
+ | `retro [--json]` | tóm tắt cho vòng retro: số liệu từng ô, đếm `reason_code`, danh sách loại task |
45
+ | `uninstall [--purge]` | gỡ hook khỏi settings (có sao lưu); `--purge` xoá cả `hook.cjs`. Dữ liệu giữ lại |
46
+
47
+ ### `choose`
48
+
49
+ 1. **Luật an toàn** (cố định trong core): trần `opus/high`, không Fable. Sàn: score ≥ 9 → opus/high; `--verifiable n` → tối thiểu opus/medium; `--reversible n` → tối thiểu opus/medium (role exec). Score ≥ 9, `--verifiable n`, `--reversible n` hoặc `urgent` → không thăm dò xuống tier thấp. Sàn được ghi rõ trong `reason`.
50
+ 2. **Leo thang** với `--task <task_id>`: fail 1 lần → cùng tier (làm lại kèm lỗi); fail 2 lần cùng tier → lên một tier; tier kế vượt opus/medium (role exec) → **không tự lập lại kế hoạch**, trả `action: "ask_user"` kèm `fails` và `tried` để hỏi người dùng. Fail = agent `fail|timeout` hoặc người dùng `reject --cause quality`. Lượt bị steer `plan`/`redirect` không tính.
51
+ 3. **Prior** từ `<skill-dir>/profile/profile.json` (không thấy thì dùng bản sao dựng sẵn của `skill/profile/profile.json` v1.0.0). `profile_source` trong kết quả cho biết đang dùng file nào (`builtin` hoặc đường dẫn).
52
+ 4. **Thompson sampling** khi ô của prior có ≥ 5 task đã xong + có verdict: rút p ~ Beta(1+pass, 1+fail) cho từng tier đủ dữ liệu, điểm = E[cost]/p + λ·E[time_phút]/p (urgent dùng p75 thời gian), chọn điểm thấp nhất. Tier có p75 vượt `time_cap_min` của loại task bị loại.
53
+ 5. **Thăm dò** một tier thấp hơn với xác suất `explore_rate` (mặc định 0.1) khi luật an toàn cho phép.
54
+
55
+ Thang tier: `haiku/low < haiku/medium < haiku/high < sonnet/medium < sonnet/high < opus/medium < opus/high`.
56
+
57
+ Kết quả `--json`: `{"action":"dispatch","model":"sonnet","model_id":"claude-sonnet-5-5","effort":"high","reason":"…","explored":false,"tier":"sonnet/high","budget_s":900,"est_cost_usd":0.2,"est_time_min":6,"est_source":"rough","profile_source":"builtin"}`. `model` là tên ngắn dùng thẳng cho tham số `model` của Agent tool; `budget_s` = 2 × trung vị thời gian của ô (chưa đủ dữ liệu thì lấy `time_cap_min`). `--seed` cho kết quả lặp lại được.
58
+
59
+ **Thước đo học** (chuẩn chung với sink, xem `CONTRACT.md` và `fixtures/parity-*.json`): mẫu = task, tính cho tier của lượt exec đầu; thành công = verdict người dùng đầu tiên (accept / reject-quality), chưa có thì verdict agent đầu của lượt đó; `reject --cause scope` và lượt đầu bị steer plan/redirect bị loại. Trường `pass1` đã bỏ, dùng `accept_rate`.
60
+
61
+ **Override:** `--override opus/medium --task <id>` trả đúng tier đó (vẫn phải ≥ sàn an toàn, dưới sàn → từ chối) và ghi sự kiện `override` khi khác đề xuất. Mỗi task tính tối đa một override; nếu ≥ 3 override gần nhất cùng hướng và ≥ 3/5 chọn cùng một tier, prior của loại đó đổi sang tier ấy (lý do ghi "theo override của người dùng").
62
+
63
+ **Tier đã duyệt:** `--approved sonnet/high` (tier người dùng chốt ở bảng phân công) trả đúng tier đó, không thăm dò/Thompson, không ghi override; sàn an toàn hoặc leo thang buộc cao hơn → trả tier cao hơn với `deviation: true`. Khi leo thang tới mức hỏi người dùng, `action: "ask_user"` thắng cả `--override` lẫn `--approved`.
64
+
65
+ **Ước tính:** ô có ≥ 5 task → trung vị chi phí và thời gian một lượt (`est_source: "data"`). Chưa đủ → ước lượng thô (`"rough"`), giả định mỗi lượt (input không cache gồm ~44k system prompt, output gồm thinking), giá theo `pricing.json`:
66
+
67
+ | Tier | input | output | phút | ≈ USD |
68
+ |---|---|---|---|---|
69
+ | haiku/low | 50k | 2k | 1 | 0.006 |
70
+ | haiku/medium | 50k | 4k | 2 | 0.007 |
71
+ | haiku/high | 55k | 6k | 3 | 0.009 |
72
+ | sonnet/medium | 55k | 6k | 4 | 0.17 |
73
+ | sonnet/high | 60k | 8k | 6 | 0.20 |
74
+ | opus/medium | 60k | 8k | 8 | 0.40 |
75
+ | opus/high | 80k | 12k | 12 | 0.56 |
76
+
77
+ ### `plan-table`
78
+
79
+ Đầu vào: mảng JSON `[{"step":"A","type":"search","score":2},{"step":"B","type":"feature-impl","score":6,"depends_on":["A"],"verifiable":"n"}]`. Mỗi bước chạy qua `choose`; bước không có `depends_on` chạy song song. Đầu ra: bảng markdown để trình người dùng duyệt trước khi giao, kèm tổng chi phí và thời gian đường găng; `--json` cho máy đọc.
80
+
81
+ ## Dữ liệu
82
+
83
+ - Sự kiện: `~/.claude/dieu-phoi/data/events.jsonl` (`dispatch`, `complete`, `verdict`, `steer`, `override`; `schema: 1`), xem `CONTRACT.md`. File chỉ ghi nối (append-only), không bao giờ viết lại. Tiến độ đẩy lên sink nằm ở `data/sync-offset.json` (byte offset sau dòng cuối đã đẩy); trạng thái segment từng agent ở `data/segments/<agent_id>.json`.
84
+ - Lỗi của hook: `~/.claude/dieu-phoi/hook-errors.log`.
85
+ - Người dùng và phiên chỉ lưu dạng sha256 rút gọn 8 ký tự (người dùng = `git config user.email`, không có thì tên tài khoản máy).
86
+ - Chi phí tính theo giá API trong `pricing.json` (USD / 1M token), đếm usage một lần cho mỗi `requestId`. Hệ số ghi cache (×1.25 cho 5 phút, ×2 cho 1 giờ) là **giả định**. Model không có giá → `cost_usd: null`. Ghi đè giá trong `config.json`:
87
+
88
+ ```json
89
+ { "pricing": { "models": { "claude-sonnet-5-5": { "input": 2, "output": 10, "cache_read": 0.2 } } } }
90
+ ```
91
+
92
+ - Có sink thì hook tự chạy `sync` ngầm (tách hẳn tiến trình) tối đa 10 phút một lần. Không có sink thì mọi thứ chỉ nằm trên máy.
93
+
94
+ ## Phát triển
95
+
96
+ ```bash
97
+ cd cli
98
+ npm test # node:test, không cần cài gì
99
+ npm pack --dry-run # kiểm danh sách file phát hành
100
+ ```
101
+
102
+ Giấy phép MIT.
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const { parseArgs } = require('../lib/args.cjs');
5
+ const core = require('../hook.cjs');
6
+
7
+ const HELP = `dieu-phoi ${core.CORE_VERSION} — log subagent Claude Code + chọn model/effort
8
+
9
+ Cách dùng: dieu-phoi <lệnh> [tuỳ chọn]
10
+
11
+ setup [--dept D] [--sink-url U --sink-token T] [--skill-dir P]
12
+ cài hook vào ~/.claude/dieu-phoi/bin, đăng ký trong settings.json
13
+ doctor kiểm hook, node, config, sink
14
+ verdict <agent_id|last> <pass|fail|timeout> [--verify tests|lint|review|judgment|none] [--reason short-kebab]
15
+ nghiệm thu tầng agent (by=agent)
16
+ verdict --task <task_id> <accept|reject> [--cause quality|scope] [--round n] [--reason R]
17
+ nghiệm thu của người dùng (by=user); reject bắt buộc --cause
18
+ steer <agent_id> --kind minor|plan|redirect [--reason R]
19
+ steer --task <task_id> --kind minor|plan|redirect [--reason R]
20
+ ghi một lần người dùng lái giữa chừng (--task: chỉ các agent đang chạy của task)
21
+ stats [--type T] [--json]
22
+ bảng task_type × model/effort: n, accept%, agent_pass%, chi phí/thời gian (TB, p50, p75), steer
23
+ choose --type T --score N [--urgency relaxed|normal|urgent] [--reversible y|n]
24
+ [--verifiable y|n] [--role exec|verify|plan] [--task ID]
25
+ [--override model/effort (cần --task) | --approved model/effort] [--skill-dir P] [--seed S] [--json]
26
+ đề xuất model + effort, lý do, action (dispatch|ask_user), ước tính chi phí/thời gian
27
+ plan-table --file <steps.json> [--json]
28
+ bảng phân công: Bước | Model/effort | Lý do | Ước tính + tổng + đường găng
29
+ sync đẩy bản ghi chưa đồng bộ lên sink
30
+ retro [--json] tóm tắt cho vòng retro (không chứa nội dung)
31
+ uninstall [--purge] gỡ hook khỏi settings.json (--purge xoá cả hook.cjs; dữ liệu giữ lại)
32
+ hook (nội bộ) đọc payload hook từ stdin
33
+
34
+ Tuỳ chọn chung: --claude-dir <dir> (mặc định $CLAUDE_CONFIG_DIR hoặc ~/.claude), --json, --help, --version
35
+ `;
36
+
37
+ async function main(argv) {
38
+ const opts = parseArgs(argv);
39
+ const cmd = opts._.shift();
40
+ if (opts.version || opts.v || cmd === 'version') {
41
+ process.stdout.write(`${core.CORE_VERSION}\n`);
42
+ return 0;
43
+ }
44
+ if (!cmd || opts.help || opts.h || cmd === 'help') {
45
+ process.stdout.write(HELP);
46
+ return cmd || opts.help || opts.h ? 0 : 1;
47
+ }
48
+ if (cmd === 'hook') {
49
+ // Hook mode: never print, always exit 0.
50
+ try { require('../lib/commands.cjs').cmdHook(opts); } catch (e) { core.logError(null, e, 'cli-hook'); }
51
+ return 0;
52
+ }
53
+ const c = require('../lib/commands.cjs');
54
+ const table = {
55
+ setup: c.cmdSetup,
56
+ doctor: c.cmdDoctor,
57
+ verdict: c.cmdVerdict,
58
+ steer: c.cmdSteer,
59
+ 'plan-table': c.cmdPlanTable,
60
+ stats: c.cmdStats,
61
+ choose: c.cmdChoose,
62
+ sync: c.cmdSync,
63
+ retro: c.cmdRetro,
64
+ uninstall: c.cmdUninstall,
65
+ };
66
+ const fn = table[cmd];
67
+ if (!fn) {
68
+ process.stderr.write(`Lệnh lạ: ${cmd}\n\n${HELP}`);
69
+ return 1;
70
+ }
71
+ const res = await fn(opts);
72
+ if (res && res.out) process.stdout.write(`${res.out}\n`);
73
+ return res ? res.code : 0;
74
+ }
75
+
76
+ main(process.argv.slice(2)).then(
77
+ (code) => { process.exitCode = code; },
78
+ (e) => {
79
+ process.stderr.write(`dieu-phoi: ${e && e.message ? e.message : e}\n`);
80
+ process.exitCode = 1;
81
+ },
82
+ );