kenaz 0.3.7 → 0.3.8
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/core/KENAZ_CORE_VERSION +1 -1
- package/lib/cli.js +31 -1
- package/lib/cli.test.js +26 -0
- package/package.json +6 -6
- package/core/k-cli/README.md +0 -330
- package/core/k-cli/package.json +0 -21
- package/core/k-cli/templates/k-cli.md +0 -91
- package/core/k-cli/test/TEST_SPEC.md +0 -117
- package/core/k-cli/test/run.sh +0 -403
package/core/KENAZ_CORE_VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.3.
|
|
1
|
+
0.3.8
|
package/lib/cli.js
CHANGED
|
@@ -14,7 +14,7 @@ const dash = require('./dashboard');
|
|
|
14
14
|
const PKG_ROOT = path.resolve(__dirname, '..');
|
|
15
15
|
const PKG = require('../package.json');
|
|
16
16
|
const MIN_NODE = 18;
|
|
17
|
-
const USAGE = 'usage: kenaz <install|uninstall|status|doctor|init|ygg join <key>|ygg status|ygg leave> [--dry-run] [--home <dir>] [--no-service] [--no-claude] [--no-codex] [--keep-dashboard] [--server-bin <path>]';
|
|
17
|
+
const USAGE = 'usage: kenaz <install|uninstall|status|doctor|init|ygg join <key>|ygg status|ygg leave|alaya status|alaya register> [--dry-run] [--home <dir>] [--no-service] [--no-claude] [--no-codex] [--keep-dashboard] [--server-bin <path>]';
|
|
18
18
|
|
|
19
19
|
function parseArgs(argv) {
|
|
20
20
|
const o = { cmd: argv[0], flags: {} };
|
|
@@ -279,7 +279,37 @@ function passthrough(argv, deps) {
|
|
|
279
279
|
return r.status === null ? 1 : r.status;
|
|
280
280
|
}
|
|
281
281
|
|
|
282
|
+
// TASK_2406: `kenaz alaya status|register` talks to the local kenaz-server. Register needs no login:
|
|
283
|
+
// the server registers by machine key (empty body).
|
|
284
|
+
function httpJson(method, port, urlPath, body) {
|
|
285
|
+
return new Promise((resolve) => {
|
|
286
|
+
const data = body === undefined ? null : JSON.stringify(body);
|
|
287
|
+
const req = http.request({ host: '127.0.0.1', port, path: urlPath, method, timeout: 30000,
|
|
288
|
+
headers: data ? { 'content-type': 'application/json', 'content-length': Buffer.byteLength(data) } : {} }, (res) => {
|
|
289
|
+
let b = ''; res.on('data', (d) => { b += d; });
|
|
290
|
+
res.on('end', () => { let j = null; try { j = JSON.parse(b); } catch (_) { /* non-JSON */ } resolve({ status: res.statusCode, json: j, text: b }); });
|
|
291
|
+
});
|
|
292
|
+
req.on('timeout', () => { req.destroy(); resolve({ status: 0, text: 'timeout' }); });
|
|
293
|
+
req.on('error', (e) => resolve({ status: 0, text: `${e.code || e.message}` }));
|
|
294
|
+
if (data) req.write(data);
|
|
295
|
+
req.end();
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
async function alaya(argv, out = console.log) {
|
|
300
|
+
const sub = argv[1];
|
|
301
|
+
if (sub !== 'status' && sub !== 'register') { out('usage: kenaz alaya <status|register>'); return 2; }
|
|
302
|
+
const port = process.env.KENAZ_HTTP_PORT || '18080';
|
|
303
|
+
const r = sub === 'status' ? await httpJson('GET', port, '/api/rust/alaya/status') : await httpJson('POST', port, '/api/rust/alaya/register', {});
|
|
304
|
+
if (!r.status) { out(`kenaz-server not reachable on 127.0.0.1:${port} (${r.text}); run \`kenaz doctor\``); return 1; }
|
|
305
|
+
if (r.status >= 400) { out(`alaya ${sub} failed (HTTP ${r.status}): ${(r.json && r.json.error) || r.text}`); return 1; }
|
|
306
|
+
const j = r.json || {};
|
|
307
|
+
out(sub === 'status' ? `alaya: registered=${j.registered} outbox_depth=${j.outbox_depth}` : `alaya: registered (already_registered=${!!j.already_registered}); queued reports will drain`);
|
|
308
|
+
return 0;
|
|
309
|
+
}
|
|
310
|
+
|
|
282
311
|
function run(argv, out = console.log, deps = {}) {
|
|
312
|
+
if (argv[0] === 'alaya') return alaya(argv, out);
|
|
283
313
|
if (argv[0] === 'ygg' || argv[0] === 'init') return passthrough(argv, deps);
|
|
284
314
|
const { cmd, flags } = parseArgs(argv);
|
|
285
315
|
const fn = { install, uninstall, status, doctor }[cmd];
|
package/lib/cli.test.js
CHANGED
|
@@ -58,6 +58,32 @@ test('kenaz ygg / init pass through to the core scripts with the remaining args'
|
|
|
58
58
|
assert.deepStrictEqual(calls[1].slice(1), ['init']);
|
|
59
59
|
});
|
|
60
60
|
|
|
61
|
+
test('kenaz alaya register posts an empty body (machine-key path); status reads the registered flag', async () => {
|
|
62
|
+
const http = require('http');
|
|
63
|
+
const seen = [];
|
|
64
|
+
const srv = http.createServer((req, res) => {
|
|
65
|
+
let b = ''; req.on('data', (d) => { b += d; });
|
|
66
|
+
req.on('end', () => {
|
|
67
|
+
seen.push([req.method, req.url, b]);
|
|
68
|
+
res.setHeader('content-type', 'application/json');
|
|
69
|
+
res.end(req.url.endsWith('status') ? '{"registered":true,"outbox_depth":0}' : '{"registered":true,"already_registered":false}');
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
await new Promise((r) => srv.listen(0, '127.0.0.1', r));
|
|
73
|
+
const old = process.env.KENAZ_HTTP_PORT; process.env.KENAZ_HTTP_PORT = String(srv.address().port);
|
|
74
|
+
try {
|
|
75
|
+
const lines = [];
|
|
76
|
+
assert.strictEqual(await run(['alaya', 'register'], (l) => lines.push(l)), 0);
|
|
77
|
+
assert.strictEqual(await run(['alaya', 'status'], (l) => lines.push(l)), 0);
|
|
78
|
+
assert.deepStrictEqual(seen[0], ['POST', '/api/rust/alaya/register', '{}']);
|
|
79
|
+
assert.match(lines[1], /registered=true outbox_depth=0/);
|
|
80
|
+
assert.strictEqual(await run(['alaya', 'bogus'], () => {}), 2);
|
|
81
|
+
} finally {
|
|
82
|
+
if (old === undefined) delete process.env.KENAZ_HTTP_PORT; else process.env.KENAZ_HTTP_PORT = old;
|
|
83
|
+
srv.close();
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
61
87
|
test('reinstall stops only the kenaz-server running from the managed path (exact exe match, by PID)', () => {
|
|
62
88
|
const { stopOwnServer } = require('./cli');
|
|
63
89
|
const killed = [];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kenaz",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.8",
|
|
4
4
|
"description": "Install Kenaz (plugin, hooks, kenaz-server) for Claude Code and Codex: npx kenaz install",
|
|
5
5
|
"bin": {
|
|
6
6
|
"kenaz": "bin/kenaz.js"
|
|
@@ -20,11 +20,11 @@
|
|
|
20
20
|
"test": "node --test lib/cli.test.js"
|
|
21
21
|
},
|
|
22
22
|
"optionalDependencies": {
|
|
23
|
-
"@kenazai/server-darwin-arm64": "0.3.
|
|
24
|
-
"@kenazai/server-darwin-x64": "0.3.
|
|
25
|
-
"@kenazai/server-linux-arm64": "0.3.
|
|
26
|
-
"@kenazai/server-linux-x64": "0.3.
|
|
27
|
-
"@kenazai/server-win32-x64": "0.3.
|
|
23
|
+
"@kenazai/server-darwin-arm64": "0.3.8",
|
|
24
|
+
"@kenazai/server-darwin-x64": "0.3.8",
|
|
25
|
+
"@kenazai/server-linux-arm64": "0.3.8",
|
|
26
|
+
"@kenazai/server-linux-x64": "0.3.8",
|
|
27
|
+
"@kenazai/server-win32-x64": "0.3.8"
|
|
28
28
|
},
|
|
29
29
|
"license": "UNLICENSED",
|
|
30
30
|
"repository": {
|
package/core/k-cli/README.md
DELETED
|
@@ -1,330 +0,0 @@
|
|
|
1
|
-
# k-cli
|
|
2
|
-
|
|
3
|
-
KenazAI 的獨立 CLI 工具 — 無需 Dashboard,直接操作 Specialist、Task、Dispatch、Project 等核心功能。
|
|
4
|
-
|
|
5
|
-
## 快速開始
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
cd k-cli && npm install && npm run build
|
|
9
|
-
|
|
10
|
-
# 1. 確認 CLI 可用
|
|
11
|
-
k-cli ping
|
|
12
|
-
|
|
13
|
-
# 2. 查看專案狀態
|
|
14
|
-
k-cli project status
|
|
15
|
-
|
|
16
|
-
# 3. 若尚未初始化(status 顯示 mode: unknown)→ 初始化(core-path 自動偵測)
|
|
17
|
-
k-cli project initialize
|
|
18
|
-
|
|
19
|
-
# 4. 查看使用指南(初始化時自動產生)
|
|
20
|
-
cat k-cli.md
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
**建議流程**:`ping` → `status` → (未初始化則 `initialize`)→ 正常使用
|
|
24
|
-
|
|
25
|
-
## 定位
|
|
26
|
-
|
|
27
|
-
| 比較 | Dashboard MCP (Rust) | mcp-server (Node,已於 TASK_2027 移除) | **k-cli** |
|
|
28
|
-
|------|---------------------|-------------------|-------------|
|
|
29
|
-
| 運行方式 | HTTP server(需 Dashboard) | stdio MCP server | **獨立 CLI** |
|
|
30
|
-
| 依賴 | Tauri app 運行中 | MCP client 呼叫 | **無依賴,直接執行** |
|
|
31
|
-
| 用途 | Claude Code 透過 `.mcp.json` 使用 | 已移除(TASK_2027) | **人類 + 腳本直接操作** |
|
|
32
|
-
|
|
33
|
-
## 核心價值
|
|
34
|
-
|
|
35
|
-
- **零依賴**:不需要 Dashboard 運行,直接呼叫底層工具
|
|
36
|
-
- **spawnClaude 架構**:寫入操作透過 `spawnClaude` 直接啟動 Claude CLI
|
|
37
|
-
- **dry-run 支援**:所有寫入命令支援 `--dry-run`,驗證但不執行
|
|
38
|
-
- **可腳本化**:所有輸出支援 `--json` 格式,方便 pipe 和自動化
|
|
39
|
-
- **明確偵測**:不猜測、不 fallback、不向上找目錄,偵測失敗就是失敗
|
|
40
|
-
|
|
41
|
-
## 設計原則
|
|
42
|
-
|
|
43
|
-
```
|
|
44
|
-
讀取操作 → 直接呼叫底層工具(快速、無副作用)
|
|
45
|
-
寫入操作 → spawnClaude 啟動 Claude CLI(Specialist 執行 / Ansuz 決策)
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
| 操作類型 | 命令 | 執行方式 |
|
|
49
|
-
|---------|------|---------|
|
|
50
|
-
| 讀取 | specialist list, task list, task get, project status... | 直接呼叫 |
|
|
51
|
-
| 寫入(任務) | task create | → spawnClaude + Ansuz(分析需求 → Amelia 審查 role → 建立) |
|
|
52
|
-
| 刪除 | task delete | → 直接 rm 檔案 |
|
|
53
|
-
| 執行 | specialist run | → spawnClaude + 指定 Specialist |
|
|
54
|
-
|
|
55
|
-
## 命令一覽
|
|
56
|
-
|
|
57
|
-
```
|
|
58
|
-
k-cli <command> [subcommand] [options]
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
### Specialist 命令
|
|
62
|
-
|
|
63
|
-
| 命令 | 說明 | 執行方式 |
|
|
64
|
-
|------|------|---------|
|
|
65
|
-
| `k-cli specialist list` | 列出所有 Specialist(名稱 + prompt) | 直接讀取 |
|
|
66
|
-
| `k-cli specialist run` | 啟動 Specialist 執行任務 | spawnClaude |
|
|
67
|
-
|
|
68
|
-
```bash
|
|
69
|
-
# 列出所有 Specialist 和 prompt
|
|
70
|
-
k-cli specialist list
|
|
71
|
-
|
|
72
|
-
# 啟動 Ansuz(positional args 作為 user prompt)
|
|
73
|
-
k-cli specialist run --agent Ansuz 幫我檢查 git 狀態
|
|
74
|
-
|
|
75
|
-
# 啟動 Amelia 協調多 Agent
|
|
76
|
-
k-cli specialist run --agent Amelia 執行 TASK_340
|
|
77
|
-
|
|
78
|
-
# dry-run:顯示會做什麼,不實際啟動
|
|
79
|
-
k-cli specialist run --agent Ansuz 分析需求 --dry-run
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
### Task 命令
|
|
83
|
-
|
|
84
|
-
| 命令 | 說明 | 執行方式 |
|
|
85
|
-
|------|------|---------|
|
|
86
|
-
| `k-cli task list` | 列出任務 | 直接讀取 |
|
|
87
|
-
| `k-cli task get <id>` | 取得任務詳情 | 直接讀取 |
|
|
88
|
-
| `k-cli task create <描述>` | 建立新任務(自然語言,人類用) | → spawnClaude + Ansuz + Amelia |
|
|
89
|
-
| `k-cli task submit --title ... ` | 建立新任務(結構化欄位,process/script 用,TASK_2147) | 直接呼叫 task-api.js,無 Claude 依賴 |
|
|
90
|
-
| `k-cli task delete <id>` | 刪除任務 | → 直接 rm 檔案 |
|
|
91
|
-
|
|
92
|
-
```bash
|
|
93
|
-
# 讀取(直接)
|
|
94
|
-
k-cli task list --status backlog
|
|
95
|
-
k-cli task get TASK_340
|
|
96
|
-
|
|
97
|
-
# 建立任務(語意描述 → Ansuz 分析 → Amelia 審查 role → 建立高品質任務)
|
|
98
|
-
k-cli task create 幫我創建 hello_world.js 輸出 Hello World
|
|
99
|
-
k-cli task create 實作登入 API 支援 OAuth2 --dry-run
|
|
100
|
-
|
|
101
|
-
# 刪除(直接 rm 檔案,不限狀態)
|
|
102
|
-
k-cli task delete TASK_340
|
|
103
|
-
k-cli task delete TASK_340 --dry-run
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
#### task submit(TASK_2147:子專案 process 用,無 Claude 依賴)
|
|
107
|
-
|
|
108
|
-
`task create` 的輕量版:不分析自然語言、不呼叫 Ansuz/Amelia,直接把旗標轉成
|
|
109
|
-
`task-api.js create` 呼叫。給已經知道自己要建什麼卡的 process/script 用(例如子專案裡的
|
|
110
|
-
in-app agent 把一個改良提案變成任務)。core 解析沿用 `resolveProject()`(讀
|
|
111
|
-
`.kenaz/project.yaml`),呼叫端不需要設定 `KENAZ_SOURCE_DIR`。
|
|
112
|
-
|
|
113
|
-
```bash
|
|
114
|
-
k-cli task submit \
|
|
115
|
-
--title "標題" --description "描述" --priority P3 --role developer \
|
|
116
|
-
--requirements "r1" --requirements "r2" --requirements "r3" \
|
|
117
|
-
--acceptance-criteria "a1" --acceptance-criteria "a2" --acceptance-criteria "a3" \
|
|
118
|
-
--technical-notes "n1" --technical-notes "n2" \
|
|
119
|
-
--origin "my-sub-project-agent" \
|
|
120
|
-
--project-path /path/to/subproject --json
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
- 任務一律落在 `backlog/`(正常 triage),**不會 auto-dispatch**。
|
|
124
|
-
- `--origin`(= `--source`)未指定時,預設為子專案名稱;用來標記任務來源方便追溯。
|
|
125
|
-
- 品質門檻(requirements/acceptance_criteria ≥ 3、technical_notes ≥ 2)與 `task create` 相同,不放寬。
|
|
126
|
-
- `--json` 會在建立後自動 `task get` 回完整任務 JSON(含 `id`/`source`/`status`),方便腳本直接讀回結果。
|
|
127
|
-
- `--dry-run` 顯示會執行的 task-api.js 指令,不實際建立。
|
|
128
|
-
|
|
129
|
-
#### task create 流程
|
|
130
|
-
|
|
131
|
-
```
|
|
132
|
-
k-cli task create "描述"
|
|
133
|
-
│
|
|
134
|
-
├─ k-cli 預先組裝 Ansuz + Amelia prompts(並行)
|
|
135
|
-
│
|
|
136
|
-
└→ spawnClaude(Ansuz) [allowedTools 含 Agent]
|
|
137
|
-
├─ Step 1: 分析需求(理解目標、搜尋相關代碼)
|
|
138
|
-
├─ Step 2: 整理任務草稿(結構化所有欄位)
|
|
139
|
-
├─ Step 3: Agent tool → Amelia subagent 審查 + 判斷 role
|
|
140
|
-
├─ Step 4: 採納建議 → task-api.js create(含正確 role)
|
|
141
|
-
└─ Step 5: 輸出結果摘要
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
> Amelia 審查透過 Claude 原生 Agent tool(subagent)執行,避免 nested CLI spawn。
|
|
145
|
-
|
|
146
|
-
### Dispatch 命令
|
|
147
|
-
|
|
148
|
-
| 命令 | 說明 | 執行方式 |
|
|
149
|
-
|------|------|---------|
|
|
150
|
-
| `k-cli dispatch status <taskId>` | 查看 dispatch 狀態 | 直接讀取 |
|
|
151
|
-
|
|
152
|
-
```bash
|
|
153
|
-
# 查看狀態(自動搜尋 .kenaz/logs/ 或 .formation/logs/)
|
|
154
|
-
k-cli dispatch status TASK_340 --tail 50
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
### Project 命令
|
|
158
|
-
|
|
159
|
-
| 命令 | 說明 | 執行方式 |
|
|
160
|
-
|------|------|---------|
|
|
161
|
-
| `k-cli project status` | 專案總覽 | 直接讀取 |
|
|
162
|
-
| `k-cli project initialize` | 初始化子專案 `.kenaz/` 結構 | 直接執行 |
|
|
163
|
-
| `k-cli project install <target>` | 安裝/更新模板檔案 | 直接執行 |
|
|
164
|
-
|
|
165
|
-
```bash
|
|
166
|
-
# 專案總覽
|
|
167
|
-
k-cli project status
|
|
168
|
-
|
|
169
|
-
# 初始化新子專案(core-path 自動從 CLI 位置推導)
|
|
170
|
-
k-cli project initialize --project-path /path/to/new-project
|
|
171
|
-
|
|
172
|
-
# 手動指定 core-path(覆蓋自動偵測)
|
|
173
|
-
k-cli project initialize --project-path /path/to/new-project --core-path /other/KenazAI
|
|
174
|
-
|
|
175
|
-
# 初始化但不 git init
|
|
176
|
-
k-cli project initialize --project-path /path/to/project --no-git
|
|
177
|
-
|
|
178
|
-
# dry-run:顯示會建立什麼
|
|
179
|
-
k-cli project initialize --project-path /path/to/project --dry-run
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
`project status` 輸出內容:
|
|
183
|
-
|
|
184
|
-
| 區塊 | 來源 | 資料 |
|
|
185
|
-
|------|------|------|
|
|
186
|
-
| 專案設定 | `.kenaz/project.yaml` | project_name、schema_version、core_path、tasks dir、logs dir |
|
|
187
|
-
| Git 狀態 | `git` | branch、最後 commit、dirty files 數量 |
|
|
188
|
-
| 任務統計 | `task-api.js stats` | backlog / active / done / blocked 數量、P0/P1 高亮 |
|
|
189
|
-
|
|
190
|
-
`project initialize` 建立內容:
|
|
191
|
-
|
|
192
|
-
| 項目 | 說明 |
|
|
193
|
-
|------|------|
|
|
194
|
-
| `.kenaz/project.yaml` | 專案配置(schema_version、project_name、core_path、路徑正規化為 `/`) |
|
|
195
|
-
| `.kenaz/tasks/{backlog,active,done,blocked,cancel}/` | 任務目錄 |
|
|
196
|
-
| `.kenaz/logs/{unit,solo,squad,legion,misc,legacy}/` | Formation log 目錄 |
|
|
197
|
-
| `.gitignore` | 標準 gitignore(預設啟用,`--no-git` 跳過) |
|
|
198
|
-
| `.gitattributes` | 行尾正規化(預設啟用,`--no-git` 跳過) |
|
|
199
|
-
| `k-cli.md` | 使用指南(可用 `project install k-cli` 更新) |
|
|
200
|
-
| `git init --initial-branch=main` | Git 初始化 + aliases + initial commit |
|
|
201
|
-
|
|
202
|
-
```bash
|
|
203
|
-
# 安裝/更新模板到現有專案
|
|
204
|
-
k-cli project install k-cli
|
|
205
|
-
|
|
206
|
-
# dry-run
|
|
207
|
-
k-cli project install k-cli --dry-run
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
### 通用命令
|
|
211
|
-
|
|
212
|
-
| 命令 | 說明 |
|
|
213
|
-
|------|------|
|
|
214
|
-
| `k-cli ping` | 健康檢查(顯示專案名稱、模式、core_path) |
|
|
215
|
-
|
|
216
|
-
## 全域選項
|
|
217
|
-
|
|
218
|
-
| Flag | 說明 |
|
|
219
|
-
|------|------|
|
|
220
|
-
| `--dry-run` | 驗證但不執行(適用所有寫入 / 調度命令) |
|
|
221
|
-
| `--json` | 輸出 JSON 格式 |
|
|
222
|
-
| `--project-path <path>` | 指定子專案路徑(使用 `.kenaz/project.yaml` 中的 `project_path` 值) |
|
|
223
|
-
| `--force` | 跳過品質檢查(僅 `task create`) |
|
|
224
|
-
| `--no-git` | 初始化時跳過 Git(僅 `project initialize`) |
|
|
225
|
-
| `--core-path <path>` | 覆蓋自動偵測的 core 路徑(僅 `project initialize`) |
|
|
226
|
-
| `--help`, `-h` | 顯示幫助 |
|
|
227
|
-
|
|
228
|
-
## 架構
|
|
229
|
-
|
|
230
|
-
```
|
|
231
|
-
k-cli/
|
|
232
|
-
├── README.md # 本文件
|
|
233
|
-
├── package.json # 依賴管理(deps: yaml)
|
|
234
|
-
├── tsconfig.json # TypeScript 配置
|
|
235
|
-
├── src/ # TypeScript 原始碼
|
|
236
|
-
│ ├── index.ts # 入口 + 命令路由 + flag 解析
|
|
237
|
-
│ ├── core/ # 共用基礎設施
|
|
238
|
-
│ │ ├── types.ts # ProjectInfo、CliFlags 型別定義
|
|
239
|
-
│ │ ├── project.ts # 專案偵測(core / sub-project / unknown)
|
|
240
|
-
│ │ ├── process.ts # nodeProcess — 統一 process spawner
|
|
241
|
-
│ │ ├── spawn.ts # spawnClaude — Claude CLI 直接啟動器
|
|
242
|
-
│ │ └── output.ts # 輸出格式化(table / JSON)
|
|
243
|
-
│ ├── specialist/ # Specialist 功能
|
|
244
|
-
│ │ ├── list.ts # 列出 Specialist + prompt(直接)
|
|
245
|
-
│ │ └── run.ts # 啟動 Specialist(spawnClaude)
|
|
246
|
-
│ ├── task/ # 任務管理功能
|
|
247
|
-
│ │ ├── list.ts # task list(直接 task-api.js)
|
|
248
|
-
│ │ ├── get.ts # task get(直接 task-api.js)
|
|
249
|
-
│ │ ├── create.ts # task create(spawnClaude + Ansuz + Amelia)
|
|
250
|
-
│ │ ├── submit.ts # task submit(直接 task-api.js,無 Claude 依賴,TASK_2147)
|
|
251
|
-
│ │ ├── update.ts # task update(直接 task-api.js)
|
|
252
|
-
│ │ └── delete.ts # task delete(直接 task-api.js)
|
|
253
|
-
│ ├── dispatch/ # Formation 調度功能
|
|
254
|
-
│ │ └── status.ts # dispatch status(直接讀 log)
|
|
255
|
-
│ └── project/ # 專案管理功能
|
|
256
|
-
│ ├── status.ts # project status(含 project.yaml 詳情)
|
|
257
|
-
│ ├── initialize.ts # project initialize(建立 .kenaz/ + Git)
|
|
258
|
-
│ └── install.ts # project install(安裝/更新模板)
|
|
259
|
-
├── templates/ # 可安裝的模板檔案
|
|
260
|
-
│ └── k-cli.md # k-cli 使用指南
|
|
261
|
-
└── dist/ # 編譯輸出(npm run build)
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
### 內部呼叫關係
|
|
265
|
-
|
|
266
|
-
```
|
|
267
|
-
k-cli
|
|
268
|
-
│
|
|
269
|
-
├─ 直接呼叫(讀取)
|
|
270
|
-
│ ├─ specialist/list → fs.readdir(.specialist/) + session.js
|
|
271
|
-
│ ├─ task/list, get → .collaboration/core/task-api.js
|
|
272
|
-
│ ├─ task/delete → 直接 rm YAML 檔案(不限狀態)
|
|
273
|
-
│ ├─ dispatch/status → .kenaz/logs/ 或 .formation/logs/ + log-reader.js
|
|
274
|
-
│ └─ project/status → .kenaz/project.yaml + task-api.js stats + git commands
|
|
275
|
-
│
|
|
276
|
-
├─ spawnClaude(Ansuz 決策)
|
|
277
|
-
│ ├─ task/create → Ansuz 分析需求
|
|
278
|
-
│ │ → Agent tool subagent(Amelia 審查 + role)
|
|
279
|
-
│ │ → task-api.js create(最終建立)
|
|
280
|
-
│
|
|
281
|
-
└─ spawnClaude(Specialist 直接執行)
|
|
282
|
-
└─ specialist/run → session.js(組裝 prompt)+ spawnClaude
|
|
283
|
-
```
|
|
284
|
-
|
|
285
|
-
### spawnClaude 架構
|
|
286
|
-
|
|
287
|
-
`core/spawn.ts` 是 k-cli 的核心,直接啟動 Claude CLI 子進程:
|
|
288
|
-
|
|
289
|
-
```
|
|
290
|
-
spawnClaude(opts)
|
|
291
|
-
├─ detectClaudeCmd() → 偵測 claude CLI 路徑(Windows: .cmd / .exe)
|
|
292
|
-
├─ getClaudeEnv() → 清理環境變數(刪除 CLAUDECODE 避免嵌套檢測)
|
|
293
|
-
│ └─ detectGitBash() → Windows: 偵測 git-bash 路徑
|
|
294
|
-
└─ spawn(claude, args) → stdin pipe prompt, stream-json 輸出
|
|
295
|
-
```
|
|
296
|
-
|
|
297
|
-
## Dry-Run 行為
|
|
298
|
-
|
|
299
|
-
`--dry-run` 適用於所有有副作用的命令:
|
|
300
|
-
|
|
301
|
-
| 命令 | dry-run 行為 |
|
|
302
|
-
|------|-------------|
|
|
303
|
-
| `specialist run` | 組裝 prompt → 顯示 agent + prompt 長度 → **不 spawn Claude** |
|
|
304
|
-
| `task create` | 組裝 Ansuz prompt → 顯示將建立的任務資訊 → **不啟動 Ansuz** |
|
|
305
|
-
| `task update` | 組裝 task-api args → 顯示將更新的內容 → **不呼叫 task-api** |
|
|
306
|
-
| `task delete` | 組裝 task-api args → 顯示將刪除的任務 → **不呼叫 task-api** |
|
|
307
|
-
|
|
308
|
-
## 專案偵測邏輯
|
|
309
|
-
|
|
310
|
-
`resolveProject` 使用明確偵測,不猜測、不 fallback、不向上搜尋目錄:
|
|
311
|
-
|
|
312
|
-
| 偵測條件 | 模式 | core_path |
|
|
313
|
-
|---------|------|-----------|
|
|
314
|
-
| 有 `.collaboration/tasks/` | `core` | 當前目錄 |
|
|
315
|
-
| 有 `.kenaz/project.yaml`(無 `.collaboration/tasks/`) | `sub-project` | yaml 中的 `core_path` 欄位 |
|
|
316
|
-
| 以上都沒有 | `unknown` | 當前目錄 |
|
|
317
|
-
|
|
318
|
-
## 安裝 & 執行
|
|
319
|
-
|
|
320
|
-
```bash
|
|
321
|
-
# 編譯
|
|
322
|
-
cd k-cli && npm install && npm run build
|
|
323
|
-
|
|
324
|
-
# 執行
|
|
325
|
-
node dist/index.js ping
|
|
326
|
-
node dist/index.js project status
|
|
327
|
-
|
|
328
|
-
# 建議建立 alias
|
|
329
|
-
alias k-cli="node /path/to/KenazAI/k-cli/dist/index.js"
|
|
330
|
-
```
|
package/core/k-cli/package.json
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "k-cli",
|
|
3
|
-
"version": "0.1.0",
|
|
4
|
-
"description": "KenazAI CLI tool — Specialist, Task, Dispatch operations",
|
|
5
|
-
"main": "dist/index.js",
|
|
6
|
-
"bin": {
|
|
7
|
-
"k-cli": "dist/index.js"
|
|
8
|
-
},
|
|
9
|
-
"scripts": {
|
|
10
|
-
"build": "tsc",
|
|
11
|
-
"start": "node dist/index.js",
|
|
12
|
-
"test": "bash test/run.sh"
|
|
13
|
-
},
|
|
14
|
-
"dependencies": {
|
|
15
|
-
"yaml": "^2.7.0"
|
|
16
|
-
},
|
|
17
|
-
"devDependencies": {
|
|
18
|
-
"@types/node": "^22.0.0",
|
|
19
|
-
"typescript": "^5.9.3"
|
|
20
|
-
}
|
|
21
|
-
}
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
# k-cli 使用指南
|
|
2
|
-
|
|
3
|
-
本專案由 KenazAI k-cli 初始化管理。
|
|
4
|
-
|
|
5
|
-
## 常用命令
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
# 健康檢查
|
|
9
|
-
k-cli ping
|
|
10
|
-
|
|
11
|
-
# 專案狀態
|
|
12
|
-
k-cli project status
|
|
13
|
-
|
|
14
|
-
# 任務管理(讀取)
|
|
15
|
-
k-cli task list
|
|
16
|
-
k-cli task list --status backlog
|
|
17
|
-
k-cli task get TASK_XXX
|
|
18
|
-
|
|
19
|
-
# 建立任務(Ansuz 分析 → Amelia subagent 審查 role → 建立)
|
|
20
|
-
k-cli task create 幫我創建 hello_world.js 輸出 Hello World
|
|
21
|
-
k-cli task create 實作登入 API 支援 OAuth2
|
|
22
|
-
|
|
23
|
-
# 刪除任務(直接 rm,不限狀態)
|
|
24
|
-
k-cli task delete TASK_XXX
|
|
25
|
-
|
|
26
|
-
# Specialist(positional args 作為 user prompt)
|
|
27
|
-
k-cli specialist list
|
|
28
|
-
k-cli specialist run --agent Ansuz 幫我檢查 git 狀態
|
|
29
|
-
k-cli specialist run --agent Amelia 執行 TASK_XXX
|
|
30
|
-
|
|
31
|
-
# Dispatch — 指定任務(讀 role → 判斷陣型 → headless.js 執行)
|
|
32
|
-
k-cli dispatch run TASK_XXX
|
|
33
|
-
|
|
34
|
-
# Dispatch — 自動掃描(掃 backlog → 按優先級逐個 dispatch)
|
|
35
|
-
k-cli dispatch run --auto
|
|
36
|
-
|
|
37
|
-
# 查看 dispatch 狀態
|
|
38
|
-
k-cli dispatch status TASK_XXX --tail 50
|
|
39
|
-
|
|
40
|
-
# 專案初始化(新子專案)
|
|
41
|
-
k-cli project initialize
|
|
42
|
-
k-cli project initialize --project-path /path/to/project --no-git
|
|
43
|
-
|
|
44
|
-
# 安裝/更新模板
|
|
45
|
-
k-cli project install k-cli
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
## Dispatch 流程
|
|
49
|
-
|
|
50
|
-
```
|
|
51
|
-
k-cli dispatch run TASK_XXX
|
|
52
|
-
└→ Ansuz 讀 role → 判斷陣型 → headless.js
|
|
53
|
-
headless.js 自動處理:
|
|
54
|
-
├─ auto-claim(backlog → active)
|
|
55
|
-
├─ 依賴預檢
|
|
56
|
-
├─ agent 執行任務
|
|
57
|
-
├─ task complete
|
|
58
|
-
└─ post-complete(archive + git branch + merge to user branch)
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
Git merge 到 main 需要用戶手動操作。
|
|
62
|
-
|
|
63
|
-
## Task Create 流程
|
|
64
|
-
|
|
65
|
-
```
|
|
66
|
-
k-cli task create "描述"
|
|
67
|
-
├─ k-cli 預先組裝 Ansuz + Amelia prompts
|
|
68
|
-
└→ spawnClaude(Ansuz)
|
|
69
|
-
├─ 分析需求 + 整理任務草稿
|
|
70
|
-
├─ Agent tool → Amelia subagent 審查 + 判斷 role
|
|
71
|
-
└─ task-api.js create(建立任務)
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
Amelia 審查透過 Claude 原生 Agent tool(subagent)執行,無需嵌套 CLI。
|
|
75
|
-
|
|
76
|
-
## 全域選項
|
|
77
|
-
|
|
78
|
-
| Flag | 說明 |
|
|
79
|
-
|------|------|
|
|
80
|
-
| `--json` | JSON 輸出 |
|
|
81
|
-
| `--dry-run` | 驗證但不執行 |
|
|
82
|
-
| `--auto` | 自動掃描模式(僅 dispatch run) |
|
|
83
|
-
| `--force` | 跳過品質檢查(僅 task create) |
|
|
84
|
-
| `--project-path <path>` | 指定專案路徑(使用 `.kenaz/project.yaml` 中的 `project_path` 值) |
|
|
85
|
-
| `--help` | 顯示幫助 |
|
|
86
|
-
|
|
87
|
-
## 更新此文件
|
|
88
|
-
|
|
89
|
-
```bash
|
|
90
|
-
k-cli project install k-cli
|
|
91
|
-
```
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
# k-cli Test Specification
|
|
2
|
-
|
|
3
|
-
## 概述
|
|
4
|
-
|
|
5
|
-
Build 後在 `test/sandbox/` 建立真實專案環境,跑完所有 k-cli 功能。
|
|
6
|
-
每次 `npm test` 自動:清空 sandbox → 初始化 → 逐項測試 → 報告結果。
|
|
7
|
-
|
|
8
|
-
## 執行方式
|
|
9
|
-
|
|
10
|
-
```bash
|
|
11
|
-
cd k-cli
|
|
12
|
-
npm run build && npm test
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
## 前置條件
|
|
16
|
-
|
|
17
|
-
- Claude CLI 可用(`task create` 會 spawnClaude)
|
|
18
|
-
- `npm run build` 必須先執行
|
|
19
|
-
|
|
20
|
-
## 測試環境
|
|
21
|
-
|
|
22
|
-
- **Sandbox**: `test/sandbox/`(每次測試前清空重建)
|
|
23
|
-
- **Core Path**: KenazAI 本體(`k-cli/../`)
|
|
24
|
-
- **路徑參數**: 使用 `.kenaz/project.yaml` 中的 `project_path`(絕對路徑)
|
|
25
|
-
|
|
26
|
-
## 11 個測試
|
|
27
|
-
|
|
28
|
-
### Test 1: project initialize → verify → delete k-cli.md → verify → install
|
|
29
|
-
- `k-cli project initialize --project-path test/sandbox/ --no-git`
|
|
30
|
-
- 驗證:`.kenaz/project.yaml` 存在、`tasks/` 5 個狀態目錄存在、`logs/` 6 個陣型目錄存在、`k-cli.md` 存在
|
|
31
|
-
- 刪除 `test/sandbox/k-cli.md`
|
|
32
|
-
- 驗證:`k-cli.md` 不存在
|
|
33
|
-
- `k-cli project install k-cli --project-path test/sandbox/`
|
|
34
|
-
- 驗證:`k-cli.md` 重新出現、內容含 `k-cli`
|
|
35
|
-
|
|
36
|
-
### Test 2: ping
|
|
37
|
-
- `k-cli ping --project-path test/sandbox/`
|
|
38
|
-
- 驗證:輸出含 `k-cli` 且含 `sub-project`
|
|
39
|
-
|
|
40
|
-
### Test 3: project status
|
|
41
|
-
- `k-cli project status --project-path test/sandbox/ --json`
|
|
42
|
-
- 驗證:JSON 解析成功、含 `sub-project`
|
|
43
|
-
|
|
44
|
-
### Test 4: specialist list
|
|
45
|
-
- `k-cli specialist list --project-path test/sandbox/`
|
|
46
|
-
- 驗證:輸出含 `Ansuz` 且含 `Amelia`
|
|
47
|
-
|
|
48
|
-
### Test 5: task create × 3 → list → get
|
|
49
|
-
建立 3 個簡單任務,驗證 Ansuz + Amelia subagent 流程:
|
|
50
|
-
|
|
51
|
-
1. `k-cli task create "寫一個 Node.js 程式,console.log 輸出 Hello World" --project-path test/sandbox/`
|
|
52
|
-
- 驗證:輸出含 TASK_ID(記為 TASK_A)
|
|
53
|
-
2. `k-cli task create "寫一個 Node.js 程式,計算 10+10 並輸出結果" --project-path test/sandbox/`
|
|
54
|
-
- 驗證:輸出含 TASK_ID(記為 TASK_B)
|
|
55
|
-
3. `k-cli task create "寫一個 Node.js 程式,讀取當前目錄的檔案列表並印出" --project-path test/sandbox/`
|
|
56
|
-
- 驗證:輸出含 TASK_ID(記為 TASK_C)
|
|
57
|
-
|
|
58
|
-
流程驗證(每個 create 都會經過):
|
|
59
|
-
- spawnClaude(Ansuz) → 分析需求 → Agent tool subagent(Amelia) 審查 + 判斷 role → task-api.js create
|
|
60
|
-
|
|
61
|
-
建立後驗證:
|
|
62
|
-
- `k-cli task list --project-path test/sandbox/`
|
|
63
|
-
- 驗證:輸出含 TASK_A、TASK_B、TASK_C,總數 ≥ 3
|
|
64
|
-
- `k-cli task get TASK_A --project-path test/sandbox/`
|
|
65
|
-
- 驗證:輸出含 TASK_A 詳細資訊
|
|
66
|
-
|
|
67
|
-
### Test 6-8: 已移除
|
|
68
|
-
- `dispatch run` 已刪除(Formation headless 調度已退役),`dispatch status` 失去可驗證的前置動作,一併移出測試
|
|
69
|
-
|
|
70
|
-
### Test 9: task delete → verify
|
|
71
|
-
- 使用 Test 5 的 TASK_A
|
|
72
|
-
- `k-cli task get TASK_A` → 驗證任務存在
|
|
73
|
-
- `k-cli task delete TASK_A --project-path test/sandbox/`(直接 rm,不限狀態)
|
|
74
|
-
- `k-cli task list --project-path test/sandbox/`
|
|
75
|
-
- 驗證:TASK_A 不在列表中
|
|
76
|
-
|
|
77
|
-
### Test 10: specialist run Amelia(簡單任務)
|
|
78
|
-
- `k-cli specialist run --agent Amelia 列出 sandbox 專案的任務狀態 --project-path test/sandbox/`
|
|
79
|
-
- 驗證:spawnClaude(Amelia) 執行成功,輸出含任務相關資訊
|
|
80
|
-
|
|
81
|
-
### Test 11: --dry-run(全指令驗證)
|
|
82
|
-
不實際執行,僅驗證 dry-run 輸出正確:
|
|
83
|
-
|
|
84
|
-
1. `k-cli project initialize --project-path test/sandbox/ --dry-run`
|
|
85
|
-
- 驗證:輸出含 `dry-run`
|
|
86
|
-
2. `k-cli task create "測試 dry-run" --project-path test/sandbox/ --dry-run`
|
|
87
|
-
- 驗證:輸出含 `dry-run`,不呼叫 spawnClaude
|
|
88
|
-
3. `k-cli task delete TASK_B --project-path test/sandbox/ --dry-run`
|
|
89
|
-
- 驗證:輸出含 `dry-run`,任務仍存在
|
|
90
|
-
4. `k-cli specialist run --agent Amelia 測試 --project-path test/sandbox/ --dry-run`
|
|
91
|
-
- 驗證:輸出含 `dry-run`,不呼叫 spawnClaude
|
|
92
|
-
5. `k-cli project install k-cli --project-path test/sandbox/ --dry-run`
|
|
93
|
-
- 驗證:輸出含 `dry-run`
|
|
94
|
-
|
|
95
|
-
## 結果格式
|
|
96
|
-
|
|
97
|
-
```
|
|
98
|
-
k-cli Test Suite
|
|
99
|
-
════════════════════════════════
|
|
100
|
-
[ 1/11] project init+install ✅ PASS
|
|
101
|
-
[ 2/11] ping ✅ PASS
|
|
102
|
-
[ 3/11] project status ✅ PASS
|
|
103
|
-
[ 4/11] specialist list ✅ PASS
|
|
104
|
-
[ 5/11] task create × 3 ✅ PASS
|
|
105
|
-
[ 9/11] task delete ✅ PASS
|
|
106
|
-
[10/11] specialist run Amelia ✅ PASS
|
|
107
|
-
[11/11] --dry-run ✅ PASS
|
|
108
|
-
|
|
109
|
-
Result: 11/11 passed
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
失敗時顯示:
|
|
113
|
-
```
|
|
114
|
-
[ 5/11] task create × 3 ❌ FAIL
|
|
115
|
-
Expected output to contain "TASK_"
|
|
116
|
-
Got: "Error: ..."
|
|
117
|
-
```
|
package/core/k-cli/test/run.sh
DELETED
|
@@ -1,403 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# k-cli Test Runner
|
|
3
|
-
# Usage: cd k-cli && bash test/run.sh
|
|
4
|
-
|
|
5
|
-
set -uo pipefail
|
|
6
|
-
|
|
7
|
-
# ─── Paths ───
|
|
8
|
-
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
9
|
-
KCLI_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
10
|
-
CORE_DIR="$(cd "$KCLI_DIR/.." && pwd)"
|
|
11
|
-
SANDBOX="$SCRIPT_DIR/sandbox"
|
|
12
|
-
KCLI="node $KCLI_DIR/dist/index.js"
|
|
13
|
-
|
|
14
|
-
# ─── Log ───
|
|
15
|
-
LOG_FILE="$SCRIPT_DIR/test-report-$(date +%Y%m%d-%H%M%S).log"
|
|
16
|
-
|
|
17
|
-
log() {
|
|
18
|
-
echo "$@" >> "$LOG_FILE"
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
log_separator() {
|
|
22
|
-
log ""
|
|
23
|
-
log "═══════════════════════════════════════════════════════════════"
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
# Run a k-cli command: log the command + full output, capture in LOG_CMD_OUTPUT
|
|
27
|
-
run_cmd() {
|
|
28
|
-
local cmd_display="$*"
|
|
29
|
-
log " ▶ $cmd_display"
|
|
30
|
-
LOG_CMD_OUTPUT=$("$@" 2>&1) || true
|
|
31
|
-
if [ -n "$LOG_CMD_OUTPUT" ]; then
|
|
32
|
-
echo "$LOG_CMD_OUTPUT" >> "$LOG_FILE"
|
|
33
|
-
else
|
|
34
|
-
log " (no output)"
|
|
35
|
-
fi
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
# ─── State ───
|
|
39
|
-
PASS=0
|
|
40
|
-
FAIL=0
|
|
41
|
-
TOTAL=11
|
|
42
|
-
|
|
43
|
-
# ─── Helpers ───
|
|
44
|
-
cleanup() {
|
|
45
|
-
rm -rf "$SANDBOX"
|
|
46
|
-
mkdir -p "$SANDBOX"
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
assert_contains() {
|
|
50
|
-
local label="$1" output="$2" expected="$3"
|
|
51
|
-
if echo "$output" | grep -qi "$expected"; then
|
|
52
|
-
log " ✓ assert_contains($label): found \"$expected\""
|
|
53
|
-
return 0
|
|
54
|
-
else
|
|
55
|
-
log " ✗ assert_contains($label): NOT found \"$expected\""
|
|
56
|
-
log " Got: $(echo "$output" | head -3)"
|
|
57
|
-
echo " Expected output to contain \"$expected\""
|
|
58
|
-
echo " Got: $(echo "$output" | head -3)"
|
|
59
|
-
return 1
|
|
60
|
-
fi
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
assert_file_exists() {
|
|
64
|
-
local filepath="$1"
|
|
65
|
-
if [ -f "$filepath" ]; then
|
|
66
|
-
log " ✓ file exists: $filepath"
|
|
67
|
-
return 0
|
|
68
|
-
else
|
|
69
|
-
log " ✗ file NOT found: $filepath"
|
|
70
|
-
echo " File not found: $filepath"
|
|
71
|
-
return 1
|
|
72
|
-
fi
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
assert_file_not_exists() {
|
|
76
|
-
local filepath="$1"
|
|
77
|
-
if [ ! -f "$filepath" ]; then
|
|
78
|
-
log " ✓ file not exists (expected): $filepath"
|
|
79
|
-
return 0
|
|
80
|
-
else
|
|
81
|
-
log " ✗ file should not exist: $filepath"
|
|
82
|
-
echo " File should not exist: $filepath"
|
|
83
|
-
return 1
|
|
84
|
-
fi
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
run_test() {
|
|
88
|
-
local num="$1" name="$2"
|
|
89
|
-
shift 2
|
|
90
|
-
local label
|
|
91
|
-
label=$(printf "[%2d/%d] %s" "$num" "$TOTAL" "$name")
|
|
92
|
-
printf "[%2d/%d] %-24s " "$num" "$TOTAL" "$name"
|
|
93
|
-
log_separator
|
|
94
|
-
log "TEST $label"
|
|
95
|
-
log " Started: $(date '+%Y-%m-%d %H:%M:%S')"
|
|
96
|
-
log ""
|
|
97
|
-
if "$@"; then
|
|
98
|
-
echo "PASS"
|
|
99
|
-
log ""
|
|
100
|
-
log " ▸ Result: PASS"
|
|
101
|
-
PASS=$((PASS + 1))
|
|
102
|
-
else
|
|
103
|
-
echo "FAIL"
|
|
104
|
-
log ""
|
|
105
|
-
log " ▸ Result: FAIL"
|
|
106
|
-
FAIL=$((FAIL + 1))
|
|
107
|
-
fi
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
# ─── Setup ───
|
|
111
|
-
echo ""
|
|
112
|
-
echo "k-cli Test Suite"
|
|
113
|
-
echo "========================================"
|
|
114
|
-
echo "Core: $CORE_DIR"
|
|
115
|
-
echo "k-cli: $KCLI_DIR"
|
|
116
|
-
echo "Sandbox: $SANDBOX"
|
|
117
|
-
echo "Log: $LOG_FILE"
|
|
118
|
-
echo ""
|
|
119
|
-
|
|
120
|
-
log "k-cli Test Report"
|
|
121
|
-
log "Generated: $(date '+%Y-%m-%d %H:%M:%S')"
|
|
122
|
-
log "Core: $CORE_DIR"
|
|
123
|
-
log "k-cli: $KCLI_DIR"
|
|
124
|
-
log "Sandbox: $SANDBOX"
|
|
125
|
-
|
|
126
|
-
# Build before testing
|
|
127
|
-
echo "Building k-cli..."
|
|
128
|
-
(cd "$KCLI_DIR" && npm run build 2>&1) || {
|
|
129
|
-
echo "ERROR: Build failed."
|
|
130
|
-
exit 1
|
|
131
|
-
}
|
|
132
|
-
echo "Build OK"
|
|
133
|
-
echo ""
|
|
134
|
-
|
|
135
|
-
cleanup
|
|
136
|
-
|
|
137
|
-
# =========================================
|
|
138
|
-
# Test 1: project initialize + install
|
|
139
|
-
# =========================================
|
|
140
|
-
test_1_init_install() {
|
|
141
|
-
log " --- Step: project initialize ---"
|
|
142
|
-
run_cmd $KCLI project initialize --project-path "$SANDBOX"
|
|
143
|
-
local output="$LOG_CMD_OUTPUT"
|
|
144
|
-
|
|
145
|
-
# Verify .kenaz/project.yaml
|
|
146
|
-
assert_file_exists "$SANDBOX/.kenaz/project.yaml" || return 1
|
|
147
|
-
|
|
148
|
-
# Verify task status dirs
|
|
149
|
-
for status in backlog active done blocked cancel; do
|
|
150
|
-
[ -d "$SANDBOX/.kenaz/tasks/$status" ] || { log " ✗ .kenaz/tasks/$status/ not found"; echo " .kenaz/tasks/$status/ not found"; return 1; }
|
|
151
|
-
log " ✓ dir exists: .kenaz/tasks/$status/"
|
|
152
|
-
done
|
|
153
|
-
|
|
154
|
-
# Verify log formation dirs
|
|
155
|
-
for formation in unit solo squad legion misc legacy; do
|
|
156
|
-
[ -d "$SANDBOX/.kenaz/logs/$formation" ] || { log " ✗ .kenaz/logs/$formation/ not found"; echo " .kenaz/logs/$formation/ not found"; return 1; }
|
|
157
|
-
log " ✓ dir exists: .kenaz/logs/$formation/"
|
|
158
|
-
done
|
|
159
|
-
|
|
160
|
-
# Verify k-cli.md was created
|
|
161
|
-
assert_file_exists "$SANDBOX/k-cli.md" || return 1
|
|
162
|
-
|
|
163
|
-
# Delete k-cli.md
|
|
164
|
-
log " --- Step: delete k-cli.md ---"
|
|
165
|
-
rm -f "$SANDBOX/k-cli.md"
|
|
166
|
-
assert_file_not_exists "$SANDBOX/k-cli.md" || return 1
|
|
167
|
-
|
|
168
|
-
# Reinstall via project install
|
|
169
|
-
log " --- Step: project install k-cli ---"
|
|
170
|
-
run_cmd $KCLI project install k-cli --project-path "$SANDBOX"
|
|
171
|
-
output="$LOG_CMD_OUTPUT"
|
|
172
|
-
|
|
173
|
-
# Verify k-cli.md restored
|
|
174
|
-
assert_file_exists "$SANDBOX/k-cli.md" || return 1
|
|
175
|
-
|
|
176
|
-
# Verify content
|
|
177
|
-
local content
|
|
178
|
-
content=$(cat "$SANDBOX/k-cli.md")
|
|
179
|
-
assert_contains "k-cli.md content" "$content" "k-cli" || return 1
|
|
180
|
-
|
|
181
|
-
return 0
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
# =========================================
|
|
185
|
-
# Test 2: ping
|
|
186
|
-
# =========================================
|
|
187
|
-
test_2_ping() {
|
|
188
|
-
run_cmd $KCLI ping --project-path "$SANDBOX"
|
|
189
|
-
local output="$LOG_CMD_OUTPUT"
|
|
190
|
-
assert_contains "ping" "$output" "k-cli" || return 1
|
|
191
|
-
assert_contains "ping" "$output" "sub-project" || return 1
|
|
192
|
-
return 0
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
# =========================================
|
|
196
|
-
# Test 3: project status
|
|
197
|
-
# =========================================
|
|
198
|
-
test_3_project_status() {
|
|
199
|
-
run_cmd $KCLI project status --project-path "$SANDBOX" --json
|
|
200
|
-
local output="$LOG_CMD_OUTPUT"
|
|
201
|
-
|
|
202
|
-
# Must be valid JSON
|
|
203
|
-
echo "$output" | node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{JSON.parse(d);process.exit(0)}catch(e){process.exit(1)}})" 2>/dev/null || {
|
|
204
|
-
log " ✗ Output is not valid JSON"
|
|
205
|
-
echo " Output is not valid JSON"
|
|
206
|
-
echo " Got: $(echo "$output" | head -3)"
|
|
207
|
-
return 1
|
|
208
|
-
}
|
|
209
|
-
log " ✓ valid JSON output"
|
|
210
|
-
|
|
211
|
-
assert_contains "status" "$output" "sub-project" || return 1
|
|
212
|
-
return 0
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
# =========================================
|
|
216
|
-
# Test 4: specialist list
|
|
217
|
-
# =========================================
|
|
218
|
-
test_4_specialist_list() {
|
|
219
|
-
run_cmd $KCLI specialist list --project-path "$SANDBOX"
|
|
220
|
-
local output="$LOG_CMD_OUTPUT"
|
|
221
|
-
assert_contains "specialist" "$output" "Ansuz" || return 1
|
|
222
|
-
assert_contains "specialist" "$output" "Amelia" || return 1
|
|
223
|
-
return 0
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
# =========================================
|
|
227
|
-
# Test 5: task create x3 -> list -> get
|
|
228
|
-
# =========================================
|
|
229
|
-
test_5_task_create() {
|
|
230
|
-
local task_id
|
|
231
|
-
|
|
232
|
-
# Task A: Hello World
|
|
233
|
-
log " --- Step: task create A (Hello World) ---"
|
|
234
|
-
run_cmd $KCLI task create "write a Node.js script that console.log outputs Hello World" \
|
|
235
|
-
--project-path "$SANDBOX"
|
|
236
|
-
local output="$LOG_CMD_OUTPUT"
|
|
237
|
-
task_id=$(echo "$output" | grep -oE "TASK_[0-9]+" | head -1)
|
|
238
|
-
[ -n "$task_id" ] || { log " ✗ Could not extract TASK_A ID"; echo " Could not extract TASK_A ID"; return 1; }
|
|
239
|
-
echo "$task_id" > "$SANDBOX/.test_task_a"
|
|
240
|
-
log " ✓ TASK_A = $task_id"
|
|
241
|
-
|
|
242
|
-
# Task B: 10+10
|
|
243
|
-
log " --- Step: task create B (10+10) ---"
|
|
244
|
-
run_cmd $KCLI task create "write a Node.js script that calculates 10+10 and prints the result" \
|
|
245
|
-
--project-path "$SANDBOX"
|
|
246
|
-
output="$LOG_CMD_OUTPUT"
|
|
247
|
-
task_id=$(echo "$output" | grep -oE "TASK_[0-9]+" | head -1)
|
|
248
|
-
[ -n "$task_id" ] || { log " ✗ Could not extract TASK_B ID"; echo " Could not extract TASK_B ID"; return 1; }
|
|
249
|
-
echo "$task_id" > "$SANDBOX/.test_task_b"
|
|
250
|
-
log " ✓ TASK_B = $task_id"
|
|
251
|
-
|
|
252
|
-
# Task C: list files
|
|
253
|
-
log " --- Step: task create C (list files) ---"
|
|
254
|
-
run_cmd $KCLI task create "write a Node.js script that reads and prints the file list of current directory" \
|
|
255
|
-
--project-path "$SANDBOX"
|
|
256
|
-
output="$LOG_CMD_OUTPUT"
|
|
257
|
-
task_id=$(echo "$output" | grep -oE "TASK_[0-9]+" | head -1)
|
|
258
|
-
[ -n "$task_id" ] || { log " ✗ Could not extract TASK_C ID"; echo " Could not extract TASK_C ID"; return 1; }
|
|
259
|
-
echo "$task_id" > "$SANDBOX/.test_task_c"
|
|
260
|
-
log " ✓ TASK_C = $task_id"
|
|
261
|
-
|
|
262
|
-
# Verify list contains all 3
|
|
263
|
-
log " --- Step: task list ---"
|
|
264
|
-
run_cmd $KCLI task list --project-path "$SANDBOX"
|
|
265
|
-
local list_output="$LOG_CMD_OUTPUT"
|
|
266
|
-
local ta tb tc
|
|
267
|
-
ta=$(cat "$SANDBOX/.test_task_a")
|
|
268
|
-
tb=$(cat "$SANDBOX/.test_task_b")
|
|
269
|
-
tc=$(cat "$SANDBOX/.test_task_c")
|
|
270
|
-
assert_contains "list" "$list_output" "$ta" || return 1
|
|
271
|
-
assert_contains "list" "$list_output" "$tb" || return 1
|
|
272
|
-
assert_contains "list" "$list_output" "$tc" || return 1
|
|
273
|
-
|
|
274
|
-
# Verify get TASK_A
|
|
275
|
-
log " --- Step: task get $ta ---"
|
|
276
|
-
run_cmd $KCLI task get "$ta" --project-path "$SANDBOX"
|
|
277
|
-
local get_output="$LOG_CMD_OUTPUT"
|
|
278
|
-
assert_contains "get" "$get_output" "$ta" || return 1
|
|
279
|
-
|
|
280
|
-
return 0
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
# =========================================
|
|
284
|
-
# Test 9: task delete -> verify
|
|
285
|
-
# =========================================
|
|
286
|
-
test_9_task_delete() {
|
|
287
|
-
local task_id
|
|
288
|
-
task_id=$(cat "$SANDBOX/.test_task_a" 2>/dev/null) || { echo " No TASK_A from test 5"; return 1; }
|
|
289
|
-
log " Target: $task_id"
|
|
290
|
-
|
|
291
|
-
# Verify via get — task should exist
|
|
292
|
-
log " --- Step: task get (before delete) ---"
|
|
293
|
-
run_cmd $KCLI task get "$task_id" --project-path "$SANDBOX"
|
|
294
|
-
local output="$LOG_CMD_OUTPUT"
|
|
295
|
-
assert_contains "get before delete" "$output" "$task_id" || return 1
|
|
296
|
-
|
|
297
|
-
# Delete (direct rm, no status restriction)
|
|
298
|
-
log " --- Step: task delete ---"
|
|
299
|
-
run_cmd $KCLI task delete "$task_id" --project-path "$SANDBOX"
|
|
300
|
-
output="$LOG_CMD_OUTPUT"
|
|
301
|
-
assert_contains "delete output" "$output" "$task_id\|Deleted\|delet" || return 1
|
|
302
|
-
|
|
303
|
-
# Verify deleted: task should not appear in list
|
|
304
|
-
log " --- Step: task list (after delete) ---"
|
|
305
|
-
run_cmd $KCLI task list --project-path "$SANDBOX"
|
|
306
|
-
local list_output="$LOG_CMD_OUTPUT"
|
|
307
|
-
if echo "$list_output" | grep -q "$task_id"; then
|
|
308
|
-
log " ✗ Task $task_id still in list after delete"
|
|
309
|
-
echo " Task $task_id still in list after delete"
|
|
310
|
-
return 1
|
|
311
|
-
fi
|
|
312
|
-
log " ✓ Task $task_id no longer in list"
|
|
313
|
-
return 0
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
# =========================================
|
|
317
|
-
# Test 10: specialist run Amelia
|
|
318
|
-
# =========================================
|
|
319
|
-
test_10_specialist_run() {
|
|
320
|
-
run_cmd $KCLI specialist run --agent Amelia "list task status in sandbox project" \
|
|
321
|
-
--project-path "$SANDBOX"
|
|
322
|
-
local output="$LOG_CMD_OUTPUT"
|
|
323
|
-
|
|
324
|
-
# Should produce some output (not empty)
|
|
325
|
-
[ -n "$output" ] || { log " ✗ specialist run produced no output"; echo " specialist run produced no output"; return 1; }
|
|
326
|
-
log " ✓ specialist run produced output (${#output} chars)"
|
|
327
|
-
return 0
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
# =========================================
|
|
331
|
-
# Test 11: --dry-run (all commands)
|
|
332
|
-
# =========================================
|
|
333
|
-
test_11_dry_run() {
|
|
334
|
-
local output
|
|
335
|
-
|
|
336
|
-
# 1. project initialize --dry-run (use fresh temp dir since sandbox is already initialized)
|
|
337
|
-
log " --- dry-run: project initialize ---"
|
|
338
|
-
local dryrun_tmp="$SANDBOX/_dryrun_tmp"
|
|
339
|
-
mkdir -p "$dryrun_tmp"
|
|
340
|
-
run_cmd $KCLI project initialize --project-path "$dryrun_tmp" --dry-run
|
|
341
|
-
output="$LOG_CMD_OUTPUT"
|
|
342
|
-
rm -rf "$dryrun_tmp"
|
|
343
|
-
assert_contains "init dry-run" "$output" "dry-run\|dry.run\|dryRun" || return 1
|
|
344
|
-
|
|
345
|
-
# 2. task create --dry-run
|
|
346
|
-
log " --- dry-run: task create ---"
|
|
347
|
-
run_cmd $KCLI task create "dry-run test" --project-path "$SANDBOX" --dry-run
|
|
348
|
-
output="$LOG_CMD_OUTPUT"
|
|
349
|
-
assert_contains "create dry-run" "$output" "dry-run\|dry.run\|dryRun" || return 1
|
|
350
|
-
|
|
351
|
-
# 3. task delete --dry-run
|
|
352
|
-
log " --- dry-run: task delete ---"
|
|
353
|
-
local tb
|
|
354
|
-
tb=$(cat "$SANDBOX/.test_task_b" 2>/dev/null) || tb="TASK_999"
|
|
355
|
-
run_cmd $KCLI task delete "$tb" --project-path "$SANDBOX" --dry-run
|
|
356
|
-
output="$LOG_CMD_OUTPUT"
|
|
357
|
-
assert_contains "delete dry-run" "$output" "dry-run\|dry.run\|dryRun" || return 1
|
|
358
|
-
|
|
359
|
-
log " --- dry-run: specialist run ---"
|
|
360
|
-
run_cmd $KCLI specialist run --agent Amelia "test" --project-path "$SANDBOX" --dry-run
|
|
361
|
-
output="$LOG_CMD_OUTPUT"
|
|
362
|
-
assert_contains "specialist dry-run" "$output" "dry-run\|dry.run\|dryRun" || return 1
|
|
363
|
-
|
|
364
|
-
# 4. project install --dry-run
|
|
365
|
-
log " --- dry-run: project install ---"
|
|
366
|
-
run_cmd $KCLI project install k-cli --project-path "$SANDBOX" --dry-run
|
|
367
|
-
output="$LOG_CMD_OUTPUT"
|
|
368
|
-
assert_contains "install dry-run" "$output" "dry-run\|dry.run\|dryRun" || return 1
|
|
369
|
-
|
|
370
|
-
return 0
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
# =========================================
|
|
374
|
-
# Run All Tests
|
|
375
|
-
# =========================================
|
|
376
|
-
run_test 1 "project init+install" test_1_init_install
|
|
377
|
-
run_test 2 "ping" test_2_ping
|
|
378
|
-
run_test 3 "project status" test_3_project_status
|
|
379
|
-
run_test 4 "specialist list" test_4_specialist_list
|
|
380
|
-
run_test 5 "task create x3" test_5_task_create
|
|
381
|
-
run_test 9 "task delete" test_9_task_delete
|
|
382
|
-
run_test 10 "specialist run Amelia" test_10_specialist_run
|
|
383
|
-
run_test 11 "--dry-run" test_11_dry_run
|
|
384
|
-
|
|
385
|
-
# =========================================
|
|
386
|
-
# Summary
|
|
387
|
-
# =========================================
|
|
388
|
-
echo ""
|
|
389
|
-
echo "========================================"
|
|
390
|
-
echo "Result: $PASS/$TOTAL passed"
|
|
391
|
-
echo "Log: $LOG_FILE"
|
|
392
|
-
|
|
393
|
-
log_separator
|
|
394
|
-
log "SUMMARY: $PASS/$TOTAL passed"
|
|
395
|
-
log "Finished: $(date '+%Y-%m-%d %H:%M:%S')"
|
|
396
|
-
|
|
397
|
-
if [ $FAIL -gt 0 ]; then
|
|
398
|
-
echo "WARNING: $FAIL test(s) failed"
|
|
399
|
-
exit 1
|
|
400
|
-
else
|
|
401
|
-
echo "All tests passed"
|
|
402
|
-
exit 0
|
|
403
|
-
fi
|