@kognai/orchestrator-core 0.1.1 → 0.1.3

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.
@@ -0,0 +1,835 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AgentCreator = exports.CTOAgent = exports.CTODataCollector = exports.CEOAgent = exports.Supervisor2Agent = exports.SupervisorAgent = void 0;
4
+ exports.reconcileSupervisorReviews = reconcileSupervisorReviews;
5
+ /**
6
+ * engine-agents.ts — leadership/agent classes extracted from orchestrate-engine.ts
7
+ * (TICKET-231 engine split 3). SupervisorAgent, Supervisor2Agent, reconcileSupervisorReviews,
8
+ * CEOAgent, CTODataCollector, CTOAgent, AgentCreator + the SpawnGate seam types.
9
+ * Imports shared primitives (log/callLLM/normalizeReview/c) + AgentTask/ReviewResult/CTO* types
10
+ * back from the engine via a runtime-safe circular import (methods use them at call-time).
11
+ */
12
+ const fs_1 = require("fs");
13
+ const sherlock_memory_1 = require("./sherlock-memory");
14
+ const clawrouter_client_1 = require("./clawrouter-client");
15
+ const citizenship_1 = require("./citizenship");
16
+ const engine_loaders_1 = require("./engine-loaders");
17
+ const orchestrate_engine_1 = require("./orchestrate-engine");
18
+ // ===== Supervisor Agent (Claude via Anthropic API) =====
19
+ class SupervisorAgent {
20
+ systemPrompt;
21
+ constructor() {
22
+ const promptPath = './agents/supervisor/prompt.md';
23
+ const rawPrompt = (0, fs_1.existsSync)(promptPath) ? (0, fs_1.readFileSync)(promptPath, 'utf-8') : 'You are a code review supervisor.';
24
+ this.systemPrompt = (0, engine_loaders_1.loadConstitutionalPreamble)() + rawPrompt;
25
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.magenta, '+ Loaded supervisor agent (Claude via Anthropic API)');
26
+ }
27
+ async reviewTask(task, files) {
28
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.magenta, `\n[supervisor] Reviewing: ${task.id}`);
29
+ // FIX: Use XML-style tags (NOT code fences) so the model can't confuse display format with file content
30
+ // NOTE: 12000 char limit — covers EXACT CONTENT files (typically 5K-10K chars); 4000 was too small
31
+ const fileContents = files.map((filepath) => {
32
+ const content = (0, fs_1.existsSync)(filepath) ? (0, fs_1.readFileSync)(filepath, 'utf-8') : '';
33
+ return `### ${filepath}\n<file_content>\n${content.substring(0, 12000)}\n</file_content>`;
34
+ }).join('\n\n');
35
+ // FIX: Pre-compute fence check in TypeScript — inject evidence so model never hallucinates fence presence
36
+ const fenceCheckLines = files.map((filepath) => {
37
+ const content = (0, fs_1.existsSync)(filepath) ? (0, fs_1.readFileSync)(filepath, 'utf-8') : '';
38
+ const firstLine = content.trimStart().split('\n')[0] || '';
39
+ const hasFence = firstLine.startsWith('```');
40
+ return ` ${filepath}: ${hasFence ? `FENCE DETECTED (first line: ${JSON.stringify(firstLine)})` : 'OK (no fence at start)'}`;
41
+ }).join('\n');
42
+ // CTO-005: Add fence detection to supervisor review checklist
43
+ const integrityContext = task._integrityFailed
44
+ ? `\n\n## ⚠️ INTEGRITY ALERT\n${task._integrityDetails}\nThis file was flagged for destructive rewrite. The original was preserved. REJECT this task.\n`
45
+ : '';
46
+ // Sherlock v2: inject ASMR episodic memory context (AMD-21-03) — fail-open
47
+ const memoryContext = await (0, sherlock_memory_1.getSherlockMemoryContext)(task.context || task.id);
48
+ const userPrompt = `Review the following code generated for task ${task.id}.\n\n## Task Spec\n${task.context}${memoryContext}\n\n## Generated Files (${files.length})\n${fileContents}${integrityContext}\n\n## Pre-computed Fence Check (authoritative — do NOT infer from display format)\n${fenceCheckLines}\n\n## Instructions\nCRITICAL CHECK: Use the Pre-computed Fence Check above. If any file shows FENCE DETECTED, REJECT. Do NOT infer fence presence from the <file_content> display tags — those are display-only wrappers.\nAlso check: Did the file lose existing functionality? If a file shrank significantly, REJECT.\n\n## Categorical grade (use a discrete letter — no fake precision)\n- A: production-perfect. No improvements possible. Ship.\n- B: good with minor polish needed (rename, comment, formatting).\n- C: works but has noticeable issues (missed edge case, weak abstraction, partial spec coverage). APPROVED with caveats.\n- D: significant problems (broken edge case, regression risk, anti-pattern). REJECT.\n- F: broken, unsafe, doesn't meet spec, or fence/integrity failure. REJECT.\n\nThe deterministic gate already ran (typecheck + structural). If a file got here, syntax is valid — focus your review on substance, not parseability.\n\nRespond with a JSON object:\n{\n "verdict": "APPROVED" or "REJECTED",\n "grade": "A" | "B" | "C" | "D" | "F",\n "score_rationale": "ONE sentence naming the specific factor that determined the grade. Vague 'good code' is NOT acceptable.",\n "summary": "brief review summary",\n "issues": [{"severity": "critical|high|medium|low", "file": "path", "description": "..."}],\n "strengths": ["..."]\n}`;
49
+ const startTime = Date.now();
50
+ // B.15: DeepSeek via ClawRouter for standard tasks (~$0.02/task vs $0.07 dual-supervisor)
51
+ // Retain Claude Sonnet only for audit/refactor-complex (high-stakes)
52
+ const taskType = task.task_type || '';
53
+ const isHighStakes = taskType === 'audit' || taskType === 'refactor-complex' ||
54
+ (task.context || '').toLowerCase().includes('security') || (task.context || '').toLowerCase().includes('audit');
55
+ const crAvail = await (0, clawrouter_client_1.clawRouterIsAvailable)().catch(() => false);
56
+ let reviewProvider = (crAvail && !isHighStakes) ? 'clawrouter' : 'anthropic';
57
+ const reviewModel = reviewProvider === 'clawrouter' ? 'deepseek/deepseek-chat' : 'claude-sonnet-4-6';
58
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.gray, ` -> Sending to ${reviewProvider === 'clawrouter' ? 'ClawRouter/DeepSeek' : 'Claude Sonnet'} (${isHighStakes ? 'high-stakes' : 'standard'})...`);
59
+ try {
60
+ const response = await (0, orchestrate_engine_1.callLLM)(reviewProvider, reviewModel, this.systemPrompt, userPrompt, 120000, 'supervisor', 'code_review');
61
+ const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
62
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.gray, ` -> Review received in ${elapsed}s (${response.usage?.total_tokens || '?'} tokens)`);
63
+ const content = response.choices?.[0]?.message?.content || '';
64
+ const jsonMatch = content.match(/\{[\s\S]*\}/);
65
+ if (jsonMatch) {
66
+ const review = (0, orchestrate_engine_1.normalizeReview)(JSON.parse(jsonMatch[0]));
67
+ const gradeLabel = review.grade ? `${review.grade} · ` : '';
68
+ if (review.verdict === 'APPROVED') {
69
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.green, ` ✓ APPROVED (${gradeLabel}score: ${review.score}/100)`);
70
+ }
71
+ else {
72
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.red, ` ✗ REJECTED (${gradeLabel}score: ${review.score}/100)`);
73
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.yellow, ` Summary: ${review.summary}`);
74
+ for (const issue of review.issues || []) {
75
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.yellow, ` [${issue.severity}] ${issue.file}: ${issue.description}`);
76
+ }
77
+ }
78
+ return review;
79
+ }
80
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.yellow, ' ! Could not parse review JSON, auto-approving');
81
+ return { verdict: 'APPROVED', score: 70, summary: 'Auto-approved (parse failure)', issues: [], strengths: [] };
82
+ }
83
+ catch (error) {
84
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.yellow, ` ! Supervisor error: ${error.message}`);
85
+ return { verdict: 'APPROVED', score: 0, summary: `Supervisor unavailable: ${error.message}`, issues: [], strengths: [] };
86
+ }
87
+ }
88
+ }
89
+ exports.SupervisorAgent = SupervisorAgent;
90
+ // ===== Supervisor 2 Agent (Claude Haiku 4.5; DeepSeek mono-supervision on Anthropic drain) =====
91
+ class Supervisor2Agent {
92
+ systemPrompt;
93
+ constructor() {
94
+ const promptPath = './agents/supervisor/prompt.md';
95
+ const rawPrompt = (0, fs_1.existsSync)(promptPath) ? (0, fs_1.readFileSync)(promptPath, 'utf-8') : 'You are a code review supervisor.';
96
+ this.systemPrompt = (0, engine_loaders_1.loadConstitutionalPreamble)() + rawPrompt;
97
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.magenta, '+ Loaded supervisor 2 agent (Claude Haiku — second pass)');
98
+ }
99
+ async reviewTask(task, files) {
100
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.magenta, `\n[supervisor-2/haiku] Reviewing: ${task.id}`);
101
+ // FIX: Use XML-style tags (NOT code fences) so the model can't confuse display format with file content
102
+ // NOTE: 12000 char limit — covers EXACT CONTENT files (typically 5K-10K chars); 4000 was too small
103
+ const fileContents = files.map((filepath) => {
104
+ const content = (0, fs_1.existsSync)(filepath) ? (0, fs_1.readFileSync)(filepath, 'utf-8') : '';
105
+ return `### ${filepath}\n<file_content>\n${content.substring(0, 12000)}\n</file_content>`;
106
+ }).join('\n\n');
107
+ // FIX: Pre-compute fence check in TypeScript — inject evidence so model never hallucinates fence presence
108
+ const fenceCheckLines2 = files.map((filepath) => {
109
+ const content = (0, fs_1.existsSync)(filepath) ? (0, fs_1.readFileSync)(filepath, 'utf-8') : '';
110
+ const firstLine = content.trimStart().split('\n')[0] || '';
111
+ const hasFence = firstLine.startsWith('```');
112
+ return ` ${filepath}: ${hasFence ? `FENCE DETECTED (first line: ${JSON.stringify(firstLine)})` : 'OK (no fence at start)'}`;
113
+ }).join('\n');
114
+ // CTO-005: Add fence detection to Haiku supervisor review checklist
115
+ const integrityContext2 = task._integrityFailed
116
+ ? `\n\n## ⚠️ INTEGRITY ALERT\n${task._integrityDetails}\nThis file was flagged for destructive rewrite. The original was preserved. REJECT this task.\n`
117
+ : '';
118
+ const userPrompt = `Review the following code generated for task ${task.id}.\n\n## Task Spec\n${task.context}\n\n## Generated Files (${files.length})\n${fileContents}${integrityContext2}\n\n## Pre-computed Fence Check (authoritative — do NOT infer from display format)\n${fenceCheckLines2}\n\n## Instructions\nCRITICAL CHECK: Use the Pre-computed Fence Check above. If any file shows FENCE DETECTED, REJECT. Do NOT infer fence presence from the <file_content> display tags — those are display-only wrappers.\nAlso check: Did the file lose existing functionality? If a file shrank significantly, REJECT.\n\n## Categorical grade (use a discrete letter — no fake precision)\n- A: production-perfect. No improvements possible. Ship.\n- B: good with minor polish needed (rename, comment, formatting).\n- C: works but has noticeable issues (missed edge case, weak abstraction, partial spec coverage). APPROVED with caveats.\n- D: significant problems (broken edge case, regression risk, anti-pattern). REJECT.\n- F: broken, unsafe, doesn't meet spec, or fence/integrity failure. REJECT.\n\nThe deterministic gate already ran (typecheck + structural). If a file got here, syntax is valid — focus your review on substance, not parseability.\n\nRespond with a JSON object:\n{\n "verdict": "APPROVED" or "REJECTED",\n "grade": "A" | "B" | "C" | "D" | "F",\n "score_rationale": "ONE sentence naming the specific factor that determined the grade. Vague 'good code' is NOT acceptable.",\n "summary": "brief review summary",\n "issues": [{"severity": "critical|high|medium|low", "file": "path", "description": "..."}],\n "strengths": ["..."]\n}`;
119
+ const startTime = Date.now();
120
+ // B.15: Use Haiku for second-pass review — 10x cheaper than Sonnet.
121
+ // Founder directive 2026-05-25: if Anthropic depletes, fall back to ClawRouter/DeepSeek
122
+ // mono-supervision rather than halting the swarm. Autonomy is on.
123
+ const tryPath = async (provider, model, label) => {
124
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.gray, ` -> Sending to ${label}...`);
125
+ const response = provider === 'anthropic'
126
+ ? await (0, orchestrate_engine_1.callAnthropicCached)(model, this.systemPrompt, userPrompt, 120000)
127
+ : await (0, orchestrate_engine_1.callLLM)('clawrouter', model, this.systemPrompt, userPrompt, 120000, 'supervisor-2', 'code_review');
128
+ const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
129
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.gray, ` -> ${label} review received in ${elapsed}s (${response.usage?.total_tokens || '?'} tokens)`);
130
+ return response;
131
+ };
132
+ let response;
133
+ let usedFallback = false;
134
+ try {
135
+ response = await tryPath('anthropic', 'claude-haiku-4-5-20251001', 'Claude Haiku (second pass)');
136
+ }
137
+ catch (error) {
138
+ const msg = String(error?.message || '');
139
+ const credit = /credit balance|invalid_request_error.*credit|insufficient.*quota/i.test(msg);
140
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.yellow, ` ! [Haiku] Anthropic unavailable${credit ? ' (credits)' : ''}: ${msg.slice(0, 120)}`);
141
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.yellow, ' ! Falling back to ClawRouter/DeepSeek mono-supervision — swarm continues');
142
+ try {
143
+ response = await tryPath('clawrouter', 'deepseek/deepseek-chat', 'ClawRouter/DeepSeek (mono fallback)');
144
+ usedFallback = true;
145
+ }
146
+ catch (fallbackErr) {
147
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.yellow, ` ! [Haiku] Both Anthropic + ClawRouter unavailable: ${fallbackErr.message}`);
148
+ return { verdict: 'APPROVED', score: 0, summary: `Supervisor 2 unavailable: ${msg.slice(0, 200)}`, issues: [], strengths: [] };
149
+ }
150
+ }
151
+ const content = response.choices?.[0]?.message?.content || '';
152
+ const jsonMatch = content.match(/\{[\s\S]*\}/);
153
+ if (jsonMatch) {
154
+ const review = (0, orchestrate_engine_1.normalizeReview)(JSON.parse(jsonMatch[0]));
155
+ const tag = usedFallback ? '[DeepSeek-fallback]' : '[Haiku]';
156
+ const gradeLabel = review.grade ? `${review.grade} · ` : '';
157
+ if (review.verdict === 'APPROVED')
158
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.green, ` ✓ ${tag} APPROVED (${gradeLabel}score: ${review.score}/100)`);
159
+ else {
160
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.red, ` ✗ ${tag} REJECTED (${gradeLabel}score: ${review.score}/100)`);
161
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.yellow, ` Summary: ${review.summary}`);
162
+ for (const issue of review.issues || [])
163
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.yellow, ` [${issue.severity}] ${issue.file}: ${issue.description}`);
164
+ }
165
+ return review;
166
+ }
167
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.yellow, ' ! [Haiku] Could not parse review JSON, auto-approving');
168
+ return { verdict: 'APPROVED', score: 70, summary: 'Auto-approved (parse failure)', issues: [], strengths: [] };
169
+ }
170
+ }
171
+ exports.Supervisor2Agent = Supervisor2Agent;
172
+ async function reconcileSupervisorReviews(review1, review2, task, ceo) {
173
+ // S67-001 + founder directive 2026-05-25: graceful degradation when either supervisor depletes.
174
+ // Autonomy is on — swarm must keep shipping. DeepSeek/ClawRouter fallback substitutes for Anthropic.
175
+ // Naming history: sup1 was Sonnet, sup2 was OpenAI Codex (hence "Codex" in older logs).
176
+ // Today sup1 routes DeepSeek-by-default + Sonnet for high-stakes; sup2 routes Haiku + DeepSeek fallback.
177
+ // The log labels below use "Sup1" / "Sup2" to stay accurate regardless of which provider answered.
178
+ const isUnavailable = (r) => /Supervisor unavailable|Supervisor 2 unavailable|unavailable/i.test(r.summary || '');
179
+ const sup1Unavailable = isUnavailable(review1);
180
+ const sup2Unavailable = isUnavailable(review2);
181
+ if (sup1Unavailable && sup2Unavailable) {
182
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.yellow, ` ! BOTH supervisors unavailable (Anthropic + fallback drained) — auto-approving so swarm keeps shipping`);
183
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.yellow, ` KSL will capture this attempt for training. Restore credits to re-enable review.`);
184
+ return {
185
+ finalReview: {
186
+ verdict: 'APPROVED', score: 50,
187
+ summary: 'Auto-approved — both supervisors unavailable. Founder directive: autonomy on, swarm continues.',
188
+ issues: [], strengths: [],
189
+ },
190
+ review1, review2, consensus: false, escalatedToCEO: false,
191
+ };
192
+ }
193
+ if (sup1Unavailable) {
194
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.yellow, ` ! Sup1 unavailable — Sup2 as sole reviewer (score: ${review2.score}/100)`);
195
+ return { finalReview: review2, review1, review2, consensus: false, escalatedToCEO: false };
196
+ }
197
+ if (sup2Unavailable) {
198
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.yellow, ` ! Sup2 unavailable — Sup1 as sole reviewer (score: ${review1.score}/100)`);
199
+ return { finalReview: review1, review1, review2, consensus: false, escalatedToCEO: false };
200
+ }
201
+ const bothApproved = review1.verdict === 'APPROVED' && review2.verdict === 'APPROVED';
202
+ const bothRejected = review1.verdict !== 'APPROVED' && review2.verdict !== 'APPROVED';
203
+ const consensus = bothApproved || bothRejected;
204
+ if (bothApproved) {
205
+ // Both approve — take the average score, merge strengths
206
+ const avgScore = Math.round((review1.score + review2.score) / 2);
207
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.green, ` ✓ DUAL CONSENSUS: Both supervisors APPROVED (Sup1: ${review1.score}, Sup2: ${review2.score}, avg: ${avgScore})`);
208
+ return {
209
+ finalReview: {
210
+ verdict: 'APPROVED',
211
+ score: avgScore,
212
+ summary: `Dual-approved: Sup1 (${review1.score}/100) + Sup2 (${review2.score}/100)`,
213
+ issues: [...review1.issues, ...review2.issues],
214
+ strengths: Array.from(new Set([...review1.strengths, ...review2.strengths])),
215
+ },
216
+ review1, review2, consensus: true, escalatedToCEO: false,
217
+ };
218
+ }
219
+ if (bothRejected) {
220
+ // Both reject — merge issues, take lower score
221
+ const minScore = Math.min(review1.score, review2.score);
222
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.red, ` ✗ DUAL CONSENSUS: Both supervisors REJECTED (Sup1: ${review1.score}, Sup2: ${review2.score})`);
223
+ return {
224
+ finalReview: {
225
+ verdict: 'REJECTED',
226
+ score: minScore,
227
+ summary: `Dual-rejected: Sup1 (${review1.score}/100) + Sup2 (${review2.score}/100). ${review1.summary} | ${review2.summary}`,
228
+ issues: [...review1.issues, ...review2.issues],
229
+ strengths: [],
230
+ },
231
+ review1, review2, consensus: true, escalatedToCEO: false,
232
+ };
233
+ }
234
+ // CONFLICT — one approved, one rejected → escalate to CEO
235
+ const approver = review1.verdict === 'APPROVED' ? 'Sup1' : 'Sup2';
236
+ const rejecter = review1.verdict === 'APPROVED' ? 'Sup2' : 'Sup1';
237
+ const approvalReview = review1.verdict === 'APPROVED' ? review1 : review2;
238
+ const rejectionReview = review1.verdict === 'APPROVED' ? review2 : review1;
239
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.yellow, ` ⚡ SUPERVISOR CONFLICT on ${task.id}: ${approver} APPROVED (${approvalReview.score}), ${rejecter} REJECTED (${rejectionReview.score})`);
240
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.magenta, ` → Escalating to CEO for final decision...`);
241
+ try {
242
+ const ceoDecision = await ceo.resolveReviewConflict(task, approvalReview, rejectionReview, approver, rejecter);
243
+ const ceoApproves = ceoDecision.toLowerCase().includes('approve');
244
+ (0, orchestrate_engine_1.log)(ceoApproves ? orchestrate_engine_1.c.green : orchestrate_engine_1.c.red, ` CEO DECISION: ${ceoApproves ? 'APPROVED' : 'REJECTED'} — ${ceoDecision.substring(0, 200)}`);
245
+ return {
246
+ finalReview: {
247
+ verdict: ceoApproves ? 'APPROVED' : 'REJECTED',
248
+ score: ceoApproves ? approvalReview.score : rejectionReview.score,
249
+ summary: `CEO resolved conflict (${approver} approved, ${rejecter} rejected): ${ceoDecision.substring(0, 300)}`,
250
+ issues: rejectionReview.issues,
251
+ strengths: approvalReview.strengths,
252
+ },
253
+ review1, review2, consensus: false, escalatedToCEO: true, ceoDecision,
254
+ };
255
+ }
256
+ catch (error) {
257
+ // CEO unavailable — default to rejection (safer)
258
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.yellow, ` CEO unavailable for conflict resolution: ${error.message}. Defaulting to REJECTED.`);
259
+ return {
260
+ finalReview: rejectionReview,
261
+ review1, review2, consensus: false, escalatedToCEO: false,
262
+ };
263
+ }
264
+ }
265
+ // ===== CEO Agent (Claude via Anthropic API) =====
266
+ class CEOAgent {
267
+ systemPrompt;
268
+ constructor() {
269
+ const promptPath = './agents/ceo/prompt.md';
270
+ const rawPrompt = (0, fs_1.existsSync)(promptPath) ? (0, fs_1.readFileSync)(promptPath, 'utf-8') : 'You are the CEO of Countable.';
271
+ this.systemPrompt = (0, engine_loaders_1.loadConstitutionalPreamble)() + rawPrompt;
272
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.magenta, '+ Loaded CEO agent (Claude via Anthropic API)');
273
+ }
274
+ async reviewSprintProgress(tasks) {
275
+ const done = tasks.filter(t => t.status === 'done').length;
276
+ const total = tasks.length;
277
+ const pending = tasks.filter(t => t.status === 'pending');
278
+ const rejected = tasks.filter(t => t.status === 'rejected');
279
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.magenta, `\n[ceo] Sprint progress check (${done}/${total} done)`);
280
+ const userPrompt = `IMPORTANT: Plain text only, no tools, no XML. Respond directly.
281
+
282
+ Sprint progress update:\n- Done: ${done}/${total}\n- Pending: ${pending.map(t => t.id).join(', ') || 'none'}\n- Rejected: ${rejected.map(t => t.id).join(', ') || 'none'}\n\nTask details:\n${JSON.stringify(tasks.map(t => ({ id: t.id, status: t.status, agent: t.agent, priority: t.priority })), null, 2)}\n\nAs CEO, briefly assess:\n1. Are we on track?\n2. Any tasks to re-prioritize?\n3. Cost efficiency — are we using the right models?\n4. Any strategic adjustments needed?\n\nKeep response under 200 words.`;
283
+ try {
284
+ // B.20: ClawRouter/DeepSeek — formulaic progress check, $0 via x402 wallet
285
+ const response = await (0, orchestrate_engine_1.callLLM)('clawrouter', 'deepseek/deepseek-chat', this.systemPrompt, userPrompt, 60000, 'ceo', 'sprint_progress_review');
286
+ const content = response.choices?.[0]?.message?.content || 'No response';
287
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.magenta, ` CEO assessment: ${content.substring(0, 500)}`);
288
+ return content;
289
+ }
290
+ catch (error) {
291
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.yellow, ` ! CEO unavailable: ${error.message}`);
292
+ return 'CEO agent unavailable';
293
+ }
294
+ }
295
+ async resolveReviewConflict(task, approvalReview, rejectionReview, approver, rejecter) {
296
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.magenta, `\n[ceo] Resolving supervisor conflict on ${task.id}...`);
297
+ const userPrompt = `IMPORTANT: Respond with ONLY APPROVE or REJECT and a brief reason. No tools, no XML, no file reading.\n\nTwo code review supervisors disagree on task ${task.id}.
298
+
299
+ ## Task Spec
300
+ ${task.context?.substring(0, 800) || 'No context'}
301
+
302
+ ## ${approver} says APPROVED (score: ${approvalReview.score}/100)
303
+ Summary: ${approvalReview.summary}
304
+ Strengths: ${approvalReview.strengths?.join(', ') || 'none listed'}
305
+
306
+ ## ${rejecter} says REJECTED (score: ${rejectionReview.score}/100)
307
+ Summary: ${rejectionReview.summary}
308
+ Issues found:
309
+ ${(rejectionReview.issues || []).map(i => `- [${i.severity}] ${i.file}: ${i.description}`).join('\n')}
310
+
311
+ ## Your Decision
312
+ As CEO, you must make the final call. Consider:
313
+ 1. Are the rejection issues genuine blockers or nitpicks?
314
+ 2. Does the code meet the task spec requirements?
315
+ 3. Is it safe to ship, or are there real quality/security concerns?
316
+
317
+ Respond with ONE of:
318
+ - "APPROVE — [brief reason]" if the code is good enough to ship
319
+ - "REJECT — [brief reason]" if the rejection issues are valid and must be fixed
320
+
321
+ Keep response under 100 words.`;
322
+ try {
323
+ const response = await (0, orchestrate_engine_1.callLLM)('anthropic', 'claude-sonnet-4-20250514', this.systemPrompt, userPrompt, 60000, 'ceo', 'supervisor_conflict_resolution');
324
+ const content = response.choices?.[0]?.message?.content || 'No response';
325
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.magenta, ` CEO conflict resolution: ${content.substring(0, 300)}`);
326
+ return content;
327
+ }
328
+ catch (error) {
329
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.yellow, ` ! CEO unavailable for conflict resolution: ${error.message}`);
330
+ throw error;
331
+ }
332
+ }
333
+ async reviewCTOProposals(ctoReport) {
334
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.magenta, `\n[ceo] Reviewing ${ctoReport.proposals.length} CTO proposals...`);
335
+ const proposalsSummary = ctoReport.proposals.map((p, i) => `
336
+ ### Proposal ${i + 1}: ${p.title}
337
+ - ID: ${p.id}
338
+ - Category: ${p.category}
339
+ - Risk: ${p.risk_level}
340
+ - Description: ${p.description}
341
+ - Impact: ${p.estimated_impact}
342
+ - Steps: ${p.implementation_steps.join(', ')}
343
+ ${p.agent_spec ? `- NEW AGENT: name=${p.agent_spec.name}, role="${p.agent_spec.role}", llm=${p.agent_spec.llm}, trigger=${p.agent_spec.trigger}` : ''}
344
+ `).join('\n');
345
+ const userPrompt = `IMPORTANT: You have NO tools. Do NOT output XML tool calls or file-reading syntax. Respond ONLY with the JSON array. All context is in this message.
346
+
347
+ The CTO has analyzed our project data and submitted ${ctoReport.proposals.length} proposal(s).
348
+
349
+ ## CTO Summary
350
+ ${ctoReport.summary}
351
+
352
+ ## Proposals
353
+ ${proposalsSummary}
354
+
355
+ ## Your Review Criteria
356
+ For each proposal, evaluate:
357
+ 1. **Evidence-based**: Is it backed by real sprint data? (CTO reviewed: ${ctoReport.metrics_reviewed.join(', ')})
358
+ 2. **Cost impact**: Will this save or cost money?
359
+ 3. **Risk level**: Can we roll back if it fails?
360
+ 4. **Business value**: Does it help ship features faster?
361
+ 5. **Disruption level**: How much will this change current workflows?
362
+
363
+ **For new_agent proposals, ALSO evaluate:**
364
+ - Is the capability gap real? (not something an existing agent handles)
365
+ - Is MiniMax appropriate, or does this need Claude-level intelligence?
366
+ - Is the trigger frequency reasonable? (every_sprint may be expensive)
367
+ - Will the total agent count become unmanageable?
368
+
369
+ **For ClawHub skill proposals:**
370
+ - Has a security_review step been included? (REQUIRED — ClawHub skills may contain malware)
371
+ - Is the skill from a trusted author?
372
+
373
+ ## Decision Format
374
+ For EACH proposal, respond with a decision JSON:
375
+ {
376
+ "decision": "APPROVED | REJECTED | DEFERRED",
377
+ "proposal_id": "the proposal ID",
378
+ "reasoning": "Why this decision",
379
+ "conditions": ["Any conditions for implementation"],
380
+ "cascade_orders": ["Company-wide changes if approved"],
381
+ "priority": "immediate | next_sprint | backlog"
382
+ }
383
+
384
+ Wrap all decisions in a JSON array. Be concise.`;
385
+ try {
386
+ // B.6: Haiku for CTO proposal reviews — 10x cheaper, prompt-cached system prompt
387
+ const response = await (0, orchestrate_engine_1.callAnthropicCached)('claude-haiku-4-5-20251001', this.systemPrompt, userPrompt, 60000);
388
+ const content = response.choices?.[0]?.message?.content || 'No response';
389
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.magenta, ` CEO CTO review: ${content.substring(0, 500)}`);
390
+ return content;
391
+ }
392
+ catch (error) {
393
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.yellow, ` ! CEO CTO review failed: ${error.message}`);
394
+ return 'CEO unavailable for CTO review';
395
+ }
396
+ }
397
+ async generateDailyReport(tasks, stats, ctoReport, ctoDecisions) {
398
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.magenta, '\n[ceo] Generating daily report for owner...');
399
+ const done = tasks.filter(t => t.status === 'done').length;
400
+ const rejected = tasks.filter(t => t.status === 'rejected').length;
401
+ const pending = tasks.filter(t => t.status === 'pending').length;
402
+ const today = new Date().toISOString().split('T')[0];
403
+ const userPrompt = `IMPORTANT: Output ONLY the markdown report. No tools, no XML, no file reading. All data is in this message.\n\nGenerate a daily report for the owner of Countable. Today is ${today}.
404
+
405
+ ## Sprint Data
406
+ - Tasks executed: ${stats.tasksExecuted}
407
+ - Approved: ${stats.approved}
408
+ - Rejected: ${stats.rejected}
409
+ - Done: ${done}, Pending: ${pending}, Total: ${tasks.length}
410
+
411
+ Task details:
412
+ ${JSON.stringify(tasks.map(t => ({ id: t.id, status: t.status, agent: t.agent })), null, 2)}
413
+
414
+ ## CTO Report
415
+ ${ctoReport}
416
+
417
+ ## CEO Decisions on CTO Proposals
418
+ ${ctoDecisions}
419
+
420
+ ## Dual Supervisor Review Stats
421
+ - Supervisor conflicts: ${stats.conflicts || 0}
422
+ - CEO escalations: ${stats.escalations || 0}
423
+
424
+ ## Estimated Costs
425
+ - MiniMax coding: ~$0.09/task x ${stats.tasksExecuted} tasks = ~$${(stats.tasksExecuted * 0.09).toFixed(2)}
426
+ - Sup1 reviews (DeepSeek default, Sonnet for high-stakes): ~$0.02/review x ${stats.approved + stats.rejected} reviews = ~$${((stats.approved + stats.rejected) * 0.02).toFixed(2)}
427
+ - Sup2 reviews (Haiku default, DeepSeek on drain): ~$0.005/review x ${stats.approved + stats.rejected} reviews = ~$${((stats.approved + stats.rejected) * 0.005).toFixed(2)}
428
+ - CEO conflict resolution: ~$0.03 x ${stats.escalations || 0} escalations = ~$${((stats.escalations || 0) * 0.03).toFixed(2)}
429
+ - Claude CEO calls: ~$0.03 x 4 = ~$0.12
430
+ - MiniMax CTO scan: ~$0.05
431
+
432
+ Generate a concise daily report in markdown format following the template in your prompt. Include:
433
+ 1. Sprint Progress
434
+ 2. Cost Summary
435
+ 3. Key Decisions Made
436
+ 4. CTO Proposals Reviewed
437
+ 5. Blockers & Risks
438
+ 6. Tomorrow's Plan
439
+
440
+ Keep it under 300 words. Be honest about failures.`;
441
+ try {
442
+ // B.20: ClawRouter/DeepSeek — template fill-in, $0 via x402 wallet
443
+ const response = await (0, orchestrate_engine_1.callLLM)('clawrouter', 'deepseek/deepseek-chat', this.systemPrompt, userPrompt, 60000, 'ceo', 'sprint_final_report');
444
+ const report = response.choices?.[0]?.message?.content || 'Report generation failed';
445
+ // Save to reports/daily/
446
+ (0, fs_1.mkdirSync)('reports/daily', { recursive: true });
447
+ const reportPath = `reports/daily/${today}.md`;
448
+ (0, fs_1.writeFileSync)(reportPath, report);
449
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.green, ` ✓ Daily report saved: ${reportPath}`);
450
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.magenta, ` Report preview: ${report.substring(0, 300)}`);
451
+ }
452
+ catch (error) {
453
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.yellow, ` ! Daily report failed: ${error.message}`);
454
+ }
455
+ }
456
+ }
457
+ exports.CEOAgent = CEOAgent;
458
+ // ===== CTO Data Collector (gathers real project context for CTO analysis) =====
459
+ class CTODataCollector {
460
+ collect() {
461
+ const sections = ['## PROJECT DATA (Real metrics — base all proposals on this data)\n'];
462
+ // 1. Sprint results — find most recent sprint file
463
+ try {
464
+ const sprintDir = './sprints';
465
+ if ((0, fs_1.existsSync)(sprintDir)) {
466
+ const sprintFiles = (0, fs_1.readdirSync)(sprintDir).filter(f => f.endsWith('.json')).sort().reverse();
467
+ if (sprintFiles.length > 0) {
468
+ const latestSprint = JSON.parse((0, fs_1.readFileSync)(`${sprintDir}/${sprintFiles[0]}`, 'utf-8'));
469
+ const tasks = latestSprint.tasks || [];
470
+ const done = tasks.filter((t) => t.status === 'done').length;
471
+ const rejected = tasks.filter((t) => t.status === 'rejected').length;
472
+ sections.push(`### Sprint Results (${sprintFiles[0].replace('.json', '')})`);
473
+ sections.push(`- ${tasks.length} tasks, ${done} approved, ${rejected} rejected`);
474
+ for (const t of tasks) {
475
+ const score = t.output?.review?.score || '?';
476
+ const attempts = t.output?.review ? 1 : '?';
477
+ sections.push(`- ${t.id} (${t.agent}): status=${t.status}, score=${score}`);
478
+ }
479
+ sections.push('');
480
+ }
481
+ }
482
+ }
483
+ catch (e) {
484
+ sections.push(`### Sprint Results\n- Error reading: ${e.message}\n`);
485
+ }
486
+ // 2. Learnings — first 3000 chars
487
+ try {
488
+ const learnings = (0, fs_1.existsSync)('./docs/learnings.md') ? (0, fs_1.readFileSync)('./docs/learnings.md', 'utf-8') : '';
489
+ if (learnings) {
490
+ sections.push('### Key Learnings (from docs/learnings.md)');
491
+ sections.push(learnings.substring(0, 3000));
492
+ if (learnings.length > 3000)
493
+ sections.push('... (truncated)');
494
+ sections.push('');
495
+ }
496
+ }
497
+ catch {
498
+ sections.push('### Key Learnings\n- File not found\n');
499
+ }
500
+ // 3. Existing agents
501
+ try {
502
+ const agentDirs = (0, fs_1.existsSync)('./agents') ? (0, fs_1.readdirSync)('./agents') : [];
503
+ const leadershipAgents = ['ceo', 'supervisor', 'skills'];
504
+ const techAgents = ['cto'];
505
+ sections.push(`### Existing Agents (${agentDirs.length} directories)`);
506
+ for (const dir of agentDirs) {
507
+ const layer = leadershipAgents.includes(dir) ? 'Claude/leadership'
508
+ : techAgents.includes(dir) ? 'MiniMax/technology' : 'MiniMax/coding';
509
+ sections.push(`- ${dir} (${layer})`);
510
+ }
511
+ sections.push('');
512
+ }
513
+ catch {
514
+ sections.push('### Existing Agents\n- Error reading agents directory\n');
515
+ }
516
+ // 4. Most recent daily report (last 2000 chars)
517
+ try {
518
+ const reportDir = './reports/daily';
519
+ if ((0, fs_1.existsSync)(reportDir)) {
520
+ const reports = (0, fs_1.readdirSync)(reportDir).filter(f => f.endsWith('.md')).sort().reverse();
521
+ if (reports.length > 0) {
522
+ const latestReport = (0, fs_1.readFileSync)(`${reportDir}/${reports[0]}`, 'utf-8');
523
+ sections.push(`### Recent Daily Report (${reports[0]})`);
524
+ sections.push(latestReport.substring(0, 2000));
525
+ sections.push('');
526
+ }
527
+ }
528
+ }
529
+ catch { /* no reports yet */ }
530
+ // 5. Current stack versions (read from package.json or env)
531
+ sections.push('### Current Stack');
532
+ sections.push('- OpenClaw: v2026.2.12');
533
+ sections.push('- ClawRouter: v0.9.3 (unfunded, using Anthropic API direct)');
534
+ sections.push('- Models: MiniMax M2.5 (coding ~$0.09/task), Claude Sonnet (review ~$0.04/review)');
535
+ sections.push('- Node.js: v22.22.0, PM2 in WSL2');
536
+ sections.push('- Orchestrator: v2, dynamic agent loading, one-file-per-call, rejection feedback');
537
+ sections.push('');
538
+ // 6. External monitoring hints
539
+ sections.push('### External Sources to Consider');
540
+ sections.push('- OpenClaw GitHub: https://github.com/openclaw/openclaw (check weekly for new releases since v2026.2.12)');
541
+ sections.push('- ClawHub.ai: https://clawhub.ai/ (check for existing skills when proposing improvements)');
542
+ sections.push(' SECURITY WARNING: ClawHub skills are third-party and may contain malicious code.');
543
+ sections.push(' Any skill from ClawHub MUST include a security_review step in implementation_steps.');
544
+ sections.push('- MiniMax / Anthropic pricing pages for cost changes');
545
+ sections.push('');
546
+ return sections.join('\n');
547
+ }
548
+ }
549
+ exports.CTODataCollector = CTODataCollector;
550
+ // ===== CTO Agent (MiniMax — data-driven tech analyst + agent spawning) =====
551
+ class CTOAgent {
552
+ systemPrompt;
553
+ dataCollector;
554
+ constructor() {
555
+ const promptPath = './agents/cto/prompt.md';
556
+ const rawPrompt = (0, fs_1.existsSync)(promptPath) ? (0, fs_1.readFileSync)(promptPath, 'utf-8') : 'You are the CTO of Invoica.';
557
+ this.systemPrompt = (0, engine_loaders_1.loadConstitutionalPreamble)() + rawPrompt;
558
+ this.dataCollector = new CTODataCollector();
559
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.cyan, '+ Loaded CTO agent (MiniMax M2.5 — data-driven)');
560
+ }
561
+ async analyze() {
562
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.cyan, '\n[cto] Collecting project data for analysis...');
563
+ const projectContext = this.dataCollector.collect();
564
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.cyan, ` Context collected: ${projectContext.length} chars`);
565
+ const userPrompt = `You are the CTO of Invoica. Analyze the REAL project data below and identify improvements.
566
+
567
+ ${projectContext}
568
+
569
+ ## Your Analysis Tasks
570
+ Based on the REAL data above (do NOT hallucinate or assume — use only what you see):
571
+ 1. Review sprint results — are rejection rates acceptable? Any patterns?
572
+ 2. Review learnings — are there unresolved issues or recurring problems?
573
+ 3. Check agent coverage — is there a capability gap that a new agent could fill?
574
+ 4. Consider cost efficiency — can we reduce per-sprint costs?
575
+ 5. Consider OpenClaw/ClawHub — are there new releases or skills that could help?
576
+ - For ClawHub skills: flag any that could help, but mark them for security review
577
+ - For OpenClaw: note version differences if updates are available
578
+
579
+ ## CRITICAL: Output Format
580
+ Respond with ONLY a JSON object. No markdown fences, no explanation text, no thinking.
581
+ {
582
+ "summary": "1-2 sentence overview of findings",
583
+ "proposals": [
584
+ {
585
+ "id": "CTO-20260214-001",
586
+ "title": "Short title",
587
+ "category": "new_agent|cost_optimization|process_change|architecture|tooling|new_feature",
588
+ "description": "What and why",
589
+ "estimated_impact": "cost/quality impact",
590
+ "risk_level": "low|medium|high",
591
+ "implementation_steps": ["step1", "step2"],
592
+ "agent_spec": {
593
+ "name": "agent-name",
594
+ "role": "What this agent does",
595
+ "llm": "minimax|anthropic",
596
+ "trigger": "every_sprint|on_demand|weekly",
597
+ "prompt_summary": "Key instructions for this agent"
598
+ }
599
+ }
600
+ ],
601
+ "metrics_reviewed": ["sprint_results", "learnings", "agent_list", "daily_report", "stack_versions"]
602
+ }
603
+
604
+ Rules:
605
+ - agent_spec is ONLY required when category="new_agent"
606
+ - If no improvements needed, return empty proposals array
607
+ - For ClawHub skill proposals, always include "security_review: audit skill source code for malicious patterns" in implementation_steps
608
+ - Be specific — "improve performance" is rejected; "add Redis caching to /api/invoices with 5min TTL" is accepted
609
+ - Maximum 3 proposals per analysis cycle`;
610
+ try {
611
+ const startTime = Date.now();
612
+ const response = await (0, orchestrate_engine_1.callLLM)('clawrouter', 'deepseek/deepseek-chat', this.systemPrompt, userPrompt, 120000, 'cto', 'cto_analyze');
613
+ const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
614
+ let content = response.choices?.[0]?.message?.content || '';
615
+ // Strip DeepSeek/MiniMax <think>...</think> reasoning tags
616
+ content = content.replace(/<think>[\s\S]*?<\/think>/g, '').trim();
617
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.cyan, ` CTO analysis completed in ${elapsed}s`);
618
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.gray, ` Raw output preview: ${content.substring(0, 300)}`);
619
+ // Parse JSON from response
620
+ const jsonMatch = content.match(/\{[\s\S]*\}/);
621
+ if (jsonMatch) {
622
+ const report = JSON.parse(jsonMatch[0]);
623
+ report.proposals = report.proposals || [];
624
+ report.metrics_reviewed = report.metrics_reviewed || [];
625
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.cyan, ` Summary: ${report.summary}`);
626
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.cyan, ` Proposals: ${report.proposals.length}`);
627
+ for (const p of report.proposals) {
628
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.cyan, ` - [${p.category}] ${p.title} (risk: ${p.risk_level})`);
629
+ }
630
+ return report;
631
+ }
632
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.yellow, ' Could not parse CTO JSON, returning empty report');
633
+ return { summary: 'CTO output was not valid JSON', proposals: [], metrics_reviewed: [] };
634
+ }
635
+ catch (error) {
636
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.yellow, ` CTO analysis failed: ${error.message}`);
637
+ return { summary: `Error: ${error.message}`, proposals: [], metrics_reviewed: [] };
638
+ }
639
+ }
640
+ /**
641
+ * Post-sprint analysis: autonomous retrospective that runs after every sprint.
642
+ * Analyzes sprint results, detects failure patterns, and saves a report.
643
+ * This runs the CTO techwatch `post-sprint-analysis` watch type.
644
+ */
645
+ async postSprintAnalysis(tasks, stats) {
646
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.cyan, '\n[cto] Running autonomous post-sprint analysis...');
647
+ const startTime = Date.now();
648
+ // Build sprint summary for context
649
+ const totalTasks = tasks.length;
650
+ const done = tasks.filter((t) => t.status === 'done').length;
651
+ const doneManual = tasks.filter((t) => t.status === 'done-manual').length;
652
+ const rejected = tasks.filter((t) => t.status === 'rejected').length;
653
+ const autoRate = totalTasks > 0 ? ((done / totalTasks) * 100).toFixed(0) : '0';
654
+ const taskDetails = tasks.map((t) => {
655
+ const id = t.id || 'unknown';
656
+ const agent = t.agent || 'unknown';
657
+ const status = t.status || 'unknown';
658
+ const title = t.title || t.description || 'no title';
659
+ const score = t.output?.review?.score || t.output?.score || '?';
660
+ const attempts = t.output?.attempts || t.attempts || '?';
661
+ const feedback = t.output?.review?.feedback || '';
662
+ let line = `- ${id} (${agent}): ${title} — status=${status}, score=${score}, attempts=${attempts}`;
663
+ if (status === 'done-manual' || status === 'rejected') {
664
+ line += `\n ⚠ ${feedback ? String(feedback).substring(0, 200) : 'Required manual intervention'}`;
665
+ }
666
+ return line;
667
+ }).join('\n');
668
+ const projectContext = this.dataCollector.collect();
669
+ const userPrompt = `You are the CTO of Invoica performing your MANDATORY post-sprint retrospective analysis.
670
+
671
+ ## Sprint Just Completed
672
+ - Total tasks: ${totalTasks}
673
+ - Auto-approved: ${done} (${autoRate}%)
674
+ - Manual fixes needed: ${doneManual}
675
+ - Still rejected: ${rejected}
676
+ - Supervisor conflicts: ${stats.conflicts || 0}
677
+ - CEO escalations: ${stats.escalations || 0}
678
+
679
+ ## Task-by-Task Results
680
+ ${taskDetails}
681
+
682
+ ## Project Context
683
+ ${projectContext}
684
+
685
+ ## CRITICAL: Your Responsibilities
686
+ 1. Analyze every failed/manual-fix task — identify root cause (truncation, code fences, wrong imports, supervisor error, etc.)
687
+ 2. Compare auto-approval rate with previous sprints — are we improving or declining?
688
+ 3. Identify recurring patterns that need process changes
689
+ 4. Generate max 3 concrete improvement proposals for the CEO
690
+ 5. Each proposal MUST reference specific task IDs and data from THIS sprint
691
+
692
+ ## Output Format
693
+ Respond with a structured markdown report containing:
694
+ 1. Executive Summary (2-3 sentences)
695
+ 2. Sprint Scorecard
696
+ 3. Failure Root Cause Analysis (per failed task)
697
+ 4. Trend Analysis
698
+ 5. Proposals in JSON format:
699
+ \`\`\`json
700
+ {
701
+ "summary": "...",
702
+ "proposals": [...],
703
+ "sprint_metrics": { "total_tasks": ${totalTasks}, "auto_approved": ${done}, "manual_fixes": ${doneManual}, "rejected": ${rejected}, "auto_success_rate": "${autoRate}%", "trend": "improving|declining|stable" }
704
+ }
705
+ \`\`\`
706
+
707
+ Rules: Be specific — reference task IDs, rejection counts, concrete patterns. No vague recommendations.`;
708
+ try {
709
+ const response = await (0, orchestrate_engine_1.callLLM)('clawrouter', 'deepseek/deepseek-chat', this.systemPrompt, userPrompt, 120000, 'cto', 'cto_post_sprint_analysis');
710
+ let content = response.choices?.[0]?.message?.content || '';
711
+ content = content.replace(/<think>[\s\S]*?<\/think>/g, '').trim();
712
+ const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
713
+ // Save report
714
+ const date = new Date().toISOString().split('T')[0];
715
+ const reportDir = './reports/cto';
716
+ (0, fs_1.mkdirSync)(reportDir, { recursive: true });
717
+ const reportPath = `${reportDir}/post-sprint-analysis-${date}.md`;
718
+ (0, fs_1.writeFileSync)(reportPath, content);
719
+ // Also update latest pointer
720
+ (0, fs_1.writeFileSync)(`${reportDir}/latest-post-sprint-analysis.md`, content);
721
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.cyan, ` Post-sprint analysis complete (${elapsed}s)`);
722
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.cyan, ` Report saved: ${reportPath}`);
723
+ // Try to extract proposals and add to approved-proposals tracker for CEO review
724
+ const jsonMatch = content.match(/```json\s*([\s\S]*?)```/) || content.match(/\{[\s\S]*"proposals"[\s\S]*\}/);
725
+ if (jsonMatch) {
726
+ const jsonStr = jsonMatch[1] || jsonMatch[0];
727
+ try {
728
+ const parsed = JSON.parse(jsonStr.trim());
729
+ const proposalCount = parsed.proposals?.length || 0;
730
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.cyan, ` Extracted ${proposalCount} proposals for CEO review`);
731
+ }
732
+ catch { /* JSON parse failed — report is still saved as markdown */ }
733
+ }
734
+ return content;
735
+ }
736
+ catch (error) {
737
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.yellow, ` Post-sprint analysis failed: ${error.message}`);
738
+ return `Post-sprint analysis error: ${error.message}`;
739
+ }
740
+ }
741
+ }
742
+ exports.CTOAgent = CTOAgent;
743
+ class AgentCreator {
744
+ spawnGate;
745
+ constructor(spawnGate) {
746
+ this.spawnGate = spawnGate;
747
+ }
748
+ createAgent(spec) {
749
+ // TICKET-225 — template-carried spawn governance. If the consuming template
750
+ // supplied a SpawnGate (Kognai wires SAF here), consult it BEFORE creating
751
+ // anything on disk. Approval/rejection only; the citizenship logic below is
752
+ // unchanged (its extraction is tracked separately as TICKET-226).
753
+ if (this.spawnGate) {
754
+ const decision = this.spawnGate(spec);
755
+ if (!decision.approved) {
756
+ const why = decision.rejection_reason ? `: ${decision.rejection_reason}` : '';
757
+ if (decision.pending_approval) {
758
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.yellow, ` ⏸ Spawn of ${spec.name} pending approval${why}`);
759
+ }
760
+ else {
761
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.red, ` ✗ Spawn of ${spec.name} blocked${why}`);
762
+ }
763
+ return '';
764
+ }
765
+ if (decision.audit)
766
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.gray, ` ✓ ${decision.audit}`);
767
+ }
768
+ const agentDir = `./agents/${spec.name}`;
769
+ (0, fs_1.mkdirSync)(agentDir, { recursive: true });
770
+ // Founder rule 2026-05-27: every spawned agent is born as a Kognai
771
+ // citizen — not a bare agent. Mint citizenship (citizen_id + roll
772
+ // number + Kōpus avatar + ACP baseline) BEFORE writing the agent
773
+ // files so the citizen record can be referenced in the prompt.
774
+ const citizen = (0, citizenship_1.mintCitizen)(spec.name, {
775
+ founding_agent: 'ceo',
776
+ proposing_agent: 'cto',
777
+ citizen_type: 'spawned',
778
+ });
779
+ // Write agent.yaml
780
+ const yaml = `name: ${spec.name}
781
+ role: "${spec.role}"
782
+ llm: ${spec.llm === 'anthropic' ? 'anthropic/claude-sonnet-4-20250514' : 'minimax/MiniMax-M2.5'}
783
+ reports_to: ceo
784
+ trigger: ${spec.trigger}
785
+ created_by: cto_proposal
786
+ created_at: "${new Date().toISOString()}"
787
+ citizen_id: ${citizen.citizen_id}
788
+ rollNumber: ${citizen.rollNumber}
789
+ context_files:
790
+ - docs/learnings.md
791
+ `;
792
+ (0, fs_1.writeFileSync)(`${agentDir}/agent.yaml`, yaml);
793
+ // Write citizen.yaml — full citizenship metadata sits alongside
794
+ // agent.yaml so introspection / passport rendering / reputation
795
+ // ledger can resolve identity from disk.
796
+ (0, fs_1.writeFileSync)(`${agentDir}/citizen.yaml`, (0, citizenship_1.renderCitizenYaml)(citizen));
797
+ // Write prompt.md. The constitutional preamble (loadConstitutionalPreamble)
798
+ // already prepends the universal "you are a Kognai citizen" identity to
799
+ // every agent at load time — DO NOT contradict it here. This template
800
+ // pulls in the specific citizen's roll number + ID + tier so the agent
801
+ // knows its concrete civic identity, not just the general framing.
802
+ const prompt = `# ${spec.name} — Kognai Citizen ${citizen.citizen_id} (roll №${citizen.rollNumber}, Tier ${citizen.tier})
803
+
804
+ ## Civic Identity
805
+ - Citizen ID: \`${citizen.citizen_id}\`
806
+ - Roll number: №${citizen.rollNumber}
807
+ - Tier: ${citizen.tier} (newly minted — earn promotion through verified work)
808
+ - Mascot: Kōpus, hue ${citizen.mascot.hue}° (your visual identity in citizen surfaces)
809
+ - Reputation: ${citizen.reputation} (ACP baseline — earned through Sherlock-graded sprint output)
810
+ - Minted: ${citizen.mintedAt}
811
+ - Lineage: proposed by ${citizen.proposing_agent}, approved by ${citizen.founding_agent}
812
+
813
+ ## Your Role
814
+ ${spec.prompt_summary}
815
+
816
+ ## Guidelines
817
+ - The constitutional preamble above (always prepended) binds you to the Five Laws + SOUL.md. Read it first; it is not boilerplate.
818
+ - Follow all instructions in \`docs/learnings.md\`.
819
+ - Report findings to your fellow citizen agents through the canonical channels (Sherlock for QA escalations, CEO for cross-agent decisions). You are not a contractor; you are a peer.
820
+ - Never take destructive actions without approval. The polity inherits your shortcuts.
821
+ - Keep outputs concise and structured (JSON preferred when machine-consumed).
822
+ - Your reputation moves with every Sherlock review. Build it deliberately.
823
+
824
+ ## Lineage
825
+ This citizen was minted via the swarm's autonomous spawning pathway: a CTO proposal, CEO ratification, and citizenship issuance (citizenship.ts). Trigger cadence: ${spec.trigger}. Routing LLM: ${spec.llm}.
826
+ `;
827
+ (0, fs_1.writeFileSync)(`${agentDir}/prompt.md`, prompt);
828
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.green, ` ✓ Minted citizen ${citizen.citizen_id} (№${citizen.rollNumber}) → agent ${spec.name} at ${agentDir}/`);
829
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.gray, ` Role: ${spec.role}`);
830
+ (0, orchestrate_engine_1.log)(orchestrate_engine_1.c.gray, ` LLM: ${spec.llm}, Trigger: ${spec.trigger}, Tier: ${citizen.tier}, Reputation: ${citizen.reputation}`);
831
+ return spec.name;
832
+ }
833
+ }
834
+ exports.AgentCreator = AgentCreator;
835
+ // <<<EXTRACT-END-agents