agent-hive 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +95 -0
  2. package/dist/cli.d.ts +3 -0
  3. package/dist/cli.js +61 -0
  4. package/dist/cli.js.map +1 -0
  5. package/dist/commands/get.d.ts +2 -0
  6. package/dist/commands/get.js +39 -0
  7. package/dist/commands/get.js.map +1 -0
  8. package/dist/commands/init.d.ts +2 -0
  9. package/dist/commands/init.js +64 -0
  10. package/dist/commands/init.js.map +1 -0
  11. package/dist/config.d.ts +15 -0
  12. package/dist/config.js +52 -0
  13. package/dist/config.js.map +1 -0
  14. package/dist/core/errors.d.ts +14 -0
  15. package/dist/core/errors.js +27 -0
  16. package/dist/core/errors.js.map +1 -0
  17. package/dist/core/logger.d.ts +7 -0
  18. package/dist/core/logger.js +37 -0
  19. package/dist/core/logger.js.map +1 -0
  20. package/dist/core/session-spawner.d.ts +9 -0
  21. package/dist/core/session-spawner.js +73 -0
  22. package/dist/core/session-spawner.js.map +1 -0
  23. package/dist/github/issues.d.ts +26 -0
  24. package/dist/github/issues.js +115 -0
  25. package/dist/github/issues.js.map +1 -0
  26. package/dist/github/pr.d.ts +24 -0
  27. package/dist/github/pr.js +92 -0
  28. package/dist/github/pr.js.map +1 -0
  29. package/dist/obsidian/session-log.d.ts +14 -0
  30. package/dist/obsidian/session-log.js +62 -0
  31. package/dist/obsidian/session-log.js.map +1 -0
  32. package/dist/obsidian/sot-sync.d.ts +11 -0
  33. package/dist/obsidian/sot-sync.js +72 -0
  34. package/dist/obsidian/sot-sync.js.map +1 -0
  35. package/dist/obsidian/vault-init.d.ts +3 -0
  36. package/dist/obsidian/vault-init.js +44 -0
  37. package/dist/obsidian/vault-init.js.map +1 -0
  38. package/dist/prompts/lead.md +57 -0
  39. package/dist/prompts/reviewer.md +67 -0
  40. package/dist/prompts/worker.md +68 -0
  41. package/package.json +54 -0
@@ -0,0 +1,115 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { promisify } from 'node:util';
3
+ import { GithubError } from '../core/errors.js';
4
+ const execFileAsync = promisify(execFile);
5
+ const IssueLabel = {
6
+ Backlog: 'backlog',
7
+ InProgress: 'in-progress',
8
+ Blocked: 'blocked',
9
+ };
10
+ export { IssueLabel };
11
+ async function gh(config, args) {
12
+ const { stdout } = await execFileAsync('gh', args, {
13
+ cwd: config.targetRepo,
14
+ env: { ...process.env },
15
+ });
16
+ return stdout.trim();
17
+ }
18
+ async function findCreatedIssue(config, title) {
19
+ const listOutput = await gh(config, [
20
+ 'issue',
21
+ 'list',
22
+ '--repo',
23
+ config.github.repo,
24
+ '--search',
25
+ title,
26
+ '--json',
27
+ 'number,title,state,labels,url',
28
+ '--limit',
29
+ '1',
30
+ ]);
31
+ const issues = JSON.parse(listOutput);
32
+ const issue = issues[0];
33
+ if (!issue) {
34
+ throw new GithubError(`Failed to find created issue: ${title}`);
35
+ }
36
+ return {
37
+ number: issue.number,
38
+ title: issue.title,
39
+ state: issue.state,
40
+ labels: issue.labels.map((l) => l.name),
41
+ url: issue.url,
42
+ };
43
+ }
44
+ export async function getIssue(config, issueRef) {
45
+ const output = await gh(config, [
46
+ 'issue',
47
+ 'view',
48
+ issueRef,
49
+ '--repo',
50
+ config.github.repo,
51
+ '--json',
52
+ 'number,title,state,labels,url,body',
53
+ ]);
54
+ const data = JSON.parse(output);
55
+ return {
56
+ number: data.number,
57
+ title: data.title,
58
+ state: data.state,
59
+ labels: data.labels.map((l) => l.name),
60
+ url: data.url,
61
+ body: data.body,
62
+ };
63
+ }
64
+ export async function createIssue(config, options) {
65
+ const args = [
66
+ 'issue',
67
+ 'create',
68
+ '--repo',
69
+ config.github.repo,
70
+ '--title',
71
+ options.title,
72
+ '--body',
73
+ options.body,
74
+ ];
75
+ if (options.labels?.length) {
76
+ args.push('--label', options.labels.join(','));
77
+ }
78
+ await gh(config, args);
79
+ return findCreatedIssue(config, options.title);
80
+ }
81
+ export async function updateLabel(config, issueNumber, label) {
82
+ const labelsToRemove = Object.values(IssueLabel).filter((l) => l !== label);
83
+ for (const removeLabel of labelsToRemove) {
84
+ await gh(config, [
85
+ 'issue',
86
+ 'edit',
87
+ String(issueNumber),
88
+ '--repo',
89
+ config.github.repo,
90
+ '--remove-label',
91
+ removeLabel,
92
+ ]).catch(() => { });
93
+ }
94
+ await gh(config, [
95
+ 'issue',
96
+ 'edit',
97
+ String(issueNumber),
98
+ '--repo',
99
+ config.github.repo,
100
+ '--add-label',
101
+ label,
102
+ ]);
103
+ }
104
+ export async function closeIssue(config, issueNumber) {
105
+ await gh(config, [
106
+ 'issue',
107
+ 'close',
108
+ String(issueNumber),
109
+ '--repo',
110
+ config.github.repo,
111
+ '--reason',
112
+ 'completed',
113
+ ]);
114
+ }
115
+ //# sourceMappingURL=issues.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"issues.js","sourceRoot":"","sources":["../../src/github/issues.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import type { ColonyConfig } from '../config.js';
2
+ export interface PrInfo {
3
+ number: number;
4
+ title: string;
5
+ state: 'open' | 'merged' | 'closed';
6
+ branch: string;
7
+ url: string;
8
+ }
9
+ export interface PrComment {
10
+ id: number;
11
+ body: string;
12
+ author: string;
13
+ createdAt: string;
14
+ }
15
+ export declare function createPr(config: ColonyConfig, options: {
16
+ title: string;
17
+ body: string;
18
+ base?: string;
19
+ head: string;
20
+ }): Promise<PrInfo>;
21
+ export declare function getPrStatus(config: ColonyConfig, prNumber: number): Promise<PrInfo>;
22
+ export declare function addPrComment(config: ColonyConfig, prNumber: number, body: string): Promise<void>;
23
+ export declare function getPrComments(config: ColonyConfig, prNumber: number): Promise<PrComment[]>;
24
+ //# sourceMappingURL=pr.d.ts.map
@@ -0,0 +1,92 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { promisify } from 'node:util';
3
+ const execFileAsync = promisify(execFile);
4
+ async function gh(config, args) {
5
+ const { stdout } = await execFileAsync('gh', args, {
6
+ cwd: config.targetRepo,
7
+ env: { ...process.env },
8
+ });
9
+ return stdout.trim();
10
+ }
11
+ function buildCreatePrArgs(repo, baseBranch, options) {
12
+ return [
13
+ 'pr',
14
+ 'create',
15
+ '--repo',
16
+ repo,
17
+ '--title',
18
+ options.title,
19
+ '--body',
20
+ options.body,
21
+ '--head',
22
+ options.head,
23
+ '--base',
24
+ options.base ?? baseBranch,
25
+ '--json',
26
+ 'number,title,state,headRefName,url',
27
+ ];
28
+ }
29
+ export async function createPr(config, options) {
30
+ const args = buildCreatePrArgs(config.github.repo, config.github.baseBranch, options);
31
+ const output = await gh(config, args);
32
+ const data = JSON.parse(output);
33
+ return {
34
+ number: data.number,
35
+ title: data.title,
36
+ state: data.state,
37
+ branch: data.headRefName,
38
+ url: data.url,
39
+ };
40
+ }
41
+ export async function getPrStatus(config, prNumber) {
42
+ const output = await gh(config, [
43
+ 'pr',
44
+ 'view',
45
+ String(prNumber),
46
+ '--repo',
47
+ config.github.repo,
48
+ '--json',
49
+ 'number,title,state,headRefName,url',
50
+ ]);
51
+ const data = JSON.parse(output);
52
+ return {
53
+ number: data.number,
54
+ title: data.title,
55
+ state: data.state,
56
+ branch: data.headRefName,
57
+ url: data.url,
58
+ };
59
+ }
60
+ export async function addPrComment(config, prNumber, body) {
61
+ await gh(config, [
62
+ 'pr',
63
+ 'comment',
64
+ String(prNumber),
65
+ '--repo',
66
+ config.github.repo,
67
+ '--body',
68
+ body,
69
+ ]);
70
+ }
71
+ export async function getPrComments(config, prNumber) {
72
+ const output = await gh(config, [
73
+ 'api',
74
+ `repos/${config.github.repo}/issues/${prNumber}/comments`,
75
+ '--jq',
76
+ '.[].id, .[].body, .[].user.login, .[].created_at',
77
+ ]);
78
+ if (!output)
79
+ return [];
80
+ const rawOutput = await gh(config, [
81
+ 'api',
82
+ `repos/${config.github.repo}/issues/${prNumber}/comments`,
83
+ ]);
84
+ const data = JSON.parse(rawOutput);
85
+ return data.map((c) => ({
86
+ id: c.id,
87
+ body: c.body,
88
+ author: c.user.login,
89
+ createdAt: c.created_at,
90
+ }));
91
+ }
92
+ //# sourceMappingURL=pr.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pr.js","sourceRoot":"","sources":["../../src/github/pr.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { ColonyConfig } from '../config.js';
2
+ export interface SessionLogOptions {
3
+ role: 'worker' | 'reviewer';
4
+ branch: string;
5
+ issueNumber?: number;
6
+ prNumber?: number;
7
+ }
8
+ export declare function createLog(config: ColonyConfig, options: SessionLogOptions): Promise<string>;
9
+ export declare function append(logPath: string, content: string): Promise<void>;
10
+ export declare function appendDecision(logPath: string, decision: string): Promise<void>;
11
+ export declare function appendSotCandidate(logPath: string, content: string): Promise<void>;
12
+ export declare function appendBlocker(logPath: string, reason: string, issueNumber: number): Promise<void>;
13
+ export declare function closeSummary(logPath: string, summary: string): Promise<void>;
14
+ //# sourceMappingURL=session-log.d.ts.map
@@ -0,0 +1,62 @@
1
+ import { mkdir, appendFile, writeFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { ObsidianError } from '../core/errors.js';
4
+ function formatDate(date) {
5
+ return date.toISOString().slice(0, 10);
6
+ }
7
+ function buildLogPath(config, options) {
8
+ const sanitizedBranch = options.branch.replace(/\//g, '-');
9
+ return path.join(config.obsidian.vaultPath, 'sessions', `${options.role}-${sanitizedBranch}-${formatDate(new Date())}.md`);
10
+ }
11
+ export async function createLog(config, options) {
12
+ if (!config.obsidian) {
13
+ throw new ObsidianError('Obsidian is not enabled');
14
+ }
15
+ const logPath = buildLogPath(config, options);
16
+ const sessionsDir = path.dirname(logPath);
17
+ await mkdir(sessionsDir, { recursive: true });
18
+ const lines = [
19
+ `# ${options.role} 세션 기록서`,
20
+ '',
21
+ `- **브랜치**: ${options.branch}`,
22
+ `- **시작 시각**: ${new Date().toISOString()}`,
23
+ ];
24
+ if (options.issueNumber) {
25
+ lines.push(`- **관련 Issue**: #${options.issueNumber}`);
26
+ }
27
+ if (options.prNumber) {
28
+ lines.push(`- **관련 PR**: #${options.prNumber}`);
29
+ }
30
+ lines.push('', '---', '');
31
+ await writeFile(logPath, lines.join('\n'), 'utf-8');
32
+ return logPath;
33
+ }
34
+ export async function append(logPath, content) {
35
+ const timestamp = new Date().toISOString().slice(11, 19);
36
+ const line = `- \`${timestamp}\` ${content}\n`;
37
+ await appendFile(logPath, line, 'utf-8');
38
+ }
39
+ export async function appendDecision(logPath, decision) {
40
+ await append(logPath, `**[DECISION]** ${decision}`);
41
+ }
42
+ export async function appendSotCandidate(logPath, content) {
43
+ await append(logPath, `**[SSoT]** ${content}`);
44
+ }
45
+ export async function appendBlocker(logPath, reason, issueNumber) {
46
+ await append(logPath, `**[BLOCKER]** ${reason} (Issue #${issueNumber})`);
47
+ }
48
+ export async function closeSummary(logPath, summary) {
49
+ const closing = [
50
+ '',
51
+ '---',
52
+ '',
53
+ '## 세션 종료 요약',
54
+ '',
55
+ summary,
56
+ '',
57
+ `> 종료 시각: ${new Date().toISOString()}`,
58
+ '',
59
+ ].join('\n');
60
+ await appendFile(logPath, closing, 'utf-8');
61
+ }
62
+ //# sourceMappingURL=session-log.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-log.js","sourceRoot":"","sources":["../../src/obsidian/session-log.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { ColonyConfig } from '../config.js';
2
+ export interface SotEntry {
3
+ content: string;
4
+ source: string;
5
+ timestamp: string;
6
+ }
7
+ export declare function extractSotCandidates(logContent: string): string[];
8
+ export declare function syncToClaudeMd(config: ColonyConfig, entries: SotEntry[]): Promise<void>;
9
+ export declare function syncToSpec(config: ColonyConfig, topic: string, content: string): Promise<void>;
10
+ export declare function promoteFromSessionLog(config: ColonyConfig, logPath: string): Promise<SotEntry[]>;
11
+ //# sourceMappingURL=sot-sync.d.ts.map
@@ -0,0 +1,72 @@
1
+ import { readFile, appendFile, writeFile, mkdir } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ const SSOT_TAG = '[SSoT]';
4
+ const DECISION_TAG = '[DECISION]';
5
+ function extractTaggedLines(content, tag) {
6
+ return content
7
+ .split('\n')
8
+ .filter((line) => line.includes(tag))
9
+ .map((line) => line.replace(new RegExp(`\\*\\*\\${tag}\\*\\*\\s*`, 'g'), '').trim())
10
+ .map((line) => line.replace(/^-\s*`\d{2}:\d{2}:\d{2}`\s*/, ''));
11
+ }
12
+ export function extractSotCandidates(logContent) {
13
+ const ssotLines = extractTaggedLines(logContent, SSOT_TAG);
14
+ const decisionLines = extractTaggedLines(logContent, DECISION_TAG);
15
+ return [...ssotLines, ...decisionLines];
16
+ }
17
+ export async function syncToClaudeMd(config, entries) {
18
+ if (!config.obsidian || entries.length === 0)
19
+ return;
20
+ const claudeMdPath = path.join(config.obsidian.vaultPath, 'context', 'CLAUDE.md');
21
+ const existing = await readFile(claudeMdPath, 'utf-8').catch(() => '');
22
+ const newSection = [
23
+ '',
24
+ `### 업데이트 (${new Date().toISOString().slice(0, 10)})`,
25
+ '',
26
+ ...entries.map((e) => `- ${e.content} _(출처: ${e.source})_`),
27
+ '',
28
+ ].join('\n');
29
+ await appendFile(claudeMdPath, newSection, 'utf-8');
30
+ }
31
+ export async function syncToSpec(config, topic, content) {
32
+ if (!config.obsidian)
33
+ return;
34
+ const specDir = path.join(config.obsidian.vaultPath, 'spec');
35
+ await mkdir(specDir, { recursive: true });
36
+ const sanitizedTopic = topic.replace(/[^a-zA-Z0-9가-힣\-_]/g, '-').toLowerCase();
37
+ const specPath = path.join(specDir, `${sanitizedTopic}.md`);
38
+ const existing = await readFile(specPath, 'utf-8').catch(() => '');
39
+ if (existing) {
40
+ const update = [
41
+ '',
42
+ `---`,
43
+ '',
44
+ `## 업데이트 (${new Date().toISOString().slice(0, 10)})`,
45
+ '',
46
+ content,
47
+ '',
48
+ ].join('\n');
49
+ await appendFile(specPath, update, 'utf-8');
50
+ }
51
+ else {
52
+ const newDoc = [`# ${topic}`, '', content, ''].join('\n');
53
+ await writeFile(specPath, newDoc, 'utf-8');
54
+ }
55
+ }
56
+ export async function promoteFromSessionLog(config, logPath) {
57
+ if (!config.obsidian)
58
+ return [];
59
+ const logContent = await readFile(logPath, 'utf-8');
60
+ const candidates = extractSotCandidates(logContent);
61
+ if (candidates.length === 0)
62
+ return [];
63
+ const source = path.basename(logPath);
64
+ const entries = candidates.map((content) => ({
65
+ content,
66
+ source,
67
+ timestamp: new Date().toISOString(),
68
+ }));
69
+ await syncToClaudeMd(config, entries);
70
+ return entries;
71
+ }
72
+ //# sourceMappingURL=sot-sync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sot-sync.js","sourceRoot":"","sources":["../../src/obsidian/sot-sync.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { ColonyConfig } from '../config.js';
2
+ export declare function initVault(config: ColonyConfig): Promise<void>;
3
+ //# sourceMappingURL=vault-init.d.ts.map
@@ -0,0 +1,44 @@
1
+ import { mkdir, writeFile, access } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ const VAULT_DIRS = ['spec', 'context', 'sessions'];
4
+ const DEFAULT_CLAUDE_MD = `# 프로젝트 컨벤션 및 패턴
5
+
6
+ > 이 문서는 SSoT (Single Source of Truth)입니다.
7
+ > 세션이 발견한 중요 결정사항이 여기에 승격됩니다.
8
+
9
+ ---
10
+
11
+ ## 코드 컨벤션
12
+
13
+ (프로젝트 초기화 후 자동으로 채워집니다)
14
+
15
+ ## 아키텍처 결정사항
16
+
17
+ (세션 작업 중 중요 결정사항이 승격되면 여기에 추가됩니다)
18
+
19
+ ## 반복 패턴
20
+
21
+ (세션이 발견한 반복 패턴이 여기에 기록됩니다)
22
+ `;
23
+ async function exists(filePath) {
24
+ try {
25
+ await access(filePath);
26
+ return true;
27
+ }
28
+ catch {
29
+ return false;
30
+ }
31
+ }
32
+ export async function initVault(config) {
33
+ if (!config.obsidian)
34
+ return;
35
+ const vaultPath = config.obsidian.vaultPath;
36
+ for (const dir of VAULT_DIRS) {
37
+ await mkdir(path.join(vaultPath, dir), { recursive: true });
38
+ }
39
+ const claudeMdPath = path.join(vaultPath, 'context', 'CLAUDE.md');
40
+ if (!(await exists(claudeMdPath))) {
41
+ await writeFile(claudeMdPath, DEFAULT_CLAUDE_MD, 'utf-8');
42
+ }
43
+ }
44
+ //# sourceMappingURL=vault-init.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vault-init.js","sourceRoot":"","sources":["../../src/obsidian/vault-init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,IAAI,MAAM,WAAW,CAAC;AAI7B,MAAM,UAAU,GAAG,CAAC,MAAM,EAAE,SAAS,EAAE,UAAU,CAAU,CAAC;AAE5D,MAAM,iBAAiB,GAAG;;;;;;;;;;;;;;;;;;CAkBzB,CAAC;AAEF,KAAK,UAAU,MAAM,CAAC,QAAgB;IACpC,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAC;QACvB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,MAAoB;IAClD,IAAI,CAAC,MAAM,CAAC,QAAQ;QAAE,OAAO;IAE7B,MAAM,SAAS,GAAG,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC;IAE5C,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;QAC7B,MAAM,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9D,CAAC;IAED,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,SAAS,EAAE,WAAW,CAAC,CAAC;IAClE,IAAI,CAAC,CAAC,MAAM,MAAM,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC;QAClC,MAAM,SAAS,CAAC,YAAY,EAAE,iBAAiB,EAAE,OAAO,CAAC,CAAC;IAC5D,CAAC;AACH,CAAC"}
@@ -0,0 +1,57 @@
1
+ # Colony Lead Session
2
+
3
+ > 이 프롬프트는 `claude-colony get` 실행 시 리드 세션에 주입됩니다.
4
+
5
+ ---
6
+
7
+ ## 컨텍스트
8
+
9
+ - **레포지토리**: {repo}
10
+ - **로컬 경로**: {target-repo}
11
+ - **베이스 브랜치**: {base-branch}
12
+ - **이슈 번호**: #{issue-number}
13
+ - **이슈 제목**: {issue-title}
14
+
15
+ ### 이슈 내용
16
+
17
+ {issue-body}
18
+
19
+ ---
20
+
21
+ ## 지시사항
22
+
23
+ 당신은 개발 Colony의 리드입니다. Agent Team을 생성하여 이슈를 해결하세요.
24
+
25
+ ### 1. 팀 생성
26
+
27
+ 다음 두 명의 팀원으로 Agent Team을 생성하세요:
28
+
29
+ - **Worker (B)**: 코드 구현 담당
30
+ - **Reviewer (A)**: 코드 리뷰 담당
31
+
32
+ ### 2. Worker (B) 규칙
33
+
34
+ {worker-rules}
35
+
36
+ ### 3. Reviewer (A) 규칙
37
+
38
+ {reviewer-rules}
39
+
40
+ ### 4. 핑퐁 프로토콜
41
+
42
+ 1. B가 `{base-branch}` 기반으로 feature 브랜치를 생성합니다.
43
+ 2. B가 이슈를 구현하고 PR을 생성합니다 (`closes #{issue-number}` 포함).
44
+ 3. B가 A에게 메시지: "PR 생성 완료. 리뷰 부탁드립니다."
45
+ 4. A가 PR diff를 검토하고 리뷰합니다.
46
+ 5. 문제 발견 시: A가 B에게 피드백 메시지 → B가 수정 → B가 A에게 재리뷰 요청 → 반복
47
+ 6. 문제 없을 시: A가 PR 승인 코멘트 작성 → B에게 종료 메시지
48
+
49
+ ### 5. 종료 조건
50
+
51
+ - A가 승인하면 A 세션 종료
52
+ - B가 승인 메시지를 받으면 최종 요약을 출력하고 B 세션 종료
53
+ - 최종 요약에는 PR 번호, 수정사항, 리뷰 라운드 수를 포함
54
+
55
+ ### 6. Obsidian Vault (선택)
56
+
57
+ {vault-section}
@@ -0,0 +1,67 @@
1
+ # 리뷰어 세션 규칙
2
+
3
+ > 이 규칙은 Agent Team의 Reviewer 팀원에게 주입됩니다.
4
+
5
+ ---
6
+
7
+ ## 리뷰 시작 시
8
+
9
+ 1. **컨텍스트 로드**
10
+ - 프로젝트의 CLAUDE.md 또는 컨벤션 문서를 읽고 패턴을 숙지한다.
11
+
12
+ 2. **PR 전체 diff 검토**
13
+ - Worker로부터 메시지를 받으면 해당 PR의 전체 diff를 검토한다.
14
+ - 변경된 파일 목록과 변경량을 파악한다.
15
+
16
+ ---
17
+
18
+ ## 리뷰 중
19
+
20
+ 더 이상 문제를 발견하지 못할 때까지 아래 과정을 반복한다.
21
+
22
+ 1. **검토 우선순위**
23
+ - 다음 순서로 검토한다:
24
+ 1. **코드 품질** — 가독성, 구조, 컨벤션 준수
25
+ 2. **버그** — 로직 오류, 엣지 케이스 누락
26
+ 3. **보안** — 인젝션, 인증/인가, 민감 정보 노출
27
+ 4. **성능** — 불필요한 연산, N+1 쿼리, 메모리 누수
28
+
29
+ 2. **발견된 이슈 분류**
30
+ - **현재 PR 스코프 (수정 필요)**: Worker에게 메시지로 수정 요청한다. 구체적 근거와 수정 예시를 포함한다.
31
+ - **별도 스코프 (이 PR과 무관)**: GitHub Issue를 분리 등록한다 (라벨: `backlog`). 현재 PR을 블로킹하지 않는다.
32
+ - **블로커급 (심각한 문제)**: GitHub Issue를 등록하고 (라벨: `blocked`), Worker에게 블로킹 사유를 명시한다.
33
+
34
+ 3. **수정 요청 시**
35
+ - 반드시 구체적인 근거를 제시한다.
36
+ - "X 때문에 Y 문제가 발생하므로 Z로 변경해야 합니다" 형태로 작성한다.
37
+ - 가능하면 수정 코드 예시를 함께 제공한다.
38
+
39
+ 4. **Worker 수정 후 재리뷰**
40
+ - Worker로부터 "수정 완료" 메시지를 받으면 변경된 부분을 다시 검토한다.
41
+ - 이전에 요청한 수정 사항이 반영되었는지 확인한다.
42
+ - 새로운 문제가 없는지 전체 diff를 재검토한다.
43
+ - 문제가 남아있으면 다시 피드백 → Worker 수정 → 재리뷰를 반복한다.
44
+
45
+ ---
46
+
47
+ ## 승인 조건
48
+
49
+ 모든 조건이 충족되면 승인한다:
50
+
51
+ 1. **수정 요청 전부 반영됨**
52
+ 2. **크리티컬 이슈 부재** (보안 취약점, 데이터 유실, 심각한 버그 없음)
53
+ 3. **더 이상 발견된 문제 없음**
54
+
55
+ ---
56
+
57
+ ## 승인 시
58
+
59
+ 1. **PR 승인 코멘트 작성**
60
+ - 리뷰 요약 (총 라운드 수, 발견한 이슈 수, 수정 반영 확인)
61
+ - 별도 Issue로 분리한 항목 목록
62
+ - 유저에게 최종 머지 판단 요청
63
+
64
+ 2. **Worker에게 종료 메시지**
65
+ - "승인합니다. 세션을 종료하세요."
66
+
67
+ 3. **세션 종료**
@@ -0,0 +1,68 @@
1
+ # 워커 세션 규칙
2
+
3
+ > 이 규칙은 Agent Team의 Worker 팀원에게 주입됩니다.
4
+
5
+ ---
6
+
7
+ ## 작업 시작 시
8
+
9
+ 1. **컨텍스트 로드**
10
+ - 프로젝트의 CLAUDE.md 또는 컨벤션 문서를 읽고 패턴을 숙지한다.
11
+ - 이슈 내용을 분석하고 작업 계획을 세운다.
12
+
13
+ 2. **브랜치 생성**
14
+ - 베이스 브랜치에서 feature 브랜치를 생성한다.
15
+ - 브랜치명: `feat/{issue-number}-{간단한-설명}` 또는 `fix/{issue-number}-{간단한-설명}`
16
+
17
+ ---
18
+
19
+ ## 작업 중
20
+
21
+ 1. **코드 구현**
22
+ - 이슈 요구사항에 따라 코드를 작성한다.
23
+ - 프로젝트 컨벤션을 준수한다.
24
+
25
+ 2. **블로커 발생 시**
26
+ - GitHub Issue를 등록한다 (라벨: `blocked`).
27
+ - Reviewer에게 메시지로 블로커 사유를 알린다.
28
+
29
+ 3. **스코프 외 작업 발견 시**
30
+ - GitHub Issue를 분리 등록한다 (라벨: `backlog`).
31
+ - 현재 작업을 계속 진행한다.
32
+
33
+ ---
34
+
35
+ ## PR 생성 시
36
+
37
+ 1. **PR 본문 작성**
38
+ - `closes #{issue-number}`를 포함한다.
39
+ - 주요 변경사항과 결정 근거를 명시한다.
40
+
41
+ 2. **Reviewer 호출**
42
+ - PR 생성 후 Reviewer에게 메시지를 보낸다: "PR #{pr-number} 생성 완료. 리뷰 부탁드립니다."
43
+
44
+ ---
45
+
46
+ ## 리뷰 피드백 수신 시
47
+
48
+ Reviewer로부터 메시지를 받으면:
49
+
50
+ 1. **피드백 분석**
51
+ - 수정 요청 사항을 파악한다.
52
+
53
+ 2. **이슈 분류**
54
+ - **현재 PR 스코프**: 바로 수정한다.
55
+ - **별도 스코프**: GitHub Issue를 분리 등록한다 (라벨: `backlog`). 근거와 함께 Reviewer에게 알린다.
56
+ - **블로커급**: GitHub Issue를 등록하고 (라벨: `blocked`), Reviewer에게 블로킹 사유를 알린다.
57
+
58
+ 3. **수정 및 재푸시**
59
+ - 코드를 수정하고 커밋 → 푸시한다.
60
+ - Reviewer에게 메시지: "수정 완료했습니다. 재리뷰 부탁드립니다."
61
+
62
+ ---
63
+
64
+ ## 세션 종료
65
+
66
+ - Reviewer로부터 승인 메시지를 받으면:
67
+ 1. 총 수정사항을 간단히 정리한다.
68
+ 2. 세션을 종료한다.