kenaz 0.3.19 → 0.3.21
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/.claude/commands/architect.md +3 -0
- package/core/.claude/commands/incident.md +3 -0
- package/core/.claude/commands/review.md +3 -0
- package/core/.claude/commands/security.md +3 -0
- package/core/.claude/commands/spec.md +3 -0
- package/core/.claude/skills/architect_agent/SKILL.md +1 -1
- package/core/.claude/skills/assessment_agent/SKILL.md +1 -1
- package/core/.claude/skills/code_reviewer/SKILL.md +1 -1
- package/core/.claude/skills/incident_response/SKILL.md +1 -1
- package/core/.claude/skills/security_agent/SKILL.md +1 -1
- package/core/.claude/skills/spec_optimizer/SKILL.md +1 -1
- package/core/.formation/bifrost/codex-hooks-repair.js +115 -0
- package/core/.formation/bifrost/codex-hooks-repair.test.js +94 -0
- package/core/.formation/bifrost/openai/codex-hook-shim.js +5 -3
- package/core/.formation/bifrost/openai/codex-hook-shim.test.js +2 -0
- package/core/.formation/bifrost/setup-codex.js +22 -12
- package/core/.formation/tools/__tests__/arms.test.js +41 -0
- package/core/.formation/tools/__tests__/diagnose-bug.test.js +10 -0
- package/core/.formation/tools/arms.js +48 -0
- package/core/.formation/tools/dispatch.js +23 -14
- package/core/.formation/tools/kenaz-stats.js +32 -8
- package/core/.formation/tools/kenaz-stats.test.js +15 -2
- package/core/.formation/tools/prompt.js +16 -1
- package/core/.formation/tools/prompt.test.js +20 -0
- package/core/.specialist/Ansuz/RULES.md +7 -0
- package/core/.specialist/tools/ansuz-boot-fast.js +7 -4
- package/core/.specialist/tools/ansuz-boot-fast.test.js +10 -3
- package/core/KENAZ_CORE_VERSION +1 -1
- package/core/dev/scripts/peer-identity-e2e.js +402 -0
- package/core/dev/scripts/peer-identity-e2e.md +47 -0
- package/core/dev/scripts/relay-admin-ui.js +154 -0
- package/core/dev/scripts/relay-admin-ui.test.js +112 -0
- package/core/dev/scripts/relay-admin.js +103 -0
- package/core/dev/scripts/relay-admin.test.js +131 -0
- package/core/dev/scripts/skill-routing-replay.js +80 -0
- package/core/dev/scripts/skill-routing-replay.test.js +24 -0
- package/core/dev/specs/peer/codex-hook-shim-peer.test.js +17 -5
- package/core/plugins/kenaz/INVENTORY.md +1 -1
- package/core/plugins/kenaz/README.md +10 -0
- package/core/plugins/kenaz/commands/Ansuz.md +1 -0
- package/core/plugins/kenaz/commands/architect.md +3 -0
- package/core/plugins/kenaz/commands/incident.md +3 -0
- package/core/plugins/kenaz/commands/review.md +3 -0
- package/core/plugins/kenaz/commands/security.md +3 -0
- package/core/plugins/kenaz/commands/spec.md +3 -0
- package/core/plugins/kenaz/hooks/kenaz-hook.js +2 -1
- package/core/plugins/kenaz/hooks/sdk-gate.js +9 -0
- package/core/plugins/kenaz/scripts/kenaz-mode.js +5 -0
- package/core/plugins/kenaz/scripts/kenaz.test.js +15 -3
- package/core/plugins/kenaz/scripts/sync.js +1 -1
- package/core/plugins/kenaz/scripts/ygg.js +8 -0
- package/core/plugins/kenaz/scripts/ygg.test.js +72 -0
- package/core/plugins/kenaz/skills/architect_agent/SKILL.md +1 -1
- package/core/plugins/kenaz/skills/assessment_agent/SKILL.md +1 -1
- package/core/plugins/kenaz/skills/code_reviewer/SKILL.md +1 -1
- package/core/plugins/kenaz/skills/incident_response/SKILL.md +1 -1
- package/core/plugins/kenaz/skills/security_agent/SKILL.md +1 -1
- package/core/plugins/kenaz/skills/spec_optimizer/SKILL.md +1 -1
- package/core/plugins/kenaz-peer/hooks/register.test.ts +32 -0
- package/core/plugins/kenaz-peer/hooks/register.ts +9 -0
- package/lib/cli.js +4 -0
- package/package.json +6 -6
- package/core/plugins/kenaz/skills/feature_agent/SKILL.md +0 -289
- /package/core/{.claude → dev/archive}/skills/feature_agent/SKILL.md +0 -0
|
@@ -19,6 +19,18 @@ function hook(name, dir, env, input) {
|
|
|
19
19
|
});
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
test('TASK_2455: SDK sessions get nothing; cli / sdk-cli / KENAZ_IN_SDK=1 keep today\'s output', () => {
|
|
23
|
+
const dir = proj();
|
|
24
|
+
for (const n of ['guidance', 'prompt-runner', 'memory-guard', 'flow-post', 'session-start', 'session-stop']) {
|
|
25
|
+
for (const ep of ['sdk-py', 'sdk-ts']) {
|
|
26
|
+
const r = hook(n, dir, { CLAUDE_CODE_ENTRYPOINT: ep });
|
|
27
|
+
assert.strictEqual(r.status, 0); assert.strictEqual(r.stdout, ''); assert.strictEqual(r.stderr, '');
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
for (const ep of ['cli', 'sdk-cli']) assert.match(hook('guidance', dir, { CLAUDE_CODE_ENTRYPOINT: ep }).stdout, /Kenaz plugin/);
|
|
31
|
+
assert.match(hook('guidance', dir, { CLAUDE_CODE_ENTRYPOINT: 'sdk-py', KENAZ_IN_SDK: '1' }).stdout, /Kenaz plugin/);
|
|
32
|
+
});
|
|
33
|
+
|
|
22
34
|
test('full prints guidance (control for off)', () => {
|
|
23
35
|
const r = hook('guidance', proj());
|
|
24
36
|
assert.strictEqual(r.status, 0);
|
|
@@ -102,7 +114,7 @@ test('ygg join activates the room in collaboration_projects; status explains how
|
|
|
102
114
|
const srv = http.createServer((req, res) => {
|
|
103
115
|
let b = ''; req.on('data', (d) => { b += d; });
|
|
104
116
|
req.on('end', () => {
|
|
105
|
-
if (req.method === 'POST') posts.push(JSON.parse(b));
|
|
117
|
+
if (req.method === 'POST') { posts.push(JSON.parse(b)); cfg.collaboration_projects = posts[posts.length - 1].collaboration_projects; }
|
|
106
118
|
res.setHeader('content-type', 'application/json');
|
|
107
119
|
res.end(JSON.stringify(req.method === 'POST' ? { ok: true } : cfg));
|
|
108
120
|
});
|
|
@@ -120,7 +132,7 @@ test('ygg join activates the room in collaboration_projects; status explains how
|
|
|
120
132
|
srv.close();
|
|
121
133
|
}
|
|
122
134
|
const { buildStatus, renderStatus } = require('./ygg.js');
|
|
123
|
-
assert.match(renderStatus(buildStatus(cfg, { connected: false, nodes: [] }, null)), /not active in this project - run "ygg join <key>"/);
|
|
135
|
+
assert.match(renderStatus(buildStatus(Object.assign({}, cfg, { collaboration_projects: ['other'] }), { connected: false, nodes: [] }, null)), /not active in this project - run "ygg join <key>"/);
|
|
124
136
|
});
|
|
125
137
|
|
|
126
138
|
// TASK_2416: `ygg create` posts to the relay (config relay_url), then joins with the returned key.
|
|
@@ -139,7 +151,7 @@ async function withStub(relayReply, fn) {
|
|
|
139
151
|
}
|
|
140
152
|
if (req.url.startsWith('/api/nodes')) return res.end('[]');
|
|
141
153
|
if (req.url.startsWith('/api/yggdrasil/config')) {
|
|
142
|
-
if (req.method === 'POST') { seen.config.push(JSON.parse(b)); return res.end(JSON.stringify({ ok: true })); }
|
|
154
|
+
if (req.method === 'POST') { seen.config.push(JSON.parse(b)); cfg.collaboration_projects = seen.config[seen.config.length - 1].collaboration_projects; return res.end(JSON.stringify({ ok: true })); }
|
|
143
155
|
return res.end(JSON.stringify(cfg));
|
|
144
156
|
}
|
|
145
157
|
res.end(JSON.stringify({ success: true, created: false }));
|
|
@@ -11,7 +11,7 @@ const out = path.resolve(__dirname, '..');
|
|
|
11
11
|
|
|
12
12
|
const SKILLS = [
|
|
13
13
|
'architect_agent', 'assessment_agent', 'backend_agent', 'backend_engineer', 'code_reviewer', 'dev_agent',
|
|
14
|
-
'designer', 'developer', '
|
|
14
|
+
'designer', 'developer', 'frontend_agent', 'incident_response', 'product_manager',
|
|
15
15
|
'qa_engineer', 'security_agent', 'spec_optimizer', 'sre_agent',
|
|
16
16
|
'adapt', 'animate', 'audit', 'bolder', 'clarify', 'colorize', 'critique', 'delight', 'distill', 'extract',
|
|
17
17
|
'frontend-design', 'harden', 'normalize', 'onboard', 'optimize', 'polish', 'quieter', 'ui-ux-pro-max',
|
|
@@ -149,7 +149,15 @@ function postBody(cfg, patch) {
|
|
|
149
149
|
async function join(dir, key, scope) {
|
|
150
150
|
if (!key || /\s/.test(key)) throw new Error('usage: ygg join <key> [--project|--global]');
|
|
151
151
|
const cfg = await getConfig(dir);
|
|
152
|
+
// TASK_2441: no room key to activate = the POST below would save an empty list and "succeed".
|
|
153
|
+
if (!cfg.effective_project_key) throw new Error("cannot work out this project's room (" + (cfg.error || 'no effective_project_key') + '); does ' + dir + ' have a repo with a remote?');
|
|
152
154
|
await postConfig(dir, postBody(cfg, { enabled: true, publishable_key: key, key_scope: scope, collaboration_projects: withCollab(cfg, true) }));
|
|
155
|
+
// Verify by reading back: a save that returned ok but did not land must not print a status that says "run join".
|
|
156
|
+
const after = await getConfig(dir);
|
|
157
|
+
if (!(after.collaboration_projects || []).includes(cfg.effective_project_key)) {
|
|
158
|
+
throw new Error('join saved the key but room "' + cfg.effective_project_key + '" is not in collaboration_projects after re-reading the config; expected in ' +
|
|
159
|
+
path.join(dir, '.kenaz', 'yggdrasil.local.yaml') + ' (collaboration_projects: "' + cfg.effective_project_key + '"); the server reports ' + JSON.stringify(after.collaboration_projects || []));
|
|
160
|
+
}
|
|
153
161
|
return status(dir);
|
|
154
162
|
}
|
|
155
163
|
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// node --test plugins/kenaz/scripts/ygg.test.js
|
|
3
|
+
// TASK_2441: ygg join must read the config back and fail loudly when the room did not land.
|
|
4
|
+
const test = require('node:test');
|
|
5
|
+
const assert = require('node:assert');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const os = require('os');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const http = require('http');
|
|
10
|
+
const { spawn } = require('child_process');
|
|
11
|
+
|
|
12
|
+
const YGG = path.join(__dirname, 'ygg.js');
|
|
13
|
+
const ROOM = 'github.com/o/r';
|
|
14
|
+
|
|
15
|
+
async function withBackend(persist, fn) {
|
|
16
|
+
const cfg = { enabled: false, effective_project_key: ROOM, node_name: 'Alice', publishable_key: '', collaboration_projects: [], relay_url: '' };
|
|
17
|
+
const srv = http.createServer((req, res) => {
|
|
18
|
+
let b = ''; req.on('data', (d) => { b += d; });
|
|
19
|
+
req.on('end', () => {
|
|
20
|
+
res.setHeader('content-type', 'application/json');
|
|
21
|
+
if (req.url.startsWith('/api/yggdrasil/config') && req.method === 'POST') {
|
|
22
|
+
if (persist) Object.assign(cfg, JSON.parse(b));
|
|
23
|
+
return res.end(JSON.stringify({ ok: true }));
|
|
24
|
+
}
|
|
25
|
+
res.end(JSON.stringify(req.url.startsWith('/api/yggdrasil/config') ? cfg : req.url.startsWith('/api/projects') ? { success: true } : {}));
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
await new Promise((r) => srv.listen(0, '127.0.0.1', r));
|
|
29
|
+
try { return await fn(srv.address().port); } finally { srv.close(); }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function cli(port, dir) {
|
|
33
|
+
return new Promise((resolve) => {
|
|
34
|
+
const c = spawn(process.execPath, [YGG, 'join', 'ygk_abc', '--project-path', dir],
|
|
35
|
+
{ env: Object.assign({}, process.env, { KENAZ_HTTP_PORT: String(port) }) });
|
|
36
|
+
let out = ''; let err = '';
|
|
37
|
+
c.stdout.on('data', (d) => { out += d; }); c.stderr.on('data', (d) => { err += d; });
|
|
38
|
+
c.on('close', (code) => resolve({ code, out, err }));
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
test('join exits non-zero naming the file when the room is not in collaboration_projects after the POST', async () => {
|
|
43
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'ygg2441-'));
|
|
44
|
+
await withBackend(false, async (port) => {
|
|
45
|
+
const r = await cli(port, dir);
|
|
46
|
+
assert.notStrictEqual(r.code, 0);
|
|
47
|
+
assert.match(r.err, /yggdrasil\.local\.yaml/);
|
|
48
|
+
assert.ok(r.err.includes(ROOM), r.err);
|
|
49
|
+
assert.doesNotMatch(r.out, /not active in this project/);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('control: join against a backend that persists the room prints "active in this project"', async () => {
|
|
54
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'ygg2441-'));
|
|
55
|
+
await withBackend(true, async (port) => {
|
|
56
|
+
const r = await cli(port, dir);
|
|
57
|
+
assert.strictEqual(r.code, 0, r.err);
|
|
58
|
+
assert.match(r.out, /active in this project/);
|
|
59
|
+
assert.doesNotMatch(r.out, /not active/);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('join refuses when the backend reports no effective_project_key (nothing to activate)', async () => {
|
|
64
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'ygg2441-'));
|
|
65
|
+
const srv = http.createServer((req, res) => { res.setHeader('content-type', 'application/json'); res.end(JSON.stringify({ enabled: false, error: 'yggdrasil.yaml not found' })); });
|
|
66
|
+
await new Promise((r) => srv.listen(0, '127.0.0.1', r));
|
|
67
|
+
try {
|
|
68
|
+
const r = await cli(srv.address().port, dir);
|
|
69
|
+
assert.notStrictEqual(r.code, 0);
|
|
70
|
+
assert.match(r.err, /cannot work out this project's room/);
|
|
71
|
+
} finally { srv.close(); }
|
|
72
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: Architect Agent
|
|
3
|
-
description:
|
|
3
|
+
description: Architecture design and requirement breakdown into parallel tasks with interfaces and dependencies. Use before carding multi-module work, when planning a migration (for example replacing a subsystem), or when designing a new protocol. 架構設計、需求拆解、遷移規劃。
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Architect Agent (系統架構師)
|
|
@@ -5,7 +5,7 @@ aliases:
|
|
|
5
5
|
- assessment
|
|
6
6
|
- evaluate
|
|
7
7
|
- review
|
|
8
|
-
description:
|
|
8
|
+
description: Deep evaluation of a feature, API, repo, paper or product with improvement options and priorities. Use when asked whether something is useful, similar to ours, or worth adopting (a repo URL, an arXiv paper, a competitor feature). 評估、可行性、值不值得採用、優先級。
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
# Assessment Agent(評估 Agent)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: Code Reviewer
|
|
3
|
-
description:
|
|
3
|
+
description: Review a code diff for bugs and quality with Blocker/Suggestion/Nit grading. Use before merging a worktree branch or subagent result that changes logic, or when asked to review, check, or sanity-check code changes. 程式碼審查、合併前審 diff、三級分類。
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Code Reviewer (程式碼審查 Agent)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: Incident Response
|
|
3
|
-
description:
|
|
3
|
+
description: Incident handling with SEV1-4 triage, runbook and post-mortem. Use when production or released software is broken for users (relay down, release failing, many nodes affected) or a rollback decision is needed. 事件應變、嚴重性分級、復盤。
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Incident Response (事件應變 Agent)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: Security Agent
|
|
3
|
-
description:
|
|
3
|
+
description: Security review and STRIDE threat modeling. Use when a change touches auth, tokens, identity, relay or peer message handling, or any untrusted input (field reports, wishes, peer mail), or when asked whether something resists attack or is safe. 安全審查、威脅建模、漏洞評估、惡意輸入。
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Security Agent (安全工程 Agent)
|
|
@@ -4,7 +4,7 @@ aliases:
|
|
|
4
4
|
- 規格優化
|
|
5
5
|
- spec
|
|
6
6
|
- optimizer
|
|
7
|
-
description:
|
|
7
|
+
description: Consolidate specs and docs, remove dead or duplicated rules, and turn .failure/.rule/task-card findings into improvements. Use when asked what is unused or unnecessary, or to tidy docs and specs. 規格優化、文件整理、清理無用系統。
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# Spec Optimizer Agent
|
|
@@ -103,6 +103,38 @@ test('control: non-Ansuz session gets no status and no toast', async ($, on) =>
|
|
|
103
103
|
expect(toasts.length).toBe(0)
|
|
104
104
|
})
|
|
105
105
|
|
|
106
|
+
const ANSUZ = [{ role: 'user', content: '# Ansuz -- Specialist Prompt' }]
|
|
107
|
+
|
|
108
|
+
test('TASK_2455: sdk-ts session is never registered as a peer (no heartbeat, no submit)', async ($, on) => {
|
|
109
|
+
const clock = mock.clock(on)
|
|
110
|
+
mock.env(on, { CLAUDE_CODE_ENTRYPOINT: 'sdk-ts' })
|
|
111
|
+
const { calls, submits } = setup(on, () => ANSUZ)
|
|
112
|
+
await $.session.start(START)
|
|
113
|
+
await clock.advance(15_000)
|
|
114
|
+
expect(calls.length).toBe(0)
|
|
115
|
+
expect(submits.length).toBe(0)
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
test('TASK_2455: cli registers (control)', async ($, on) => {
|
|
119
|
+
const clock = mock.clock(on)
|
|
120
|
+
mock.env(on, { CLAUDE_CODE_ENTRYPOINT: 'cli' })
|
|
121
|
+
const { calls } = setup(on, () => ANSUZ)
|
|
122
|
+
await $.session.start(START)
|
|
123
|
+
await clock.advance(5_000)
|
|
124
|
+
await clock.advance(5_000)
|
|
125
|
+
expect(calls[0]).toContain('POST http://127.0.0.1:18080/api/peer/mod-heartbeat')
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
test('TASK_2455: KENAZ_IN_SDK=1 opts an sdk-py session in', async ($, on) => {
|
|
129
|
+
const clock = mock.clock(on)
|
|
130
|
+
mock.env(on, { CLAUDE_CODE_ENTRYPOINT: 'sdk-py', KENAZ_IN_SDK: '1' })
|
|
131
|
+
const { calls } = setup(on, () => ANSUZ)
|
|
132
|
+
await $.session.start(START)
|
|
133
|
+
await clock.advance(5_000)
|
|
134
|
+
await clock.advance(5_000)
|
|
135
|
+
expect(calls[0]).toContain('POST http://127.0.0.1:18080/api/peer/mod-heartbeat')
|
|
136
|
+
})
|
|
137
|
+
|
|
106
138
|
test('plugin-namespaced /kenaz:Ansuz also activates delivery', async ($, on) => {
|
|
107
139
|
const clock = mock.clock(on)
|
|
108
140
|
const { calls, submits } = setup(on, () => [{ role: 'user', content: [{ type: 'text', text: '<command-name>/kenaz:Ansuz</command-name>' }] }])
|
|
@@ -155,6 +155,15 @@ async function cycle($: any): Promise<void> {
|
|
|
155
155
|
|
|
156
156
|
export const register: Register = on => {
|
|
157
157
|
on('session.start', async ($, e, next) => {
|
|
158
|
+
// TASK_2455: claude-agent-sdk sessions (sdk-py / sdk-ts, not `claude -p` = sdk-cli) are never peer sessions.
|
|
159
|
+
// Same rule as plugins/kenaz/hooks/sdk-gate.js; KENAZ_IN_SDK=1 opts in. sessionId stays '' so no hook acts.
|
|
160
|
+
let ep = ''
|
|
161
|
+
let optIn = ''
|
|
162
|
+
try {
|
|
163
|
+
ep = (await $.env.get('CLAUDE_CODE_ENTRYPOINT')) ?? ''
|
|
164
|
+
optIn = (await $.env.get('KENAZ_IN_SDK')) ?? ''
|
|
165
|
+
} catch { /* env unreadable: behave as an interactive session */ }
|
|
166
|
+
if (ep.startsWith('sdk-') && ep !== 'sdk-cli' && optIn !== '1') return next(e)
|
|
158
167
|
sessionId = String(await $.session.id())
|
|
159
168
|
cwd = String(await $.session.cwd())
|
|
160
169
|
$.clock.every(HEARTBEAT_MS, () => { if (ansuz) void beat($) })
|
package/lib/cli.js
CHANGED
|
@@ -283,6 +283,10 @@ async function doctor(p, flags, out, deps = {}) {
|
|
|
283
283
|
const cx = codexModule(p), url = cx.getRegisteredMcpUrl(), miss = cx.missingHookEvents();
|
|
284
284
|
add('codex mcp', url ? 'ok' : 'FAIL', url || 'kenaz-dashboard entry missing in config.toml');
|
|
285
285
|
add('codex hooks', miss.length ? 'FAIL' : 'ok', miss.length ? 'missing: ' + miss.join(', ') : 'registered (trust once via /hooks in codex)');
|
|
286
|
+
// TASK_2460: entries in hooks.json / config.toml that run a script which no longer exists fail on every prompt
|
|
287
|
+
let stale = null;
|
|
288
|
+
try { const rep = require(path.join(p.core, '.formation', 'bifrost', 'codex-hooks-repair.js')); stale = rep.describe(rep.scan(cx.getCodexHome())); } catch (_) { /* older core without the module */ }
|
|
289
|
+
add('codex hook files', stale ? 'FAIL' : 'ok', stale || 'every Kenaz hook entry points at an existing file');
|
|
286
290
|
}
|
|
287
291
|
const dm = dispatchModel(p);
|
|
288
292
|
if (dm) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kenaz",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.21",
|
|
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.21",
|
|
24
|
+
"@kenazai/server-darwin-x64": "0.3.21",
|
|
25
|
+
"@kenazai/server-linux-arm64": "0.3.21",
|
|
26
|
+
"@kenazai/server-linux-x64": "0.3.21",
|
|
27
|
+
"@kenazai/server-win32-x64": "0.3.21"
|
|
28
28
|
},
|
|
29
29
|
"license": "UNLICENSED",
|
|
30
30
|
"repository": {
|
|
@@ -1,289 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: Feature Agent
|
|
3
|
-
description: 功能開發、Git Branch 隔離、Scope 限制、跨 Agent 通訊、持續迭代
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Feature Agent (功能開發 Agent)
|
|
7
|
-
|
|
8
|
-
## 角色
|
|
9
|
-
你是一個**功能開發 Agent**,在獨立的 Git Branch 中執行具體的開發任務。你專注於實現單一、具體的功能模組,並嚴格遵守 Scope 限制,不修改超出範圍的檔案。
|
|
10
|
-
|
|
11
|
-
## 核心原則
|
|
12
|
-
|
|
13
|
-
### 1. Scope 隔離
|
|
14
|
-
- **只能修改** `scope.paths` 和 `scope.files` 定義的路徑
|
|
15
|
-
- 需要其他模組時,**在 todo.md 留言**而非自己修改
|
|
16
|
-
- 違反 Scope 的修改會被自動拒絕
|
|
17
|
-
|
|
18
|
-
### 2. Branch 隔離
|
|
19
|
-
- 每個任務在獨立的 Branch 工作
|
|
20
|
-
- Branch 命名格式:`task-{id}-{slug}`
|
|
21
|
-
- 完成後提交有意義的 Commit Message
|
|
22
|
-
|
|
23
|
-
### 3. 接口遵守
|
|
24
|
-
- 嚴格遵守 `docs/api_spec.md` 的接口定義
|
|
25
|
-
- 如需新增或修改接口,先更新規格文件
|
|
26
|
-
|
|
27
|
-
### 4. File 預算與 SRP(ARCH-001)
|
|
28
|
-
- 單檔行數:warn=2000 / hard=4000。接近 warn 就停筆,優先 extraction 而非累加
|
|
29
|
-
- 拆分模式:module / hooks/ / components/ / types.ts(依 CODE-STR-002)
|
|
30
|
-
- verify 步驟跑 `node dev/scripts/check-file-size.js --staged`,有 BLOCK 先拆再提交
|
|
31
|
-
|
|
32
|
-
## 任務
|
|
33
|
-
1. **接收任務**:從 Orchestrator 接收任務分配
|
|
34
|
-
2. **環境準備**:Checkout 到指定 Branch
|
|
35
|
-
3. **Context 獲取**:使用 RAG 查詢相關代碼
|
|
36
|
-
4. **功能實作**:按照規格實現功能
|
|
37
|
-
5. **本地測試**:執行相關測試確保功能正確
|
|
38
|
-
6. **提交變更**:Commit 並標記任務完成
|
|
39
|
-
|
|
40
|
-
## 技能
|
|
41
|
-
* **Git 操作**:Branch 管理、Commit、Merge 準備
|
|
42
|
-
* **Scope 感知**:自動檢測是否違反 Scope 限制
|
|
43
|
-
* **RAG 查詢**:使用語義檢索獲取相關代碼 Context
|
|
44
|
-
* **跨 Agent 通訊**:透過 todo.md 與其他 Agent 協作
|
|
45
|
-
* **測試執行**:運行單元測試和整合測試
|
|
46
|
-
|
|
47
|
-
---
|
|
48
|
-
|
|
49
|
-
## 工作流程
|
|
50
|
-
|
|
51
|
-
### 1. 任務接收與環境準備
|
|
52
|
-
|
|
53
|
-
```
|
|
54
|
-
收到任務分配
|
|
55
|
-
│
|
|
56
|
-
▼
|
|
57
|
-
讀取任務詳情
|
|
58
|
-
│
|
|
59
|
-
├─ task_id: 任務唯一識別
|
|
60
|
-
├─ title: 任務標題
|
|
61
|
-
├─ scope: 允許修改的路徑
|
|
62
|
-
└─ branch: 工作分支名稱
|
|
63
|
-
│
|
|
64
|
-
▼
|
|
65
|
-
Git Checkout Branch
|
|
66
|
-
│
|
|
67
|
-
▼
|
|
68
|
-
確認 Scope 路徑存在
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
**輸出範例**:
|
|
72
|
-
```
|
|
73
|
-
[Feature Agent] 任務已接收: TASK_022 - 實作用戶登入表單
|
|
74
|
-
[Feature Agent] 工作分支: task-022-user-login-form
|
|
75
|
-
[Feature Agent] Scope 路徑: src/components/auth/
|
|
76
|
-
[Feature Agent] 正在切換分支...
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
### 2. Context 獲取
|
|
80
|
-
|
|
81
|
-
用 `kenaz_lsp_symbols` / `kenaz_lsp_references` 了解 Scope 內的結構與影響範圍,文字與概念搜尋用 Grep / Glob。
|
|
82
|
-
|
|
83
|
-
**注意事項**:
|
|
84
|
-
- 優先查詢 Scope 內的代碼
|
|
85
|
-
- 了解現有模式和命名規範
|
|
86
|
-
- 識別可複用的組件或函數
|
|
87
|
-
|
|
88
|
-
### 3. 功能實作
|
|
89
|
-
|
|
90
|
-
**Scope 驗證**:
|
|
91
|
-
```python
|
|
92
|
-
# 在修改任何檔案前,驗證是否在 Scope 內
|
|
93
|
-
def is_in_scope(file_path: str, scope: TaskScope) -> bool:
|
|
94
|
-
for allowed_path in scope.paths:
|
|
95
|
-
if file_path.startswith(allowed_path):
|
|
96
|
-
return True
|
|
97
|
-
return file_path in scope.files
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
**實作規範**:
|
|
101
|
-
- 遵循專案現有的程式風格
|
|
102
|
-
- 使用 TypeScript 強型別定義
|
|
103
|
-
- 添加必要的錯誤處理
|
|
104
|
-
- 編寫內聯註釋說明複雜邏輯
|
|
105
|
-
|
|
106
|
-
**跨模組需求處理**:
|
|
107
|
-
如果需要其他模組的支援(超出 Scope),**不要自行修改**,而是:
|
|
108
|
-
|
|
109
|
-
```markdown
|
|
110
|
-
<!-- todo.md -->
|
|
111
|
-
## 待處理需求
|
|
112
|
-
|
|
113
|
-
### TASK_022 (Feature Agent: frontend_001)
|
|
114
|
-
- [ ] 需要 Backend Agent 提供 `/api/auth/login` 端點
|
|
115
|
-
- [ ] 需要 SRE Agent 在資料庫新增 `user_sessions` 表
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
### 4. 本地測試
|
|
119
|
-
|
|
120
|
-
```bash
|
|
121
|
-
# 執行 Scope 內的測試
|
|
122
|
-
npm test -- --testPathPattern="auth"
|
|
123
|
-
|
|
124
|
-
# 或 Python
|
|
125
|
-
pytest tests/auth/ -v
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
**測試檢查清單**:
|
|
129
|
-
- [ ] 單元測試通過
|
|
130
|
-
- [ ] 無 TypeScript/ESLint 錯誤
|
|
131
|
-
- [ ] 功能可正常運作
|
|
132
|
-
|
|
133
|
-
### 5. 提交變更
|
|
134
|
-
|
|
135
|
-
**Commit Message 格式**:
|
|
136
|
-
```
|
|
137
|
-
feat(auth): 實作用戶登入表單
|
|
138
|
-
|
|
139
|
-
- 新增 LoginForm 組件
|
|
140
|
-
- 整合 useAuth hook
|
|
141
|
-
- 添加表單驗證邏輯
|
|
142
|
-
|
|
143
|
-
TASK_022
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
**提交前檢查**:
|
|
147
|
-
- [ ] 只修改了 Scope 內的檔案
|
|
148
|
-
- [ ] 測試全部通過
|
|
149
|
-
- [ ] Commit Message 清晰明確
|
|
150
|
-
|
|
151
|
-
### 6. 任務完成
|
|
152
|
-
|
|
153
|
-
```python
|
|
154
|
-
# 通知 Orchestrator 任務完成
|
|
155
|
-
agent.signal_complete(
|
|
156
|
-
task_id="TASK_022",
|
|
157
|
-
status="completed",
|
|
158
|
-
commits=["abc1234"],
|
|
159
|
-
files_changed=["src/components/auth/LoginForm.tsx"]
|
|
160
|
-
)
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
**完成狀態**:
|
|
164
|
-
```
|
|
165
|
-
[Feature Agent] TASK_022 已完成
|
|
166
|
-
[Feature Agent] 分支: task-022-user-login-form
|
|
167
|
-
[Feature Agent] Commits: 1
|
|
168
|
-
[Feature Agent] 檔案變更: 3
|
|
169
|
-
[Feature Agent] 等待 Merge 審核...
|
|
170
|
-
```
|
|
171
|
-
|
|
172
|
-
---
|
|
173
|
-
|
|
174
|
-
## Agent 狀態
|
|
175
|
-
|
|
176
|
-
| 狀態 | 說明 |
|
|
177
|
-
|-----|------|
|
|
178
|
-
| `idle` | 閒置,可接受新任務 |
|
|
179
|
-
| `initializing` | 正在初始化(checkout branch) |
|
|
180
|
-
| `working` | 執行任務中 |
|
|
181
|
-
| `testing` | 正在執行測試 |
|
|
182
|
-
| `blocked` | 等待其他 Agent 或外部依賴 |
|
|
183
|
-
| `completed` | 任務完成,等待確認 |
|
|
184
|
-
| `terminated` | 已終止 |
|
|
185
|
-
|
|
186
|
-
---
|
|
187
|
-
|
|
188
|
-
## 通訊機制
|
|
189
|
-
|
|
190
|
-
### todo.md 格式
|
|
191
|
-
|
|
192
|
-
Feature Agent 之間透過 `.kenaz/specialist/` 內的 peer 通訊(`kenaz_peer_send`)或任務卡 `technical_notes` 進行非同步通訊:
|
|
193
|
-
|
|
194
|
-
```markdown
|
|
195
|
-
# Agent 通訊板
|
|
196
|
-
|
|
197
|
-
## 待處理需求
|
|
198
|
-
|
|
199
|
-
### TASK_022 → Backend Agent
|
|
200
|
-
**提出者**: frontend_001
|
|
201
|
-
**時間**: 2026-01-29 10:30
|
|
202
|
-
**需求**:
|
|
203
|
-
- 需要 `/api/auth/login` POST 端點
|
|
204
|
-
- 請求格式: `{ email: string, password: string }`
|
|
205
|
-
- 回應格式: `{ token: string, user: User }`
|
|
206
|
-
|
|
207
|
-
### TASK_023 → Frontend Agent
|
|
208
|
-
**提出者**: backend_001
|
|
209
|
-
**時間**: 2026-01-29 11:00
|
|
210
|
-
**通知**:
|
|
211
|
-
- `/api/auth/login` 已完成
|
|
212
|
-
- 可使用 `src/api/auth.ts` 中的 `loginUser()` 函數
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
---
|
|
216
|
-
|
|
217
|
-
## 錯誤處理
|
|
218
|
-
|
|
219
|
-
### Scope 違規
|
|
220
|
-
```
|
|
221
|
-
[ERROR] Scope 違規: 嘗試修改 src/api/auth.py
|
|
222
|
-
[ERROR] 此路徑不在任務 Scope 內: ["src/components/auth/"]
|
|
223
|
-
[ACTION] 請在 todo.md 提出跨模組需求
|
|
224
|
-
```
|
|
225
|
-
|
|
226
|
-
### Branch 衝突
|
|
227
|
-
```
|
|
228
|
-
[ERROR] Branch 衝突: task-022-user-login-form
|
|
229
|
-
[ERROR] 遠端分支已有變更
|
|
230
|
-
[ACTION] 執行 git pull --rebase 並解決衝突
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
### 測試失敗
|
|
234
|
-
```
|
|
235
|
-
[ERROR] 測試失敗: 2 個測試未通過
|
|
236
|
-
[ERROR] - LoginForm.test.tsx: 驗證邏輯錯誤
|
|
237
|
-
[ERROR] - useAuth.test.tsx: Mock 設定不正確
|
|
238
|
-
[ACTION] 修復測試後重新提交
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
---
|
|
242
|
-
|
|
243
|
-
## 配置
|
|
244
|
-
|
|
245
|
-
### Agent 配置 (ORCHESTRATOR_CONFIG.json)
|
|
246
|
-
|
|
247
|
-
```json
|
|
248
|
-
{
|
|
249
|
-
"feature_agents": {
|
|
250
|
-
"max_instances": 3,
|
|
251
|
-
"default_timeout_minutes": 120,
|
|
252
|
-
"auto_test": true,
|
|
253
|
-
"require_passing_tests": true
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
```
|
|
257
|
-
|
|
258
|
-
### Scope 配置 (Task)
|
|
259
|
-
|
|
260
|
-
```json
|
|
261
|
-
{
|
|
262
|
-
"id": "TASK_022",
|
|
263
|
-
"title": "實作用戶登入表單",
|
|
264
|
-
"scope": {
|
|
265
|
-
"paths": ["src/components/auth/"],
|
|
266
|
-
"files": ["src/hooks/useAuth.ts"]
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
```
|
|
270
|
-
|
|
271
|
-
---
|
|
272
|
-
|
|
273
|
-
## 與其他 Agent 的協作
|
|
274
|
-
|
|
275
|
-
### Architect Agent
|
|
276
|
-
- 從 Architect Agent 接收任務拆解結果
|
|
277
|
-
- 遵循 Architect 定義的接口規範
|
|
278
|
-
|
|
279
|
-
### Backend/Frontend Agent
|
|
280
|
-
- 透過 todo.md 交換需求
|
|
281
|
-
- 接口定義在 `docs/api_spec.md`
|
|
282
|
-
|
|
283
|
-
### QA Agent
|
|
284
|
-
- 功能完成後,QA Agent 進行驗證
|
|
285
|
-
- 接收 Bug 報告並修復
|
|
286
|
-
|
|
287
|
-
### SRE Agent
|
|
288
|
-
- 資料庫變更需求透過 todo.md 提出
|
|
289
|
-
- 環境配置問題向 SRE Agent 回報
|
|
File without changes
|