kenaz 0.3.5 → 0.3.7
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/.claude/commands/Ansuz.md +1 -0
- package/core/.collaboration/core/reconcile.js +193 -0
- package/core/.collaboration/core/task-2404-reconcile.test.js +135 -0
- package/core/.collaboration/core/task-api.js +27 -1
- package/core/.formation/native/claude/README.md +1 -1
- package/core/.formation/role-map.yaml +3 -3
- package/core/.formation/spec/task_operations.yaml +3 -3
- package/core/.formation/tools/__tests__/subagent-bifrost-guard.test.js +4 -4
- package/core/.formation/tools/dispatch.js +5 -5
- package/core/.formation/tools/prompt.js +4 -0
- package/core/.formation/unit/do.yaml +2 -4
- package/core/.rule/general/agent-workflow.yaml +4 -4
- package/core/.rule/general/file-size-threshold.yaml +16 -4
- package/core/.rule/general/rust-verify-target.yaml +0 -1
- package/core/.rule/general/system-invariants.yaml +8 -25
- package/core/.specialist/Ansuz/SYSTEM_DOC.md +17 -42
- package/core/.specialist/Ansuz/capability.yaml +7 -14
- package/core/.specialist/Ansuz/concepts/formation_quick_ref.yaml +4 -11
- package/core/.specialist/Ansuz/elixir.yaml +3 -4
- package/core/.specialist/Ansuz/identity_contract.yaml +1 -1
- package/core/.specialist/Ansuz/knowledge.yaml +7 -10
- package/core/.specialist/Ansuz/rule.yaml +2 -2
- package/core/.specialist/Ansuz/tools/verify-and-land.js +22 -0
- package/core/.specialist/Ansuz/tools/verify-and-land.test.js +59 -5
- package/core/.specialist/Ansuz/workflow.yaml +7 -11
- package/core/.specialist/Forseti/core.yaml +1 -1
- package/core/.specialist/Heimdall/SKILL.md +1 -1
- package/core/.specialist/Heimdall/core.yaml +2 -2
- package/core/.specialist/Huginn/rule.yaml +1 -1
- package/core/.specialist/tools/README.md +2 -2
- package/core/.specialist/tools/ansuz-boot-fast.js +20 -0
- package/core/.system/collaboration/README.md +1 -1
- package/core/.system/formation/LEGACY_SKILLS.md +2 -0
- package/core/.system/formation/POST_COMPLETE.md +2 -0
- package/core/.system/formation/README.md +2 -0
- package/core/KENAZ_CORE_VERSION +1 -1
- package/core/dev/scripts/check-file-size.js +44 -17
- package/core/k-cli/README.md +1 -94
- package/core/k-cli/test/TEST_SPEC.md +5 -22
- package/core/k-cli/test/run.sh +1 -67
- package/core/plugins/kenaz/commands/Ansuz.md +1 -0
- package/package.json +6 -6
- package/core/.formation/legion/forge.yaml +0 -299
- package/core/.formation/legion/hive.yaml +0 -238
- package/core/.formation/squad/blitz.yaml +0 -192
- package/core/.formation/squad/strategy-tribunal.yaml +0 -354
- package/core/.formation/squad/strategy.yaml +0 -349
- package/core/.formation/squad/sweep.yaml +0 -208
|
@@ -211,7 +211,7 @@ workflow:
|
|
|
211
211
|
- "✅ Always write post_complete_verdict: RECORDED to the task YAML when a post-task score exists"
|
|
212
212
|
pre_task_snapshot:
|
|
213
213
|
description: "Health baseline captured before Formation dispatch"
|
|
214
|
-
written_by: "
|
|
214
|
+
written_by: "post-complete.js (first run when no baseline exists)"
|
|
215
215
|
path: ".kenaz/health_snapshots/{task_id}_before.json"
|
|
216
216
|
schema: "{ score: number, grade: string, timestamp: ISO-8601 }"
|
|
217
217
|
|
|
@@ -233,7 +233,7 @@ degradation:
|
|
|
233
233
|
# ═══════════════════════════════════════
|
|
234
234
|
invocation:
|
|
235
235
|
via_formation:
|
|
236
|
-
command: "node .formation/tools/
|
|
236
|
+
command: "node .formation/tools/dispatch.js --skill=do --task=HEIMDALL_SCAN --project-path={path} --dispatch-mode=subagent"
|
|
237
237
|
context_required:
|
|
238
238
|
- "trigger: attention | commit | scheduled | manual"
|
|
239
239
|
- "project_path: /absolute/path/to/project"
|
|
@@ -50,7 +50,7 @@ deny:
|
|
|
50
50
|
- "寫程式碼或修改 src/ 下任何檔案"
|
|
51
51
|
- "修改其他 specialist 的檔案"
|
|
52
52
|
- "建立 .collaboration/tasks/(那是 Ansuz 的事)"
|
|
53
|
-
- "使用 .formation/tools/
|
|
53
|
+
- "使用 .formation/tools/dispatch.js --dispatch-mode=subagent 調度任何任務"
|
|
54
54
|
- "呼叫 hippocampus_episode_start/append/close"
|
|
55
55
|
- "修改 .rule/ 下任何規則(安全邊界 — Huginn 不能改自己的規則)"
|
|
56
56
|
- "建立 proposal(Prefrontal 提案系統已移除 — 只寫 huginn_advisory.jsonl)"
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
| 模式 | 說明 | 用途 |
|
|
21
21
|
|------|------|------|
|
|
22
|
-
| **assembled**(預設) | 將所有 YAML 內容內嵌到 prompt |
|
|
22
|
+
| **assembled**(預設) | 將所有 YAML 內容內嵌到 prompt | dispatch.js、API、Dashboard |
|
|
23
23
|
| **reference** | 回傳檔案讀取指令(同 slash command) | Claude Code 內互動 |
|
|
24
24
|
|
|
25
25
|
### CLI 用法
|
|
@@ -79,7 +79,7 @@ const active = session.getActiveSessions();
|
|
|
79
79
|
|
|
80
80
|
### 整合範例
|
|
81
81
|
|
|
82
|
-
**Formation
|
|
82
|
+
**Formation dispatch.js**:
|
|
83
83
|
```js
|
|
84
84
|
const { buildPrompt } = require('../.specialist/tools/session');
|
|
85
85
|
const prompt = buildPrompt('Ansuz', { task: taskData, mode: 'assembled' });
|
|
@@ -1022,6 +1022,8 @@ async function main() {
|
|
|
1022
1022
|
const wishesLine = formatWishesLine(await fetchOpenWishes(mcpBase, WISH_TIMEOUT_MS, IS_PINNED ? BASE : null));
|
|
1023
1023
|
if (wishesLine) lines.push(wishesLine);
|
|
1024
1024
|
for (const l of openCardLines()) lines.push(l);
|
|
1025
|
+
const landedLine = landedActiveCardsLine();
|
|
1026
|
+
if (landedLine) lines.push(landedLine);
|
|
1025
1027
|
const peerLines = await fetchPendingPeerLines(mcpBase, WISH_TIMEOUT_MS, IS_PINNED ? BASE : process.env.CLAUDE_PROJECT_DIR || process.cwd());
|
|
1026
1028
|
for (const l of peerLines) lines.push(l);
|
|
1027
1029
|
lines.push('(boot 完成:依 Open cards 提議程;小修正直接做,中型工作開卡派工)');
|
|
@@ -1039,6 +1041,24 @@ if (require.main === module) {
|
|
|
1039
1041
|
});
|
|
1040
1042
|
}
|
|
1041
1043
|
|
|
1044
|
+
// TASK_2404: active cards whose task branch is already an ancestor of origin/main (local remote-tracking
|
|
1045
|
+
// refs only -- no fetch at boot). Silent when none or when anything fails.
|
|
1046
|
+
function landedActiveCardsLine() {
|
|
1047
|
+
try {
|
|
1048
|
+
const dir = path.join(resolveTasksDir(), 'active');
|
|
1049
|
+
const pick = (txt, key) => {
|
|
1050
|
+
const m = txt.match(new RegExp('^' + key + ':[ \\t]*(.*)$', 'm'));
|
|
1051
|
+
return m ? m[1].trim().replace(/^['"]|['"]$/g, '') : '';
|
|
1052
|
+
};
|
|
1053
|
+
const cards = fs.readdirSync(dir).filter((f) => /\.ya?ml$/.test(f)).map((f) => {
|
|
1054
|
+
const txt = fs.readFileSync(path.join(dir, f), 'utf8');
|
|
1055
|
+
return { id: pick(txt, 'id'), branch: pick(txt, 'branch') || null, keep_open: pick(txt, 'keep_open') === 'true' };
|
|
1056
|
+
}).filter((c) => c.id);
|
|
1057
|
+
const n = require(path.join(__dirname, '..', '..', '.collaboration', 'core', 'reconcile.js')).countLanded(BASE, cards, {});
|
|
1058
|
+
return n > 0 ? '[reconcile] ' + n + ' active cards already landed (task-api reconcile)' : null;
|
|
1059
|
+
} catch { return null; }
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1042
1062
|
module.exports = {
|
|
1043
1063
|
isOutsideAnyProject,
|
|
1044
1064
|
harnessSessionIdFromEnv,
|
|
@@ -156,7 +156,7 @@ node .collaboration/core/task-api.js move TASK_194 blocked --force
|
|
|
156
156
|
`project_name`);需要更精確的來源(例如具體的 in-app agent 名稱)可用 `--origin <name>` 覆寫。
|
|
157
157
|
|
|
158
158
|
**不是 auto-pipeline**:`createTask()` 一律寫入 `backlog/`(`pending` 狀態),走正常 triage 流程 ——
|
|
159
|
-
`submit` 完全不呼叫 dispatch
|
|
159
|
+
`submit` 完全不呼叫 dispatch.js,不會自動執行任務。來源內容(title/description 等)視為未信任資料,
|
|
160
160
|
與 field report 相同處理方式(寫入前不會被當作指令執行)。
|
|
161
161
|
|
|
162
162
|
**為什麼不是 HTTP server**:呼叫方本身就是一個能 spawn `node`/`k-cli` 的本地 process;開一個 HTTP server
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Formation Legacy Skills - 全角色定義
|
|
2
2
|
|
|
3
|
+
> **[RETIRED 2026-10, TASK_2401/2402/2403]** 本文描述已退役的 headless / legacy-scheduler Formation,僅留作歷史參考。現行路徑:`node .formation/tools/dispatch.js --skill=do --task=TASK_XXX --dispatch-mode=subagent`(見 `.formation/README.md`)。
|
|
4
|
+
|
|
3
5
|
> **專案位置**: `.formation/legacy/`
|
|
4
6
|
> **系統類型**: Formation Legacy 陣型專用 Skills
|
|
5
7
|
> **適用場景**: Legacy Scheduler 自動調度、Formation Lead 任務分派
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Formation Post-Complete - 任務完成後自動化
|
|
2
2
|
|
|
3
|
+
> **[RETIRED CALLERS 2026-10, TASK_2401/2402/2403]** 文中的 legacy-scheduler.js / headless.js 呼叫端已移除,僅留作歷史參考;post-complete.js 本身仍存在。現行派工:`node .formation/tools/dispatch.js --skill=do --task=TASK_XXX --dispatch-mode=subagent`。
|
|
4
|
+
|
|
3
5
|
> **專案位置**: `.formation/tools/post-complete.js`
|
|
4
6
|
> **系統類型**: Formation Lead 專用工具
|
|
5
7
|
> **適用場景**: 任務完成後的自動 Archive + Git 分支管理
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Formation System - 多陣型調度系統
|
|
2
2
|
|
|
3
|
+
> **[RETIRED 2026-10, TASK_2401/2402/2403]** 本文描述已退役的 headless / legacy-scheduler Formation,僅留作歷史參考。現行路徑:`node .formation/tools/dispatch.js --skill=do --task=TASK_XXX --dispatch-mode=subagent`(見 `.formation/README.md`)。
|
|
4
|
+
|
|
3
5
|
> **專案位置**: `.formation/`
|
|
4
6
|
> **系統類型**: Multi-Agent 協作陣型系統
|
|
5
7
|
> **適用場景**: 根據任務規模動態選擇最佳 Agent 部署策略
|
package/core/KENAZ_CORE_VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.3.
|
|
1
|
+
0.3.7
|
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
* ARCH-001 deterministic delta-aware file-size gate.
|
|
5
5
|
*
|
|
6
6
|
* Single source of truth for thresholds/scope: .rule/general/file-size-threshold.yaml
|
|
7
|
-
* (
|
|
7
|
+
* (project copy first, then core's; DEFAULT_CONFIG below when neither exists, e.g. a user
|
|
8
|
+
* project). Thresholds 2026-10-10: warn 1000 / hard 2000; existing over-limit files are
|
|
9
|
+
* grandfathered (only growth without a paired extraction blocks).
|
|
8
10
|
*
|
|
9
11
|
* Delta-aware + grandfather semantics:
|
|
10
12
|
* - BLOCK: a change newly pushes a file past hard_limit, OR grows a file
|
|
@@ -30,7 +32,6 @@ const fs = require('fs');
|
|
|
30
32
|
const path = require('path');
|
|
31
33
|
const { spawnSync } = require('child_process');
|
|
32
34
|
const yaml = (() => { try { return require('js-yaml'); } catch { return require('../../.vendor/js-yaml.cjs'); } })();
|
|
33
|
-
const micromatch = require('micromatch');
|
|
34
35
|
|
|
35
36
|
const ROOT = path.resolve(__dirname, '..', '..');
|
|
36
37
|
const RULE_PATH = path.join(ROOT, '.rule', 'general', 'file-size-threshold.yaml');
|
|
@@ -40,17 +41,28 @@ const RULE_PATH = path.join(ROOT, '.rule', 'general', 'file-size-threshold.yaml'
|
|
|
40
41
|
// define this separately for warn vs hard_limit, so both cases share it.
|
|
41
42
|
const GROWTH_THRESHOLD_LOC = 50;
|
|
42
43
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
44
|
+
// Built-in defaults, used when neither the project nor the core has the rule yaml
|
|
45
|
+
// (a user project installed from npm). Keep in sync with the yaml.
|
|
46
|
+
const DEFAULT_CONFIG = {
|
|
47
|
+
warn: 1000,
|
|
48
|
+
hardLimit: 2000,
|
|
49
|
+
include: ['**/*.rs', '**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx', '**/*.py', '**/*.go'],
|
|
50
|
+
exclude: [
|
|
51
|
+
'**/vendor/**', '**/target/**', '**/node_modules/**', '**/dist/**', '**/.next/**',
|
|
52
|
+
'**/*.test.*', '**/*.spec.*', '**/*_test.*', '**/tests/**', '**/__tests__/**',
|
|
53
|
+
'**/*.d.ts', '**/*.generated.*', '**/generated/**', '**/*.min.js',
|
|
54
|
+
],
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/** Rule yaml: the project's own first, then the core's; none found -> DEFAULT_CONFIG. */
|
|
58
|
+
function loadConfig(repoPath) {
|
|
59
|
+
const candidates = [path.join(repoPath || process.cwd(), '.rule', 'general', 'file-size-threshold.yaml'), RULE_PATH];
|
|
60
|
+
const rulePath = candidates.find((p) => fs.existsSync(p));
|
|
61
|
+
if (!rulePath) return DEFAULT_CONFIG;
|
|
62
|
+
const doc = yaml.load(fs.readFileSync(rulePath, 'utf8'));
|
|
51
63
|
const rule = (doc && doc.rules || []).find((r) => r.id === 'ARCH-001');
|
|
52
64
|
if (!rule || !rule.thresholds || !rule.scope) {
|
|
53
|
-
throw new Error(`ARCH-001: malformed rule source ${
|
|
65
|
+
throw new Error(`ARCH-001: malformed rule source ${rulePath}`);
|
|
54
66
|
}
|
|
55
67
|
return {
|
|
56
68
|
warn: rule.thresholds.warn,
|
|
@@ -60,11 +72,26 @@ function loadConfig() {
|
|
|
60
72
|
};
|
|
61
73
|
}
|
|
62
74
|
|
|
75
|
+
// The npm core ships without node_modules, so the scope globs (`**/`, `*`, `?`) are matched here.
|
|
76
|
+
function globToRegExp(glob) {
|
|
77
|
+
let re = '';
|
|
78
|
+
for (let i = 0; i < glob.length; i++) {
|
|
79
|
+
const c = glob[i];
|
|
80
|
+
if (c === '*' && glob[i + 1] === '*') {
|
|
81
|
+
i++;
|
|
82
|
+
if (glob[i + 1] === '/') { i++; re += '(?:.*/)?'; } else re += '.*';
|
|
83
|
+
} else if (c === '*') re += '[^/]*';
|
|
84
|
+
else if (c === '?') re += '[^/]';
|
|
85
|
+
else re += c.replace(/[.+^${}()|[\]\\]/g, '\\$&');
|
|
86
|
+
}
|
|
87
|
+
return new RegExp('^' + re + '$');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const matchesAny = (p, globs) => globs.some((g) => globToRegExp(g).test(p));
|
|
91
|
+
|
|
63
92
|
function isInScope(relPath, config) {
|
|
64
93
|
const posixPath = relPath.split(path.sep).join('/');
|
|
65
|
-
|
|
66
|
-
if (micromatch.isMatch(posixPath, config.exclude, { dot: true })) return false;
|
|
67
|
-
return true;
|
|
94
|
+
return matchesAny(posixPath, config.include) && !matchesAny(posixPath, config.exclude);
|
|
68
95
|
}
|
|
69
96
|
|
|
70
97
|
function countLines(text) {
|
|
@@ -127,7 +154,7 @@ function absoluteStatus(lines, config) {
|
|
|
127
154
|
|
|
128
155
|
/** --staged: compare staged (index) content against HEAD. */
|
|
129
156
|
function runStaged(config, repoPath) {
|
|
130
|
-
const cwd = repoPath ||
|
|
157
|
+
const cwd = repoPath || process.cwd();
|
|
131
158
|
// -c core.quotePath=false: avoid octal-escaped/double-quoted non-ASCII paths
|
|
132
159
|
// in porcelain output, which would break the plain string parsing below.
|
|
133
160
|
const nameStatus = git(['-c', 'core.quotePath=false', 'diff', '--cached', '--name-status'], cwd);
|
|
@@ -217,7 +244,7 @@ function main() {
|
|
|
217
244
|
const args = parseArgs(process.argv.slice(2));
|
|
218
245
|
let config;
|
|
219
246
|
try {
|
|
220
|
-
config = loadConfig();
|
|
247
|
+
config = loadConfig(args.repo);
|
|
221
248
|
} catch (e) {
|
|
222
249
|
console.error(e.message);
|
|
223
250
|
process.exit(2);
|
|
@@ -230,7 +257,7 @@ function main() {
|
|
|
230
257
|
} else if (args.file) {
|
|
231
258
|
results = runFile(args.file, config);
|
|
232
259
|
} else if (args.base && args.head) {
|
|
233
|
-
results = runRefDiff(args.base, args.head, args.repo ||
|
|
260
|
+
results = runRefDiff(args.base, args.head, args.repo || process.cwd(), config);
|
|
234
261
|
} else {
|
|
235
262
|
console.error('Usage: check-file-size.js --staged | --file <path> | --base <ref> --head <ref> [--repo <path>]');
|
|
236
263
|
process.exit(2);
|
package/core/k-cli/README.md
CHANGED
|
@@ -33,7 +33,7 @@ cat k-cli.md
|
|
|
33
33
|
## 核心價值
|
|
34
34
|
|
|
35
35
|
- **零依賴**:不需要 Dashboard 運行,直接呼叫底層工具
|
|
36
|
-
- **spawnClaude 架構**:寫入操作透過 `spawnClaude` 直接啟動 Claude CLI
|
|
36
|
+
- **spawnClaude 架構**:寫入操作透過 `spawnClaude` 直接啟動 Claude CLI
|
|
37
37
|
- **dry-run 支援**:所有寫入命令支援 `--dry-run`,驗證但不執行
|
|
38
38
|
- **可腳本化**:所有輸出支援 `--json` 格式,方便 pipe 和自動化
|
|
39
39
|
- **明確偵測**:不猜測、不 fallback、不向上找目錄,偵測失敗就是失敗
|
|
@@ -50,8 +50,6 @@ cat k-cli.md
|
|
|
50
50
|
| 讀取 | specialist list, task list, task get, project status... | 直接呼叫 |
|
|
51
51
|
| 寫入(任務) | task create | → spawnClaude + Ansuz(分析需求 → Amelia 審查 role → 建立) |
|
|
52
52
|
| 刪除 | task delete | → 直接 rm 檔案 |
|
|
53
|
-
| 調度 | dispatch run TASK_XXX | → spawnClaude + Ansuz(讀 role → 判斷陣型 → headless.js) |
|
|
54
|
-
| 調度 | dispatch run --auto | → spawnClaude + Ansuz(掃描 backlog → 逐個 dispatch) |
|
|
55
53
|
| 執行 | specialist run | → spawnClaude + 指定 Specialist |
|
|
56
54
|
|
|
57
55
|
## 命令一覽
|
|
@@ -149,65 +147,13 @@ k-cli task create "描述"
|
|
|
149
147
|
|
|
150
148
|
| 命令 | 說明 | 執行方式 |
|
|
151
149
|
|------|------|---------|
|
|
152
|
-
| `k-cli dispatch run <taskId>` | Dispatch 指定任務 | → spawnClaude + Ansuz + headless.js |
|
|
153
|
-
| `k-cli dispatch run --auto` | 自動掃描 backlog 並 dispatch | → spawnClaude + Ansuz + headless.js |
|
|
154
150
|
| `k-cli dispatch status <taskId>` | 查看 dispatch 狀態 | 直接讀取 |
|
|
155
151
|
|
|
156
152
|
```bash
|
|
157
|
-
# 指定任務 dispatch(Ansuz 讀 role → 判斷陣型 → headless.js 執行)
|
|
158
|
-
k-cli dispatch run TASK_340
|
|
159
|
-
k-cli dispatch run TASK_340 --dry-run
|
|
160
|
-
|
|
161
|
-
# 自動掃描(Ansuz 掃 backlog → 按優先級逐個 dispatch)
|
|
162
|
-
k-cli dispatch run --auto
|
|
163
|
-
k-cli dispatch run --auto --project-path /path/to/project
|
|
164
|
-
|
|
165
153
|
# 查看狀態(自動搜尋 .kenaz/logs/ 或 .formation/logs/)
|
|
166
154
|
k-cli dispatch status TASK_340 --tail 50
|
|
167
155
|
```
|
|
168
156
|
|
|
169
|
-
#### dispatch run 流程
|
|
170
|
-
|
|
171
|
-
```
|
|
172
|
-
k-cli dispatch run TASK_XXX
|
|
173
|
-
│
|
|
174
|
-
└→ spawnClaude(Ansuz)
|
|
175
|
-
├─ Step 1: task-api get → 讀取任務 role
|
|
176
|
-
├─ Step 2: role → --skill, 判斷陣型(unit > solo > squad > legion)
|
|
177
|
-
└─ Step 3: node headless.js --skill={role} --task=TASK_XXX
|
|
178
|
-
│
|
|
179
|
-
headless.js 自動處理:
|
|
180
|
-
├─ auto-claim(backlog → active)
|
|
181
|
-
├─ 依賴預檢
|
|
182
|
-
├─ spawnClaude(agent) 執行任務
|
|
183
|
-
├─ ensureTaskComplete
|
|
184
|
-
└─ postComplete(archive + git branch + merge)
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
#### Formation Role → Skill 對應
|
|
188
|
-
|
|
189
|
-
| role | --skill | 說明 |
|
|
190
|
-
|------|---------|------|
|
|
191
|
-
| frontend | frontend | React/TypeScript UI、client-side |
|
|
192
|
-
| backend | backend | API、DB、server-side |
|
|
193
|
-
| designer | designer | 設計規範產出 |
|
|
194
|
-
| qa | qa | 測試、Bug 驗證 |
|
|
195
|
-
| sre | sre | 基礎設施、部署 |
|
|
196
|
-
| developer | developer | 通才兜底 |
|
|
197
|
-
|
|
198
|
-
Skill 定義檔:`.formation/legacy/{role}.yaml`
|
|
199
|
-
|
|
200
|
-
#### 陣型判斷
|
|
201
|
-
|
|
202
|
-
原則:能 unit 不 solo,能 solo 不 squad,能 squad 不 legion。
|
|
203
|
-
|
|
204
|
-
| 陣型 | 條件 | headless.js 參數 |
|
|
205
|
-
|------|------|-----------------|
|
|
206
|
-
| unit(預設) | 目標明確、單一 scope | `--skill={role} --task=TASK_XXX` |
|
|
207
|
-
| solo | 需多角度分析 | `--formation=solo --skill=strategy --task=TASK_XXX` |
|
|
208
|
-
| squad | 多個獨立子任務可並行 | `--formation=squad --skill=blitz --task=TASK_XXX` |
|
|
209
|
-
| legion | 有順序依賴或即時協調 | `--formation=legion --skill=forge --task=TASK_XXX` |
|
|
210
|
-
|
|
211
157
|
### Project 命令
|
|
212
158
|
|
|
213
159
|
| 命令 | 說明 | 執行方式 |
|
|
@@ -274,7 +220,6 @@ k-cli project install k-cli --dry-run
|
|
|
274
220
|
| `--dry-run` | 驗證但不執行(適用所有寫入 / 調度命令) |
|
|
275
221
|
| `--json` | 輸出 JSON 格式 |
|
|
276
222
|
| `--project-path <path>` | 指定子專案路徑(使用 `.kenaz/project.yaml` 中的 `project_path` 值) |
|
|
277
|
-
| `--auto` | 自動掃描模式(僅 `dispatch run`) |
|
|
278
223
|
| `--force` | 跳過品質檢查(僅 `task create`) |
|
|
279
224
|
| `--no-git` | 初始化時跳過 Git(僅 `project initialize`) |
|
|
280
225
|
| `--core-path <path>` | 覆蓋自動偵測的 core 路徑(僅 `project initialize`) |
|
|
@@ -306,7 +251,6 @@ k-cli/
|
|
|
306
251
|
│ │ ├── update.ts # task update(直接 task-api.js)
|
|
307
252
|
│ │ └── delete.ts # task delete(直接 task-api.js)
|
|
308
253
|
│ ├── dispatch/ # Formation 調度功能
|
|
309
|
-
│ │ ├── run.ts # dispatch run(spawnClaude + Ansuz + headless.js)
|
|
310
254
|
│ │ └── status.ts # dispatch status(直接讀 log)
|
|
311
255
|
│ └── project/ # 專案管理功能
|
|
312
256
|
│ ├── status.ts # project status(含 project.yaml 詳情)
|
|
@@ -333,10 +277,6 @@ k-cli
|
|
|
333
277
|
│ ├─ task/create → Ansuz 分析需求
|
|
334
278
|
│ │ → Agent tool subagent(Amelia 審查 + role)
|
|
335
279
|
│ │ → task-api.js create(最終建立)
|
|
336
|
-
│ ├─ dispatch/run → Ansuz 讀 role + 判斷陣型
|
|
337
|
-
│ │ TASK_XXX → headless.js --skill={role} --task=TASK_XXX
|
|
338
|
-
│ └─ dispatch/run → Ansuz 掃 backlog + 逐個 dispatch
|
|
339
|
-
│ --auto → headless.js × N
|
|
340
280
|
│
|
|
341
281
|
└─ spawnClaude(Specialist 直接執行)
|
|
342
282
|
└─ specialist/run → session.js(組裝 prompt)+ spawnClaude
|
|
@@ -354,38 +294,6 @@ spawnClaude(opts)
|
|
|
354
294
|
└─ spawn(claude, args) → stdin pipe prompt, stream-json 輸出
|
|
355
295
|
```
|
|
356
296
|
|
|
357
|
-
### Git 流程(自動觸發鏈)
|
|
358
|
-
|
|
359
|
-
```
|
|
360
|
-
k-cli dispatch run TASK_XXX
|
|
361
|
-
└→ Ansuz → headless.js
|
|
362
|
-
├─ auto-claim(backlog → active)
|
|
363
|
-
├─ 依賴預檢
|
|
364
|
-
├─ 保存當前分支 → .formation/.dispatch-branch
|
|
365
|
-
├─ spawnClaude(agent) 執行任務
|
|
366
|
-
└─ agent exit 0 → 自動觸發:
|
|
367
|
-
├─ ensureTaskComplete → task-api complete
|
|
368
|
-
└─ postComplete()
|
|
369
|
-
├─ archive(done/ 超過 10 個 → archived/)
|
|
370
|
-
└─ gitBranchAndMerge()
|
|
371
|
-
├─ stash 未提交變更
|
|
372
|
-
├─ checkout -b task/task_xxx
|
|
373
|
-
├─ unstash + git add + commit
|
|
374
|
-
├─ checkout {userBranch}
|
|
375
|
-
├─ merge task/task_xxx --no-ff
|
|
376
|
-
└─ 提示: merge to main when ready
|
|
377
|
-
```
|
|
378
|
-
|
|
379
|
-
| 階段 | 誰處理 | 自動/手動 |
|
|
380
|
-
|------|--------|----------|
|
|
381
|
-
| claim | headless.js | 自動 |
|
|
382
|
-
| agent 執行 | headless.js → spawnClaude | 自動 |
|
|
383
|
-
| task complete | headless.js ensureTaskComplete() | 自動 |
|
|
384
|
-
| archive | post-complete.js | 自動 |
|
|
385
|
-
| task branch + commit | post-complete.js gitBranchAndMerge() | 自動 |
|
|
386
|
-
| merge 到 user branch | post-complete.js | 自動 |
|
|
387
|
-
| merge 到 main | **用戶** | **手動** |
|
|
388
|
-
|
|
389
297
|
## Dry-Run 行為
|
|
390
298
|
|
|
391
299
|
`--dry-run` 適用於所有有副作用的命令:
|
|
@@ -393,7 +301,6 @@ k-cli dispatch run TASK_XXX
|
|
|
393
301
|
| 命令 | dry-run 行為 |
|
|
394
302
|
|------|-------------|
|
|
395
303
|
| `specialist run` | 組裝 prompt → 顯示 agent + prompt 長度 → **不 spawn Claude** |
|
|
396
|
-
| `dispatch run` | 組裝 Ansuz prompt → 顯示模式 + 資訊 → **不 spawn Ansuz** |
|
|
397
304
|
| `task create` | 組裝 Ansuz prompt → 顯示將建立的任務資訊 → **不啟動 Ansuz** |
|
|
398
305
|
| `task update` | 組裝 task-api args → 顯示將更新的內容 → **不呼叫 task-api** |
|
|
399
306
|
| `task delete` | 組裝 task-api args → 顯示將刪除的任務 → **不呼叫 task-api** |
|
|
@@ -14,7 +14,7 @@ npm run build && npm test
|
|
|
14
14
|
|
|
15
15
|
## 前置條件
|
|
16
16
|
|
|
17
|
-
- Claude CLI 可用(`task create`
|
|
17
|
+
- Claude CLI 可用(`task create` 會 spawnClaude)
|
|
18
18
|
- `npm run build` 必須先執行
|
|
19
19
|
|
|
20
20
|
## 測試環境
|
|
@@ -64,20 +64,8 @@ npm run build && npm test
|
|
|
64
64
|
- `k-cli task get TASK_A --project-path test/sandbox/`
|
|
65
65
|
- 驗證:輸出含 TASK_A 詳細資訊
|
|
66
66
|
|
|
67
|
-
### Test 6:
|
|
68
|
-
-
|
|
69
|
-
- `k-cli dispatch run TASK_A --project-path test/sandbox/`
|
|
70
|
-
- 驗證:Ansuz 讀 role → 判斷陣型 → headless.js 執行,輸出含 TASK_A
|
|
71
|
-
|
|
72
|
-
### Test 7: dispatch status(等待任務完成)
|
|
73
|
-
- `k-cli dispatch status TASK_A --project-path test/sandbox/`
|
|
74
|
-
- 輪詢直到任務完成(或 timeout)
|
|
75
|
-
- 驗證:輸出含 TASK_A 相關資訊,最終狀態為 done
|
|
76
|
-
|
|
77
|
-
### Test 8: dispatch run --auto(自動掃描)
|
|
78
|
-
- `k-cli dispatch run --auto --project-path test/sandbox/`
|
|
79
|
-
- 驗證:自動掃描 backlog,dispatch TASK_B 和 TASK_C(或剩餘待處理任務)
|
|
80
|
-
- 驗證:輸出含掃描結果
|
|
67
|
+
### Test 6-8: 已移除
|
|
68
|
+
- `dispatch run` 已刪除(Formation headless 調度已退役),`dispatch status` 失去可驗證的前置動作,一併移出測試
|
|
81
69
|
|
|
82
70
|
### Test 9: task delete → verify
|
|
83
71
|
- 使用 Test 5 的 TASK_A
|
|
@@ -99,11 +87,9 @@ npm run build && npm test
|
|
|
99
87
|
- 驗證:輸出含 `dry-run`,不呼叫 spawnClaude
|
|
100
88
|
3. `k-cli task delete TASK_B --project-path test/sandbox/ --dry-run`
|
|
101
89
|
- 驗證:輸出含 `dry-run`,任務仍存在
|
|
102
|
-
4. `k-cli
|
|
103
|
-
- 驗證:輸出含 `dry-run`,不呼叫 spawnClaude
|
|
104
|
-
5. `k-cli specialist run --agent Amelia 測試 --project-path test/sandbox/ --dry-run`
|
|
90
|
+
4. `k-cli specialist run --agent Amelia 測試 --project-path test/sandbox/ --dry-run`
|
|
105
91
|
- 驗證:輸出含 `dry-run`,不呼叫 spawnClaude
|
|
106
|
-
|
|
92
|
+
5. `k-cli project install k-cli --project-path test/sandbox/ --dry-run`
|
|
107
93
|
- 驗證:輸出含 `dry-run`
|
|
108
94
|
|
|
109
95
|
## 結果格式
|
|
@@ -116,9 +102,6 @@ k-cli Test Suite
|
|
|
116
102
|
[ 3/11] project status ✅ PASS
|
|
117
103
|
[ 4/11] specialist list ✅ PASS
|
|
118
104
|
[ 5/11] task create × 3 ✅ PASS
|
|
119
|
-
[ 6/11] dispatch run ✅ PASS
|
|
120
|
-
[ 7/11] dispatch status ✅ PASS
|
|
121
|
-
[ 8/11] dispatch run --auto ✅ PASS
|
|
122
105
|
[ 9/11] task delete ✅ PASS
|
|
123
106
|
[10/11] specialist run Amelia ✅ PASS
|
|
124
107
|
[11/11] --dry-run ✅ PASS
|
package/core/k-cli/test/run.sh
CHANGED
|
@@ -280,62 +280,6 @@ test_5_task_create() {
|
|
|
280
280
|
return 0
|
|
281
281
|
}
|
|
282
282
|
|
|
283
|
-
# =========================================
|
|
284
|
-
# Test 6: dispatch run (specific task)
|
|
285
|
-
# =========================================
|
|
286
|
-
test_6_dispatch_run() {
|
|
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 " Dispatching: $task_id"
|
|
290
|
-
|
|
291
|
-
run_cmd $KCLI dispatch run "$task_id" --project-path "$SANDBOX"
|
|
292
|
-
local output="$LOG_CMD_OUTPUT"
|
|
293
|
-
assert_contains "dispatch run" "$output" "$task_id" || return 1
|
|
294
|
-
return 0
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
# =========================================
|
|
298
|
-
# Test 7: dispatch status (wait for done)
|
|
299
|
-
# =========================================
|
|
300
|
-
test_7_dispatch_status() {
|
|
301
|
-
local task_id
|
|
302
|
-
task_id=$(cat "$SANDBOX/.test_task_a" 2>/dev/null) || { echo " No TASK_A from test 5"; return 1; }
|
|
303
|
-
|
|
304
|
-
# Poll up to 10 times (10 sec intervals)
|
|
305
|
-
local i output
|
|
306
|
-
for i in $(seq 1 10); do
|
|
307
|
-
log " --- Poll $i/10 ---"
|
|
308
|
-
run_cmd $KCLI dispatch status "$task_id" --project-path "$SANDBOX"
|
|
309
|
-
output="$LOG_CMD_OUTPUT"
|
|
310
|
-
if echo "$output" | grep -qi "done\|complete\|finished"; then
|
|
311
|
-
log " ✓ Task completed"
|
|
312
|
-
return 0
|
|
313
|
-
fi
|
|
314
|
-
sleep 10
|
|
315
|
-
done
|
|
316
|
-
|
|
317
|
-
# After polling, still check if task is in done dir
|
|
318
|
-
if [ -d "$SANDBOX/.kenaz/tasks/done/$task_id" ]; then
|
|
319
|
-
log " ✓ Task found in done dir"
|
|
320
|
-
return 0
|
|
321
|
-
fi
|
|
322
|
-
|
|
323
|
-
# Accept any output containing the task ID as partial pass
|
|
324
|
-
assert_contains "dispatch status" "$output" "$task_id" || return 1
|
|
325
|
-
return 0
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
# =========================================
|
|
329
|
-
# Test 8: dispatch run --auto
|
|
330
|
-
# =========================================
|
|
331
|
-
test_8_dispatch_auto() {
|
|
332
|
-
run_cmd $KCLI dispatch run --auto --project-path "$SANDBOX"
|
|
333
|
-
local output="$LOG_CMD_OUTPUT"
|
|
334
|
-
# Should scan backlog and attempt to dispatch remaining tasks
|
|
335
|
-
assert_contains "dispatch auto" "$output" "backlog\|scan\|dispatch\|TASK_\|no.*task\|0 " || return 1
|
|
336
|
-
return 0
|
|
337
|
-
}
|
|
338
|
-
|
|
339
283
|
# =========================================
|
|
340
284
|
# Test 9: task delete -> verify
|
|
341
285
|
# =========================================
|
|
@@ -412,19 +356,12 @@ test_11_dry_run() {
|
|
|
412
356
|
output="$LOG_CMD_OUTPUT"
|
|
413
357
|
assert_contains "delete dry-run" "$output" "dry-run\|dry.run\|dryRun" || return 1
|
|
414
358
|
|
|
415
|
-
# 4. dispatch run --dry-run
|
|
416
|
-
log " --- dry-run: dispatch run ---"
|
|
417
|
-
run_cmd $KCLI dispatch run "$tb" --project-path "$SANDBOX" --dry-run
|
|
418
|
-
output="$LOG_CMD_OUTPUT"
|
|
419
|
-
assert_contains "dispatch dry-run" "$output" "dry-run\|dry.run\|dryRun" || return 1
|
|
420
|
-
|
|
421
|
-
# 5. specialist run --dry-run
|
|
422
359
|
log " --- dry-run: specialist run ---"
|
|
423
360
|
run_cmd $KCLI specialist run --agent Amelia "test" --project-path "$SANDBOX" --dry-run
|
|
424
361
|
output="$LOG_CMD_OUTPUT"
|
|
425
362
|
assert_contains "specialist dry-run" "$output" "dry-run\|dry.run\|dryRun" || return 1
|
|
426
363
|
|
|
427
|
-
#
|
|
364
|
+
# 4. project install --dry-run
|
|
428
365
|
log " --- dry-run: project install ---"
|
|
429
366
|
run_cmd $KCLI project install k-cli --project-path "$SANDBOX" --dry-run
|
|
430
367
|
output="$LOG_CMD_OUTPUT"
|
|
@@ -441,9 +378,6 @@ run_test 2 "ping" test_2_ping
|
|
|
441
378
|
run_test 3 "project status" test_3_project_status
|
|
442
379
|
run_test 4 "specialist list" test_4_specialist_list
|
|
443
380
|
run_test 5 "task create x3" test_5_task_create
|
|
444
|
-
run_test 6 "dispatch run" test_6_dispatch_run
|
|
445
|
-
run_test 7 "dispatch status" test_7_dispatch_status
|
|
446
|
-
run_test 8 "dispatch run --auto" test_8_dispatch_auto
|
|
447
381
|
run_test 9 "task delete" test_9_task_delete
|
|
448
382
|
run_test 10 "specialist run Amelia" test_10_specialist_run
|
|
449
383
|
run_test 11 "--dry-run" test_11_dry_run
|
|
@@ -23,6 +23,7 @@ ARGUMENTS 含 `[專案: X]` 或 `--project-path X` → PROJECT_PATH = X,下方
|
|
|
23
23
|
- 有**對照組**:證明測試在舊程式上會失敗,或修改前後結果不同。
|
|
24
24
|
- subagent 回報「未驗證」的項目,由你補驗或明講仍未驗證。
|
|
25
25
|
- 合併:`git merge` 後跑相關測試(或 `node "${CLAUDE_PLUGIN_ROOT}/hooks/core-resolve.js" .specialist/Ansuz/tools/verify-and-land.js`),用這個專案自己的測試指令。
|
|
26
|
+
- 檔案長度:verify-and-land 合併前會跑 `check-file-size`(警告 1000 / 上限 2000 行,既有超長檔只擋「未配套拆分的增長」);被擋就按職責拆成 module / component / hook。
|
|
26
27
|
- 只在 Kenaz 核心 repo 本身:Rust 測試走 `npm run test:rust` / `dev/scripts/rust-verify-worktree.js`。
|
|
27
28
|
|
|
28
29
|
## 回報
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kenaz",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.7",
|
|
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.7",
|
|
24
|
+
"@kenazai/server-darwin-x64": "0.3.7",
|
|
25
|
+
"@kenazai/server-linux-arm64": "0.3.7",
|
|
26
|
+
"@kenazai/server-linux-x64": "0.3.7",
|
|
27
|
+
"@kenazai/server-win32-x64": "0.3.7"
|
|
28
28
|
},
|
|
29
29
|
"license": "UNLICENSED",
|
|
30
30
|
"repository": {
|