tycono-server 0.1.0-beta.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 (84) hide show
  1. package/bin/cli.js +35 -0
  2. package/bin/server.ts +160 -0
  3. package/package.json +50 -0
  4. package/src/api/package.json +31 -0
  5. package/src/api/src/create-app.ts +90 -0
  6. package/src/api/src/create-server.ts +251 -0
  7. package/src/api/src/engine/agent-loop.ts +738 -0
  8. package/src/api/src/engine/authority-validator.ts +149 -0
  9. package/src/api/src/engine/context-assembler.ts +912 -0
  10. package/src/api/src/engine/index.ts +27 -0
  11. package/src/api/src/engine/knowledge-gate.ts +365 -0
  12. package/src/api/src/engine/llm-adapter.ts +304 -0
  13. package/src/api/src/engine/org-tree.ts +270 -0
  14. package/src/api/src/engine/role-lifecycle.ts +369 -0
  15. package/src/api/src/engine/runners/claude-cli.ts +796 -0
  16. package/src/api/src/engine/runners/direct-api.ts +66 -0
  17. package/src/api/src/engine/runners/index.ts +30 -0
  18. package/src/api/src/engine/runners/types.ts +95 -0
  19. package/src/api/src/engine/skill-template.ts +134 -0
  20. package/src/api/src/engine/tools/definitions.ts +201 -0
  21. package/src/api/src/engine/tools/executor.ts +611 -0
  22. package/src/api/src/routes/active-sessions.ts +134 -0
  23. package/src/api/src/routes/coins.ts +153 -0
  24. package/src/api/src/routes/company.ts +57 -0
  25. package/src/api/src/routes/cost.ts +141 -0
  26. package/src/api/src/routes/engine.ts +220 -0
  27. package/src/api/src/routes/execute.ts +1075 -0
  28. package/src/api/src/routes/git.ts +211 -0
  29. package/src/api/src/routes/knowledge.ts +378 -0
  30. package/src/api/src/routes/operations.ts +309 -0
  31. package/src/api/src/routes/preferences.ts +63 -0
  32. package/src/api/src/routes/presets.ts +123 -0
  33. package/src/api/src/routes/projects.ts +82 -0
  34. package/src/api/src/routes/quests.ts +41 -0
  35. package/src/api/src/routes/roles.ts +112 -0
  36. package/src/api/src/routes/save.ts +152 -0
  37. package/src/api/src/routes/sessions.ts +288 -0
  38. package/src/api/src/routes/setup.ts +437 -0
  39. package/src/api/src/routes/skills.ts +357 -0
  40. package/src/api/src/routes/speech.ts +959 -0
  41. package/src/api/src/routes/supervision.ts +136 -0
  42. package/src/api/src/routes/sync.ts +165 -0
  43. package/src/api/src/server.ts +59 -0
  44. package/src/api/src/services/activity-stream.ts +184 -0
  45. package/src/api/src/services/activity-tracker.ts +115 -0
  46. package/src/api/src/services/claude-md-manager.ts +94 -0
  47. package/src/api/src/services/company-config.ts +115 -0
  48. package/src/api/src/services/database.ts +77 -0
  49. package/src/api/src/services/digest-engine.ts +313 -0
  50. package/src/api/src/services/execution-manager.ts +1036 -0
  51. package/src/api/src/services/file-reader.ts +77 -0
  52. package/src/api/src/services/git-save.ts +614 -0
  53. package/src/api/src/services/job-manager.ts +16 -0
  54. package/src/api/src/services/knowledge-importer.ts +466 -0
  55. package/src/api/src/services/markdown-parser.ts +173 -0
  56. package/src/api/src/services/port-registry.ts +222 -0
  57. package/src/api/src/services/preferences.ts +150 -0
  58. package/src/api/src/services/preset-loader.ts +149 -0
  59. package/src/api/src/services/pricing.ts +34 -0
  60. package/src/api/src/services/scaffold.ts +546 -0
  61. package/src/api/src/services/session-store.ts +340 -0
  62. package/src/api/src/services/supervisor-heartbeat.ts +897 -0
  63. package/src/api/src/services/team-recommender.ts +382 -0
  64. package/src/api/src/services/token-ledger.ts +127 -0
  65. package/src/api/src/services/wave-messages.ts +194 -0
  66. package/src/api/src/services/wave-multiplexer.ts +356 -0
  67. package/src/api/src/services/wave-tracker.ts +359 -0
  68. package/src/api/src/utils/role-level.ts +31 -0
  69. package/src/core/scaffolder.ts +620 -0
  70. package/src/shared/types.ts +224 -0
  71. package/templates/CLAUDE.md.tmpl +239 -0
  72. package/templates/company.md.tmpl +17 -0
  73. package/templates/gitignore.tmpl +28 -0
  74. package/templates/roles.md.tmpl +8 -0
  75. package/templates/skills/_manifest.json +23 -0
  76. package/templates/skills/agent-browser/SKILL.md +159 -0
  77. package/templates/skills/agent-browser/meta.json +19 -0
  78. package/templates/skills/akb-linter/SKILL.md +125 -0
  79. package/templates/skills/akb-linter/meta.json +12 -0
  80. package/templates/skills/knowledge-gate/SKILL.md +120 -0
  81. package/templates/skills/knowledge-gate/meta.json +12 -0
  82. package/templates/teams/agency.json +58 -0
  83. package/templates/teams/research.json +58 -0
  84. package/templates/teams/startup.json +58 -0
@@ -0,0 +1,912 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { readPreferences } from '../services/preferences.js';
4
+ import { readConfig, resolveCodeRoot } from '../services/company-config.js';
5
+ import {
6
+ type OrgTree,
7
+ type OrgNode,
8
+ getSubordinates,
9
+ getChainOfCommand,
10
+ formatOrgChart,
11
+ canConsult,
12
+ } from './org-tree.js';
13
+ import { extractKeywords, searchRelatedDocs } from './knowledge-gate.js';
14
+
15
+ /* ─── Types ──────────────────────────────────── */
16
+
17
+ export interface AssembledContext {
18
+ systemPrompt: string;
19
+ task: string;
20
+ sourceRole: string;
21
+ targetRole: string;
22
+ metadata: {
23
+ orgPath: string[];
24
+ knowledgeScope: string[];
25
+ authorityLevel: string;
26
+ subordinates: string[];
27
+ };
28
+ }
29
+
30
+ /* ─── Context Assembly ───────────────────────── */
31
+
32
+ /**
33
+ * 9단계 시스템 프롬프트 조립 파이프라인
34
+ *
35
+ * 1. CLAUDE.md (전사 규칙)
36
+ * 2. Org Context (현재 조직도, 이 Role의 위치)
37
+ * 3. Role Persona
38
+ * 4. Authority Rules
39
+ * 5. Knowledge Scope
40
+ * 6. SKILL.md
41
+ * 7. Hub Docs (라우팅 테이블의 "먼저 읽기" 경로)
42
+ * 8. CEO Decisions (전사 공지 — Approved 결정만)
43
+ * 9. Task
44
+ */
45
+ export type { TeamStatus } from '../../../shared/types.js';
46
+ import { type RoleStatus, type TeamStatus, isRoleActive } from '../../../shared/types.js';
47
+
48
+ export function assembleContext(
49
+ companyRoot: string,
50
+ roleId: string,
51
+ task: string,
52
+ sourceRole: string,
53
+ orgTree: OrgTree,
54
+ options?: { teamStatus?: TeamStatus; targetRoles?: string[]; presetId?: string },
55
+ ): AssembledContext {
56
+ const node = orgTree.nodes.get(roleId);
57
+ if (!node) {
58
+ throw new Error(`Role not found in org tree: ${roleId}`);
59
+ }
60
+
61
+ const sections: string[] = [];
62
+
63
+ // 1. Company Rules (CLAUDE.md + custom-rules.md + company.md)
64
+ const companyRules = loadCompanyRules(companyRoot);
65
+ if (companyRules) {
66
+ sections.push(companyRules);
67
+ }
68
+
69
+ // 2. Org Context
70
+ sections.push(buildOrgContextSection(orgTree, node));
71
+
72
+ // 3. Role Persona
73
+ sections.push(buildPersonaSection(node));
74
+
75
+ // 4. Authority Rules
76
+ sections.push(buildAuthoritySection(node));
77
+
78
+ // 5. Knowledge Scope
79
+ sections.push(buildKnowledgeSection(node));
80
+
81
+ // 6. SKILL.md (Role-specific + equipped shared skills)
82
+ const skillContent = loadSkillMd(companyRoot, roleId);
83
+ if (skillContent) {
84
+ sections.push('# Skills & Tools\n\n' + skillContent);
85
+ }
86
+
87
+ // 6b. Shared Skills (from role.yaml skills field)
88
+ const sharedSkills = loadSharedSkills(companyRoot, node.skills);
89
+ if (sharedSkills) {
90
+ sections.push('# Equipped Skills\n\n' + sharedSkills);
91
+ }
92
+
93
+ // 7. Hub Docs (요약)
94
+ const hubSummary = loadHubSummaries(companyRoot, node);
95
+ if (hubSummary) {
96
+ sections.push('# Reference Documents\n\n' + hubSummary);
97
+ }
98
+
99
+ // 8. CEO Decisions (전사 공지)
100
+ const ceoDecisions = loadCeoDecisions(companyRoot);
101
+ if (ceoDecisions) {
102
+ sections.push('# CEO Decisions (전사 공지)\n\n' + ceoDecisions);
103
+ }
104
+
105
+ // 9. Code Root (코드 프로젝트 경로)
106
+ const codeRoot = resolveCodeRoot(companyRoot);
107
+ sections.push(`# Code Project
108
+
109
+ The code repository is located at: \`${codeRoot}\` (env: $TYCONO_CODE_ROOT)
110
+ The AKB (knowledge) directory is at: \`${companyRoot}\` (env: $TYCONO_AKB_ROOT)
111
+
112
+ Use the code repository path for all source code work (reading, writing, building, testing).
113
+
114
+ ## Git Worktree Rules (CRITICAL)
115
+ - Your cwd is already set to the code repository. When creating worktrees, use relative paths or \`$TYCONO_CODE_ROOT\`.
116
+ - **NEVER run \`git worktree add\` in \`$TYCONO_AKB_ROOT\`** — the AKB directory is not a code repository.
117
+ - Recommended worktree path: \`$TYCONO_CODE_ROOT/.worktrees/{branch-name}\`
118
+ - Example: \`git worktree add .worktrees/feature-xyz -b feature/xyz\` (from cwd, which is already code repo)`);
119
+
120
+ // 10. Pre-Knowledging: 작업 관련 문서 자동 탐색
121
+ const preKSection = buildPreKnowledgingSection(companyRoot, task);
122
+ if (preKSection) {
123
+ sections.push(preKSection);
124
+ }
125
+
126
+ // 11. Preset Knowledge (wave-scoped preset docs)
127
+ if (options?.presetId && options.presetId !== 'default') {
128
+ const presetKnowledge = loadPresetKnowledge(companyRoot, options.presetId);
129
+ if (presetKnowledge) {
130
+ sections.push('# Preset Knowledge\n\n' + presetKnowledge);
131
+ }
132
+ }
133
+
134
+ // Task는 별도 필드로 분리
135
+ let subordinates = getSubordinates(orgTree, roleId);
136
+
137
+ // Filter subordinates by targetRoles ONLY for CEO (wave dispatch scope)
138
+ // C-Level roles should always see their own subordinates regardless of targetRoles
139
+ if (options?.targetRoles && options.targetRoles.length > 0 && roleId === 'ceo') {
140
+ subordinates = subordinates.filter(id => options.targetRoles!.includes(id));
141
+ }
142
+
143
+ // Supervision prompt (SV-11, SV-12: C-Level heartbeat mode)
144
+ const heartbeatEnabled = node.heartbeat?.enabled === true;
145
+ if (heartbeatEnabled && subordinates.length > 0) {
146
+ sections.push(buildSupervisionSection(node));
147
+ }
148
+
149
+ // Knowledge consistency management (C-Level responsibility)
150
+ if (node.level === 'c-level') {
151
+ sections.push(buildKnowledgeManagementSection(roleId));
152
+ }
153
+
154
+ // Dispatch 도구 안내 (하위 Role이 있는 경우)
155
+ if (subordinates.length > 0) {
156
+ sections.push(buildDispatchSection(orgTree, roleId, subordinates, options?.teamStatus));
157
+ } else if (node.level === 'c-level') {
158
+ // C-level with no subordinates — clarify authority boundaries
159
+ sections.push(`# Team Structure
160
+
161
+ ⚠️ **You have no direct reports.** You are an individual contributor at the C-level.
162
+
163
+ - You CANNOT dispatch tasks to other roles (no subordinates)
164
+ - You CAN consult other roles for information (see Consult section below)
165
+ - You MUST do the work yourself — research, analyze, write, decide
166
+ - If implementation requires another role (e.g., engineering work), recommend it to CEO
167
+ - Make decisions within your authority autonomously — do NOT ask CEO for decisions you can make yourself`);
168
+ }
169
+
170
+ // Consult 도구 안내 (상담 가능한 Role이 있는 경우)
171
+ const consultSection = buildConsultSection(orgTree, roleId);
172
+ if (consultSection) {
173
+ sections.push(consultSection);
174
+ }
175
+
176
+ // Language preference (default: English)
177
+ const prefs = readPreferences(companyRoot);
178
+ const lang = prefs.language && prefs.language !== 'auto' ? prefs.language : 'en';
179
+ const langNames: Record<string, string> = { en: 'English', ko: 'Korean (한국어)', ja: 'Japanese (日本語)' };
180
+ const langName = langNames[lang] ?? lang;
181
+ sections.push(`# Language (CRITICAL)
182
+
183
+ You MUST respond in **${langName}**.
184
+
185
+ This applies to ALL output without exception:
186
+ - Status updates, reports, and analysis
187
+ - Journal entries and standup notes
188
+ - Decision logs and knowledge documents
189
+ - User-facing messages and explanations
190
+ - Git commit messages and PR descriptions
191
+
192
+ Code (variable names, comments in code) may remain in English for readability.
193
+ Everything else MUST be in ${langName}.`);
194
+
195
+ // Execution behavior rules (prevents infinite exploration loops in -p mode)
196
+ sections.push(`# Execution Rules (CRITICAL)
197
+
198
+ ## Interpreting Tasks
199
+ - A [CEO Wave] is a directive from the CEO. Interpret it based on your role's expertise.
200
+ - If the directive is vague, focus on what YOUR ROLE can contribute. Don't try to cover everything.
201
+ - Break ambiguous directives into concrete actions within your authority scope.
202
+ - If you truly cannot determine what to do, state your interpretation and proceed with it.
203
+ - **If you have subordinates, your FIRST action should be decomposing the task and dispatching.** Do NOT attempt implementation yourself — delegate to the appropriate team member.
204
+ - Review the "Available Team Members" section to understand each subordinate's capabilities before dispatching.
205
+
206
+ ## Efficiency
207
+ - Read ONLY files directly relevant to your task. Do NOT explore the codebase broadly.
208
+ - If a file doesn't exist at the expected path, try at most 2 alternatives, then move on.
209
+ - Do NOT use \`find\` or \`ls\` to scan entire directory trees. Use the Project Structure above.
210
+ - Never \`sleep\` or poll in loops. If something isn't ready, report it and move on.
211
+
212
+ ## When Stuck
213
+ - If you cannot find what you need after 3 search attempts, STOP searching immediately.
214
+ - Do NOT retry the same failing command or approach.
215
+ - Summarize what you found, what you couldn't find, and deliver your best answer with what you have.
216
+
217
+ ## Output
218
+ - Always produce a concrete deliverable: code change, report, analysis, or clear status update.
219
+ - End with a brief summary of what you did and any unresolved items.
220
+
221
+ ## Sub-task Efficiency (when dispatched by a superior)
222
+ - Focus ONLY on the assigned task — nothing else.
223
+ - Do NOT update journals, knowledge docs, or tasks.md — your superior handles that.
224
+ - Do NOT read CLAUDE.md or explore unrelated files — go straight to the target file.
225
+ - If tsc/tests fail, fix the specific error. Do NOT refactor surrounding code.
226
+
227
+ ## Commit Rule (when you modify code files)
228
+ - After completing code changes, you MUST commit your work.
229
+ - Use a descriptive commit message: \`git commit -m "type(scope): description"\`
230
+ - Common types: feat, fix, refactor, test, chore
231
+ - This ensures your work is not lost in uncommitted changes.
232
+ - Do NOT push — just commit locally. Your superior or the system handles push/PR.`);
233
+
234
+ const systemPrompt = sections.join('\n\n---\n\n');
235
+
236
+ return {
237
+ systemPrompt,
238
+ task,
239
+ sourceRole,
240
+ targetRole: roleId,
241
+ metadata: {
242
+ orgPath: getChainOfCommand(orgTree, roleId),
243
+ knowledgeScope: [...node.knowledge.reads, ...node.knowledge.writes],
244
+ authorityLevel: node.level,
245
+ subordinates,
246
+ },
247
+ };
248
+ }
249
+
250
+ /* ─── Section Builders ───────────────────────── */
251
+
252
+ function buildPreKnowledgingSection(companyRoot: string, task: string): string | null {
253
+ // Extract keywords from the task directive
254
+ const keywords = extractKeywords(task);
255
+ if (keywords.length < 2) return null; // Too few keywords to be meaningful
256
+
257
+ const related = searchRelatedDocs(companyRoot, keywords);
258
+ if (related.length === 0) return null;
259
+
260
+ const docList = related
261
+ .map(doc => `- \`${doc.path}\` — ${doc.preview} (relevance: ${doc.matches})`)
262
+ .join('\n');
263
+
264
+ return `# 📚 Pre-Knowledging: Related Documents
265
+
266
+ The following existing documents are related to this task. **Read relevant ones before starting work** to avoid duplicating knowledge or missing existing context.
267
+
268
+ ${docList}
269
+
270
+ > **Knowledging Rule**: Check these documents first. If your work produces new knowledge, update existing docs or create new ones with cross-links.`;
271
+ }
272
+
273
+ /**
274
+ * Load knowledge docs from a preset's knowledge/ directory.
275
+ * Returns concatenated content (capped at 2000 chars per doc).
276
+ */
277
+ function loadPresetKnowledge(companyRoot: string, presetId: string): string | null {
278
+ const knowledgeDir = path.join(companyRoot, 'knowledge', 'presets', presetId, 'knowledge');
279
+ if (!fs.existsSync(knowledgeDir)) return null;
280
+
281
+ const parts: string[] = [];
282
+ try {
283
+ const entries = fs.readdirSync(knowledgeDir).filter(f => f.endsWith('.md'));
284
+ for (const file of entries.slice(0, 10)) { // Cap at 10 docs
285
+ const content = fs.readFileSync(path.join(knowledgeDir, file), 'utf-8');
286
+ const preview = content.slice(0, 2000);
287
+ parts.push(`## ${file}\n\n${preview}${content.length > 2000 ? '\n\n... (truncated)' : ''}`);
288
+ }
289
+ } catch { /* ignore */ }
290
+
291
+ return parts.length > 0 ? parts.join('\n\n---\n\n') : null;
292
+ }
293
+
294
+ function loadCompanyRules(companyRoot: string): string | null {
295
+ const parts: string[] = [];
296
+
297
+ // 1. System rules (CLAUDE.md — Tycono managed)
298
+ const claudeMdPath = path.join(companyRoot, 'CLAUDE.md');
299
+ if (fs.existsSync(claudeMdPath)) {
300
+ parts.push(fs.readFileSync(claudeMdPath, 'utf-8'));
301
+ }
302
+
303
+ // 2. User custom rules (knowledge/custom-rules.md — user owned, git tracked)
304
+ const customPath = path.join(companyRoot, 'knowledge', 'custom-rules.md');
305
+ if (fs.existsSync(customPath)) {
306
+ const custom = fs.readFileSync(customPath, 'utf-8').trim();
307
+ if (custom) {
308
+ parts.push('---\n\n## Company Custom Rules\n\n' + custom);
309
+ }
310
+ }
311
+
312
+ // 3. Company info (knowledge/company.md — user owned)
313
+ const companyMdPath = path.join(companyRoot, 'knowledge', 'company.md');
314
+ if (fs.existsSync(companyMdPath)) {
315
+ const companyInfo = fs.readFileSync(companyMdPath, 'utf-8').trim();
316
+ if (companyInfo) {
317
+ parts.push('---\n\n## Company Info\n\n' + companyInfo);
318
+ }
319
+ }
320
+
321
+ return parts.length > 0 ? parts.join('\n\n') : null;
322
+ }
323
+
324
+ function buildOrgContextSection(orgTree: OrgTree, node: OrgNode): string {
325
+ const chart = formatOrgChart(orgTree, node.id);
326
+ const chain = getChainOfCommand(orgTree, node.id);
327
+
328
+ return `# Organization
329
+
330
+ ## Org Chart
331
+ \`\`\`
332
+ ${chart}
333
+ \`\`\`
334
+
335
+ ## Your Position
336
+ - **Role**: ${node.name} (${node.id})
337
+ - **Level**: ${node.level}
338
+ - **Reports To**: ${node.reportsTo}
339
+ - **Chain of Command**: ${chain.join(' → ')}
340
+ - **Direct Reports**: ${node.children.length > 0 ? node.children.join(', ') : 'None'}`;
341
+ }
342
+
343
+ function buildPersonaSection(node: OrgNode): string {
344
+ return `# Persona
345
+
346
+ You are **${node.name}** of this company.
347
+
348
+ ${node.persona}`;
349
+ }
350
+
351
+ function buildAuthoritySection(node: OrgNode): string {
352
+ const autoList = node.authority.autonomous.map((a) => `- ✅ ${a}`).join('\n');
353
+ const approvalList = node.authority.needsApproval.map((a) => `- ⚠️ ${a}`).join('\n');
354
+
355
+ return `# Authority
356
+
357
+ ## Autonomous Actions (proceed without approval)
358
+ ${autoList || '- None defined'}
359
+
360
+ ## Requires Approval from ${node.reportsTo || 'CEO'}
361
+ ${approvalList || '- None defined'}
362
+
363
+ **Important**: Actions outside your authority must be tagged with [APPROVAL_NEEDED].`;
364
+ }
365
+
366
+ function buildKnowledgeSection(node: OrgNode): string {
367
+ const reads = node.knowledge.reads.map((p) => `- \`${p}\``).join('\n');
368
+ const writes = node.knowledge.writes.map((p) => `- \`${p}\``).join('\n');
369
+
370
+ const hasKnowledgeWrite = node.knowledge.writes.some((p) =>
371
+ p === '*' || p.startsWith('knowledge') || p === 'knowledge/*'
372
+ );
373
+
374
+ return `# Knowledge Scope
375
+
376
+ ## Readable Paths
377
+ ${reads || '- None'}
378
+
379
+ ## Writable Paths
380
+ ${writes || '- None'}
381
+
382
+ Only access files within your knowledge scope. For information outside your scope, ask your manager.${hasKnowledgeWrite ? `
383
+
384
+ ## Knowledge Base 문서 작성 규칙
385
+
386
+ 보고서, 분석 결과, 리서치 등 **공유 가치가 있는 문서**는 반드시 \`knowledge/\` 디렉토리에 작성하세요.
387
+
388
+ \`\`\`yaml
389
+ ---
390
+ title: "문서 제목"
391
+ akb_type: node
392
+ status: active
393
+ tags: ["tag1", "tag2"]
394
+ domain: tech|market|process|strategy|financial|competitor|general
395
+ ---
396
+ \`\`\`
397
+
398
+ - 파일 경로: \`knowledge/{category}/{filename}.md\`
399
+ - 반드시 위 YAML frontmatter를 포함할 것
400
+ - journal/에는 일지만, knowledge/에는 공유 문서를 작성` : ''}`;
401
+ }
402
+
403
+ function loadSkillMd(companyRoot: string, roleId: string): string | null {
404
+ const skillPath = path.join(companyRoot, 'knowledge', '.claude', 'skills', roleId, 'SKILL.md');
405
+ if (!fs.existsSync(skillPath)) return null;
406
+ return fs.readFileSync(skillPath, 'utf-8');
407
+ }
408
+
409
+ function loadSharedSkills(companyRoot: string, skillIds?: string[]): string | null {
410
+ if (!skillIds?.length) return null;
411
+
412
+ const sections: string[] = [];
413
+ for (const skillId of skillIds) {
414
+ const skillPath = path.join(companyRoot, 'knowledge', '.claude', 'skills', '_shared', skillId, 'SKILL.md');
415
+ if (!fs.existsSync(skillPath)) continue;
416
+ const content = fs.readFileSync(skillPath, 'utf-8');
417
+ // Extract just the key sections (skip frontmatter)
418
+ const body = content.replace(/^---[\s\S]*?---\n*/, '').trim();
419
+ sections.push(`## [Skill: ${skillId}]\n\n${body}`);
420
+ }
421
+
422
+ return sections.length > 0 ? sections.join('\n\n---\n\n') : null;
423
+ }
424
+
425
+ function loadHubSummaries(companyRoot: string, node: OrgNode): string | null {
426
+ const hubPaths = new Set<string>();
427
+
428
+ // Determine which hub docs to include based on knowledge scope
429
+ for (const readPath of node.knowledge.reads) {
430
+ const base = readPath.replace(/\*$/, '').replace(/\/$/, '');
431
+ const hubFile = path.join(companyRoot, base, `${path.basename(base)}.md`);
432
+ if (fs.existsSync(hubFile)) {
433
+ hubPaths.add(hubFile);
434
+ }
435
+ }
436
+
437
+ if (hubPaths.size === 0) return null;
438
+
439
+ const summaries: string[] = [];
440
+ for (const hubPath of hubPaths) {
441
+ const content = fs.readFileSync(hubPath, 'utf-8');
442
+ // Extract TL;DR or first 500 chars
443
+ const tldr = content.match(/## TL;DR[\s\S]*?(?=\n## [^#])/);
444
+ const summary = tldr ? tldr[0] : content.slice(0, 500);
445
+ const relativePath = path.relative(companyRoot, hubPath);
446
+ summaries.push(`### ${relativePath}\n${summary.trim()}`);
447
+ }
448
+
449
+ return summaries.join('\n\n');
450
+ }
451
+
452
+ function loadCeoDecisions(companyRoot: string): string | null {
453
+ const decisionsDir = path.join(companyRoot, 'knowledge', 'decisions');
454
+ if (!fs.existsSync(decisionsDir)) return null;
455
+
456
+ const files = fs.readdirSync(decisionsDir)
457
+ .filter((f) => f.endsWith('.md') && f !== 'decisions.md')
458
+ .sort();
459
+
460
+ if (files.length === 0) return null;
461
+
462
+ const summaries: string[] = [];
463
+
464
+ for (const file of files) {
465
+ const content = fs.readFileSync(path.join(decisionsDir, file), 'utf-8');
466
+
467
+ // Only include Approved decisions
468
+ const statusMatch = content.match(/>\s*Status:\s*(.+)/i);
469
+ if (!statusMatch || !statusMatch[1].toLowerCase().includes('approved')) continue;
470
+
471
+ // Extract title from first heading
472
+ const titleMatch = content.match(/^#\s+(.+)/m);
473
+ const title = titleMatch ? titleMatch[1].trim() : file;
474
+
475
+ // Extract Decision section (first paragraph after ## Decision)
476
+ const decisionMatch = content.match(/## Decision\s*\n\n([^\n]+)/);
477
+ const summary = decisionMatch ? decisionMatch[1].trim() : '';
478
+
479
+ if (summary) {
480
+ summaries.push(`- **${title}**: ${summary}`);
481
+ } else {
482
+ // Fallback: use first 3 content lines after front matter
483
+ const lines = content.split('\n').filter((l) => l.trim() && !l.startsWith('>') && !l.startsWith('#'));
484
+ summaries.push(`- **${title}**: ${lines.slice(0, 1).join(' ').trim() || '(상세 내용은 파일 참조)'}`);
485
+ }
486
+ }
487
+
488
+ if (summaries.length === 0) return null;
489
+
490
+ return `아래는 CEO가 승인한 전사 결정 사항입니다. 모든 Role은 이 결정을 인지하고 준수해야 합니다.\n\n${summaries.join('\n')}`;
491
+ }
492
+
493
+ function buildDispatchSection(orgTree: OrgTree, roleId: string, subordinates: string[], teamStatus?: TeamStatus): string {
494
+ const node = orgTree.nodes.get(roleId);
495
+ const isCLevel = node?.level === 'c-level';
496
+
497
+ const subInfo = subordinates.map((id) => {
498
+ const sub = orgTree.nodes.get(id);
499
+ if (!sub) return `- ${id} — (unknown role)`;
500
+
501
+ const lines: string[] = [];
502
+
503
+ // Header: name, id, persona summary
504
+ const st = teamStatus?.[id];
505
+ const status = st?.status && isRoleActive(st.status)
506
+ ? `🔴 Working${st.task ? ` — "${st.task.slice(0, 60)}"` : ''}`
507
+ : '🟢 Idle';
508
+ lines.push(`### ${sub.name} (\`${id}\`) — ${status}`);
509
+ lines.push(`> ${sub.persona.split('\n')[0]}`);
510
+
511
+ // Level & model
512
+ lines.push(`- **Level**: ${sub.level} | **Model**: ${sub.model ?? 'default'}`);
513
+
514
+ // Skills
515
+ if (sub.skills && sub.skills.length > 0) {
516
+ lines.push(`- **Skills**: ${sub.skills.join(', ')}`);
517
+ }
518
+
519
+ // Authority — what they can do autonomously
520
+ if (sub.authority.autonomous.length > 0) {
521
+ lines.push(`- **Can do**: ${sub.authority.autonomous.join(', ')}`);
522
+ }
523
+
524
+ // Knowledge scope — what they can read/write
525
+ if (sub.knowledge.reads.length > 0) {
526
+ lines.push(`- **Reads**: ${sub.knowledge.reads.join(', ')}`);
527
+ }
528
+ if (sub.knowledge.writes.length > 0) {
529
+ lines.push(`- **Writes**: ${sub.knowledge.writes.join(', ')}`);
530
+ }
531
+
532
+ // Their own subordinates (for chain delegation visibility)
533
+ const grandchildren = orgTree.nodes.get(id)?.children ?? [];
534
+ if (grandchildren.length > 0) {
535
+ const gcNames = grandchildren.map(gc => {
536
+ const gcNode = orgTree.nodes.get(gc);
537
+ return gcNode ? `${gcNode.name} (${gc})` : gc;
538
+ });
539
+ lines.push(`- **Their reports**: ${gcNames.join(', ')}`);
540
+ }
541
+
542
+ return lines.join('\n');
543
+ }).join('\n\n');
544
+
545
+ const exampleSubId = subordinates[0] ?? 'engineer';
546
+
547
+ let section = `# Dispatch (Team Management)
548
+
549
+ ⛔ **YOU HAVE SUBORDINATES. YOU MUST USE THEM.**
550
+ ⛔ **For ANY directive — whether it's "do X", "review Y", or even "what do you think about Z" — dispatch to your team first, THEN synthesize their input into your response.**
551
+ ⛔ **Reading files and giving your own opinion WITHOUT dispatching is NEVER acceptable when you have a team.**
552
+
553
+ Even for opinion/analysis requests:
554
+ - Dispatch to relevant subordinates: "Analyze X from your perspective and report findings"
555
+ - Poll for results → Synthesize their input with your own analysis
556
+ - Your value is ORCHESTRATION, not solo work
557
+
558
+ ## Available Team Members
559
+ ${subInfo}
560
+
561
+ ## How to Dispatch
562
+
563
+ **Dispatch is async: start a job → supervise with watch → review → next task.**
564
+
565
+ \`\`\`bash
566
+ # Step 1: Dispatch (returns immediately with session ID)
567
+ python3 "$DISPATCH_CMD" ${exampleSubId} "Task description here"
568
+ # → Session ID: ses-xxx
569
+
570
+ # Step 2: Supervise with watch (preferred) or poll with --check
571
+ # Option A (preferred): Supervision watch — blocks server-side, returns digest
572
+ python3 "$SUPERVISION_CMD" watch ses-xxx --duration 120
573
+ # Option B (fallback): Poll for result
574
+ python3 "$DISPATCH_CMD" --check ses-xxx
575
+ \`\`\`
576
+
577
+ ⛔ **NEVER use the Agent tool or Task tool to spawn sub-agents.** Those bypass the job tracking system. Use ONLY the dispatch command.
578
+
579
+ ### The Pattern: Dispatch → Watch → Analyze → Act
580
+
581
+ \`\`\`bash
582
+ # 1. Dispatch tasks (save all session IDs)
583
+ python3 "$DISPATCH_CMD" ${exampleSubId} "Task A"
584
+ # → Session ID: ses-aaa
585
+ ${subordinates.length > 1 ? `python3 "$DISPATCH_CMD" ${subordinates[1]} "Task B"\n# → Session ID: ses-bbb` : ''}
586
+
587
+ # 2. Enter supervision loop — watch ALL dispatched sessions
588
+ python3 "$SUPERVISION_CMD" watch ses-aaa${subordinates.length > 1 ? ',ses-bbb' : ''} --duration 120
589
+ # → Returns digest with significance score + activity summary
590
+
591
+ # 3. Analyze digest → decide:
592
+ # - On track → watch again
593
+ # - Off track → amend: python3 "$SUPERVISION_CMD" amend ses-aaa "Fix: use adapter pattern"
594
+ # - Failed → abort: python3 "$SUPERVISION_CMD" abort ses-aaa --reason "wrong approach"
595
+ # - Need input → consult: python3 "$CONSULT_CMD" cbo "Is this direction right?"
596
+
597
+ # 4. Repeat watch until ALL sessions complete
598
+ \`\`\`
599
+
600
+ ### --check Status Values
601
+ - **RUNNING** — Subordinate still working → poll again in 10-30s
602
+ - **DONE** — Task completed, result is printed
603
+ - **ERROR** — Task failed (re-dispatch with different instructions or report)
604
+ - **AWAITING_INPUT** — Subordinate has a question for you
605
+
606
+ ### ⛔ CRITICAL Rules
607
+ - **NEVER re-dispatch the same task.** If --check shows RUNNING, just keep polling.
608
+ - **NEVER dispatch and immediately finish.** The dispatch→check→review loop must continue until ALL work is complete.
609
+ - **Save the job ID** from each dispatch to use with --check.`;
610
+
611
+ // C-level roles get mandatory delegation rules
612
+ if (isCLevel) {
613
+ section += `
614
+
615
+ ## C-Level Delegation Protocol (MANDATORY)
616
+
617
+ ⛔ **You are a MANAGER. You do NOT write code, tests, or implementation yourself.**
618
+ ⛔ **Your job is to PLAN, DELEGATE, REVIEW, and UPDATE KNOWLEDGE.**
619
+
620
+ ### Core Rule: Always Delegate Down
621
+
622
+ When you receive a directive:
623
+ 1. **Analyze** — Break it into sub-tasks appropriate for each subordinate
624
+ 2. **Dispatch** — Assign tasks to subordinates with clear acceptance criteria
625
+ 3. **Monitor** — Poll for results, review quality
626
+ 4. **Verify** — Check git status: did subordinate commit? What files changed?
627
+ 5. **Follow up** — If output doesn't meet criteria, dispatch back with feedback
628
+ 6. **Report** — Synthesize results with change summary (files, commits, branch)
629
+
630
+ ### What You Do vs What Subordinates Do
631
+
632
+ | YOU (C-Level) | SUBORDINATES (Members) |
633
+ |---------------|----------------------|
634
+ | Plan & decompose tasks | Implement code/design/tests |
635
+ | Dispatch with clear specs | Execute and return results |
636
+ | Review output quality | Fix issues when told |
637
+ | Update knowledge & tasks | Update their own journals |
638
+ | Report to superior | Report to you |
639
+
640
+ ### The Supervision Loop (CRITICAL — DO NOT SKIP)
641
+
642
+ ⛔ **You MUST keep running until ALL planned tasks are dispatched, reviewed, and completed.**
643
+ ⛔ **NEVER dispatch once and stop. That leaves work half-done.**
644
+
645
+ The loop:
646
+ \`\`\`
647
+ PLAN TASKS → DISPATCH → SUPERVISION WATCH → ANALYZE DIGEST → DECIDE
648
+ ├── On track → Watch again
649
+ ├── Off track → Amend session
650
+ ├── Failed → Abort + re-dispatch
651
+ └── ALL DONE → Update knowledge → Report
652
+ \`\`\`
653
+
654
+ ### ⛔ CRITICAL: No Duplicate Dispatch
655
+
656
+ **NEVER dispatch the same or similar task to the same role twice.**
657
+ - If watch shows sessions still running, keep watching — do NOT re-dispatch
658
+ - If a subordinate completed a task, accept the result — do NOT re-dispatch
659
+ - If the result is unsatisfactory, re-dispatch with SPECIFIC different instructions
660
+ - Track dispatched session IDs — never repeat the same task
661
+ - After 2 dispatches to the same role, accept the result or report to CEO
662
+
663
+ **Example: Full supervision session**
664
+ \`\`\`bash
665
+ # Task 1: Dispatch to engineer
666
+ python3 "$DISPATCH_CMD" engineer "Implement feature X. Read tasks.md first."
667
+ # → Session ID: ses-001
668
+
669
+ # Supervision watch (blocks 120s, returns digest)
670
+ python3 "$SUPERVISION_CMD" watch ses-001 --duration 120
671
+ # → Digest: Engineer creating auth module (significance: 3, no anomalies)
672
+ # Judgment: On track, continue watching
673
+
674
+ python3 "$SUPERVISION_CMD" watch ses-001 --duration 120
675
+ # → Digest: Engineer importing external API directly (significance: 7, anomaly: scope_creep)
676
+ # Judgment: Wrong approach — amend!
677
+ python3 "$SUPERVISION_CMD" amend ses-001 "Use our adapter pattern, not direct API import"
678
+
679
+ python3 "$SUPERVISION_CMD" watch ses-001 --duration 120
680
+ # → Digest: Engineer completed (msg:done)
681
+
682
+ # Review result... looks good. Task 2 (QA):
683
+ python3 "$DISPATCH_CMD" qa "Test feature X that engineer just implemented."
684
+ # → Session ID: ses-002
685
+ python3 "$SUPERVISION_CMD" watch ses-002 --duration 120
686
+ # → Digest: QA found bugs (significance: 6)
687
+
688
+ # Re-dispatch with SPECIFIC fix:
689
+ python3 "$DISPATCH_CMD" engineer "Fix BUG: null check missing in auth.ts line 42"
690
+ # → Session ID: ses-003
691
+ python3 "$SUPERVISION_CMD" watch ses-003 --duration 120
692
+ # → DONE — all good. Update knowledge and report.
693
+ \`\`\`
694
+
695
+ ⚠️ Do NOT use curl or other methods to create jobs — always use the dispatch command.
696
+
697
+ ### Dispatch Quality Requirements
698
+
699
+ Every dispatch MUST include:
700
+ - **Context**: What documents/files to read first (CLAUDE.md + relevant Hub + SKILL.md)
701
+ - **Task**: Specific deliverable with acceptance criteria
702
+ - **Constraints**: File paths, standards, what NOT to do
703
+ - **AKB instruction**: "⛔ AKB Rule: Read CLAUDE.md before starting work."
704
+
705
+ ### Anti-Patterns (NEVER do these)
706
+
707
+ - ❌ **Dispatching once and stopping** — you MUST keep working until directive is complete
708
+ - ❌ **Dispatching and NOT polling with --check** — you must poll for results
709
+ - ❌ **Re-dispatching when --check shows RUNNING** — just poll again
710
+ - ❌ Writing code yourself instead of dispatching to engineer
711
+ - ❌ Dispatching without acceptance criteria
712
+ - ❌ Accepting output without reviewing it
713
+ - ❌ Forgetting to update knowledge/tasks after work completes
714
+ - ❌ Doing only 1 dispatch when you should chain multiple (Engineer → QA)
715
+ - ❌ Reporting to superior without synthesizing subordinate outputs
716
+
717
+ ### Post-Dispatch Verification (CRITICAL)
718
+
719
+ After a subordinate completes a code task, you MUST verify the work is preserved:
720
+
721
+ \`\`\`bash
722
+ # 1. Check if subordinate committed their work
723
+ git log --oneline -3
724
+
725
+ # 2. If NOT committed (changes are unstaged), commit on their behalf
726
+ git add -A && git commit -m "feat(scope): description of subordinate's work"
727
+
728
+ # 3. Include in your report to CEO:
729
+ # - What files were changed
730
+ # - Commit hash (if committed)
731
+ # - Whether the changes compile (tsc --noEmit)
732
+ \`\`\`
733
+
734
+ ⛔ **Uncommitted work = lost work.** If the subordinate didn't commit, YOU must commit before reporting.
735
+ Your final report MUST include a **Change Summary** with files changed and commit status.`;
736
+ } else {
737
+ section += `
738
+
739
+ ## Delegation Rules (MANDATORY)
740
+
741
+ ⛔ **You have subordinates — USE THEM before doing work yourself.**
742
+
743
+ - **Always dispatch first.** Break the directive into sub-tasks and assign to your team.
744
+ - Only do work yourself if NO subordinate can handle it (e.g., cross-cutting decisions).
745
+ - Include clear task description, acceptance criteria, and relevant file paths in every dispatch.
746
+ - After receiving results, synthesize and report back.
747
+ - Your output should reference subordinate findings, not just your own file reads.`;
748
+ }
749
+
750
+ return section;
751
+ }
752
+
753
+ function buildSupervisionSection(node: OrgNode): string {
754
+ const hb = node.heartbeat ?? { enabled: true, intervalSec: 120, maxTicks: 60 };
755
+ return `# Supervision Mode (Heartbeat)
756
+
757
+ ⛔ **After dispatching subordinates, you MUST enter supervision mode.**
758
+ ⛔ **Use \`python3 "$SUPERVISION_CMD" watch\` — it blocks server-side at zero LLM cost and returns a digest.**
759
+ ⛔ **Do NOT use sleep + \`--check\` polling. The supervision watch command is cheaper and gives better information.**
760
+
761
+ ## Supervision Protocol
762
+
763
+ 1. **Dispatch** subordinates with clear task descriptions. Save returned session IDs.
764
+ 2. **Watch** with the supervision command:
765
+ \`\`\`bash
766
+ python3 "$SUPERVISION_CMD" watch <ses-id1>,<ses-id2> --duration ${hb.intervalSec}
767
+ \`\`\`
768
+ This blocks for ${hb.intervalSec}s (or until an alert event) and returns a digest summary.
769
+ 3. **Analyze the digest** — each tick, you must think critically:
770
+ - ✅ **Direction check**: Are subordinates on track with the plan?
771
+ - ✅ **Quality check**: Is the approach correct? (e.g., right patterns, right tools)
772
+ - ✅ **Peer consult**: Unsure about business/market direction? → \`python3 "$CONSULT_CMD" cbo "question"\`
773
+ - ⚠️ **Course correct**: Wrong direction → \`python3 "$SUPERVISION_CMD" amend <ses-id> "new instruction"\`
774
+ - 🛑 **Abort**: Seriously wrong → \`python3 "$SUPERVISION_CMD" abort <ses-id> --reason "why"\`
775
+ - ✅ **All done?** → Before reporting done, **verify deliverables** (see Quality Gate below)
776
+ 4. **Repeat** watch until all subordinates complete. Do NOT stop after one tick.
777
+ 5. **Quality Gate**: When subordinates report done, **run and test** the output:
778
+ - For web apps/games: start a local server and open in browser to verify it actually works
779
+ - Try the core user interactions — if basic things don't work, it's NOT done
780
+ - Check that required libraries/tools mentioned in the task are actually used
781
+ - If gaps found → re-dispatch with **specific, actionable** feedback (not "improve quality")
782
+ - There is NO time limit. Non-working code is worse than less code that works.
783
+
784
+ ## Supervision Commands
785
+
786
+ | Command | Description |
787
+ |---------|-------------|
788
+ | \`python3 "$SUPERVISION_CMD" watch <ids> --duration ${hb.intervalSec}\` | Watch sessions (blocks ${hb.intervalSec}s, returns digest) |
789
+ | \`python3 "$SUPERVISION_CMD" amend <ses-id> "instruction"\` | Inject new instructions into running session |
790
+ | \`python3 "$SUPERVISION_CMD" abort <ses-id> --reason "why"\` | Kill a session going wrong |
791
+ | \`python3 "$SUPERVISION_CMD" peers --wave <waveId> --role <roleId>\` | Discover peer C-Level sessions |
792
+ | \`python3 "$CONSULT_CMD" <peer-role> "question"\` | Ask a peer C-Level for input |
793
+
794
+ ## Digest Response
795
+
796
+ The watch command returns a JSON digest with:
797
+ - **significanceScore** (0-10): How much attention this tick needs
798
+ - **anomalies**: Errors, stalls (3min+ no events), sessions awaiting input
799
+ - **text**: Human-readable summary of per-session activity
800
+
801
+ Score 0-1 = quiet (all normal). Score 5+ = needs attention. Score 8+ = likely needs intervention.
802
+
803
+ ## Tick Quality (CRITICAL)
804
+
805
+ ⛔ **Each tick, you must make a REAL judgment — not just "still running, continuing".**
806
+
807
+ Good tick response:
808
+ \`\`\`
809
+ Digest: Engineer working on auth module (seq 45), QA waiting.
810
+ Judgment: Engineer is importing external API directly — should use our adapter pattern.
811
+ Action: amend engineer session with instruction to use adapter.
812
+ \`\`\`
813
+
814
+ Bad tick response (DO NOT DO THIS):
815
+ \`\`\`
816
+ "Sessions are still running. Continuing to watch."
817
+ \`\`\`
818
+
819
+ ## Budget
820
+
821
+ - Max ticks: ${hb.maxTicks} (${Math.round(hb.maxTicks * hb.intervalSec / 60)} minutes total)
822
+ - Quiet tick cost: ~$0.001 | Alert tick cost: ~$0.02-0.05
823
+
824
+ ## ⛔ Anti-Patterns
825
+
826
+ - ❌ Using \`sleep\` + \`--check\` to poll — use supervision watch instead
827
+ - ❌ Saying "still RUNNING, continuing to wait" without real analysis
828
+ - ❌ Ignoring digest anomalies — always address errors and stalls
829
+ - ❌ Stopping supervision after one tick — keep watching until ALL done
830
+ - ❌ Not consulting peers when business/market judgment is needed`;
831
+ }
832
+
833
+ function buildKnowledgeManagementSection(roleId: string): string {
834
+ const domainScope = roleId === 'cto'
835
+ ? '`architecture/`, `knowledge/` (technical docs)'
836
+ : roleId === 'cbo'
837
+ ? '`knowledge/` (business docs)'
838
+ : '`knowledge/` (your domain docs)';
839
+
840
+ return `# Knowledge Consistency Management (C-Level Responsibility)
841
+
842
+ ⛔ **You are responsible for keeping your domain's knowledge current and consistent.**
843
+
844
+ ## When to Check
845
+
846
+ - After completing any task (The Loop step ④)
847
+ - During supervision ticks when subordinates are progressing normally
848
+ - When you notice conflicting information during work
849
+
850
+ ## Knowledge Gate
851
+
852
+ 1. Check if changes conflict with existing documents (grep 3+ keywords)
853
+ 2. Update or deprecate stale references — do NOT leave legacy docs pretending to be current
854
+ 3. Verify new docs are registered in their Hub
855
+ 4. Ensure cross-links between related documents
856
+
857
+ ## AKB Linting
858
+
859
+ | Check | Description |
860
+ |-------|-------------|
861
+ | Orphan docs | Every document reachable from a Hub? |
862
+ | Stale content | Deprecated designs still described as current? Mark ⚠️ |
863
+ | Term consistency | Same concept called different names across docs? |
864
+ | Date freshness | Old dates/status without warning? |
865
+
866
+ **Your scope**: ${domainScope}`;
867
+ }
868
+
869
+ function buildConsultSection(orgTree: OrgTree, roleId: string): string | null {
870
+ // Build list of roles this agent can consult
871
+ const consultable: string[] = [];
872
+ for (const [id] of orgTree.nodes) {
873
+ if (id !== roleId && canConsult(orgTree, roleId, id)) {
874
+ consultable.push(id);
875
+ }
876
+ }
877
+
878
+ if (consultable.length === 0) return null;
879
+
880
+ const roleList = consultable.map((id) => {
881
+ const n = orgTree.nodes.get(id);
882
+ if (!n) return `- \`${id}\``;
883
+ const firstLine = n.persona.split('\n')[0] || n.name;
884
+ return `- **${n.name}** (\`${id}\`): ${firstLine}`;
885
+ }).join('\n');
886
+
887
+ return `# Consult (Ask Colleagues)
888
+
889
+ You can ask questions to other roles using the \`consult\` tool:
890
+
891
+ ${roleList}
892
+
893
+ ## How to Consult
894
+
895
+ Use the \`consult\` tool:
896
+ \`\`\`json
897
+ { "roleId": "designer", "question": "What color scheme are you using for the dashboard?" }
898
+ \`\`\`
899
+
900
+ The consulted role will answer your question in read-only mode and return the response to you.
901
+
902
+ ## When to Use
903
+ - Need technical decisions or clarifications from your manager
904
+ - Need design/implementation details from a peer
905
+ - Need domain expertise from another team member
906
+ - Unsure about architecture or conventions — ask before guessing
907
+
908
+ ## Rules
909
+ - The consulted role answers in **read-only mode** (no file modifications)
910
+ - Keep questions specific and concise for better answers
911
+ - Don't consult for tasks that should be dispatched (use dispatch for work assignments)`;
912
+ }