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.
Files changed (65) hide show
  1. package/core/.claude/commands/Ansuz.md +1 -0
  2. package/core/.claude/commands/architect.md +3 -0
  3. package/core/.claude/commands/incident.md +3 -0
  4. package/core/.claude/commands/review.md +3 -0
  5. package/core/.claude/commands/security.md +3 -0
  6. package/core/.claude/commands/spec.md +3 -0
  7. package/core/.claude/skills/architect_agent/SKILL.md +1 -1
  8. package/core/.claude/skills/assessment_agent/SKILL.md +1 -1
  9. package/core/.claude/skills/code_reviewer/SKILL.md +1 -1
  10. package/core/.claude/skills/incident_response/SKILL.md +1 -1
  11. package/core/.claude/skills/security_agent/SKILL.md +1 -1
  12. package/core/.claude/skills/spec_optimizer/SKILL.md +1 -1
  13. package/core/.formation/bifrost/codex-hooks-repair.js +115 -0
  14. package/core/.formation/bifrost/codex-hooks-repair.test.js +94 -0
  15. package/core/.formation/bifrost/openai/codex-hook-shim.js +5 -3
  16. package/core/.formation/bifrost/openai/codex-hook-shim.test.js +2 -0
  17. package/core/.formation/bifrost/setup-codex.js +22 -12
  18. package/core/.formation/tools/__tests__/arms.test.js +41 -0
  19. package/core/.formation/tools/__tests__/diagnose-bug.test.js +10 -0
  20. package/core/.formation/tools/arms.js +48 -0
  21. package/core/.formation/tools/dispatch.js +23 -14
  22. package/core/.formation/tools/kenaz-stats.js +32 -8
  23. package/core/.formation/tools/kenaz-stats.test.js +15 -2
  24. package/core/.formation/tools/prompt.js +16 -1
  25. package/core/.formation/tools/prompt.test.js +20 -0
  26. package/core/.specialist/Ansuz/RULES.md +7 -0
  27. package/core/.specialist/tools/ansuz-boot-fast.js +7 -4
  28. package/core/.specialist/tools/ansuz-boot-fast.test.js +10 -3
  29. package/core/KENAZ_CORE_VERSION +1 -1
  30. package/core/dev/scripts/peer-identity-e2e.js +402 -0
  31. package/core/dev/scripts/peer-identity-e2e.md +47 -0
  32. package/core/dev/scripts/relay-admin-ui.js +154 -0
  33. package/core/dev/scripts/relay-admin-ui.test.js +112 -0
  34. package/core/dev/scripts/relay-admin.js +103 -0
  35. package/core/dev/scripts/relay-admin.test.js +131 -0
  36. package/core/dev/scripts/skill-routing-replay.js +80 -0
  37. package/core/dev/scripts/skill-routing-replay.test.js +24 -0
  38. package/core/dev/specs/peer/codex-hook-shim-peer.test.js +17 -5
  39. package/core/plugins/kenaz/INVENTORY.md +1 -1
  40. package/core/plugins/kenaz/README.md +10 -0
  41. package/core/plugins/kenaz/commands/Ansuz.md +1 -0
  42. package/core/plugins/kenaz/commands/architect.md +3 -0
  43. package/core/plugins/kenaz/commands/incident.md +3 -0
  44. package/core/plugins/kenaz/commands/review.md +3 -0
  45. package/core/plugins/kenaz/commands/security.md +3 -0
  46. package/core/plugins/kenaz/commands/spec.md +3 -0
  47. package/core/plugins/kenaz/hooks/kenaz-hook.js +2 -1
  48. package/core/plugins/kenaz/hooks/sdk-gate.js +9 -0
  49. package/core/plugins/kenaz/scripts/kenaz-mode.js +5 -0
  50. package/core/plugins/kenaz/scripts/kenaz.test.js +15 -3
  51. package/core/plugins/kenaz/scripts/sync.js +1 -1
  52. package/core/plugins/kenaz/scripts/ygg.js +8 -0
  53. package/core/plugins/kenaz/scripts/ygg.test.js +72 -0
  54. package/core/plugins/kenaz/skills/architect_agent/SKILL.md +1 -1
  55. package/core/plugins/kenaz/skills/assessment_agent/SKILL.md +1 -1
  56. package/core/plugins/kenaz/skills/code_reviewer/SKILL.md +1 -1
  57. package/core/plugins/kenaz/skills/incident_response/SKILL.md +1 -1
  58. package/core/plugins/kenaz/skills/security_agent/SKILL.md +1 -1
  59. package/core/plugins/kenaz/skills/spec_optimizer/SKILL.md +1 -1
  60. package/core/plugins/kenaz-peer/hooks/register.test.ts +32 -0
  61. package/core/plugins/kenaz-peer/hooks/register.ts +9 -0
  62. package/lib/cli.js +4 -0
  63. package/package.json +6 -6
  64. package/core/plugins/kenaz/skills/feature_agent/SKILL.md +0 -289
  65. /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', 'feature_agent', 'frontend_agent', 'incident_response', 'product_manager',
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: 架構設計、需求拆解、任務分派、接口定義、多Agent協調
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: 功能/頁面/組件/API 深度評估、改良建議、優先級規劃、技術方案設計
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: 程式碼審查、三級問題分類(Blocker/Suggestion/Nit)、教育式回饋、品質守門
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: 事件應變、嚴重性矩陣(SEV1-4)、Runbook 模板、Post-Mortem、On-Call 輪值
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: 安全工程、STRIDE 威脅建模、安全 SDLC、CI/CD 安全掃描、漏洞評估
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: 專案規格優化、文件整理、知識沉澱專家。根據 .failure、.rule 與任務卡進行系統性改良。
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.19",
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.19",
24
- "@kenazai/server-darwin-x64": "0.3.19",
25
- "@kenazai/server-linux-arm64": "0.3.19",
26
- "@kenazai/server-linux-x64": "0.3.19",
27
- "@kenazai/server-win32-x64": "0.3.19"
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 回報