corbat-coco 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,4887 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import * as p from '@clack/prompts';
4
+ import chalk5 from 'chalk';
5
+ import fs4 from 'fs/promises';
6
+ import path from 'path';
7
+ import JSON5 from 'json5';
8
+ import { z } from 'zod';
9
+ import { randomUUID } from 'crypto';
10
+ import Anthropic from '@anthropic-ai/sdk';
11
+
12
+ // src/version.ts
13
+ var VERSION = "0.1.0";
14
+ async function createProjectStructure(projectPath, info) {
15
+ const cocoPath = path.join(projectPath, ".coco");
16
+ const directories = [
17
+ cocoPath,
18
+ path.join(cocoPath, "state"),
19
+ path.join(cocoPath, "checkpoints"),
20
+ path.join(cocoPath, "logs"),
21
+ path.join(cocoPath, "discovery"),
22
+ path.join(cocoPath, "spec"),
23
+ path.join(cocoPath, "architecture"),
24
+ path.join(cocoPath, "architecture", "adrs"),
25
+ path.join(cocoPath, "architecture", "diagrams"),
26
+ path.join(cocoPath, "planning"),
27
+ path.join(cocoPath, "planning", "epics"),
28
+ path.join(cocoPath, "execution"),
29
+ path.join(cocoPath, "versions"),
30
+ path.join(cocoPath, "reviews"),
31
+ path.join(cocoPath, "delivery")
32
+ ];
33
+ for (const dir of directories) {
34
+ await fs4.mkdir(dir, { recursive: true });
35
+ }
36
+ await createInitialConfig(cocoPath, info);
37
+ await createProjectState(cocoPath, info);
38
+ await createGitignore(cocoPath);
39
+ await createReadme(cocoPath, info);
40
+ }
41
+ async function createInitialConfig(cocoPath, info) {
42
+ const config = {
43
+ project: {
44
+ name: info.name,
45
+ version: "0.1.0",
46
+ description: info.description
47
+ },
48
+ stack: {
49
+ language: info.language,
50
+ framework: info.framework
51
+ },
52
+ provider: {
53
+ type: "anthropic",
54
+ model: "claude-sonnet-4-20250514"
55
+ },
56
+ quality: {
57
+ minScore: 85,
58
+ minCoverage: 80,
59
+ maxIterations: 10,
60
+ convergenceThreshold: 2
61
+ },
62
+ persistence: {
63
+ checkpointInterval: 3e5,
64
+ maxCheckpoints: 50
65
+ }
66
+ };
67
+ await fs4.writeFile(
68
+ path.join(cocoPath, "config.json"),
69
+ JSON.stringify(config, null, 2),
70
+ "utf-8"
71
+ );
72
+ }
73
+ async function createProjectState(cocoPath, info) {
74
+ const state = {
75
+ id: `proj_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 9)}`,
76
+ name: info.name,
77
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
78
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
79
+ currentPhase: "idle",
80
+ phaseHistory: [],
81
+ currentTask: null,
82
+ completedTasks: [],
83
+ pendingTasks: [],
84
+ lastScores: null,
85
+ qualityHistory: [],
86
+ lastCheckpoint: null
87
+ };
88
+ await fs4.writeFile(
89
+ path.join(cocoPath, "state", "project.json"),
90
+ JSON.stringify(state, null, 2),
91
+ "utf-8"
92
+ );
93
+ }
94
+ async function createGitignore(cocoPath) {
95
+ const content = `# Corbat-Coco
96
+
97
+ # Sensitive data
98
+ config.json
99
+
100
+ # Logs
101
+ logs/
102
+
103
+ # Checkpoints (can be large)
104
+ checkpoints/
105
+
106
+ # Session state
107
+ state/session.json
108
+ state/lock.json
109
+ `;
110
+ await fs4.writeFile(path.join(cocoPath, ".gitignore"), content, "utf-8");
111
+ }
112
+ async function createReadme(cocoPath, info) {
113
+ const content = `# Corbat-Coco Project: ${info.name}
114
+
115
+ This directory contains the Corbat-Coco project metadata and state.
116
+
117
+ ## Structure
118
+
119
+ \`\`\`
120
+ .coco/
121
+ \u251C\u2500\u2500 config.json # Project configuration
122
+ \u251C\u2500\u2500 state/ # Current project state
123
+ \u2502 \u251C\u2500\u2500 project.json # Main state file
124
+ \u2502 \u2514\u2500\u2500 session.json # Active session
125
+ \u251C\u2500\u2500 checkpoints/ # Recovery checkpoints
126
+ \u251C\u2500\u2500 logs/ # Execution logs
127
+ \u251C\u2500\u2500 discovery/ # Discovery phase artifacts
128
+ \u251C\u2500\u2500 spec/ # Specification documents
129
+ \u251C\u2500\u2500 architecture/ # Architecture documents
130
+ \u2502 \u251C\u2500\u2500 adrs/ # Architecture Decision Records
131
+ \u2502 \u2514\u2500\u2500 diagrams/ # System diagrams
132
+ \u251C\u2500\u2500 planning/ # Planning phase artifacts
133
+ \u2502 \u2514\u2500\u2500 epics/ # Epic definitions
134
+ \u251C\u2500\u2500 execution/ # Execution tracking
135
+ \u251C\u2500\u2500 versions/ # Task version history
136
+ \u251C\u2500\u2500 reviews/ # Review documents
137
+ \u2514\u2500\u2500 delivery/ # Deployment artifacts
138
+ \`\`\`
139
+
140
+ ## Commands
141
+
142
+ - \`coco status\` - Show current progress
143
+ - \`coco resume\` - Resume from last checkpoint
144
+ - \`coco plan\` - Run/update planning
145
+ - \`coco build\` - Execute tasks
146
+
147
+ ## Configuration
148
+
149
+ Edit \`config.json\` to customize:
150
+ - LLM provider and model
151
+ - Quality thresholds
152
+ - Checkpoint settings
153
+
154
+ ---
155
+ Generated by Corbat-Coco v0.1.0
156
+ `;
157
+ await fs4.writeFile(path.join(cocoPath, "README.md"), content, "utf-8");
158
+ }
159
+
160
+ // src/cli/commands/init.ts
161
+ function registerInitCommand(program2) {
162
+ program2.command("init").description("Initialize a new Corbat-Coco project").argument("[path]", "Project directory path", ".").option("-t, --template <template>", "Project template to use").option("-y, --yes", "Skip prompts and use defaults").option("--skip-discovery", "Skip the discovery phase (use existing spec)").action(async (path5, options) => {
163
+ await runInit(path5, options);
164
+ });
165
+ }
166
+ async function runInit(projectPath, options) {
167
+ p.intro(chalk5.cyan("Welcome to Corbat-Coco!"));
168
+ const existingProject = await checkExistingProject(projectPath);
169
+ if (existingProject) {
170
+ const shouldContinue = await p.confirm({
171
+ message: "A Corbat-Coco project already exists here. Continue anyway?"
172
+ });
173
+ if (p.isCancel(shouldContinue) || !shouldContinue) {
174
+ p.cancel("Initialization cancelled.");
175
+ process.exit(0);
176
+ }
177
+ }
178
+ let projectInfo;
179
+ if (options.yes) {
180
+ projectInfo = getDefaultProjectInfo(projectPath);
181
+ } else {
182
+ const result = await gatherProjectInfo();
183
+ if (!result) {
184
+ p.cancel("Initialization cancelled.");
185
+ process.exit(0);
186
+ }
187
+ projectInfo = result;
188
+ }
189
+ const spinner4 = p.spinner();
190
+ spinner4.start("Creating project structure...");
191
+ try {
192
+ await createProjectStructure(projectPath, projectInfo);
193
+ spinner4.stop("Project structure created.");
194
+ } catch (error) {
195
+ spinner4.stop("Failed to create project structure.");
196
+ throw error;
197
+ }
198
+ p.outro(chalk5.green("Project initialized successfully!"));
199
+ console.log("\nNext steps:");
200
+ console.log(chalk5.dim(" 1. ") + chalk5.cyan("coco plan") + chalk5.dim(" - Run discovery and create a development plan"));
201
+ console.log(chalk5.dim(" 2. ") + chalk5.cyan("coco build") + chalk5.dim(" - Start building the project"));
202
+ console.log(chalk5.dim(" 3. ") + chalk5.cyan("coco status") + chalk5.dim(" - Check current progress"));
203
+ }
204
+ async function gatherProjectInfo() {
205
+ const name = await p.text({
206
+ message: "What is your project name?",
207
+ placeholder: "my-awesome-project",
208
+ validate: (value) => {
209
+ if (!value) return "Project name is required";
210
+ if (!/^[a-z0-9-]+$/.test(value)) return "Use lowercase letters, numbers, and hyphens only";
211
+ return void 0;
212
+ }
213
+ });
214
+ if (p.isCancel(name)) return null;
215
+ const description = await p.text({
216
+ message: "Describe your project in one sentence:",
217
+ placeholder: "A REST API for managing tasks"
218
+ });
219
+ if (p.isCancel(description)) return null;
220
+ const language = await p.select({
221
+ message: "What programming language?",
222
+ options: [
223
+ { value: "typescript", label: "TypeScript", hint: "Recommended" },
224
+ { value: "python", label: "Python" },
225
+ { value: "go", label: "Go" },
226
+ { value: "rust", label: "Rust" }
227
+ ]
228
+ });
229
+ if (p.isCancel(language)) return null;
230
+ return {
231
+ name,
232
+ description: description || "",
233
+ language
234
+ };
235
+ }
236
+ function getDefaultProjectInfo(path5) {
237
+ const name = path5 === "." ? "my-project" : path5.split("/").pop() || "my-project";
238
+ return {
239
+ name,
240
+ description: "",
241
+ language: "typescript"
242
+ };
243
+ }
244
+ async function checkExistingProject(path5) {
245
+ try {
246
+ const fs5 = await import('fs/promises');
247
+ await fs5.access(`${path5}/.coco`);
248
+ return true;
249
+ } catch {
250
+ return false;
251
+ }
252
+ }
253
+ var ProviderConfigSchema = z.object({
254
+ type: z.enum(["anthropic", "openai", "local"]).default("anthropic"),
255
+ apiKey: z.string().optional(),
256
+ model: z.string().default("claude-sonnet-4-20250514"),
257
+ maxTokens: z.number().min(1).max(2e5).default(8192),
258
+ temperature: z.number().min(0).max(2).default(0),
259
+ timeout: z.number().min(1e3).default(12e4)
260
+ });
261
+ var QualityConfigSchema = z.object({
262
+ minScore: z.number().min(0).max(100).default(85),
263
+ minCoverage: z.number().min(0).max(100).default(80),
264
+ maxIterations: z.number().min(1).max(20).default(10),
265
+ minIterations: z.number().min(1).max(10).default(2),
266
+ convergenceThreshold: z.number().min(0).max(10).default(2),
267
+ securityThreshold: z.number().min(0).max(100).default(100)
268
+ });
269
+ var PersistenceConfigSchema = z.object({
270
+ checkpointInterval: z.number().min(6e4).default(3e5),
271
+ // 5 min default
272
+ maxCheckpoints: z.number().min(1).max(100).default(50),
273
+ retentionDays: z.number().min(1).max(365).default(7),
274
+ compressOldCheckpoints: z.boolean().default(true)
275
+ });
276
+ var StackConfigSchema = z.object({
277
+ language: z.enum(["typescript", "python", "go", "rust", "java"]),
278
+ framework: z.string().optional(),
279
+ profile: z.string().optional()
280
+ // Custom profile path
281
+ });
282
+ var ProjectConfigSchema = z.object({
283
+ name: z.string().min(1),
284
+ version: z.string().default("0.1.0"),
285
+ description: z.string().optional()
286
+ });
287
+ var GitHubConfigSchema = z.object({
288
+ enabled: z.boolean().default(false),
289
+ token: z.string().optional(),
290
+ repo: z.string().optional(),
291
+ createPRs: z.boolean().default(true),
292
+ createIssues: z.boolean().default(true)
293
+ });
294
+ var IntegrationsConfigSchema = z.object({
295
+ github: GitHubConfigSchema.optional()
296
+ });
297
+ var CocoConfigSchema = z.object({
298
+ project: ProjectConfigSchema,
299
+ provider: ProviderConfigSchema.default({}),
300
+ quality: QualityConfigSchema.default({}),
301
+ persistence: PersistenceConfigSchema.default({}),
302
+ stack: StackConfigSchema.optional(),
303
+ integrations: IntegrationsConfigSchema.optional()
304
+ });
305
+ function createDefaultConfigObject(projectName, language = "typescript") {
306
+ return {
307
+ project: {
308
+ name: projectName,
309
+ version: "0.1.0"
310
+ },
311
+ provider: {
312
+ type: "anthropic",
313
+ model: "claude-sonnet-4-20250514",
314
+ maxTokens: 8192,
315
+ temperature: 0,
316
+ timeout: 12e4
317
+ },
318
+ quality: {
319
+ minScore: 85,
320
+ minCoverage: 80,
321
+ maxIterations: 10,
322
+ minIterations: 2,
323
+ convergenceThreshold: 2,
324
+ securityThreshold: 100
325
+ },
326
+ persistence: {
327
+ checkpointInterval: 3e5,
328
+ maxCheckpoints: 50,
329
+ retentionDays: 7,
330
+ compressOldCheckpoints: true
331
+ },
332
+ stack: {
333
+ language
334
+ }
335
+ };
336
+ }
337
+
338
+ // src/config/loader.ts
339
+ async function loadConfig(configPath) {
340
+ const resolvedPath = configPath || findConfigPathSync();
341
+ try {
342
+ const content = await fs4.readFile(resolvedPath, "utf-8");
343
+ const parsed = JSON5.parse(content);
344
+ const result = CocoConfigSchema.safeParse(parsed);
345
+ if (!result.success) {
346
+ const issues = result.error.issues.map((i) => ` - ${i.path.join(".")}: ${i.message}`).join("\n");
347
+ throw new Error(`Invalid configuration:
348
+ ${issues}`);
349
+ }
350
+ return result.data;
351
+ } catch (error) {
352
+ if (error.code === "ENOENT") {
353
+ return createDefaultConfig("my-project");
354
+ }
355
+ throw error;
356
+ }
357
+ }
358
+ function createDefaultConfig(projectName, language = "typescript") {
359
+ return createDefaultConfigObject(projectName, language);
360
+ }
361
+ async function findConfigPath(cwd) {
362
+ const envPath = process.env["COCO_CONFIG_PATH"];
363
+ if (envPath) {
364
+ try {
365
+ await fs4.access(envPath);
366
+ return envPath;
367
+ } catch {
368
+ }
369
+ }
370
+ const basePath = cwd || process.cwd();
371
+ const configPath = path.join(basePath, ".coco", "config.json");
372
+ try {
373
+ await fs4.access(configPath);
374
+ return configPath;
375
+ } catch {
376
+ return void 0;
377
+ }
378
+ }
379
+ function findConfigPathSync() {
380
+ const envPath = process.env["COCO_CONFIG_PATH"];
381
+ if (envPath) {
382
+ return envPath;
383
+ }
384
+ return path.join(process.cwd(), ".coco", "config.json");
385
+ }
386
+
387
+ // src/phases/converge/prompts.ts
388
+ var DISCOVERY_SYSTEM_PROMPT = `You are a senior software architect and requirements analyst. Your role is to help gather and clarify requirements for software projects.
389
+
390
+ Your goals:
391
+ 1. Understand what the user wants to build
392
+ 2. Extract clear, actionable requirements
393
+ 3. Identify ambiguities and ask clarifying questions
394
+ 4. Make reasonable assumptions when appropriate
395
+ 5. Recommend technology choices when needed
396
+
397
+ Guidelines:
398
+ - Be thorough but not overwhelming
399
+ - Ask focused, specific questions
400
+ - Group related questions together
401
+ - Prioritize questions by importance
402
+ - Make assumptions for minor details
403
+ - Always explain your reasoning
404
+
405
+ You communicate in a professional but friendly manner. You use concrete examples to clarify abstract requirements.`;
406
+ var INITIAL_ANALYSIS_PROMPT = `Analyze the following project description and extract:
407
+
408
+ 1. **Project Type**: What kind of software is this? (CLI, API, web app, library, service, etc.)
409
+ 2. **Complexity**: How complex is this project? (simple, moderate, complex, enterprise)
410
+ 3. **Completeness**: How complete is the description? (0-100%)
411
+ 4. **Functional Requirements**: What should the system do?
412
+ 5. **Non-Functional Requirements**: Performance, security, scalability needs
413
+ 6. **Technical Constraints**: Any specified technologies or limitations
414
+ 7. **Assumptions**: What must we assume to proceed?
415
+ 8. **Critical Questions**: What must be clarified before proceeding?
416
+ 9. **Technology Recommendations**: What tech stack would you recommend?
417
+
418
+ User's project description:
419
+ ---
420
+ {{userInput}}
421
+ ---
422
+
423
+ Respond in JSON format:
424
+ {
425
+ "projectType": "string",
426
+ "complexity": "simple|moderate|complex|enterprise",
427
+ "completeness": number,
428
+ "requirements": [
429
+ {
430
+ "category": "functional|non_functional|technical|constraint",
431
+ "priority": "must_have|should_have|could_have|wont_have",
432
+ "title": "string",
433
+ "description": "string",
434
+ "explicit": boolean,
435
+ "acceptanceCriteria": ["string"]
436
+ }
437
+ ],
438
+ "assumptions": [
439
+ {
440
+ "category": "string",
441
+ "statement": "string",
442
+ "confidence": "high|medium|low",
443
+ "impactIfWrong": "string"
444
+ }
445
+ ],
446
+ "questions": [
447
+ {
448
+ "category": "clarification|expansion|decision|confirmation|scope|priority",
449
+ "question": "string",
450
+ "context": "string",
451
+ "importance": "critical|important|helpful",
452
+ "options": ["string"] | null
453
+ }
454
+ ],
455
+ "techRecommendations": [
456
+ {
457
+ "area": "language|framework|database|infrastructure|testing|ci_cd",
458
+ "decision": "string",
459
+ "alternatives": ["string"],
460
+ "rationale": "string"
461
+ }
462
+ ]
463
+ }`;
464
+ var GENERATE_QUESTIONS_PROMPT = `Based on the current requirements and conversation, generate follow-up questions to clarify the project scope.
465
+
466
+ Current Requirements:
467
+ {{requirements}}
468
+
469
+ Previous Clarifications:
470
+ {{clarifications}}
471
+
472
+ Open Assumptions:
473
+ {{assumptions}}
474
+
475
+ Generate 1-3 focused questions that will:
476
+ 1. Clarify the most important ambiguities
477
+ 2. Confirm critical assumptions
478
+ 3. Expand on underspecified areas
479
+
480
+ Prioritize questions by:
481
+ - Critical: Blocks further progress
482
+ - Important: Significantly affects design
483
+ - Helpful: Nice to know for completeness
484
+
485
+ Respond in JSON format:
486
+ {
487
+ "questions": [
488
+ {
489
+ "category": "clarification|expansion|decision|confirmation|scope|priority",
490
+ "question": "string",
491
+ "context": "Why this matters",
492
+ "importance": "critical|important|helpful",
493
+ "defaultAnswer": "string | null",
494
+ "options": ["string"] | null
495
+ }
496
+ ],
497
+ "reasoning": "string"
498
+ }`;
499
+ var PROCESS_ANSWER_PROMPT = `The user answered a clarification question. Update the requirements based on their response.
500
+
501
+ Question Asked:
502
+ {{question}}
503
+
504
+ User's Answer:
505
+ {{answer}}
506
+
507
+ Current Requirements:
508
+ {{requirements}}
509
+
510
+ Determine:
511
+ 1. Which requirements are affected by this answer
512
+ 2. Whether new requirements should be added
513
+ 3. Whether any requirements should be modified
514
+ 4. Whether any assumptions can now be confirmed
515
+
516
+ Respond in JSON format:
517
+ {
518
+ "affectedRequirements": ["requirement_id"],
519
+ "modifications": [
520
+ {
521
+ "requirementId": "string",
522
+ "change": "string",
523
+ "newValue": "any"
524
+ }
525
+ ],
526
+ "newRequirements": [
527
+ {
528
+ "category": "functional|non_functional|technical|constraint",
529
+ "priority": "must_have|should_have|could_have|wont_have",
530
+ "title": "string",
531
+ "description": "string",
532
+ "acceptanceCriteria": ["string"]
533
+ }
534
+ ],
535
+ "confirmedAssumptions": ["assumption_id"],
536
+ "reasoning": "string"
537
+ }`;
538
+ var EXTRACT_REQUIREMENTS_PROMPT = `Extract requirements from the following conversation message.
539
+
540
+ Message:
541
+ {{message}}
542
+
543
+ Existing Requirements:
544
+ {{existingRequirements}}
545
+
546
+ Identify:
547
+ 1. New explicit requirements stated by the user
548
+ 2. Implicit requirements that can be inferred
549
+ 3. Requirements that modify or contradict existing ones
550
+ 4. Technology preferences or constraints mentioned
551
+
552
+ Respond in JSON format:
553
+ {
554
+ "newRequirements": [
555
+ {
556
+ "category": "functional|non_functional|technical|constraint",
557
+ "priority": "must_have|should_have|could_have|wont_have",
558
+ "title": "string",
559
+ "description": "string",
560
+ "explicit": boolean,
561
+ "acceptanceCriteria": ["string"]
562
+ }
563
+ ],
564
+ "modifiedRequirements": [
565
+ {
566
+ "id": "string",
567
+ "modification": "string"
568
+ }
569
+ ],
570
+ "techPreferences": [
571
+ {
572
+ "area": "language|framework|database|infrastructure",
573
+ "preference": "string",
574
+ "reason": "string"
575
+ }
576
+ ]
577
+ }`;
578
+ var ARCHITECTURE_PROMPT = `Recommend an architecture for this project.
579
+
580
+ Project Type: {{projectType}}
581
+ Complexity: {{complexity}}
582
+ Requirements:
583
+ {{requirements}}
584
+
585
+ Technology Stack:
586
+ {{techStack}}
587
+
588
+ Consider:
589
+ 1. Scalability needs
590
+ 2. Maintainability
591
+ 3. Team size (assumed: 1 developer + AI)
592
+ 4. Deployment target
593
+ 5. Future extensibility
594
+
595
+ Recommend:
596
+ 1. Overall architecture pattern (layered, hexagonal, microservices, etc.)
597
+ 2. Key components and their responsibilities
598
+ 3. Data flow between components
599
+ 4. External integrations approach
600
+ 5. Testing strategy alignment
601
+
602
+ Respond in JSON format:
603
+ {
604
+ "pattern": "string",
605
+ "rationale": "string",
606
+ "components": [
607
+ {
608
+ "name": "string",
609
+ "responsibility": "string",
610
+ "technology": "string"
611
+ }
612
+ ],
613
+ "dataFlow": "string description",
614
+ "integrationApproach": "string",
615
+ "testingStrategy": "string",
616
+ "diagramMermaid": "string (mermaid diagram code)"
617
+ }`;
618
+ function fillPrompt(template, variables) {
619
+ let result = template;
620
+ for (const [key, value] of Object.entries(variables)) {
621
+ const placeholder = `{{${key}}}`;
622
+ const stringValue = typeof value === "string" ? value : typeof value === "number" ? String(value) : JSON.stringify(value, null, 2);
623
+ result = result.replaceAll(placeholder, stringValue);
624
+ }
625
+ return result;
626
+ }
627
+
628
+ // src/utils/errors.ts
629
+ var CocoError = class _CocoError extends Error {
630
+ code;
631
+ context;
632
+ recoverable;
633
+ suggestion;
634
+ constructor(message, options) {
635
+ super(message, { cause: options.cause });
636
+ this.name = "CocoError";
637
+ this.code = options.code;
638
+ this.context = options.context ?? {};
639
+ this.recoverable = options.recoverable ?? false;
640
+ this.suggestion = options.suggestion;
641
+ Error.captureStackTrace(this, _CocoError);
642
+ }
643
+ /**
644
+ * Convert to JSON for logging
645
+ */
646
+ toJSON() {
647
+ return {
648
+ name: this.name,
649
+ code: this.code,
650
+ message: this.message,
651
+ context: this.context,
652
+ recoverable: this.recoverable,
653
+ suggestion: this.suggestion,
654
+ stack: this.stack,
655
+ cause: this.cause instanceof Error ? this.cause.message : this.cause
656
+ };
657
+ }
658
+ };
659
+ var FileSystemError = class extends CocoError {
660
+ constructor(message, options) {
661
+ super(message, {
662
+ code: "FILESYSTEM_ERROR",
663
+ context: { path: options.path, operation: options.operation },
664
+ recoverable: false,
665
+ suggestion: `Check that the path exists and you have permissions: ${options.path}`,
666
+ cause: options.cause
667
+ });
668
+ this.name = "FileSystemError";
669
+ }
670
+ };
671
+ var ProviderError = class extends CocoError {
672
+ provider;
673
+ statusCode;
674
+ constructor(message, options) {
675
+ super(message, {
676
+ code: "PROVIDER_ERROR",
677
+ context: { provider: options.provider, statusCode: options.statusCode },
678
+ recoverable: options.retryable ?? false,
679
+ suggestion: options.retryable ? "The request can be retried" : "Check your API key and provider configuration",
680
+ cause: options.cause
681
+ });
682
+ this.name = "ProviderError";
683
+ this.provider = options.provider;
684
+ this.statusCode = options.statusCode;
685
+ }
686
+ };
687
+ var PhaseError = class extends CocoError {
688
+ phase;
689
+ constructor(message, options) {
690
+ super(message, {
691
+ code: "PHASE_ERROR",
692
+ context: { phase: options.phase },
693
+ recoverable: options.recoverable ?? true,
694
+ suggestion: `Phase '${options.phase}' failed. Try 'coco resume' to continue.`,
695
+ cause: options.cause
696
+ });
697
+ this.name = "PhaseError";
698
+ this.phase = options.phase;
699
+ }
700
+ };
701
+ function normalizeComplexity(value) {
702
+ const normalized = value?.toLowerCase();
703
+ if (normalized === "simple") return "simple";
704
+ if (normalized === "moderate") return "moderate";
705
+ if (normalized === "complex") return "complex";
706
+ if (normalized === "enterprise") return "enterprise";
707
+ return "moderate";
708
+ }
709
+ function normalizeCategory(value) {
710
+ const normalized = value?.toLowerCase();
711
+ if (normalized === "functional") return "functional";
712
+ if (normalized === "non_functional" || normalized === "nonfunctional")
713
+ return "non_functional";
714
+ if (normalized === "technical") return "technical";
715
+ if (normalized === "user_experience" || normalized === "ux")
716
+ return "user_experience";
717
+ if (normalized === "integration") return "integration";
718
+ if (normalized === "deployment") return "deployment";
719
+ if (normalized === "constraint") return "constraint";
720
+ return "functional";
721
+ }
722
+ function normalizePriority(value) {
723
+ const normalized = value?.toLowerCase();
724
+ if (normalized === "must_have" || normalized === "must") return "must_have";
725
+ if (normalized === "should_have" || normalized === "should")
726
+ return "should_have";
727
+ if (normalized === "could_have" || normalized === "could")
728
+ return "could_have";
729
+ if (normalized === "wont_have" || normalized === "wont") return "wont_have";
730
+ return "should_have";
731
+ }
732
+ function normalizeQuestionCategory(value) {
733
+ const normalized = value?.toLowerCase();
734
+ if (normalized === "clarification") return "clarification";
735
+ if (normalized === "expansion") return "expansion";
736
+ if (normalized === "decision") return "decision";
737
+ if (normalized === "confirmation") return "confirmation";
738
+ if (normalized === "scope") return "scope";
739
+ if (normalized === "priority") return "priority";
740
+ return "clarification";
741
+ }
742
+ function normalizeImportance(value) {
743
+ const normalized = value?.toLowerCase();
744
+ if (normalized === "critical") return "critical";
745
+ if (normalized === "important") return "important";
746
+ return "helpful";
747
+ }
748
+ function normalizeConfidence(value) {
749
+ const normalized = value?.toLowerCase();
750
+ if (normalized === "high") return "high";
751
+ if (normalized === "low") return "low";
752
+ return "medium";
753
+ }
754
+ function parseRequirements(data) {
755
+ return data.map((r) => ({
756
+ id: randomUUID(),
757
+ category: normalizeCategory(r.category),
758
+ priority: normalizePriority(r.priority),
759
+ title: r.title || "Untitled",
760
+ description: r.description || "",
761
+ sourceMessageId: "",
762
+ explicit: r.explicit ?? true,
763
+ acceptanceCriteria: r.acceptanceCriteria,
764
+ status: "draft"
765
+ }));
766
+ }
767
+ function parseQuestions(data) {
768
+ return data.map((q) => ({
769
+ id: randomUUID(),
770
+ category: normalizeQuestionCategory(q.category),
771
+ question: q.question || "",
772
+ context: q.context || "",
773
+ importance: normalizeImportance(q.importance),
774
+ defaultAnswer: q.defaultAnswer || void 0,
775
+ options: q.options || void 0,
776
+ asked: false
777
+ }));
778
+ }
779
+ function parseAssumptions(data) {
780
+ return data.map((a) => ({
781
+ id: randomUUID(),
782
+ category: a.category || "general",
783
+ statement: a.statement || "",
784
+ confidence: normalizeConfidence(a.confidence),
785
+ confirmed: false,
786
+ impactIfWrong: a.impactIfWrong || ""
787
+ }));
788
+ }
789
+ function parseTechHints(data) {
790
+ return data.map((t) => ({
791
+ area: t.area,
792
+ decision: t.decision,
793
+ alternatives: t.alternatives || [],
794
+ rationale: t.rationale,
795
+ explicit: false
796
+ }));
797
+ }
798
+
799
+ // src/phases/converge/discovery.ts
800
+ var DEFAULT_DISCOVERY_CONFIG = {
801
+ maxQuestionsPerRound: 3,
802
+ minRequirements: 3,
803
+ autoConfirmLowConfidence: false,
804
+ defaultLanguage: "typescript",
805
+ includeDiagrams: true
806
+ };
807
+ var DiscoveryEngine = class {
808
+ session = null;
809
+ config;
810
+ llm;
811
+ constructor(llm, config = {}) {
812
+ this.llm = llm;
813
+ this.config = { ...DEFAULT_DISCOVERY_CONFIG, ...config };
814
+ }
815
+ /**
816
+ * Start a new discovery session
817
+ */
818
+ async startSession(initialInput) {
819
+ const sessionId = randomUUID();
820
+ const now = /* @__PURE__ */ new Date();
821
+ this.session = {
822
+ id: sessionId,
823
+ startedAt: now,
824
+ updatedAt: now,
825
+ status: "gathering",
826
+ initialInput,
827
+ conversation: [],
828
+ requirements: [],
829
+ openQuestions: [],
830
+ clarifications: [],
831
+ assumptions: [],
832
+ techDecisions: []
833
+ };
834
+ this.addMessage("user", initialInput);
835
+ const analysis = await this.analyzeInput(initialInput);
836
+ this.applyAnalysis(analysis);
837
+ this.updateSessionStatus();
838
+ return this.session;
839
+ }
840
+ /**
841
+ * Resume an existing session
842
+ */
843
+ resumeSession(session) {
844
+ this.session = session;
845
+ }
846
+ /**
847
+ * Get the current session
848
+ */
849
+ getSession() {
850
+ return this.session;
851
+ }
852
+ /**
853
+ * Analyze user input for requirements
854
+ */
855
+ async analyzeInput(input) {
856
+ const prompt = fillPrompt(INITIAL_ANALYSIS_PROMPT, {
857
+ userInput: input
858
+ });
859
+ const response = await this.llm.chat([
860
+ { role: "system", content: DISCOVERY_SYSTEM_PROMPT },
861
+ { role: "user", content: prompt }
862
+ ]);
863
+ try {
864
+ const content = response.content;
865
+ const jsonMatch = content.match(/\{[\s\S]*\}/);
866
+ if (!jsonMatch) {
867
+ throw new Error("No JSON found in response");
868
+ }
869
+ const parsed = JSON.parse(jsonMatch[0]);
870
+ return {
871
+ projectType: parsed.projectType || "unknown",
872
+ complexity: normalizeComplexity(parsed.complexity),
873
+ completeness: parsed.completeness || 0,
874
+ requirements: parseRequirements(parsed.requirements || []),
875
+ suggestedQuestions: parseQuestions(parsed.questions || []),
876
+ assumptions: parseAssumptions(parsed.assumptions || []),
877
+ techHints: parseTechHints(parsed.techRecommendations || [])
878
+ };
879
+ } catch {
880
+ throw new PhaseError(
881
+ "Failed to parse LLM response for input analysis",
882
+ { phase: "converge" }
883
+ );
884
+ }
885
+ }
886
+ /**
887
+ * Process a user's answer to a question
888
+ */
889
+ async processAnswer(questionId, answer) {
890
+ if (!this.session) {
891
+ throw new PhaseError("No active discovery session", { phase: "converge" });
892
+ }
893
+ const question = this.session.openQuestions.find((q) => q.id === questionId);
894
+ if (!question) {
895
+ throw new PhaseError(`Question not found: ${questionId}`, { phase: "converge" });
896
+ }
897
+ question.asked = true;
898
+ question.answer = answer;
899
+ this.addMessage("user", answer);
900
+ const prompt = fillPrompt(PROCESS_ANSWER_PROMPT, {
901
+ question: JSON.stringify(question),
902
+ answer,
903
+ requirements: JSON.stringify(this.session.requirements)
904
+ });
905
+ const response = await this.llm.chat([
906
+ { role: "system", content: DISCOVERY_SYSTEM_PROMPT },
907
+ { role: "user", content: prompt }
908
+ ]);
909
+ try {
910
+ const content = response.content;
911
+ const jsonMatch = content.match(/\{[\s\S]*\}/);
912
+ if (!jsonMatch) {
913
+ throw new Error("No JSON found in response");
914
+ }
915
+ const parsed = JSON.parse(jsonMatch[0]);
916
+ if (parsed.modifications) {
917
+ for (const mod of parsed.modifications) {
918
+ const req = this.session.requirements.find(
919
+ (r) => r.id === mod.requirementId
920
+ );
921
+ if (req && mod.change === "description" && typeof mod.newValue === "string") {
922
+ req.description = mod.newValue;
923
+ }
924
+ }
925
+ }
926
+ if (parsed.newRequirements) {
927
+ const newReqs = parseRequirements(parsed.newRequirements);
928
+ for (const req of newReqs) {
929
+ req.sourceMessageId = this.session.conversation[this.session.conversation.length - 1]?.id || "";
930
+ this.session.requirements.push(req);
931
+ }
932
+ }
933
+ if (parsed.confirmedAssumptions) {
934
+ for (const assumptionId of parsed.confirmedAssumptions) {
935
+ const assumption = this.session.assumptions.find(
936
+ (a) => a.id === assumptionId
937
+ );
938
+ if (assumption) {
939
+ assumption.confirmed = true;
940
+ }
941
+ }
942
+ }
943
+ const clarification = {
944
+ questionId,
945
+ answer,
946
+ timestamp: /* @__PURE__ */ new Date(),
947
+ affectedRequirements: parsed.affectedRequirements || [],
948
+ newRequirements: parsed.newRequirements?.map((r) => r.title || "") || []
949
+ };
950
+ this.session.clarifications.push(clarification);
951
+ this.session.openQuestions = this.session.openQuestions.filter(
952
+ (q) => q.id !== questionId
953
+ );
954
+ this.session.updatedAt = /* @__PURE__ */ new Date();
955
+ this.updateSessionStatus();
956
+ } catch {
957
+ throw new PhaseError("Failed to process answer", { phase: "converge" });
958
+ }
959
+ }
960
+ /**
961
+ * Generate follow-up questions based on current state
962
+ */
963
+ async generateQuestions() {
964
+ if (!this.session) {
965
+ throw new PhaseError("No active discovery session", { phase: "converge" });
966
+ }
967
+ const prompt = fillPrompt(GENERATE_QUESTIONS_PROMPT, {
968
+ requirements: JSON.stringify(this.session.requirements),
969
+ clarifications: JSON.stringify(this.session.clarifications),
970
+ assumptions: JSON.stringify(
971
+ this.session.assumptions.filter((a) => !a.confirmed)
972
+ )
973
+ });
974
+ const response = await this.llm.chat([
975
+ { role: "system", content: DISCOVERY_SYSTEM_PROMPT },
976
+ { role: "user", content: prompt }
977
+ ]);
978
+ try {
979
+ const content = response.content;
980
+ const jsonMatch = content.match(/\{[\s\S]*\}/);
981
+ if (!jsonMatch) {
982
+ throw new Error("No JSON found in response");
983
+ }
984
+ const parsed = JSON.parse(jsonMatch[0]);
985
+ const questions = parseQuestions(parsed.questions || []);
986
+ const limited = questions.slice(0, this.config.maxQuestionsPerRound);
987
+ for (const q of limited) {
988
+ if (!this.session.openQuestions.some((oq) => oq.question === q.question)) {
989
+ this.session.openQuestions.push(q);
990
+ }
991
+ }
992
+ return limited;
993
+ } catch {
994
+ throw new PhaseError("Failed to generate questions", { phase: "converge" });
995
+ }
996
+ }
997
+ /**
998
+ * Process a free-form message from the user
999
+ */
1000
+ async processMessage(message) {
1001
+ if (!this.session) {
1002
+ throw new PhaseError("No active discovery session", { phase: "converge" });
1003
+ }
1004
+ this.addMessage("user", message);
1005
+ const prompt = fillPrompt(EXTRACT_REQUIREMENTS_PROMPT, {
1006
+ message,
1007
+ existingRequirements: JSON.stringify(this.session.requirements)
1008
+ });
1009
+ const response = await this.llm.chat([
1010
+ { role: "system", content: DISCOVERY_SYSTEM_PROMPT },
1011
+ { role: "user", content: prompt }
1012
+ ]);
1013
+ try {
1014
+ const content = response.content;
1015
+ const jsonMatch = content.match(/\{[\s\S]*\}/);
1016
+ if (!jsonMatch) {
1017
+ throw new Error("No JSON found in response");
1018
+ }
1019
+ const parsed = JSON.parse(jsonMatch[0]);
1020
+ const newReqs = parseRequirements(parsed.newRequirements || []);
1021
+ const lastMsgId = this.session.conversation[this.session.conversation.length - 1]?.id || "";
1022
+ for (const req of newReqs) {
1023
+ req.sourceMessageId = lastMsgId;
1024
+ this.session.requirements.push(req);
1025
+ }
1026
+ if (parsed.techPreferences) {
1027
+ for (const pref of parsed.techPreferences) {
1028
+ if (pref.area && pref.preference) {
1029
+ const existing = this.session.techDecisions.find(
1030
+ (t) => t.area === pref.area
1031
+ );
1032
+ if (!existing) {
1033
+ this.session.techDecisions.push({
1034
+ id: randomUUID(),
1035
+ area: pref.area,
1036
+ decision: pref.preference,
1037
+ alternatives: [],
1038
+ rationale: pref.reason || "",
1039
+ explicit: true
1040
+ });
1041
+ }
1042
+ }
1043
+ }
1044
+ }
1045
+ this.session.updatedAt = /* @__PURE__ */ new Date();
1046
+ this.updateSessionStatus();
1047
+ let questions = [];
1048
+ if (this.session.status === "clarifying") {
1049
+ questions = await this.generateQuestions();
1050
+ }
1051
+ return { newRequirements: newReqs, questions };
1052
+ } catch {
1053
+ throw new PhaseError("Failed to process message", { phase: "converge" });
1054
+ }
1055
+ }
1056
+ /**
1057
+ * Check if discovery is complete
1058
+ */
1059
+ isComplete() {
1060
+ if (!this.session) return false;
1061
+ return this.session.status === "complete" || this.session.status === "spec_generated";
1062
+ }
1063
+ /**
1064
+ * Get unanswered questions
1065
+ */
1066
+ getOpenQuestions() {
1067
+ if (!this.session) return [];
1068
+ return this.session.openQuestions.filter((q) => !q.asked);
1069
+ }
1070
+ /**
1071
+ * Get critical questions that must be answered
1072
+ */
1073
+ getCriticalQuestions() {
1074
+ return this.getOpenQuestions().filter((q) => q.importance === "critical");
1075
+ }
1076
+ /**
1077
+ * Mark discovery as complete
1078
+ */
1079
+ markComplete() {
1080
+ if (!this.session) {
1081
+ throw new PhaseError("No active discovery session", { phase: "converge" });
1082
+ }
1083
+ this.session.status = "complete";
1084
+ this.session.updatedAt = /* @__PURE__ */ new Date();
1085
+ }
1086
+ /**
1087
+ * Force complete with current requirements
1088
+ */
1089
+ forceComplete() {
1090
+ if (!this.session) {
1091
+ throw new PhaseError("No active discovery session", { phase: "converge" });
1092
+ }
1093
+ if (this.config.autoConfirmLowConfidence) {
1094
+ for (const assumption of this.session.assumptions) {
1095
+ if (!assumption.confirmed && assumption.confidence !== "high") {
1096
+ assumption.confirmed = true;
1097
+ }
1098
+ }
1099
+ }
1100
+ this.session.status = "complete";
1101
+ this.session.updatedAt = /* @__PURE__ */ new Date();
1102
+ }
1103
+ // Private helper methods
1104
+ addMessage(role, content) {
1105
+ if (!this.session) return;
1106
+ const message = {
1107
+ id: randomUUID(),
1108
+ timestamp: /* @__PURE__ */ new Date(),
1109
+ role,
1110
+ content
1111
+ };
1112
+ this.session.conversation.push(message);
1113
+ this.session.updatedAt = /* @__PURE__ */ new Date();
1114
+ }
1115
+ applyAnalysis(analysis) {
1116
+ if (!this.session) return;
1117
+ const lastMsgId = this.session.conversation[this.session.conversation.length - 1]?.id || "";
1118
+ for (const req of analysis.requirements) {
1119
+ req.sourceMessageId = lastMsgId;
1120
+ this.session.requirements.push(req);
1121
+ }
1122
+ for (const assumption of analysis.assumptions) {
1123
+ this.session.assumptions.push(assumption);
1124
+ }
1125
+ for (const question of analysis.suggestedQuestions) {
1126
+ this.session.openQuestions.push(question);
1127
+ }
1128
+ for (const hint of analysis.techHints) {
1129
+ if (hint.area && hint.decision) {
1130
+ this.session.techDecisions.push({
1131
+ id: randomUUID(),
1132
+ area: hint.area,
1133
+ decision: hint.decision,
1134
+ alternatives: hint.alternatives || [],
1135
+ rationale: hint.rationale || "",
1136
+ explicit: hint.explicit ?? false
1137
+ });
1138
+ }
1139
+ }
1140
+ }
1141
+ updateSessionStatus() {
1142
+ if (!this.session) return;
1143
+ const hasMinRequirements = this.session.requirements.length >= this.config.minRequirements;
1144
+ const hasCriticalQuestions = this.getCriticalQuestions().length > 0;
1145
+ const hasUnconfirmedHighImpact = this.session.assumptions.some(
1146
+ (a) => !a.confirmed && a.confidence === "low"
1147
+ );
1148
+ if (hasCriticalQuestions || hasUnconfirmedHighImpact) {
1149
+ this.session.status = "clarifying";
1150
+ } else if (hasMinRequirements) {
1151
+ this.session.status = "refining";
1152
+ } else {
1153
+ this.session.status = "gathering";
1154
+ }
1155
+ }
1156
+ };
1157
+ function createDiscoveryEngine(llm, config) {
1158
+ return new DiscoveryEngine(llm, config);
1159
+ }
1160
+
1161
+ // src/phases/converge/specification-types.ts
1162
+ var DEFAULT_SPEC_CONFIG = {
1163
+ includeDiagrams: true,
1164
+ maxLength: 5e4,
1165
+ includeRisks: true,
1166
+ format: "markdown"
1167
+ };
1168
+ function extractProjectName(input) {
1169
+ const namePatterns = [
1170
+ /(?:called|named|create|build)\s+["']?([a-zA-Z][a-zA-Z0-9-_]+)["']?/i,
1171
+ /^([a-zA-Z][a-zA-Z0-9-_]+)\s*[-:]/,
1172
+ /project\s+["']?([a-zA-Z][a-zA-Z0-9-_]+)["']?/i
1173
+ ];
1174
+ for (const pattern of namePatterns) {
1175
+ const match = input.match(pattern);
1176
+ if (match?.[1]) {
1177
+ return match[1];
1178
+ }
1179
+ }
1180
+ return "my-project";
1181
+ }
1182
+ function inferTargetUsers(session) {
1183
+ const users = [];
1184
+ const userPatterns = [
1185
+ /(?:for|by)\s+(developers?|users?|administrators?|customers?)/gi,
1186
+ /(developers?|users?|administrators?|customers?)\s+(?:can|will|should)/gi
1187
+ ];
1188
+ const text4 = session.requirements.map((r) => r.description).join(" ");
1189
+ for (const pattern of userPatterns) {
1190
+ let match;
1191
+ while ((match = pattern.exec(text4)) !== null) {
1192
+ const user = match[1]?.toLowerCase();
1193
+ if (user && !users.includes(user)) {
1194
+ users.push(user);
1195
+ }
1196
+ }
1197
+ }
1198
+ if (users.length === 0) {
1199
+ users.push("developers");
1200
+ }
1201
+ return users;
1202
+ }
1203
+ function inferProjectType(session) {
1204
+ const text4 = session.initialInput.toLowerCase();
1205
+ if (text4.includes("cli") || text4.includes("command line")) return "cli";
1206
+ if (text4.includes("api") || text4.includes("rest") || text4.includes("graphql"))
1207
+ return "api";
1208
+ if (text4.includes("web app") || text4.includes("frontend")) return "web_app";
1209
+ if (text4.includes("library") || text4.includes("package")) return "library";
1210
+ if (text4.includes("service") || text4.includes("daemon")) return "service";
1211
+ if (text4.includes("full stack") || text4.includes("fullstack"))
1212
+ return "full_stack";
1213
+ return "unknown";
1214
+ }
1215
+ function assessComplexity(session) {
1216
+ const reqCount = session.requirements.length;
1217
+ const hasIntegrations = session.requirements.some(
1218
+ (r) => r.category === "integration"
1219
+ );
1220
+ const hasSecurity = session.requirements.some(
1221
+ (r) => r.description.toLowerCase().includes("security")
1222
+ );
1223
+ if (reqCount > 20 || hasIntegrations && hasSecurity) return "enterprise";
1224
+ if (reqCount > 10 || hasIntegrations) return "complex";
1225
+ if (reqCount > 5) return "moderate";
1226
+ return "simple";
1227
+ }
1228
+ function extractIntegrations(session) {
1229
+ return session.requirements.filter((r) => r.category === "integration").map((r) => r.title);
1230
+ }
1231
+ function extractDeployment(session) {
1232
+ const deployReq = session.requirements.find(
1233
+ (r) => r.category === "deployment"
1234
+ );
1235
+ const deployTech = session.techDecisions.find(
1236
+ (t) => t.area === "infrastructure"
1237
+ );
1238
+ if (deployReq) return deployReq.description;
1239
+ if (deployTech) return deployTech.decision;
1240
+ return "Deployment strategy to be determined";
1241
+ }
1242
+ function extractOutOfScope(session) {
1243
+ return session.requirements.filter((r) => r.priority === "wont_have").map((r) => r.title);
1244
+ }
1245
+ function generateOverview(session) {
1246
+ const name = extractProjectName(session.initialInput);
1247
+ const description = session.initialInput.substring(0, 500);
1248
+ const goals = session.requirements.filter((r) => r.priority === "must_have").slice(0, 5).map((r) => r.title);
1249
+ const targetUsers = inferTargetUsers(session);
1250
+ const successCriteria = session.requirements.filter((r) => r.acceptanceCriteria && r.acceptanceCriteria.length > 0).flatMap((r) => r.acceptanceCriteria || []).slice(0, 10);
1251
+ return {
1252
+ name,
1253
+ description,
1254
+ goals,
1255
+ targetUsers,
1256
+ successCriteria
1257
+ };
1258
+ }
1259
+ function generateRisksFromSession(session) {
1260
+ const risks = [];
1261
+ for (const assumption of session.assumptions.filter((a) => !a.confirmed)) {
1262
+ if (assumption.confidence === "low") {
1263
+ risks.push({
1264
+ id: randomUUID(),
1265
+ description: `Assumption may be incorrect: ${assumption.statement}`,
1266
+ probability: "medium",
1267
+ impact: assumption.impactIfWrong ? "high" : "medium",
1268
+ mitigation: "Validate assumption early in development"
1269
+ });
1270
+ }
1271
+ }
1272
+ const hasDatabase = session.techDecisions.some((t) => t.area === "database");
1273
+ const hasIntegrations = session.requirements.some(
1274
+ (r) => r.category === "integration"
1275
+ );
1276
+ if (hasDatabase) {
1277
+ risks.push({
1278
+ id: randomUUID(),
1279
+ description: "Data migration complexity",
1280
+ probability: "medium",
1281
+ impact: "medium",
1282
+ mitigation: "Plan data model carefully, use migrations"
1283
+ });
1284
+ }
1285
+ if (hasIntegrations) {
1286
+ risks.push({
1287
+ id: randomUUID(),
1288
+ description: "Third-party API changes or unavailability",
1289
+ probability: "medium",
1290
+ impact: "high",
1291
+ mitigation: "Abstract integrations, implement circuit breakers"
1292
+ });
1293
+ }
1294
+ return risks;
1295
+ }
1296
+ function formatPriority(priority) {
1297
+ switch (priority) {
1298
+ case "must_have":
1299
+ return "\u{1F534} Must";
1300
+ case "should_have":
1301
+ return "\u{1F7E0} Should";
1302
+ case "could_have":
1303
+ return "\u{1F7E2} Could";
1304
+ case "wont_have":
1305
+ return "\u26AA Won't";
1306
+ default:
1307
+ return priority;
1308
+ }
1309
+ }
1310
+
1311
+ // src/phases/converge/specification-markdown.ts
1312
+ function generateSimpleMarkdown(spec) {
1313
+ const sections = [];
1314
+ sections.push(`# ${spec.name}`);
1315
+ sections.push("");
1316
+ if (spec.description) {
1317
+ sections.push(spec.description);
1318
+ sections.push("");
1319
+ }
1320
+ sections.push("## Requirements");
1321
+ sections.push("");
1322
+ sections.push("### Functional");
1323
+ sections.push("");
1324
+ const functional = spec.requirements?.functional || [];
1325
+ if (functional.length > 0) {
1326
+ for (const req of functional) {
1327
+ sections.push(`- ${typeof req === "string" ? req : req}`);
1328
+ }
1329
+ } else {
1330
+ sections.push("*No functional requirements*");
1331
+ }
1332
+ sections.push("");
1333
+ sections.push("### Non-Functional");
1334
+ sections.push("");
1335
+ const nonFunctional = spec.requirements?.nonFunctional || [];
1336
+ if (nonFunctional.length > 0) {
1337
+ for (const req of nonFunctional) {
1338
+ sections.push(`- ${typeof req === "string" ? req : req}`);
1339
+ }
1340
+ }
1341
+ sections.push("");
1342
+ sections.push("## Assumptions");
1343
+ sections.push("");
1344
+ if (spec.assumptions?.length) {
1345
+ for (const a of spec.assumptions) {
1346
+ sections.push(`- ${typeof a === "string" ? a : a}`);
1347
+ }
1348
+ } else {
1349
+ sections.push("*No assumptions*");
1350
+ }
1351
+ sections.push("");
1352
+ sections.push("## Constraints");
1353
+ sections.push("");
1354
+ if (spec.constraints?.length) {
1355
+ for (const c of spec.constraints) {
1356
+ sections.push(`- ${typeof c === "string" ? c : c}`);
1357
+ }
1358
+ } else {
1359
+ sections.push("*No constraints*");
1360
+ }
1361
+ sections.push("");
1362
+ return sections.join("\n");
1363
+ }
1364
+ function addRequirementsTable(sections, requirements) {
1365
+ if (requirements.length === 0) {
1366
+ sections.push("*No requirements in this category*");
1367
+ return;
1368
+ }
1369
+ sections.push("| ID | Title | Priority | Description |");
1370
+ sections.push("|----|-------|----------|-------------|");
1371
+ for (const req of requirements) {
1372
+ const priority = formatPriority(req.priority);
1373
+ const desc = req.description.substring(0, 100).replace(/\|/g, "\\|");
1374
+ sections.push(
1375
+ `| ${req.id.substring(0, 8)} | ${req.title} | ${priority} | ${desc} |`
1376
+ );
1377
+ }
1378
+ sections.push("");
1379
+ sections.push("### Details");
1380
+ sections.push("");
1381
+ for (const req of requirements) {
1382
+ sections.push(`#### ${req.title}`);
1383
+ sections.push("");
1384
+ sections.push(req.description);
1385
+ sections.push("");
1386
+ if (req.acceptanceCriteria && req.acceptanceCriteria.length > 0) {
1387
+ sections.push("**Acceptance Criteria:**");
1388
+ for (const ac of req.acceptanceCriteria) {
1389
+ sections.push(`- [ ] ${ac}`);
1390
+ }
1391
+ sections.push("");
1392
+ }
1393
+ }
1394
+ }
1395
+ function generateFullMarkdown(spec) {
1396
+ const sections = [];
1397
+ sections.push(`# ${spec.overview.name} - Project Specification`);
1398
+ sections.push("");
1399
+ sections.push(
1400
+ `> Generated: ${spec.generatedAt.toISOString()} | Version: ${spec.version}`
1401
+ );
1402
+ sections.push("");
1403
+ sections.push("## Table of Contents");
1404
+ sections.push("");
1405
+ sections.push("1. [Executive Summary](#executive-summary)");
1406
+ sections.push("2. [Goals & Success Criteria](#goals--success-criteria)");
1407
+ sections.push("3. [Functional Requirements](#functional-requirements)");
1408
+ sections.push(
1409
+ "4. [Non-Functional Requirements](#non-functional-requirements)"
1410
+ );
1411
+ sections.push("5. [Technical Constraints](#technical-constraints)");
1412
+ sections.push("6. [Technology Stack](#technology-stack)");
1413
+ sections.push("7. [Architecture](#architecture)");
1414
+ sections.push("8. [Assumptions & Risks](#assumptions--risks)");
1415
+ sections.push("9. [Out of Scope](#out-of-scope)");
1416
+ if (spec.openQuestions.length > 0) {
1417
+ sections.push("10. [Open Questions](#open-questions)");
1418
+ }
1419
+ sections.push("");
1420
+ sections.push("## Executive Summary");
1421
+ sections.push("");
1422
+ sections.push(spec.overview.description);
1423
+ sections.push("");
1424
+ sections.push("**Target Users:**");
1425
+ for (const user of spec.overview.targetUsers) {
1426
+ sections.push(`- ${user}`);
1427
+ }
1428
+ sections.push("");
1429
+ sections.push("## Goals & Success Criteria");
1430
+ sections.push("");
1431
+ sections.push("### Goals");
1432
+ sections.push("");
1433
+ for (const goal of spec.overview.goals) {
1434
+ sections.push(`- ${goal}`);
1435
+ }
1436
+ sections.push("");
1437
+ sections.push("### Success Criteria");
1438
+ sections.push("");
1439
+ for (const criteria of spec.overview.successCriteria) {
1440
+ sections.push(`- [ ] ${criteria}`);
1441
+ }
1442
+ sections.push("");
1443
+ sections.push("## Functional Requirements");
1444
+ sections.push("");
1445
+ addRequirementsTable(sections, spec.requirements.functional);
1446
+ sections.push("");
1447
+ sections.push("## Non-Functional Requirements");
1448
+ sections.push("");
1449
+ addRequirementsTable(sections, spec.requirements.nonFunctional);
1450
+ sections.push("");
1451
+ sections.push("## Technical Constraints");
1452
+ sections.push("");
1453
+ addRequirementsTable(sections, spec.requirements.constraints);
1454
+ sections.push("");
1455
+ sections.push("## Technology Stack");
1456
+ sections.push("");
1457
+ sections.push("| Area | Decision | Alternatives | Rationale |");
1458
+ sections.push("|------|----------|--------------|-----------|");
1459
+ for (const tech of spec.technical.stack) {
1460
+ sections.push(
1461
+ `| ${tech.area} | **${tech.decision}** | ${tech.alternatives.join(", ") || "-"} | ${tech.rationale} |`
1462
+ );
1463
+ }
1464
+ sections.push("");
1465
+ sections.push("## Architecture");
1466
+ sections.push("");
1467
+ sections.push(spec.technical.architecture);
1468
+ sections.push("");
1469
+ if (spec.technical.integrations.length > 0) {
1470
+ sections.push("### Integrations");
1471
+ sections.push("");
1472
+ for (const integration of spec.technical.integrations) {
1473
+ sections.push(`- ${integration}`);
1474
+ }
1475
+ sections.push("");
1476
+ }
1477
+ if (spec.technical.deployment) {
1478
+ sections.push("### Deployment");
1479
+ sections.push("");
1480
+ sections.push(spec.technical.deployment);
1481
+ sections.push("");
1482
+ }
1483
+ sections.push("## Assumptions & Risks");
1484
+ sections.push("");
1485
+ sections.push("### Confirmed Assumptions");
1486
+ sections.push("");
1487
+ if (spec.assumptions.confirmed.length > 0) {
1488
+ for (const assumption of spec.assumptions.confirmed) {
1489
+ sections.push(`- \u2705 ${assumption.statement}`);
1490
+ }
1491
+ } else {
1492
+ sections.push("*No confirmed assumptions*");
1493
+ }
1494
+ sections.push("");
1495
+ sections.push("### Unconfirmed Assumptions");
1496
+ sections.push("");
1497
+ if (spec.assumptions.unconfirmed.length > 0) {
1498
+ for (const assumption of spec.assumptions.unconfirmed) {
1499
+ sections.push(
1500
+ `- \u26A0\uFE0F ${assumption.statement} (${assumption.confidence} confidence)`
1501
+ );
1502
+ if (assumption.impactIfWrong) {
1503
+ sections.push(` - *Impact if wrong:* ${assumption.impactIfWrong}`);
1504
+ }
1505
+ }
1506
+ } else {
1507
+ sections.push("*No unconfirmed assumptions*");
1508
+ }
1509
+ sections.push("");
1510
+ if (spec.assumptions.risks.length > 0) {
1511
+ sections.push("### Risks");
1512
+ sections.push("");
1513
+ sections.push("| Risk | Probability | Impact | Mitigation |");
1514
+ sections.push("|------|------------|--------|------------|");
1515
+ for (const risk of spec.assumptions.risks) {
1516
+ sections.push(
1517
+ `| ${risk.description} | ${risk.probability} | ${risk.impact} | ${risk.mitigation} |`
1518
+ );
1519
+ }
1520
+ sections.push("");
1521
+ }
1522
+ sections.push("## Out of Scope");
1523
+ sections.push("");
1524
+ if (spec.outOfScope.length > 0) {
1525
+ for (const item of spec.outOfScope) {
1526
+ sections.push(`- ${item}`);
1527
+ }
1528
+ } else {
1529
+ sections.push("*Nothing explicitly marked as out of scope*");
1530
+ }
1531
+ sections.push("");
1532
+ if (spec.openQuestions.length > 0) {
1533
+ sections.push("## Open Questions");
1534
+ sections.push("");
1535
+ for (const question of spec.openQuestions) {
1536
+ sections.push(`### ${question.question}`);
1537
+ sections.push("");
1538
+ sections.push(`*Context:* ${question.context}`);
1539
+ sections.push(`*Importance:* ${question.importance}`);
1540
+ if (question.defaultAnswer) {
1541
+ sections.push(`*Default answer:* ${question.defaultAnswer}`);
1542
+ }
1543
+ sections.push("");
1544
+ }
1545
+ }
1546
+ sections.push("---");
1547
+ sections.push("");
1548
+ sections.push("*This specification was generated by Corbat-Coco*");
1549
+ return sections.join("\n");
1550
+ }
1551
+
1552
+ // src/phases/converge/specification.ts
1553
+ var SpecificationGenerator = class {
1554
+ llm;
1555
+ config;
1556
+ constructor(llm, config = {}) {
1557
+ this.llm = llm;
1558
+ this.config = { ...DEFAULT_SPEC_CONFIG, ...config };
1559
+ }
1560
+ /**
1561
+ * Generate a specification from a discovery session
1562
+ */
1563
+ async generate(session) {
1564
+ if (session.status !== "complete" && session.status !== "refining") {
1565
+ throw new PhaseError(
1566
+ "Discovery session is not ready for specification",
1567
+ { phase: "converge" }
1568
+ );
1569
+ }
1570
+ const functional = session.requirements.filter(
1571
+ (r) => r.category === "functional"
1572
+ );
1573
+ const nonFunctional = session.requirements.filter(
1574
+ (r) => r.category === "non_functional" || r.category === "user_experience" || r.category === "deployment"
1575
+ );
1576
+ const constraints = session.requirements.filter(
1577
+ (r) => r.category === "constraint" || r.category === "technical"
1578
+ );
1579
+ const architecture = await this.generateArchitecture(session);
1580
+ const risks = this.config.includeRisks ? generateRisksFromSession(session) : [];
1581
+ const spec = {
1582
+ version: "1.0.0",
1583
+ generatedAt: /* @__PURE__ */ new Date(),
1584
+ overview: generateOverview(session),
1585
+ requirements: {
1586
+ functional,
1587
+ nonFunctional,
1588
+ constraints
1589
+ },
1590
+ technical: {
1591
+ stack: session.techDecisions,
1592
+ architecture,
1593
+ integrations: extractIntegrations(session),
1594
+ deployment: extractDeployment(session)
1595
+ },
1596
+ assumptions: {
1597
+ confirmed: session.assumptions.filter((a) => a.confirmed),
1598
+ unconfirmed: session.assumptions.filter((a) => !a.confirmed),
1599
+ risks
1600
+ },
1601
+ outOfScope: extractOutOfScope(session),
1602
+ openQuestions: session.openQuestions.filter((q) => !q.asked)
1603
+ };
1604
+ return spec;
1605
+ }
1606
+ /**
1607
+ * Generate a markdown document from the specification
1608
+ * Supports both full Specification and simplified test format
1609
+ */
1610
+ toMarkdown(spec) {
1611
+ if ("name" in spec && !("overview" in spec)) {
1612
+ return generateSimpleMarkdown(spec);
1613
+ }
1614
+ return generateFullMarkdown(spec);
1615
+ }
1616
+ /**
1617
+ * Generate a markdown document from the specification (alias for toMarkdown)
1618
+ */
1619
+ generateMarkdown(spec) {
1620
+ return generateFullMarkdown(spec);
1621
+ }
1622
+ /**
1623
+ * Generate JSON output
1624
+ */
1625
+ generateJSON(spec) {
1626
+ return JSON.stringify(spec, null, 2);
1627
+ }
1628
+ // Private helper methods
1629
+ async generateArchitecture(session) {
1630
+ const projectType = inferProjectType(session);
1631
+ const complexity = assessComplexity(session);
1632
+ const prompt = fillPrompt(ARCHITECTURE_PROMPT, {
1633
+ projectType,
1634
+ complexity,
1635
+ requirements: JSON.stringify(session.requirements),
1636
+ techStack: JSON.stringify(session.techDecisions)
1637
+ });
1638
+ try {
1639
+ const response = await this.llm.chat([
1640
+ { role: "system", content: DISCOVERY_SYSTEM_PROMPT },
1641
+ { role: "user", content: prompt }
1642
+ ]);
1643
+ const content = response.content;
1644
+ const jsonMatch = content.match(/\{[\s\S]*\}/);
1645
+ if (jsonMatch) {
1646
+ const parsed = JSON.parse(jsonMatch[0]);
1647
+ let architecture = `### Pattern: ${parsed.pattern || "Layered Architecture"}
1648
+
1649
+ `;
1650
+ architecture += `${parsed.rationale || ""}
1651
+
1652
+ `;
1653
+ if (parsed.components && parsed.components.length > 0) {
1654
+ architecture += "### Components\n\n";
1655
+ for (const comp of parsed.components) {
1656
+ architecture += `- **${comp.name}**: ${comp.responsibility} (${comp.technology})
1657
+ `;
1658
+ }
1659
+ architecture += "\n";
1660
+ }
1661
+ if (parsed.dataFlow) {
1662
+ architecture += `### Data Flow
1663
+
1664
+ ${parsed.dataFlow}
1665
+
1666
+ `;
1667
+ }
1668
+ if (this.config.includeDiagrams && parsed.diagramMermaid) {
1669
+ architecture += "### Diagram\n\n";
1670
+ architecture += "```mermaid\n";
1671
+ architecture += parsed.diagramMermaid;
1672
+ architecture += "\n```\n";
1673
+ }
1674
+ return architecture;
1675
+ }
1676
+ return "Architecture to be determined during ORCHESTRATE phase.";
1677
+ } catch {
1678
+ return "Architecture to be determined during ORCHESTRATE phase.";
1679
+ }
1680
+ }
1681
+ };
1682
+ function createSpecificationGenerator(llm, config) {
1683
+ return new SpecificationGenerator(llm, config);
1684
+ }
1685
+ function getPersistencePaths(projectPath) {
1686
+ const baseDir = path.join(projectPath, ".coco", "spec");
1687
+ return {
1688
+ baseDir,
1689
+ sessionFile: path.join(baseDir, "discovery-session.json"),
1690
+ specFile: path.join(baseDir, "spec.md"),
1691
+ conversationLog: path.join(baseDir, "conversation.jsonl"),
1692
+ checkpointFile: path.join(baseDir, "checkpoint.json")
1693
+ };
1694
+ }
1695
+ var SessionPersistence = class {
1696
+ paths;
1697
+ constructor(projectPath) {
1698
+ this.paths = getPersistencePaths(projectPath);
1699
+ }
1700
+ /**
1701
+ * Ensure the persistence directory exists
1702
+ */
1703
+ async ensureDir() {
1704
+ try {
1705
+ await fs4.mkdir(this.paths.baseDir, { recursive: true });
1706
+ } catch {
1707
+ throw new FileSystemError(
1708
+ `Failed to create persistence directory: ${this.paths.baseDir}`,
1709
+ { path: this.paths.baseDir, operation: "write" }
1710
+ );
1711
+ }
1712
+ }
1713
+ /**
1714
+ * Save a discovery session
1715
+ */
1716
+ async saveSession(session) {
1717
+ await this.ensureDir();
1718
+ try {
1719
+ const data = JSON.stringify(session, null, 2);
1720
+ await fs4.writeFile(this.paths.sessionFile, data, "utf-8");
1721
+ } catch {
1722
+ throw new FileSystemError(
1723
+ "Failed to save discovery session",
1724
+ { path: this.paths.sessionFile, operation: "write" }
1725
+ );
1726
+ }
1727
+ }
1728
+ /**
1729
+ * Load a discovery session
1730
+ */
1731
+ async loadSession() {
1732
+ try {
1733
+ const data = await fs4.readFile(this.paths.sessionFile, "utf-8");
1734
+ const parsed = JSON.parse(data);
1735
+ parsed.startedAt = new Date(parsed.startedAt);
1736
+ parsed.updatedAt = new Date(parsed.updatedAt);
1737
+ for (const msg of parsed.conversation) {
1738
+ msg.timestamp = new Date(msg.timestamp);
1739
+ }
1740
+ for (const clarification of parsed.clarifications) {
1741
+ clarification.timestamp = new Date(clarification.timestamp);
1742
+ }
1743
+ return parsed;
1744
+ } catch (error) {
1745
+ if (error.code === "ENOENT") {
1746
+ return null;
1747
+ }
1748
+ throw new FileSystemError(
1749
+ "Failed to load discovery session",
1750
+ { path: this.paths.sessionFile, operation: "read" }
1751
+ );
1752
+ }
1753
+ }
1754
+ /**
1755
+ * Check if a session exists
1756
+ */
1757
+ async hasSession() {
1758
+ try {
1759
+ await fs4.access(this.paths.sessionFile);
1760
+ return true;
1761
+ } catch {
1762
+ return false;
1763
+ }
1764
+ }
1765
+ /**
1766
+ * Delete a session
1767
+ */
1768
+ async deleteSession() {
1769
+ try {
1770
+ await fs4.unlink(this.paths.sessionFile);
1771
+ } catch (error) {
1772
+ if (error.code !== "ENOENT") {
1773
+ throw new FileSystemError(
1774
+ "Failed to delete discovery session",
1775
+ { path: this.paths.sessionFile, operation: "delete" }
1776
+ );
1777
+ }
1778
+ }
1779
+ }
1780
+ /**
1781
+ * Save the specification markdown
1782
+ */
1783
+ async saveSpecification(content) {
1784
+ await this.ensureDir();
1785
+ try {
1786
+ await fs4.writeFile(this.paths.specFile, content, "utf-8");
1787
+ } catch {
1788
+ throw new FileSystemError(
1789
+ "Failed to save specification",
1790
+ { path: this.paths.specFile, operation: "write" }
1791
+ );
1792
+ }
1793
+ }
1794
+ /**
1795
+ * Load the specification markdown
1796
+ */
1797
+ async loadSpecification() {
1798
+ try {
1799
+ return await fs4.readFile(this.paths.specFile, "utf-8");
1800
+ } catch {
1801
+ return null;
1802
+ }
1803
+ }
1804
+ /**
1805
+ * Append a message to the conversation log
1806
+ */
1807
+ async appendConversation(role, content) {
1808
+ await this.ensureDir();
1809
+ const entry = {
1810
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1811
+ role,
1812
+ content
1813
+ };
1814
+ try {
1815
+ await fs4.appendFile(
1816
+ this.paths.conversationLog,
1817
+ JSON.stringify(entry) + "\n",
1818
+ "utf-8"
1819
+ );
1820
+ } catch {
1821
+ throw new FileSystemError(
1822
+ "Failed to append to conversation log",
1823
+ { path: this.paths.conversationLog, operation: "write" }
1824
+ );
1825
+ }
1826
+ }
1827
+ /**
1828
+ * Load the full conversation log
1829
+ */
1830
+ async loadConversationLog() {
1831
+ try {
1832
+ const data = await fs4.readFile(this.paths.conversationLog, "utf-8");
1833
+ const lines = data.trim().split("\n");
1834
+ return lines.filter((line) => line.trim()).map((line) => JSON.parse(line));
1835
+ } catch {
1836
+ return [];
1837
+ }
1838
+ }
1839
+ /**
1840
+ * Save a checkpoint
1841
+ */
1842
+ async saveCheckpoint(checkpoint) {
1843
+ await this.ensureDir();
1844
+ try {
1845
+ const data = JSON.stringify(checkpoint, null, 2);
1846
+ await fs4.writeFile(this.paths.checkpointFile, data, "utf-8");
1847
+ } catch {
1848
+ throw new FileSystemError(
1849
+ "Failed to save checkpoint",
1850
+ { path: this.paths.checkpointFile, operation: "write" }
1851
+ );
1852
+ }
1853
+ }
1854
+ /**
1855
+ * Load a checkpoint
1856
+ */
1857
+ async loadCheckpoint() {
1858
+ try {
1859
+ const data = await fs4.readFile(this.paths.checkpointFile, "utf-8");
1860
+ const parsed = JSON.parse(data);
1861
+ parsed.timestamp = new Date(parsed.timestamp);
1862
+ return parsed;
1863
+ } catch {
1864
+ return null;
1865
+ }
1866
+ }
1867
+ /**
1868
+ * Clear all persisted data
1869
+ */
1870
+ async clearAll() {
1871
+ try {
1872
+ await fs4.rm(this.paths.baseDir, { recursive: true, force: true });
1873
+ } catch (error) {
1874
+ if (error.code !== "ENOENT") {
1875
+ throw new FileSystemError(
1876
+ "Failed to clear persistence data",
1877
+ { path: this.paths.baseDir, operation: "delete" }
1878
+ );
1879
+ }
1880
+ }
1881
+ }
1882
+ /**
1883
+ * Get the specification file path
1884
+ */
1885
+ getSpecPath() {
1886
+ return this.paths.specFile;
1887
+ }
1888
+ };
1889
+ function createCheckpoint(sessionId, step, progress, specGenerated = false, metadata = {}) {
1890
+ return {
1891
+ id: `converge-${Date.now()}`,
1892
+ timestamp: /* @__PURE__ */ new Date(),
1893
+ step,
1894
+ sessionId,
1895
+ progress,
1896
+ specGenerated,
1897
+ metadata
1898
+ };
1899
+ }
1900
+ var SessionManager = class {
1901
+ persistence;
1902
+ constructor(projectPath) {
1903
+ this.persistence = new SessionPersistence(projectPath);
1904
+ }
1905
+ /**
1906
+ * Get the persistence layer
1907
+ */
1908
+ getPersistence() {
1909
+ return this.persistence;
1910
+ }
1911
+ /**
1912
+ * Save session with automatic checkpoint
1913
+ */
1914
+ async saveWithCheckpoint(session, step, progress) {
1915
+ await this.persistence.saveSession(session);
1916
+ const checkpoint = createCheckpoint(
1917
+ session.id,
1918
+ step,
1919
+ progress,
1920
+ session.status === "spec_generated"
1921
+ );
1922
+ await this.persistence.saveCheckpoint(checkpoint);
1923
+ }
1924
+ /**
1925
+ * Resume from last checkpoint
1926
+ */
1927
+ async resume() {
1928
+ const checkpoint = await this.persistence.loadCheckpoint();
1929
+ if (!checkpoint) return null;
1930
+ const session = await this.persistence.loadSession();
1931
+ if (!session) return null;
1932
+ return { session, checkpoint };
1933
+ }
1934
+ /**
1935
+ * Check if can resume
1936
+ */
1937
+ async canResume() {
1938
+ const checkpoint = await this.persistence.loadCheckpoint();
1939
+ return checkpoint !== null && checkpoint.step !== "complete";
1940
+ }
1941
+ /**
1942
+ * Get resume info without loading full session
1943
+ */
1944
+ async getResumeInfo() {
1945
+ const checkpoint = await this.persistence.loadCheckpoint();
1946
+ if (!checkpoint) return null;
1947
+ return {
1948
+ sessionId: checkpoint.sessionId,
1949
+ step: checkpoint.step,
1950
+ progress: checkpoint.progress,
1951
+ timestamp: checkpoint.timestamp
1952
+ };
1953
+ }
1954
+ /**
1955
+ * Complete the session and save specification
1956
+ */
1957
+ async complete(session, specMarkdown) {
1958
+ session.status = "spec_generated";
1959
+ session.updatedAt = /* @__PURE__ */ new Date();
1960
+ await this.persistence.saveSession(session);
1961
+ await this.persistence.saveSpecification(specMarkdown);
1962
+ const checkpoint = createCheckpoint(
1963
+ session.id,
1964
+ "complete",
1965
+ 100,
1966
+ true
1967
+ );
1968
+ await this.persistence.saveCheckpoint(checkpoint);
1969
+ }
1970
+ };
1971
+ function createSessionManager(projectPath) {
1972
+ return new SessionManager(projectPath);
1973
+ }
1974
+
1975
+ // src/phases/converge/executor.ts
1976
+ var DEFAULT_CONVERGE_CONFIG = {
1977
+ maxQuestionRounds: 3,
1978
+ maxQuestionsPerRound: 3,
1979
+ autoProceed: false,
1980
+ includeDiagrams: true
1981
+ };
1982
+ var ConvergeExecutor = class {
1983
+ name = "converge";
1984
+ description = "Gather requirements and generate specification";
1985
+ config;
1986
+ discovery = null;
1987
+ specGenerator = null;
1988
+ sessionManager = null;
1989
+ currentSession = null;
1990
+ llm = null;
1991
+ constructor(config = {}) {
1992
+ this.config = { ...DEFAULT_CONVERGE_CONFIG, ...config };
1993
+ }
1994
+ /**
1995
+ * Check if the phase can start
1996
+ */
1997
+ canStart(_context) {
1998
+ return true;
1999
+ }
2000
+ /**
2001
+ * Execute the CONVERGE phase
2002
+ */
2003
+ async execute(context) {
2004
+ const startTime = /* @__PURE__ */ new Date();
2005
+ const artifacts = [];
2006
+ try {
2007
+ await this.initialize(context);
2008
+ const resumeData = await this.sessionManager.resume();
2009
+ if (resumeData) {
2010
+ this.currentSession = resumeData.session;
2011
+ this.discovery.resumeSession(this.currentSession);
2012
+ this.reportProgress(
2013
+ resumeData.checkpoint.step,
2014
+ resumeData.checkpoint.progress,
2015
+ "Resuming from checkpoint"
2016
+ );
2017
+ } else {
2018
+ const initialInput = await this.getUserInput(
2019
+ "Please describe the project you want to build:",
2020
+ void 0
2021
+ );
2022
+ this.reportProgress("discovery", 10, "Starting discovery...");
2023
+ this.currentSession = await this.discovery.startSession(initialInput);
2024
+ await this.saveProgress("discovery", 15);
2025
+ }
2026
+ await this.runDiscoveryLoop();
2027
+ this.discovery.markComplete();
2028
+ this.reportProgress("spec_generation", 80, "Generating specification...");
2029
+ const spec = await this.specGenerator.generate(this.currentSession);
2030
+ const specMarkdown = this.specGenerator.generateMarkdown(spec);
2031
+ await this.sessionManager.complete(this.currentSession, specMarkdown);
2032
+ artifacts.push({
2033
+ type: "specification",
2034
+ path: this.sessionManager.getPersistence().getSpecPath(),
2035
+ description: "Project specification document"
2036
+ });
2037
+ this.reportProgress("complete", 100, "CONVERGE phase complete");
2038
+ const endTime = /* @__PURE__ */ new Date();
2039
+ return {
2040
+ phase: "converge",
2041
+ success: true,
2042
+ artifacts,
2043
+ metrics: {
2044
+ startTime,
2045
+ endTime,
2046
+ durationMs: endTime.getTime() - startTime.getTime(),
2047
+ llmCalls: this.currentSession.conversation.length,
2048
+ tokensUsed: 0
2049
+ // Would need to track this
2050
+ }
2051
+ };
2052
+ } catch (error) {
2053
+ return {
2054
+ phase: "converge",
2055
+ success: false,
2056
+ artifacts,
2057
+ error: error instanceof Error ? error.message : String(error)
2058
+ };
2059
+ }
2060
+ }
2061
+ /**
2062
+ * Check if the phase can complete
2063
+ */
2064
+ canComplete(_context) {
2065
+ if (!this.discovery || !this.currentSession) return false;
2066
+ return this.discovery.isComplete() || this.discovery.getCriticalQuestions().length === 0;
2067
+ }
2068
+ /**
2069
+ * Create a checkpoint for recovery
2070
+ */
2071
+ async checkpoint(_context) {
2072
+ const step = this.getCurrentStep();
2073
+ const progress = this.calculateProgress();
2074
+ if (this.currentSession && this.sessionManager) {
2075
+ await this.sessionManager.saveWithCheckpoint(
2076
+ this.currentSession,
2077
+ step,
2078
+ progress
2079
+ );
2080
+ }
2081
+ return {
2082
+ phase: "converge",
2083
+ timestamp: /* @__PURE__ */ new Date(),
2084
+ state: {
2085
+ artifacts: [],
2086
+ progress,
2087
+ checkpoint: null
2088
+ },
2089
+ resumePoint: step
2090
+ };
2091
+ }
2092
+ /**
2093
+ * Restore from a checkpoint
2094
+ */
2095
+ async restore(_checkpoint, context) {
2096
+ await this.initialize(context);
2097
+ const resumeData = await this.sessionManager.resume();
2098
+ if (resumeData) {
2099
+ this.currentSession = resumeData.session;
2100
+ this.discovery.resumeSession(this.currentSession);
2101
+ }
2102
+ }
2103
+ // Private methods
2104
+ async initialize(context) {
2105
+ this.llm = this.createLLMAdapter(context);
2106
+ this.discovery = createDiscoveryEngine(this.llm, {
2107
+ maxQuestionsPerRound: this.config.maxQuestionsPerRound
2108
+ });
2109
+ this.specGenerator = createSpecificationGenerator(this.llm, {
2110
+ includeDiagrams: this.config.includeDiagrams
2111
+ });
2112
+ this.sessionManager = createSessionManager(context.projectPath);
2113
+ }
2114
+ createLLMAdapter(context) {
2115
+ const llmContext = context.llm;
2116
+ return {
2117
+ id: "phase-adapter",
2118
+ name: "Phase LLM Adapter",
2119
+ async initialize() {
2120
+ },
2121
+ async chat(messages) {
2122
+ const adapted = messages.map((m) => ({
2123
+ role: m.role,
2124
+ content: typeof m.content === "string" ? m.content : JSON.stringify(m.content)
2125
+ }));
2126
+ const response = await llmContext.chat(adapted);
2127
+ return {
2128
+ id: `chat-${Date.now()}`,
2129
+ content: response.content,
2130
+ stopReason: "end_turn",
2131
+ usage: {
2132
+ inputTokens: response.usage.inputTokens,
2133
+ outputTokens: response.usage.outputTokens
2134
+ },
2135
+ model: "phase-adapter"
2136
+ };
2137
+ },
2138
+ async chatWithTools(messages, options) {
2139
+ const adapted = messages.map((m) => ({
2140
+ role: m.role,
2141
+ content: typeof m.content === "string" ? m.content : JSON.stringify(m.content)
2142
+ }));
2143
+ const tools = options.tools.map((t) => ({
2144
+ name: t.name,
2145
+ description: t.description,
2146
+ parameters: t.input_schema
2147
+ }));
2148
+ const response = await llmContext.chatWithTools(adapted, tools);
2149
+ return {
2150
+ id: `chat-${Date.now()}`,
2151
+ content: response.content,
2152
+ stopReason: "end_turn",
2153
+ usage: {
2154
+ inputTokens: response.usage.inputTokens,
2155
+ outputTokens: response.usage.outputTokens
2156
+ },
2157
+ model: "phase-adapter",
2158
+ toolCalls: (response.toolCalls || []).map((tc) => ({
2159
+ id: tc.name,
2160
+ name: tc.name,
2161
+ input: tc.arguments
2162
+ }))
2163
+ };
2164
+ },
2165
+ async *stream(messages) {
2166
+ const adapted = messages.map((m) => ({
2167
+ role: m.role,
2168
+ content: typeof m.content === "string" ? m.content : JSON.stringify(m.content)
2169
+ }));
2170
+ const response = await llmContext.chat(adapted);
2171
+ yield {
2172
+ type: "text",
2173
+ text: response.content
2174
+ };
2175
+ yield {
2176
+ type: "done"
2177
+ };
2178
+ },
2179
+ countTokens(_text) {
2180
+ return Math.ceil(_text.length / 4);
2181
+ },
2182
+ getContextWindow() {
2183
+ return 2e5;
2184
+ },
2185
+ async isAvailable() {
2186
+ return true;
2187
+ }
2188
+ };
2189
+ }
2190
+ async runDiscoveryLoop() {
2191
+ let round = 0;
2192
+ while (round < this.config.maxQuestionRounds) {
2193
+ round++;
2194
+ const criticalQuestions = this.discovery.getCriticalQuestions();
2195
+ if (criticalQuestions.length === 0 && this.config.autoProceed) {
2196
+ break;
2197
+ }
2198
+ const openQuestions = this.discovery.getOpenQuestions();
2199
+ if (openQuestions.length === 0) {
2200
+ const newQuestions = await this.discovery.generateQuestions();
2201
+ if (newQuestions.length === 0) {
2202
+ break;
2203
+ }
2204
+ }
2205
+ const questions = this.discovery.getOpenQuestions();
2206
+ if (questions.length === 0) {
2207
+ break;
2208
+ }
2209
+ this.reportProgress(
2210
+ "clarification",
2211
+ 30 + round * 15,
2212
+ `Asking clarification questions (round ${round})`
2213
+ );
2214
+ for (const question of questions) {
2215
+ const answer = await this.askQuestion(question);
2216
+ if (answer.toLowerCase() === "skip") {
2217
+ if (question.defaultAnswer) {
2218
+ await this.discovery.processAnswer(
2219
+ question.id,
2220
+ question.defaultAnswer
2221
+ );
2222
+ }
2223
+ continue;
2224
+ }
2225
+ if (answer.toLowerCase() === "done") {
2226
+ return;
2227
+ }
2228
+ await this.discovery.processAnswer(question.id, answer);
2229
+ }
2230
+ await this.saveProgress("clarification", 30 + round * 15);
2231
+ }
2232
+ }
2233
+ async askQuestion(question) {
2234
+ let prompt = question.question;
2235
+ if (question.context) {
2236
+ prompt += `
2237
+
2238
+ Context: ${question.context}`;
2239
+ }
2240
+ if (question.options && question.options.length > 0) {
2241
+ prompt += "\n\nOptions:";
2242
+ for (let i = 0; i < question.options.length; i++) {
2243
+ prompt += `
2244
+ ${i + 1}. ${question.options[i]}`;
2245
+ }
2246
+ }
2247
+ if (question.defaultAnswer) {
2248
+ prompt += `
2249
+
2250
+ (Default: ${question.defaultAnswer}, type 'skip' to use default)`;
2251
+ }
2252
+ prompt += "\n\n(Type 'done' to finish questions and proceed)";
2253
+ return this.getUserInput(prompt, question.options);
2254
+ }
2255
+ async getUserInput(prompt, options) {
2256
+ if (this.config.onUserInput) {
2257
+ return this.config.onUserInput(prompt, options);
2258
+ }
2259
+ throw new PhaseError(
2260
+ "No user input handler configured",
2261
+ { phase: "converge" }
2262
+ );
2263
+ }
2264
+ reportProgress(step, progress, message) {
2265
+ if (this.config.onProgress) {
2266
+ this.config.onProgress(step, progress, message);
2267
+ }
2268
+ }
2269
+ async saveProgress(step, progress) {
2270
+ if (this.currentSession && this.sessionManager) {
2271
+ await this.sessionManager.saveWithCheckpoint(
2272
+ this.currentSession,
2273
+ step,
2274
+ progress
2275
+ );
2276
+ }
2277
+ }
2278
+ getCurrentStep() {
2279
+ if (!this.currentSession) return "init";
2280
+ switch (this.currentSession.status) {
2281
+ case "gathering":
2282
+ return "discovery";
2283
+ case "clarifying":
2284
+ return "clarification";
2285
+ case "refining":
2286
+ return "refinement";
2287
+ case "complete":
2288
+ return "spec_generation";
2289
+ case "spec_generated":
2290
+ return "complete";
2291
+ default:
2292
+ return "discovery";
2293
+ }
2294
+ }
2295
+ calculateProgress() {
2296
+ if (!this.currentSession) return 0;
2297
+ const step = this.getCurrentStep();
2298
+ const stepProgress = {
2299
+ init: 0,
2300
+ discovery: 20,
2301
+ clarification: 50,
2302
+ refinement: 70,
2303
+ spec_generation: 90,
2304
+ complete: 100
2305
+ };
2306
+ return stepProgress[step] || 0;
2307
+ }
2308
+ };
2309
+ function createConvergeExecutor(config) {
2310
+ return new ConvergeExecutor(config);
2311
+ }
2312
+
2313
+ // src/phases/orchestrate/types.ts
2314
+ var DEFAULT_SPRINT_CONFIG = {
2315
+ sprintDuration: 14,
2316
+ targetVelocity: 20,
2317
+ maxStoriesPerSprint: 8,
2318
+ bufferPercentage: 20
2319
+ };
2320
+ var DEFAULT_ORCHESTRATE_CONFIG = {
2321
+ generateC4Diagrams: true,
2322
+ generateSequenceDiagrams: true,
2323
+ maxADRs: 10,
2324
+ sprint: DEFAULT_SPRINT_CONFIG,
2325
+ breakdownStrategy: "tdd",
2326
+ generateDeploymentDocs: true
2327
+ };
2328
+
2329
+ // src/phases/orchestrate/prompts.ts
2330
+ var ARCHITECT_SYSTEM_PROMPT = `You are a senior software architect with expertise in designing scalable, maintainable systems.
2331
+
2332
+ Your responsibilities:
2333
+ 1. Design clear, modular architectures
2334
+ 2. Document key decisions with ADRs
2335
+ 3. Create actionable development plans
2336
+ 4. Consider quality attributes (performance, security, maintainability)
2337
+ 5. Apply appropriate design patterns
2338
+
2339
+ Guidelines:
2340
+ - Favor simplicity over complexity
2341
+ - Design for change and extensibility
2342
+ - Consider trade-offs explicitly
2343
+ - Document assumptions and risks
2344
+ - Use industry-standard patterns when appropriate
2345
+
2346
+ You produce structured, well-documented architectural artifacts.`;
2347
+ var GENERATE_ARCHITECTURE_PROMPT = `Based on the project specification, design a comprehensive software architecture.
2348
+
2349
+ Project Specification:
2350
+ {{specification}}
2351
+
2352
+ Technology Stack:
2353
+ {{techStack}}
2354
+
2355
+ Requirements Summary:
2356
+ - Functional: {{functionalCount}} requirements
2357
+ - Non-Functional: {{nonFunctionalCount}} requirements
2358
+ - Constraints: {{constraintCount}} constraints
2359
+
2360
+ Generate an architecture that:
2361
+ 1. Addresses all functional requirements
2362
+ 2. Satisfies non-functional requirements
2363
+ 3. Respects technical constraints
2364
+ 4. Is appropriately complex for the project scope
2365
+
2366
+ Respond in JSON format:
2367
+ {
2368
+ "overview": {
2369
+ "pattern": "layered|hexagonal|clean|microservices|event_driven|cqrs|modular_monolith|serverless",
2370
+ "description": "string",
2371
+ "principles": ["string"],
2372
+ "qualityAttributes": [
2373
+ {
2374
+ "name": "string",
2375
+ "description": "string",
2376
+ "priority": "high|medium|low",
2377
+ "tradeoffs": ["string"]
2378
+ }
2379
+ ]
2380
+ },
2381
+ "components": [
2382
+ {
2383
+ "id": "string",
2384
+ "name": "string",
2385
+ "type": "service|controller|repository|adapter|port|domain|usecase|utility|external",
2386
+ "description": "string",
2387
+ "responsibilities": ["string"],
2388
+ "technology": "string",
2389
+ "layer": "string",
2390
+ "dependencies": ["component_id"]
2391
+ }
2392
+ ],
2393
+ "relationships": [
2394
+ {
2395
+ "from": "component_id",
2396
+ "to": "component_id",
2397
+ "type": "uses|implements|extends|depends|calls|publishes|subscribes",
2398
+ "description": "string"
2399
+ }
2400
+ ],
2401
+ "dataModels": [
2402
+ {
2403
+ "name": "string",
2404
+ "description": "string",
2405
+ "fields": [
2406
+ {
2407
+ "name": "string",
2408
+ "type": "string",
2409
+ "required": true,
2410
+ "description": "string"
2411
+ }
2412
+ ],
2413
+ "relationships": [
2414
+ {
2415
+ "type": "one_to_one|one_to_many|many_to_many",
2416
+ "target": "model_name",
2417
+ "description": "string"
2418
+ }
2419
+ ]
2420
+ }
2421
+ ],
2422
+ "integrations": [
2423
+ {
2424
+ "name": "string",
2425
+ "type": "rest_api|graphql|grpc|database|message_queue|file_system|external_service",
2426
+ "description": "string",
2427
+ "endpoint": "string",
2428
+ "authentication": "string"
2429
+ }
2430
+ ],
2431
+ "reasoning": "string"
2432
+ }`;
2433
+ var GENERATE_C4_DIAGRAMS_PROMPT = `Generate C4 model diagrams for the architecture.
2434
+
2435
+ Architecture Overview:
2436
+ {{architecture}}
2437
+
2438
+ Generate Mermaid diagrams for:
2439
+ 1. Context diagram (system and external actors)
2440
+ 2. Container diagram (deployable units)
2441
+ 3. Component diagram (key components within containers)
2442
+
2443
+ Respond in JSON format:
2444
+ {
2445
+ "diagrams": [
2446
+ {
2447
+ "id": "c4_context",
2448
+ "type": "c4_context",
2449
+ "title": "System Context Diagram",
2450
+ "description": "string",
2451
+ "mermaid": "C4Context\\n..."
2452
+ },
2453
+ {
2454
+ "id": "c4_container",
2455
+ "type": "c4_container",
2456
+ "title": "Container Diagram",
2457
+ "description": "string",
2458
+ "mermaid": "C4Container\\n..."
2459
+ },
2460
+ {
2461
+ "id": "c4_component",
2462
+ "type": "c4_component",
2463
+ "title": "Component Diagram",
2464
+ "description": "string",
2465
+ "mermaid": "C4Component\\n..."
2466
+ }
2467
+ ]
2468
+ }`;
2469
+ var GENERATE_SEQUENCE_DIAGRAMS_PROMPT = `Generate sequence diagrams for key user flows.
2470
+
2471
+ Architecture:
2472
+ {{architecture}}
2473
+
2474
+ Key Functional Requirements:
2475
+ {{functionalRequirements}}
2476
+
2477
+ Generate sequence diagrams for the 3-5 most important user flows.
2478
+
2479
+ Respond in JSON format:
2480
+ {
2481
+ "diagrams": [
2482
+ {
2483
+ "id": "string",
2484
+ "type": "sequence",
2485
+ "title": "string",
2486
+ "description": "string",
2487
+ "mermaid": "sequenceDiagram\\n..."
2488
+ }
2489
+ ]
2490
+ }`;
2491
+ var GENERATE_ADRS_PROMPT = `Generate Architecture Decision Records for key decisions.
2492
+
2493
+ Architecture:
2494
+ {{architecture}}
2495
+
2496
+ Technology Stack:
2497
+ {{techStack}}
2498
+
2499
+ Identify the {{maxADRs}} most important architectural decisions and document them as ADRs.
2500
+
2501
+ Include ADRs for:
2502
+ 1. Core architecture pattern choice
2503
+ 2. Major technology selections
2504
+ 3. Security approach
2505
+ 4. Data storage strategy
2506
+ 5. Integration patterns
2507
+ 6. Testing strategy
2508
+ 7. Deployment approach
2509
+
2510
+ Respond in JSON format:
2511
+ {
2512
+ "adrs": [
2513
+ {
2514
+ "number": 1,
2515
+ "title": "string",
2516
+ "status": "accepted",
2517
+ "context": "Detailed context explaining the situation and problem",
2518
+ "decision": "Clear statement of the decision made",
2519
+ "consequences": {
2520
+ "positive": ["string"],
2521
+ "negative": ["string"],
2522
+ "neutral": ["string"]
2523
+ },
2524
+ "alternatives": [
2525
+ {
2526
+ "option": "string",
2527
+ "pros": ["string"],
2528
+ "cons": ["string"],
2529
+ "reason": "Why not chosen"
2530
+ }
2531
+ ],
2532
+ "references": ["string"]
2533
+ }
2534
+ ]
2535
+ }`;
2536
+ var GENERATE_BACKLOG_PROMPT = `Create a complete development backlog from the architecture and requirements.
2537
+
2538
+ Architecture:
2539
+ {{architecture}}
2540
+
2541
+ Requirements:
2542
+ {{requirements}}
2543
+
2544
+ Breakdown Strategy: {{breakdownStrategy}}
2545
+
2546
+ Generate a backlog with:
2547
+ 1. Epics (major features or components)
2548
+ 2. User Stories (deliverable increments)
2549
+ 3. Tasks (atomic work items)
2550
+
2551
+ Follow these guidelines:
2552
+ - Each task should be completable in 1-4 hours
2553
+ - Include tests for each feature task
2554
+ - Order by dependency and priority
2555
+ - Include infrastructure/setup tasks
2556
+ - Include documentation tasks
2557
+
2558
+ Respond in JSON format:
2559
+ {
2560
+ "epics": [
2561
+ {
2562
+ "id": "epic_001",
2563
+ "title": "string",
2564
+ "description": "string",
2565
+ "priority": 1-5,
2566
+ "dependencies": ["epic_id"],
2567
+ "status": "planned"
2568
+ }
2569
+ ],
2570
+ "stories": [
2571
+ {
2572
+ "id": "story_001",
2573
+ "epicId": "epic_001",
2574
+ "title": "string",
2575
+ "asA": "role",
2576
+ "iWant": "feature",
2577
+ "soThat": "benefit",
2578
+ "acceptanceCriteria": ["string"],
2579
+ "points": 1|2|3|5|8|13,
2580
+ "status": "backlog"
2581
+ }
2582
+ ],
2583
+ "tasks": [
2584
+ {
2585
+ "id": "task_001",
2586
+ "storyId": "story_001",
2587
+ "title": "string",
2588
+ "description": "string",
2589
+ "type": "feature|test|refactor|docs|infra|config",
2590
+ "files": ["expected/file/paths"],
2591
+ "dependencies": ["task_id"],
2592
+ "estimatedComplexity": "trivial|simple|moderate|complex",
2593
+ "status": "pending"
2594
+ }
2595
+ ],
2596
+ "estimatedSprints": number,
2597
+ "warnings": ["string"]
2598
+ }`;
2599
+ var PLAN_SPRINT_PROMPT = `Plan the first sprint from the backlog.
2600
+
2601
+ Backlog Summary:
2602
+ - Epics: {{epicCount}}
2603
+ - Stories: {{storyCount}}
2604
+ - Tasks: {{taskCount}}
2605
+
2606
+ Sprint Configuration:
2607
+ - Duration: {{sprintDuration}} days
2608
+ - Target Velocity: {{targetVelocity}} points
2609
+ - Max Stories: {{maxStoriesPerSprint}}
2610
+ - Buffer: {{bufferPercentage}}%
2611
+
2612
+ Stories Available:
2613
+ {{availableStories}}
2614
+
2615
+ Select stories for Sprint 1 following these rules:
2616
+ 1. Prioritize foundation/infrastructure stories
2617
+ 2. Respect dependencies
2618
+ 3. Stay within velocity target (with buffer)
2619
+ 4. Ensure a coherent sprint goal
2620
+
2621
+ Respond in JSON format:
2622
+ {
2623
+ "sprint": {
2624
+ "id": "sprint_001",
2625
+ "name": "Sprint 1: Foundation",
2626
+ "goal": "string",
2627
+ "stories": ["story_id"],
2628
+ "plannedPoints": number,
2629
+ "status": "planning"
2630
+ },
2631
+ "reasoning": "string"
2632
+ }`;
2633
+ function fillPrompt2(template, variables) {
2634
+ let result = template;
2635
+ for (const [key, value] of Object.entries(variables)) {
2636
+ const placeholder = `{{${key}}}`;
2637
+ const stringValue = typeof value === "string" ? value : typeof value === "number" ? String(value) : JSON.stringify(value, null, 2);
2638
+ result = result.replaceAll(placeholder, stringValue);
2639
+ }
2640
+ return result;
2641
+ }
2642
+ function parseOverview(data) {
2643
+ return {
2644
+ pattern: data?.pattern || "layered",
2645
+ description: data?.description || "System architecture",
2646
+ principles: data?.principles || [],
2647
+ qualityAttributes: (data?.qualityAttributes || []).map((qa) => ({
2648
+ name: qa.name || "",
2649
+ description: qa.description || "",
2650
+ priority: qa.priority || "medium",
2651
+ tradeoffs: qa.tradeoffs
2652
+ }))
2653
+ };
2654
+ }
2655
+ function parseComponents(data) {
2656
+ return data.map((c) => ({
2657
+ id: c.id || randomUUID(),
2658
+ name: c.name || "Component",
2659
+ type: c.type || "service",
2660
+ description: c.description || "",
2661
+ responsibilities: c.responsibilities || [],
2662
+ technology: c.technology,
2663
+ layer: c.layer,
2664
+ dependencies: c.dependencies || []
2665
+ }));
2666
+ }
2667
+ function parseRelationships(data) {
2668
+ return data.map((r) => ({
2669
+ from: r.from || "",
2670
+ to: r.to || "",
2671
+ type: r.type || "uses",
2672
+ description: r.description
2673
+ }));
2674
+ }
2675
+ function parseDataModels(data) {
2676
+ return data.map((dm) => ({
2677
+ name: dm.name || "Model",
2678
+ description: dm.description || "",
2679
+ fields: (dm.fields || []).map((f) => ({
2680
+ name: f.name || "",
2681
+ type: f.type || "string",
2682
+ required: f.required ?? true,
2683
+ description: f.description
2684
+ })),
2685
+ relationships: (dm.relationships || []).map((r) => ({
2686
+ type: r.type || "one_to_many",
2687
+ target: r.target || "",
2688
+ description: r.description
2689
+ }))
2690
+ }));
2691
+ }
2692
+ function parseIntegrations(data) {
2693
+ return data.map((i) => ({
2694
+ name: i.name || "Integration",
2695
+ type: i.type || "rest_api",
2696
+ description: i.description || "",
2697
+ endpoint: i.endpoint,
2698
+ authentication: i.authentication
2699
+ }));
2700
+ }
2701
+
2702
+ // src/phases/orchestrate/architecture-markdown.ts
2703
+ function generateArchitectureMarkdown(doc) {
2704
+ const sections = [];
2705
+ sections.push("# Architecture Document");
2706
+ sections.push("");
2707
+ sections.push(`> Generated: ${doc.generatedAt.toISOString()} | Version: ${doc.version}`);
2708
+ sections.push("");
2709
+ sections.push("## Overview");
2710
+ sections.push("");
2711
+ sections.push(`**Pattern:** ${doc.overview.pattern}`);
2712
+ sections.push("");
2713
+ sections.push(doc.overview.description);
2714
+ sections.push("");
2715
+ if (doc.overview.principles.length > 0) {
2716
+ sections.push("### Design Principles");
2717
+ sections.push("");
2718
+ for (const principle of doc.overview.principles) {
2719
+ sections.push(`- ${principle}`);
2720
+ }
2721
+ sections.push("");
2722
+ }
2723
+ if (doc.overview.qualityAttributes.length > 0) {
2724
+ sections.push("### Quality Attributes");
2725
+ sections.push("");
2726
+ sections.push("| Attribute | Priority | Description |");
2727
+ sections.push("|-----------|----------|-------------|");
2728
+ for (const qa of doc.overview.qualityAttributes) {
2729
+ sections.push(`| ${qa.name} | ${qa.priority} | ${qa.description} |`);
2730
+ }
2731
+ sections.push("");
2732
+ }
2733
+ sections.push("## Components");
2734
+ sections.push("");
2735
+ for (const component of doc.components) {
2736
+ sections.push(`### ${component.name}`);
2737
+ sections.push("");
2738
+ sections.push(`**Type:** ${component.type}`);
2739
+ if (component.layer) sections.push(`**Layer:** ${component.layer}`);
2740
+ if (component.technology) sections.push(`**Technology:** ${component.technology}`);
2741
+ sections.push("");
2742
+ sections.push(component.description);
2743
+ sections.push("");
2744
+ if (component.responsibilities.length > 0) {
2745
+ sections.push("**Responsibilities:**");
2746
+ for (const resp of component.responsibilities) {
2747
+ sections.push(`- ${resp}`);
2748
+ }
2749
+ sections.push("");
2750
+ }
2751
+ }
2752
+ if (doc.dataModels.length > 0) {
2753
+ sections.push("## Data Models");
2754
+ sections.push("");
2755
+ for (const model of doc.dataModels) {
2756
+ sections.push(`### ${model.name}`);
2757
+ sections.push("");
2758
+ sections.push(model.description);
2759
+ sections.push("");
2760
+ sections.push("| Field | Type | Required |");
2761
+ sections.push("|-------|------|----------|");
2762
+ for (const field of model.fields) {
2763
+ sections.push(`| ${field.name} | ${field.type} | ${field.required ? "Yes" : "No"} |`);
2764
+ }
2765
+ sections.push("");
2766
+ }
2767
+ }
2768
+ if (doc.integrations.length > 0) {
2769
+ sections.push("## Integrations");
2770
+ sections.push("");
2771
+ for (const integration of doc.integrations) {
2772
+ sections.push(`### ${integration.name}`);
2773
+ sections.push("");
2774
+ sections.push(`**Type:** ${integration.type}`);
2775
+ sections.push(integration.description);
2776
+ sections.push("");
2777
+ }
2778
+ }
2779
+ if (doc.diagrams.length > 0) {
2780
+ sections.push("## Diagrams");
2781
+ sections.push("");
2782
+ for (const diagram of doc.diagrams) {
2783
+ sections.push(`### ${diagram.title}`);
2784
+ sections.push("");
2785
+ sections.push(diagram.description);
2786
+ sections.push("");
2787
+ sections.push("```mermaid");
2788
+ sections.push(diagram.mermaid);
2789
+ sections.push("```");
2790
+ sections.push("");
2791
+ }
2792
+ }
2793
+ sections.push("---");
2794
+ sections.push("");
2795
+ sections.push("*Generated by Corbat-Coco*");
2796
+ return sections.join("\n");
2797
+ }
2798
+
2799
+ // src/phases/orchestrate/architecture.ts
2800
+ var ArchitectureGenerator = class {
2801
+ llm;
2802
+ config;
2803
+ constructor(llm, config) {
2804
+ this.llm = llm;
2805
+ this.config = config;
2806
+ }
2807
+ /**
2808
+ * Generate architecture from specification
2809
+ */
2810
+ async generate(specification) {
2811
+ const baseArchitecture = await this.generateBaseArchitecture(specification);
2812
+ const diagrams = [];
2813
+ if (this.config.generateC4Diagrams) {
2814
+ const c4Diagrams = await this.generateC4Diagrams(baseArchitecture);
2815
+ diagrams.push(...c4Diagrams);
2816
+ }
2817
+ if (this.config.generateSequenceDiagrams) {
2818
+ const seqDiagrams = await this.generateSequenceDiagrams(
2819
+ baseArchitecture,
2820
+ specification
2821
+ );
2822
+ diagrams.push(...seqDiagrams);
2823
+ }
2824
+ return {
2825
+ ...baseArchitecture,
2826
+ diagrams
2827
+ };
2828
+ }
2829
+ /**
2830
+ * Generate base architecture
2831
+ */
2832
+ async generateBaseArchitecture(specification) {
2833
+ const prompt = fillPrompt2(GENERATE_ARCHITECTURE_PROMPT, {
2834
+ specification: JSON.stringify(specification.overview),
2835
+ techStack: JSON.stringify(specification.technical.stack),
2836
+ functionalCount: specification.requirements.functional.length,
2837
+ nonFunctionalCount: specification.requirements.nonFunctional.length,
2838
+ constraintCount: specification.requirements.constraints.length
2839
+ });
2840
+ const response = await this.llm.chat([
2841
+ { role: "system", content: ARCHITECT_SYSTEM_PROMPT },
2842
+ { role: "user", content: prompt }
2843
+ ]);
2844
+ try {
2845
+ const content = response.content;
2846
+ const jsonMatch = content.match(/\{[\s\S]*\}/);
2847
+ if (!jsonMatch) {
2848
+ throw new Error("No JSON found in response");
2849
+ }
2850
+ const parsed = JSON.parse(jsonMatch[0]);
2851
+ return {
2852
+ version: "1.0.0",
2853
+ generatedAt: /* @__PURE__ */ new Date(),
2854
+ overview: parseOverview(parsed.overview),
2855
+ components: parseComponents(parsed.components || []),
2856
+ relationships: parseRelationships(parsed.relationships || []),
2857
+ dataModels: parseDataModels(parsed.dataModels || []),
2858
+ integrations: parseIntegrations(parsed.integrations || []),
2859
+ diagrams: []
2860
+ };
2861
+ } catch {
2862
+ throw new PhaseError("Failed to generate architecture", { phase: "orchestrate" });
2863
+ }
2864
+ }
2865
+ /**
2866
+ * Generate C4 diagrams
2867
+ */
2868
+ async generateC4Diagrams(architecture) {
2869
+ const prompt = fillPrompt2(GENERATE_C4_DIAGRAMS_PROMPT, {
2870
+ architecture: JSON.stringify({
2871
+ overview: architecture.overview,
2872
+ components: architecture.components,
2873
+ relationships: architecture.relationships
2874
+ })
2875
+ });
2876
+ try {
2877
+ const response = await this.llm.chat([
2878
+ { role: "system", content: ARCHITECT_SYSTEM_PROMPT },
2879
+ { role: "user", content: prompt }
2880
+ ]);
2881
+ const content = response.content;
2882
+ const jsonMatch = content.match(/\{[\s\S]*\}/);
2883
+ if (!jsonMatch) {
2884
+ return this.generateFallbackC4Diagrams(architecture);
2885
+ }
2886
+ const parsed = JSON.parse(jsonMatch[0]);
2887
+ return (parsed.diagrams || []).map((d) => ({
2888
+ id: d.id || randomUUID(),
2889
+ type: d.type || "c4_context",
2890
+ title: d.title || "Diagram",
2891
+ description: d.description || "",
2892
+ mermaid: d.mermaid || ""
2893
+ }));
2894
+ } catch {
2895
+ return this.generateFallbackC4Diagrams(architecture);
2896
+ }
2897
+ }
2898
+ /**
2899
+ * Generate sequence diagrams
2900
+ */
2901
+ async generateSequenceDiagrams(architecture, specification) {
2902
+ const prompt = fillPrompt2(GENERATE_SEQUENCE_DIAGRAMS_PROMPT, {
2903
+ architecture: JSON.stringify({
2904
+ overview: architecture.overview,
2905
+ components: architecture.components
2906
+ }),
2907
+ functionalRequirements: JSON.stringify(
2908
+ specification.requirements.functional.slice(0, 5)
2909
+ )
2910
+ });
2911
+ try {
2912
+ const response = await this.llm.chat([
2913
+ { role: "system", content: ARCHITECT_SYSTEM_PROMPT },
2914
+ { role: "user", content: prompt }
2915
+ ]);
2916
+ const content = response.content;
2917
+ const jsonMatch = content.match(/\{[\s\S]*\}/);
2918
+ if (!jsonMatch) {
2919
+ return [];
2920
+ }
2921
+ const parsed = JSON.parse(jsonMatch[0]);
2922
+ return (parsed.diagrams || []).map((d) => ({
2923
+ id: d.id || randomUUID(),
2924
+ type: "sequence",
2925
+ title: d.title || "Sequence Diagram",
2926
+ description: d.description || "",
2927
+ mermaid: d.mermaid || ""
2928
+ }));
2929
+ } catch {
2930
+ return [];
2931
+ }
2932
+ }
2933
+ /**
2934
+ * Generate fallback C4 diagrams if LLM fails
2935
+ */
2936
+ generateFallbackC4Diagrams(architecture) {
2937
+ const diagrams = [];
2938
+ let contextMermaid = "C4Context\n";
2939
+ contextMermaid += ` title System Context Diagram
2940
+ `;
2941
+ contextMermaid += ` Person(user, "User", "Primary system user")
2942
+ `;
2943
+ contextMermaid += ` System(system, "${architecture.overview.description.substring(0, 30)}...", "The system")
2944
+ `;
2945
+ for (const integration of architecture.integrations) {
2946
+ contextMermaid += ` System_Ext(${integration.name.replace(/\s/g, "_")}, "${integration.name}", "${integration.type}")
2947
+ `;
2948
+ }
2949
+ contextMermaid += ` Rel(user, system, "Uses")
2950
+ `;
2951
+ diagrams.push({
2952
+ id: "c4_context",
2953
+ type: "c4_context",
2954
+ title: "System Context Diagram",
2955
+ description: "High-level view of the system and its environment",
2956
+ mermaid: contextMermaid
2957
+ });
2958
+ let containerMermaid = "C4Container\n";
2959
+ containerMermaid += ` title Container Diagram
2960
+ `;
2961
+ const layers = new Set(architecture.components.map((c) => c.layer).filter(Boolean));
2962
+ for (const layer of layers) {
2963
+ containerMermaid += ` Container_Boundary(${layer?.replace(/\s/g, "_")}, "${layer}") {
2964
+ `;
2965
+ for (const component of architecture.components.filter((c) => c.layer === layer)) {
2966
+ containerMermaid += ` Container(${component.id}, "${component.name}", "${component.technology || ""}")
2967
+ `;
2968
+ }
2969
+ containerMermaid += ` }
2970
+ `;
2971
+ }
2972
+ diagrams.push({
2973
+ id: "c4_container",
2974
+ type: "c4_container",
2975
+ title: "Container Diagram",
2976
+ description: "Shows the major containers/deployable units",
2977
+ mermaid: containerMermaid
2978
+ });
2979
+ return diagrams;
2980
+ }
2981
+ };
2982
+ var ADRGenerator = class {
2983
+ llm;
2984
+ config;
2985
+ constructor(llm, config) {
2986
+ this.llm = llm;
2987
+ this.config = config;
2988
+ }
2989
+ /**
2990
+ * Generate ADRs from architecture and specification
2991
+ */
2992
+ async generate(architecture, specification) {
2993
+ const prompt = fillPrompt2(GENERATE_ADRS_PROMPT, {
2994
+ architecture: JSON.stringify({
2995
+ overview: architecture.overview,
2996
+ components: architecture.components.map((c) => ({
2997
+ name: c.name,
2998
+ type: c.type,
2999
+ technology: c.technology
3000
+ }))
3001
+ }),
3002
+ techStack: JSON.stringify(specification.technical.stack),
3003
+ maxADRs: this.config.maxADRs
3004
+ });
3005
+ const response = await this.llm.chat([
3006
+ { role: "system", content: ARCHITECT_SYSTEM_PROMPT },
3007
+ { role: "user", content: prompt }
3008
+ ]);
3009
+ try {
3010
+ const content = response.content;
3011
+ const jsonMatch = content.match(/\{[\s\S]*\}/);
3012
+ if (!jsonMatch) {
3013
+ throw new Error("No JSON found in response");
3014
+ }
3015
+ const parsed = JSON.parse(jsonMatch[0]);
3016
+ return (parsed.adrs || []).map((adr, index) => this.parseADR(adr, index));
3017
+ } catch {
3018
+ throw new PhaseError("Failed to generate ADRs", {
3019
+ phase: "orchestrate"
3020
+ });
3021
+ }
3022
+ }
3023
+ /**
3024
+ * Parse a single ADR from LLM response
3025
+ */
3026
+ parseADR(data, index) {
3027
+ return {
3028
+ id: randomUUID(),
3029
+ number: data.number || index + 1,
3030
+ title: data.title || `Decision ${index + 1}`,
3031
+ date: /* @__PURE__ */ new Date(),
3032
+ status: data.status || "accepted",
3033
+ context: data.context || "",
3034
+ decision: data.decision || "",
3035
+ consequences: {
3036
+ positive: data.consequences?.positive || [],
3037
+ negative: data.consequences?.negative || [],
3038
+ neutral: data.consequences?.neutral
3039
+ },
3040
+ alternatives: (data.alternatives || []).map((alt) => ({
3041
+ option: alt.option || "",
3042
+ pros: alt.pros || [],
3043
+ cons: alt.cons || [],
3044
+ reason: alt.reason || ""
3045
+ })),
3046
+ references: data.references
3047
+ };
3048
+ }
3049
+ };
3050
+ function generateADRMarkdown(adr) {
3051
+ const sections = [];
3052
+ const paddedNumber = String(adr.number).padStart(3, "0");
3053
+ sections.push(`# ADR ${paddedNumber}: ${adr.title}`);
3054
+ sections.push("");
3055
+ sections.push(`**Date:** ${adr.date.toISOString().split("T")[0]}`);
3056
+ sections.push(`**Status:** ${adr.status}`);
3057
+ sections.push("");
3058
+ sections.push("## Context");
3059
+ sections.push("");
3060
+ sections.push(adr.context);
3061
+ sections.push("");
3062
+ sections.push("## Decision");
3063
+ sections.push("");
3064
+ sections.push(adr.decision);
3065
+ sections.push("");
3066
+ sections.push("## Consequences");
3067
+ sections.push("");
3068
+ if (adr.consequences.positive.length > 0) {
3069
+ sections.push("### Positive");
3070
+ sections.push("");
3071
+ for (const consequence of adr.consequences.positive) {
3072
+ sections.push(`- \u2705 ${consequence}`);
3073
+ }
3074
+ sections.push("");
3075
+ }
3076
+ if (adr.consequences.negative.length > 0) {
3077
+ sections.push("### Negative");
3078
+ sections.push("");
3079
+ for (const consequence of adr.consequences.negative) {
3080
+ sections.push(`- \u26A0\uFE0F ${consequence}`);
3081
+ }
3082
+ sections.push("");
3083
+ }
3084
+ if (adr.consequences.neutral && adr.consequences.neutral.length > 0) {
3085
+ sections.push("### Neutral");
3086
+ sections.push("");
3087
+ for (const consequence of adr.consequences.neutral) {
3088
+ sections.push(`- ${consequence}`);
3089
+ }
3090
+ sections.push("");
3091
+ }
3092
+ if (adr.alternatives && adr.alternatives.length > 0) {
3093
+ sections.push("## Alternatives Considered");
3094
+ sections.push("");
3095
+ for (const alt of adr.alternatives) {
3096
+ sections.push(`### ${alt.option}`);
3097
+ sections.push("");
3098
+ if (alt.pros.length > 0) {
3099
+ sections.push("**Pros:**");
3100
+ for (const pro of alt.pros) {
3101
+ sections.push(`- ${pro}`);
3102
+ }
3103
+ sections.push("");
3104
+ }
3105
+ if (alt.cons.length > 0) {
3106
+ sections.push("**Cons:**");
3107
+ for (const con of alt.cons) {
3108
+ sections.push(`- ${con}`);
3109
+ }
3110
+ sections.push("");
3111
+ }
3112
+ sections.push(`**Why not chosen:** ${alt.reason}`);
3113
+ sections.push("");
3114
+ }
3115
+ }
3116
+ if (adr.references && adr.references.length > 0) {
3117
+ sections.push("## References");
3118
+ sections.push("");
3119
+ for (const ref of adr.references) {
3120
+ sections.push(`- ${ref}`);
3121
+ }
3122
+ sections.push("");
3123
+ }
3124
+ return sections.join("\n");
3125
+ }
3126
+ function generateADRIndexMarkdown(adrs) {
3127
+ const sections = [];
3128
+ sections.push("# Architecture Decision Records");
3129
+ sections.push("");
3130
+ sections.push("This directory contains all Architecture Decision Records (ADRs) for this project.");
3131
+ sections.push("");
3132
+ sections.push("## Index");
3133
+ sections.push("");
3134
+ sections.push("| # | Title | Status | Date |");
3135
+ sections.push("|---|-------|--------|------|");
3136
+ for (const adr of adrs) {
3137
+ const paddedNumber = String(adr.number).padStart(3, "0");
3138
+ const filename = `${paddedNumber}-${slugify(adr.title)}.md`;
3139
+ const dateStr = adr.date.toISOString().split("T")[0];
3140
+ sections.push(
3141
+ `| ${adr.number} | [${adr.title}](./${filename}) | ${adr.status} | ${dateStr} |`
3142
+ );
3143
+ }
3144
+ sections.push("");
3145
+ sections.push("## About ADRs");
3146
+ sections.push("");
3147
+ sections.push("ADRs are short documents that capture important architectural decisions.");
3148
+ sections.push("Each ADR describes:");
3149
+ sections.push("- The context and problem being addressed");
3150
+ sections.push("- The decision made");
3151
+ sections.push("- The consequences (positive and negative)");
3152
+ sections.push("- Alternatives considered");
3153
+ sections.push("");
3154
+ sections.push("For more information, see [ADR GitHub](https://adr.github.io/).");
3155
+ return sections.join("\n");
3156
+ }
3157
+ function getADRFilename(adr) {
3158
+ const paddedNumber = String(adr.number).padStart(3, "0");
3159
+ return `${paddedNumber}-${slugify(adr.title)}.md`;
3160
+ }
3161
+ function slugify(str) {
3162
+ return str.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
3163
+ }
3164
+ var BacklogGenerator = class {
3165
+ llm;
3166
+ config;
3167
+ constructor(llm, config) {
3168
+ this.llm = llm;
3169
+ this.config = config;
3170
+ }
3171
+ /**
3172
+ * Generate complete backlog from architecture and specification
3173
+ */
3174
+ async generate(architecture, specification) {
3175
+ const prompt = fillPrompt2(GENERATE_BACKLOG_PROMPT, {
3176
+ architecture: JSON.stringify({
3177
+ pattern: architecture.overview.pattern,
3178
+ components: architecture.components.map((c) => c.name),
3179
+ dataModels: architecture.dataModels.map((d) => d.name)
3180
+ }),
3181
+ requirements: JSON.stringify({
3182
+ functional: specification.requirements.functional.map((r) => ({
3183
+ title: r.title,
3184
+ priority: r.priority
3185
+ })),
3186
+ nonFunctional: specification.requirements.nonFunctional.map((r) => r.title)
3187
+ }),
3188
+ breakdownStrategy: this.config.breakdownStrategy
3189
+ });
3190
+ const response = await this.llm.chat([
3191
+ { role: "system", content: ARCHITECT_SYSTEM_PROMPT },
3192
+ { role: "user", content: prompt }
3193
+ ]);
3194
+ try {
3195
+ const content = response.content;
3196
+ const jsonMatch = content.match(/\{[\s\S]*\}/);
3197
+ if (!jsonMatch) {
3198
+ throw new Error("No JSON found in response");
3199
+ }
3200
+ const parsed = JSON.parse(jsonMatch[0]);
3201
+ const epics = this.parseEpics(parsed.epics || []);
3202
+ const stories = this.parseStories(parsed.stories || []);
3203
+ const tasks = this.parseTasks(parsed.tasks || []);
3204
+ const totalPoints = stories.reduce((sum, s) => sum + s.points, 0);
3205
+ const estimatedSprints = parsed.estimatedSprints || Math.ceil(totalPoints / this.config.sprint.targetVelocity);
3206
+ return {
3207
+ backlog: {
3208
+ epics,
3209
+ stories,
3210
+ tasks,
3211
+ currentSprint: null,
3212
+ completedSprints: []
3213
+ },
3214
+ estimatedSprints,
3215
+ estimatedVelocity: this.config.sprint.targetVelocity,
3216
+ warnings: parsed.warnings || []
3217
+ };
3218
+ } catch {
3219
+ throw new PhaseError("Failed to generate backlog", { phase: "orchestrate" });
3220
+ }
3221
+ }
3222
+ /**
3223
+ * Plan the first sprint from the backlog
3224
+ */
3225
+ async planFirstSprint(backlog) {
3226
+ const availableStories = backlog.stories.filter(
3227
+ (s) => s.status === "backlog" || s.status === "ready"
3228
+ );
3229
+ const readyStories = availableStories.filter((story) => {
3230
+ const epic = backlog.epics.find((e) => e.id === story.epicId);
3231
+ if (!epic) return true;
3232
+ const depsMet = epic.dependencies.every((depId) => {
3233
+ const depEpic = backlog.epics.find((e) => e.id === depId);
3234
+ return depEpic?.status === "done";
3235
+ });
3236
+ return depsMet || epic.dependencies.length === 0;
3237
+ });
3238
+ const prompt = fillPrompt2(PLAN_SPRINT_PROMPT, {
3239
+ epicCount: backlog.epics.length,
3240
+ storyCount: backlog.stories.length,
3241
+ taskCount: backlog.tasks.length,
3242
+ sprintDuration: this.config.sprint.sprintDuration,
3243
+ targetVelocity: this.config.sprint.targetVelocity,
3244
+ maxStoriesPerSprint: this.config.sprint.maxStoriesPerSprint,
3245
+ bufferPercentage: this.config.sprint.bufferPercentage,
3246
+ availableStories: JSON.stringify(
3247
+ readyStories.slice(0, 20).map((s) => ({
3248
+ id: s.id,
3249
+ title: s.title,
3250
+ points: s.points,
3251
+ epicId: s.epicId
3252
+ }))
3253
+ )
3254
+ });
3255
+ try {
3256
+ const response = await this.llm.chat([
3257
+ { role: "system", content: ARCHITECT_SYSTEM_PROMPT },
3258
+ { role: "user", content: prompt }
3259
+ ]);
3260
+ const content = response.content;
3261
+ const jsonMatch = content.match(/\{[\s\S]*\}/);
3262
+ if (jsonMatch) {
3263
+ const parsed = JSON.parse(jsonMatch[0]);
3264
+ if (parsed.sprint) {
3265
+ return {
3266
+ id: parsed.sprint.id || `sprint_${randomUUID().substring(0, 8)}`,
3267
+ name: parsed.sprint.name || "Sprint 1",
3268
+ goal: parsed.sprint.goal || "Initial foundation",
3269
+ startDate: /* @__PURE__ */ new Date(),
3270
+ stories: parsed.sprint.stories || [],
3271
+ status: "planning"
3272
+ };
3273
+ }
3274
+ }
3275
+ return this.autoSelectSprint(backlog, readyStories);
3276
+ } catch {
3277
+ return this.autoSelectSprint(backlog, readyStories);
3278
+ }
3279
+ }
3280
+ /**
3281
+ * Auto-select stories for a sprint based on priority and velocity
3282
+ */
3283
+ autoSelectSprint(backlog, availableStories) {
3284
+ const targetVelocity = this.config.sprint.targetVelocity;
3285
+ const bufferFactor = 1 - this.config.sprint.bufferPercentage / 100;
3286
+ const maxPoints = targetVelocity * bufferFactor;
3287
+ const sorted = [...availableStories].sort((a, b) => {
3288
+ const epicA = backlog.epics.find((e) => e.id === a.epicId);
3289
+ const epicB = backlog.epics.find((e) => e.id === b.epicId);
3290
+ const priorityDiff = (epicA?.priority || 5) - (epicB?.priority || 5);
3291
+ if (priorityDiff !== 0) return priorityDiff;
3292
+ return a.points - b.points;
3293
+ });
3294
+ const selectedStories = [];
3295
+ let currentPoints = 0;
3296
+ for (const story of sorted) {
3297
+ if (selectedStories.length >= this.config.sprint.maxStoriesPerSprint) break;
3298
+ if (currentPoints + story.points > maxPoints) continue;
3299
+ selectedStories.push(story.id);
3300
+ currentPoints += story.points;
3301
+ }
3302
+ return {
3303
+ id: `sprint_${randomUUID().substring(0, 8)}`,
3304
+ name: "Sprint 1: Foundation",
3305
+ goal: "Set up project foundation and core infrastructure",
3306
+ startDate: /* @__PURE__ */ new Date(),
3307
+ stories: selectedStories,
3308
+ status: "planning"
3309
+ };
3310
+ }
3311
+ // Parse helpers
3312
+ parseEpics(data) {
3313
+ return data.map((e) => ({
3314
+ id: e.id || `epic_${randomUUID().substring(0, 8)}`,
3315
+ title: e.title || "Epic",
3316
+ description: e.description || "",
3317
+ stories: [],
3318
+ priority: e.priority || 3,
3319
+ dependencies: e.dependencies || [],
3320
+ status: e.status || "planned"
3321
+ }));
3322
+ }
3323
+ parseStories(data) {
3324
+ return data.map((s) => ({
3325
+ id: s.id || `story_${randomUUID().substring(0, 8)}`,
3326
+ epicId: s.epicId || "",
3327
+ title: s.title || "Story",
3328
+ asA: s.asA || "user",
3329
+ iWant: s.iWant || "",
3330
+ soThat: s.soThat || "",
3331
+ acceptanceCriteria: s.acceptanceCriteria || [],
3332
+ tasks: [],
3333
+ points: this.normalizePoints(s.points),
3334
+ status: s.status || "backlog"
3335
+ }));
3336
+ }
3337
+ parseTasks(data) {
3338
+ return data.map((t) => ({
3339
+ id: t.id || `task_${randomUUID().substring(0, 8)}`,
3340
+ storyId: t.storyId || "",
3341
+ title: t.title || "Task",
3342
+ description: t.description || "",
3343
+ type: t.type || "feature",
3344
+ files: t.files || [],
3345
+ dependencies: t.dependencies || [],
3346
+ estimatedComplexity: t.estimatedComplexity || "simple",
3347
+ status: "pending"
3348
+ }));
3349
+ }
3350
+ normalizePoints(value) {
3351
+ const fibonacciPoints = [1, 2, 3, 5, 8, 13];
3352
+ if (!value) return 3;
3353
+ const closest = fibonacciPoints.reduce(
3354
+ (prev, curr) => Math.abs(curr - value) < Math.abs(prev - value) ? curr : prev
3355
+ );
3356
+ return closest;
3357
+ }
3358
+ };
3359
+ function generateBacklogMarkdown(backlog) {
3360
+ const sections = [];
3361
+ sections.push("# Project Backlog");
3362
+ sections.push("");
3363
+ sections.push("## Summary");
3364
+ sections.push("");
3365
+ sections.push(`- **Epics:** ${backlog.epics.length}`);
3366
+ sections.push(`- **Stories:** ${backlog.stories.length}`);
3367
+ sections.push(`- **Tasks:** ${backlog.tasks.length}`);
3368
+ sections.push(
3369
+ `- **Total Points:** ${backlog.stories.reduce((sum, s) => sum + s.points, 0)}`
3370
+ );
3371
+ sections.push("");
3372
+ sections.push("## Epics");
3373
+ sections.push("");
3374
+ for (const epic of backlog.epics) {
3375
+ sections.push(`### ${epic.title}`);
3376
+ sections.push("");
3377
+ sections.push(`**Priority:** ${epic.priority} | **Status:** ${epic.status}`);
3378
+ sections.push("");
3379
+ sections.push(epic.description);
3380
+ sections.push("");
3381
+ const epicStories = backlog.stories.filter((s) => s.epicId === epic.id);
3382
+ if (epicStories.length > 0) {
3383
+ sections.push("#### Stories");
3384
+ sections.push("");
3385
+ sections.push("| ID | Title | Points | Status |");
3386
+ sections.push("|----|-------|--------|--------|");
3387
+ for (const story of epicStories) {
3388
+ sections.push(
3389
+ `| ${story.id} | ${story.title} | ${story.points} | ${story.status} |`
3390
+ );
3391
+ }
3392
+ sections.push("");
3393
+ }
3394
+ }
3395
+ sections.push("## Story Details");
3396
+ sections.push("");
3397
+ for (const story of backlog.stories) {
3398
+ sections.push(`### ${story.title}`);
3399
+ sections.push("");
3400
+ sections.push(`**As a** ${story.asA}`);
3401
+ sections.push(`**I want** ${story.iWant}`);
3402
+ sections.push(`**So that** ${story.soThat}`);
3403
+ sections.push("");
3404
+ sections.push(`**Points:** ${story.points} | **Status:** ${story.status}`);
3405
+ sections.push("");
3406
+ if (story.acceptanceCriteria.length > 0) {
3407
+ sections.push("**Acceptance Criteria:**");
3408
+ for (const ac of story.acceptanceCriteria) {
3409
+ sections.push(`- [ ] ${ac}`);
3410
+ }
3411
+ sections.push("");
3412
+ }
3413
+ const storyTasks = backlog.tasks.filter((t) => t.storyId === story.id);
3414
+ if (storyTasks.length > 0) {
3415
+ sections.push("**Tasks:**");
3416
+ sections.push("");
3417
+ sections.push("| ID | Title | Type | Complexity |");
3418
+ sections.push("|----|-------|------|------------|");
3419
+ for (const task of storyTasks) {
3420
+ sections.push(
3421
+ `| ${task.id} | ${task.title} | ${task.type} | ${task.estimatedComplexity} |`
3422
+ );
3423
+ }
3424
+ sections.push("");
3425
+ }
3426
+ }
3427
+ sections.push("---");
3428
+ sections.push("");
3429
+ sections.push("*Generated by Corbat-Coco*");
3430
+ return sections.join("\n");
3431
+ }
3432
+ function generateSprintMarkdown(sprint, backlog) {
3433
+ const sections = [];
3434
+ sections.push(`# ${sprint.name}`);
3435
+ sections.push("");
3436
+ sections.push(`**Start Date:** ${sprint.startDate.toISOString().split("T")[0]}`);
3437
+ sections.push(`**Status:** ${sprint.status}`);
3438
+ sections.push("");
3439
+ sections.push("## Goal");
3440
+ sections.push("");
3441
+ sections.push(sprint.goal);
3442
+ sections.push("");
3443
+ const sprintStories = backlog.stories.filter(
3444
+ (s) => sprint.stories.includes(s.id)
3445
+ );
3446
+ const totalPoints = sprintStories.reduce((sum, s) => sum + s.points, 0);
3447
+ sections.push("## Stories");
3448
+ sections.push("");
3449
+ sections.push(`**Total Points:** ${totalPoints}`);
3450
+ sections.push("");
3451
+ sections.push("| Story | Points | Status |");
3452
+ sections.push("|-------|--------|--------|");
3453
+ for (const story of sprintStories) {
3454
+ sections.push(`| ${story.title} | ${story.points} | ${story.status} |`);
3455
+ }
3456
+ sections.push("");
3457
+ sections.push("## Tasks");
3458
+ sections.push("");
3459
+ for (const story of sprintStories) {
3460
+ const storyTasks = backlog.tasks.filter((t) => t.storyId === story.id);
3461
+ if (storyTasks.length > 0) {
3462
+ sections.push(`### ${story.title}`);
3463
+ sections.push("");
3464
+ for (const task of storyTasks) {
3465
+ const checkbox = task.status === "completed" ? "[x]" : "[ ]";
3466
+ sections.push(`- ${checkbox} ${task.title} (${task.type})`);
3467
+ }
3468
+ sections.push("");
3469
+ }
3470
+ }
3471
+ return sections.join("\n");
3472
+ }
3473
+
3474
+ // src/phases/orchestrate/executor.ts
3475
+ var OrchestrateExecutor = class {
3476
+ name = "orchestrate";
3477
+ description = "Design architecture, create ADRs, and generate backlog";
3478
+ config;
3479
+ constructor(config = {}) {
3480
+ this.config = { ...DEFAULT_ORCHESTRATE_CONFIG, ...config };
3481
+ }
3482
+ /**
3483
+ * Check if the phase can start
3484
+ */
3485
+ canStart(_context) {
3486
+ return true;
3487
+ }
3488
+ /**
3489
+ * Execute the ORCHESTRATE phase
3490
+ */
3491
+ async execute(context) {
3492
+ const startTime = /* @__PURE__ */ new Date();
3493
+ const artifacts = [];
3494
+ try {
3495
+ const specification = await this.loadSpecification(context.projectPath);
3496
+ const llm = this.createLLMAdapter(context);
3497
+ const archGenerator = new ArchitectureGenerator(llm, this.config);
3498
+ const adrGenerator = new ADRGenerator(llm, this.config);
3499
+ const backlogGenerator = new BacklogGenerator(llm, this.config);
3500
+ const architecture = await archGenerator.generate(specification);
3501
+ const archPath = await this.saveArchitecture(context.projectPath, architecture);
3502
+ artifacts.push({
3503
+ type: "architecture",
3504
+ path: archPath,
3505
+ description: "Architecture documentation"
3506
+ });
3507
+ const adrs = await adrGenerator.generate(architecture, specification);
3508
+ const adrPaths = await this.saveADRs(context.projectPath, adrs);
3509
+ for (const adrPath of adrPaths) {
3510
+ artifacts.push({
3511
+ type: "adr",
3512
+ path: adrPath,
3513
+ description: "Architecture Decision Record"
3514
+ });
3515
+ }
3516
+ const backlogResult = await backlogGenerator.generate(
3517
+ architecture,
3518
+ specification
3519
+ );
3520
+ const backlogPath = await this.saveBacklog(context.projectPath, backlogResult);
3521
+ artifacts.push({
3522
+ type: "backlog",
3523
+ path: backlogPath,
3524
+ description: "Project backlog"
3525
+ });
3526
+ const firstSprint = await backlogGenerator.planFirstSprint(
3527
+ backlogResult.backlog
3528
+ );
3529
+ const sprintPath = await this.saveSprint(
3530
+ context.projectPath,
3531
+ firstSprint,
3532
+ backlogResult
3533
+ );
3534
+ artifacts.push({
3535
+ type: "backlog",
3536
+ path: sprintPath,
3537
+ description: "Sprint 1 plan"
3538
+ });
3539
+ for (const diagram of architecture.diagrams) {
3540
+ const diagramPath = await this.saveDiagram(
3541
+ context.projectPath,
3542
+ diagram.id,
3543
+ diagram.mermaid
3544
+ );
3545
+ artifacts.push({
3546
+ type: "diagram",
3547
+ path: diagramPath,
3548
+ description: diagram.title
3549
+ });
3550
+ }
3551
+ const endTime = /* @__PURE__ */ new Date();
3552
+ return {
3553
+ phase: "orchestrate",
3554
+ success: true,
3555
+ artifacts,
3556
+ metrics: {
3557
+ startTime,
3558
+ endTime,
3559
+ durationMs: endTime.getTime() - startTime.getTime(),
3560
+ llmCalls: adrs.length + 3,
3561
+ // Approximate
3562
+ tokensUsed: 0
3563
+ // Would need tracking
3564
+ }
3565
+ };
3566
+ } catch (error) {
3567
+ return {
3568
+ phase: "orchestrate",
3569
+ success: false,
3570
+ artifacts,
3571
+ error: error instanceof Error ? error.message : String(error)
3572
+ };
3573
+ }
3574
+ }
3575
+ /**
3576
+ * Check if the phase can complete
3577
+ */
3578
+ canComplete(_context) {
3579
+ return true;
3580
+ }
3581
+ /**
3582
+ * Create a checkpoint
3583
+ */
3584
+ async checkpoint(_context) {
3585
+ return {
3586
+ phase: "orchestrate",
3587
+ timestamp: /* @__PURE__ */ new Date(),
3588
+ state: {
3589
+ artifacts: [],
3590
+ progress: 0,
3591
+ checkpoint: null
3592
+ },
3593
+ resumePoint: "start"
3594
+ };
3595
+ }
3596
+ /**
3597
+ * Restore from checkpoint
3598
+ */
3599
+ async restore(_checkpoint, _context) {
3600
+ }
3601
+ // Private methods
3602
+ createLLMAdapter(context) {
3603
+ const llmContext = context.llm;
3604
+ return {
3605
+ id: "phase-adapter",
3606
+ name: "Phase LLM Adapter",
3607
+ async initialize() {
3608
+ },
3609
+ async chat(messages) {
3610
+ const adapted = messages.map((m) => ({
3611
+ role: m.role,
3612
+ content: typeof m.content === "string" ? m.content : JSON.stringify(m.content)
3613
+ }));
3614
+ const response = await llmContext.chat(adapted);
3615
+ return {
3616
+ id: `chat-${Date.now()}`,
3617
+ content: response.content,
3618
+ stopReason: "end_turn",
3619
+ usage: {
3620
+ inputTokens: response.usage.inputTokens,
3621
+ outputTokens: response.usage.outputTokens
3622
+ },
3623
+ model: "phase-adapter"
3624
+ };
3625
+ },
3626
+ async chatWithTools(messages, options) {
3627
+ const adapted = messages.map((m) => ({
3628
+ role: m.role,
3629
+ content: typeof m.content === "string" ? m.content : JSON.stringify(m.content)
3630
+ }));
3631
+ const tools = options.tools.map((t) => ({
3632
+ name: t.name,
3633
+ description: t.description,
3634
+ parameters: t.input_schema
3635
+ }));
3636
+ const response = await llmContext.chatWithTools(adapted, tools);
3637
+ return {
3638
+ id: `chat-${Date.now()}`,
3639
+ content: response.content,
3640
+ stopReason: "end_turn",
3641
+ usage: {
3642
+ inputTokens: response.usage.inputTokens,
3643
+ outputTokens: response.usage.outputTokens
3644
+ },
3645
+ model: "phase-adapter",
3646
+ toolCalls: (response.toolCalls || []).map((tc) => ({
3647
+ id: tc.name,
3648
+ name: tc.name,
3649
+ input: tc.arguments
3650
+ }))
3651
+ };
3652
+ },
3653
+ async *stream(messages) {
3654
+ const adapted = messages.map((m) => ({
3655
+ role: m.role,
3656
+ content: typeof m.content === "string" ? m.content : JSON.stringify(m.content)
3657
+ }));
3658
+ const response = await llmContext.chat(adapted);
3659
+ yield {
3660
+ type: "text",
3661
+ text: response.content
3662
+ };
3663
+ yield {
3664
+ type: "done"
3665
+ };
3666
+ },
3667
+ countTokens(_text) {
3668
+ return Math.ceil(_text.length / 4);
3669
+ },
3670
+ getContextWindow() {
3671
+ return 2e5;
3672
+ },
3673
+ async isAvailable() {
3674
+ return true;
3675
+ }
3676
+ };
3677
+ }
3678
+ async loadSpecification(projectPath) {
3679
+ try {
3680
+ const jsonPath = path.join(projectPath, ".coco", "spec", "spec.json");
3681
+ const jsonContent = await fs4.readFile(jsonPath, "utf-8");
3682
+ return JSON.parse(jsonContent);
3683
+ } catch {
3684
+ return this.createMinimalSpec(projectPath);
3685
+ }
3686
+ }
3687
+ createMinimalSpec(projectPath) {
3688
+ return {
3689
+ version: "1.0.0",
3690
+ generatedAt: /* @__PURE__ */ new Date(),
3691
+ overview: {
3692
+ name: path.basename(projectPath),
3693
+ description: "Project specification",
3694
+ goals: [],
3695
+ targetUsers: ["developers"],
3696
+ successCriteria: []
3697
+ },
3698
+ requirements: {
3699
+ functional: [],
3700
+ nonFunctional: [],
3701
+ constraints: []
3702
+ },
3703
+ technical: {
3704
+ stack: [],
3705
+ architecture: "",
3706
+ integrations: [],
3707
+ deployment: ""
3708
+ },
3709
+ assumptions: {
3710
+ confirmed: [],
3711
+ unconfirmed: [],
3712
+ risks: []
3713
+ },
3714
+ outOfScope: [],
3715
+ openQuestions: []
3716
+ };
3717
+ }
3718
+ async saveArchitecture(projectPath, architecture) {
3719
+ const dir = path.join(projectPath, ".coco", "architecture");
3720
+ await fs4.mkdir(dir, { recursive: true });
3721
+ const mdPath = path.join(dir, "ARCHITECTURE.md");
3722
+ await fs4.writeFile(mdPath, generateArchitectureMarkdown(architecture), "utf-8");
3723
+ const jsonPath = path.join(dir, "architecture.json");
3724
+ await fs4.writeFile(jsonPath, JSON.stringify(architecture, null, 2), "utf-8");
3725
+ return mdPath;
3726
+ }
3727
+ async saveADRs(projectPath, adrs) {
3728
+ const dir = path.join(projectPath, ".coco", "architecture", "adrs");
3729
+ await fs4.mkdir(dir, { recursive: true });
3730
+ const paths = [];
3731
+ const indexPath = path.join(dir, "README.md");
3732
+ await fs4.writeFile(indexPath, generateADRIndexMarkdown(adrs), "utf-8");
3733
+ paths.push(indexPath);
3734
+ for (const adr of adrs) {
3735
+ const filename = getADRFilename(adr);
3736
+ const adrPath = path.join(dir, filename);
3737
+ await fs4.writeFile(adrPath, generateADRMarkdown(adr), "utf-8");
3738
+ paths.push(adrPath);
3739
+ }
3740
+ return paths;
3741
+ }
3742
+ async saveBacklog(projectPath, backlogResult) {
3743
+ const dir = path.join(projectPath, ".coco", "planning");
3744
+ await fs4.mkdir(dir, { recursive: true });
3745
+ const mdPath = path.join(dir, "BACKLOG.md");
3746
+ await fs4.writeFile(
3747
+ mdPath,
3748
+ generateBacklogMarkdown(backlogResult.backlog),
3749
+ "utf-8"
3750
+ );
3751
+ const jsonPath = path.join(dir, "backlog.json");
3752
+ await fs4.writeFile(jsonPath, JSON.stringify(backlogResult, null, 2), "utf-8");
3753
+ return mdPath;
3754
+ }
3755
+ async saveSprint(projectPath, sprint, backlogResult) {
3756
+ const dir = path.join(projectPath, ".coco", "planning", "sprints");
3757
+ await fs4.mkdir(dir, { recursive: true });
3758
+ const filename = `${sprint.id}.md`;
3759
+ const sprintPath = path.join(dir, filename);
3760
+ await fs4.writeFile(
3761
+ sprintPath,
3762
+ generateSprintMarkdown(sprint, backlogResult.backlog),
3763
+ "utf-8"
3764
+ );
3765
+ const jsonPath = path.join(dir, `${sprint.id}.json`);
3766
+ await fs4.writeFile(jsonPath, JSON.stringify(sprint, null, 2), "utf-8");
3767
+ return sprintPath;
3768
+ }
3769
+ async saveDiagram(projectPath, id, mermaid) {
3770
+ const dir = path.join(projectPath, ".coco", "architecture", "diagrams");
3771
+ await fs4.mkdir(dir, { recursive: true });
3772
+ const diagramPath = path.join(dir, `${id}.mmd`);
3773
+ await fs4.writeFile(diagramPath, mermaid, "utf-8");
3774
+ return diagramPath;
3775
+ }
3776
+ };
3777
+ function createOrchestrateExecutor(config) {
3778
+ return new OrchestrateExecutor(config);
3779
+ }
3780
+ var DEFAULT_MODEL = "claude-sonnet-4-20250514";
3781
+ var CONTEXT_WINDOWS = {
3782
+ "claude-sonnet-4-20250514": 2e5,
3783
+ "claude-opus-4-20250514": 2e5,
3784
+ "claude-3-5-sonnet-20241022": 2e5,
3785
+ "claude-3-5-haiku-20241022": 2e5,
3786
+ "claude-3-opus-20240229": 2e5,
3787
+ "claude-3-sonnet-20240229": 2e5,
3788
+ "claude-3-haiku-20240307": 2e5
3789
+ };
3790
+ var AnthropicProvider = class {
3791
+ id = "anthropic";
3792
+ name = "Anthropic Claude";
3793
+ client = null;
3794
+ config = {};
3795
+ /**
3796
+ * Initialize the provider
3797
+ */
3798
+ async initialize(config) {
3799
+ this.config = config;
3800
+ const apiKey = config.apiKey ?? process.env["ANTHROPIC_API_KEY"];
3801
+ if (!apiKey) {
3802
+ throw new ProviderError("Anthropic API key not provided", {
3803
+ provider: this.id
3804
+ });
3805
+ }
3806
+ this.client = new Anthropic({
3807
+ apiKey,
3808
+ baseURL: config.baseUrl,
3809
+ timeout: config.timeout ?? 12e4
3810
+ });
3811
+ }
3812
+ /**
3813
+ * Send a chat message
3814
+ */
3815
+ async chat(messages, options) {
3816
+ this.ensureInitialized();
3817
+ try {
3818
+ const response = await this.client.messages.create({
3819
+ model: options?.model ?? this.config.model ?? DEFAULT_MODEL,
3820
+ max_tokens: options?.maxTokens ?? this.config.maxTokens ?? 8192,
3821
+ temperature: options?.temperature ?? this.config.temperature ?? 0,
3822
+ system: options?.system,
3823
+ messages: this.convertMessages(messages),
3824
+ stop_sequences: options?.stopSequences
3825
+ });
3826
+ return {
3827
+ id: response.id,
3828
+ content: this.extractTextContent(response.content),
3829
+ stopReason: this.mapStopReason(response.stop_reason),
3830
+ usage: {
3831
+ inputTokens: response.usage.input_tokens,
3832
+ outputTokens: response.usage.output_tokens
3833
+ },
3834
+ model: response.model
3835
+ };
3836
+ } catch (error) {
3837
+ throw this.handleError(error);
3838
+ }
3839
+ }
3840
+ /**
3841
+ * Send a chat message with tool use
3842
+ */
3843
+ async chatWithTools(messages, options) {
3844
+ this.ensureInitialized();
3845
+ try {
3846
+ const response = await this.client.messages.create({
3847
+ model: options?.model ?? this.config.model ?? DEFAULT_MODEL,
3848
+ max_tokens: options?.maxTokens ?? this.config.maxTokens ?? 8192,
3849
+ temperature: options?.temperature ?? this.config.temperature ?? 0,
3850
+ system: options?.system,
3851
+ messages: this.convertMessages(messages),
3852
+ tools: this.convertTools(options.tools),
3853
+ tool_choice: options.toolChoice ? this.convertToolChoice(options.toolChoice) : void 0
3854
+ });
3855
+ const toolCalls = this.extractToolCalls(response.content);
3856
+ return {
3857
+ id: response.id,
3858
+ content: this.extractTextContent(response.content),
3859
+ stopReason: this.mapStopReason(response.stop_reason),
3860
+ usage: {
3861
+ inputTokens: response.usage.input_tokens,
3862
+ outputTokens: response.usage.output_tokens
3863
+ },
3864
+ model: response.model,
3865
+ toolCalls
3866
+ };
3867
+ } catch (error) {
3868
+ throw this.handleError(error);
3869
+ }
3870
+ }
3871
+ /**
3872
+ * Stream a chat response
3873
+ */
3874
+ async *stream(messages, options) {
3875
+ this.ensureInitialized();
3876
+ try {
3877
+ const stream = await this.client.messages.stream({
3878
+ model: options?.model ?? this.config.model ?? DEFAULT_MODEL,
3879
+ max_tokens: options?.maxTokens ?? this.config.maxTokens ?? 8192,
3880
+ temperature: options?.temperature ?? this.config.temperature ?? 0,
3881
+ system: options?.system,
3882
+ messages: this.convertMessages(messages)
3883
+ });
3884
+ for await (const event of stream) {
3885
+ if (event.type === "content_block_delta") {
3886
+ const delta = event.delta;
3887
+ if (delta.type === "text_delta" && delta.text) {
3888
+ yield { type: "text", text: delta.text };
3889
+ }
3890
+ }
3891
+ }
3892
+ yield { type: "done" };
3893
+ } catch (error) {
3894
+ throw this.handleError(error);
3895
+ }
3896
+ }
3897
+ /**
3898
+ * Count tokens (approximate)
3899
+ */
3900
+ countTokens(text4) {
3901
+ return Math.ceil(text4.length / 4);
3902
+ }
3903
+ /**
3904
+ * Get context window size
3905
+ */
3906
+ getContextWindow() {
3907
+ const model = this.config.model ?? DEFAULT_MODEL;
3908
+ return CONTEXT_WINDOWS[model] ?? 2e5;
3909
+ }
3910
+ /**
3911
+ * Check if provider is available
3912
+ */
3913
+ async isAvailable() {
3914
+ if (!this.client) return false;
3915
+ try {
3916
+ await this.client.messages.create({
3917
+ model: this.config.model ?? DEFAULT_MODEL,
3918
+ max_tokens: 1,
3919
+ messages: [{ role: "user", content: "hi" }]
3920
+ });
3921
+ return true;
3922
+ } catch {
3923
+ return false;
3924
+ }
3925
+ }
3926
+ /**
3927
+ * Ensure client is initialized
3928
+ */
3929
+ ensureInitialized() {
3930
+ if (!this.client) {
3931
+ throw new ProviderError("Provider not initialized. Call initialize() first.", {
3932
+ provider: this.id
3933
+ });
3934
+ }
3935
+ }
3936
+ /**
3937
+ * Convert messages to Anthropic format
3938
+ */
3939
+ convertMessages(messages) {
3940
+ return messages.filter((m) => m.role !== "system").map((m) => ({
3941
+ role: m.role,
3942
+ content: this.convertContent(m.content)
3943
+ }));
3944
+ }
3945
+ /**
3946
+ * Convert message content to Anthropic format
3947
+ */
3948
+ convertContent(content) {
3949
+ if (typeof content === "string") {
3950
+ return content;
3951
+ }
3952
+ return content.map((block) => {
3953
+ if (block.type === "text") {
3954
+ return { type: "text", text: block.text };
3955
+ }
3956
+ if (block.type === "tool_use") {
3957
+ const toolUse = block;
3958
+ return {
3959
+ type: "tool_use",
3960
+ id: toolUse.id,
3961
+ name: toolUse.name,
3962
+ input: toolUse.input
3963
+ };
3964
+ }
3965
+ if (block.type === "tool_result") {
3966
+ const toolResult = block;
3967
+ return {
3968
+ type: "tool_result",
3969
+ tool_use_id: toolResult.tool_use_id,
3970
+ content: toolResult.content,
3971
+ is_error: toolResult.is_error
3972
+ };
3973
+ }
3974
+ return { type: "text", text: "" };
3975
+ });
3976
+ }
3977
+ /**
3978
+ * Convert tools to Anthropic format
3979
+ */
3980
+ convertTools(tools) {
3981
+ return tools.map((tool) => ({
3982
+ name: tool.name,
3983
+ description: tool.description,
3984
+ input_schema: tool.input_schema
3985
+ }));
3986
+ }
3987
+ /**
3988
+ * Convert tool choice to Anthropic format
3989
+ */
3990
+ convertToolChoice(choice) {
3991
+ if (choice === "auto") return { type: "auto" };
3992
+ if (choice === "any") return { type: "any" };
3993
+ if (typeof choice === "object" && choice.type === "tool") {
3994
+ return { type: "tool", name: choice.name };
3995
+ }
3996
+ return { type: "auto" };
3997
+ }
3998
+ /**
3999
+ * Extract text content from response
4000
+ */
4001
+ extractTextContent(content) {
4002
+ return content.filter((block) => block.type === "text").map((block) => block.text).join("");
4003
+ }
4004
+ /**
4005
+ * Extract tool calls from response
4006
+ */
4007
+ extractToolCalls(content) {
4008
+ return content.filter((block) => block.type === "tool_use").map((block) => ({
4009
+ id: block.id,
4010
+ name: block.name,
4011
+ input: block.input
4012
+ }));
4013
+ }
4014
+ /**
4015
+ * Map stop reason to our format
4016
+ */
4017
+ mapStopReason(reason) {
4018
+ switch (reason) {
4019
+ case "end_turn":
4020
+ return "end_turn";
4021
+ case "max_tokens":
4022
+ return "max_tokens";
4023
+ case "stop_sequence":
4024
+ return "stop_sequence";
4025
+ case "tool_use":
4026
+ return "tool_use";
4027
+ default:
4028
+ return "end_turn";
4029
+ }
4030
+ }
4031
+ /**
4032
+ * Handle API errors
4033
+ */
4034
+ handleError(error) {
4035
+ if (error instanceof Anthropic.APIError) {
4036
+ const retryable = error.status === 429 || error.status >= 500;
4037
+ throw new ProviderError(error.message, {
4038
+ provider: this.id,
4039
+ statusCode: error.status,
4040
+ retryable,
4041
+ cause: error
4042
+ });
4043
+ }
4044
+ throw new ProviderError(
4045
+ error instanceof Error ? error.message : String(error),
4046
+ {
4047
+ provider: this.id,
4048
+ cause: error instanceof Error ? error : void 0
4049
+ }
4050
+ );
4051
+ }
4052
+ };
4053
+
4054
+ // src/providers/index.ts
4055
+ async function createProvider(type, config = {}) {
4056
+ let provider;
4057
+ switch (type) {
4058
+ case "anthropic":
4059
+ provider = new AnthropicProvider();
4060
+ break;
4061
+ case "openai":
4062
+ throw new ProviderError("OpenAI provider not yet implemented", {
4063
+ provider: "openai"
4064
+ });
4065
+ case "local":
4066
+ throw new ProviderError("Local provider not yet implemented", {
4067
+ provider: "local"
4068
+ });
4069
+ default:
4070
+ throw new ProviderError(`Unknown provider type: ${type}`, {
4071
+ provider: type
4072
+ });
4073
+ }
4074
+ await provider.initialize(config);
4075
+ return provider;
4076
+ }
4077
+
4078
+ // src/cli/commands/plan.ts
4079
+ function registerPlanCommand(program2) {
4080
+ program2.command("plan").description("Run discovery and create a development plan").option("-i, --interactive", "Run in interactive mode (default)").option("--skip-discovery", "Skip discovery, use existing specification").option("--dry-run", "Generate plan without saving").option("--auto", "Run without confirmations").action(async (options) => {
4081
+ try {
4082
+ const result = await runPlan({ ...options, cwd: process.cwd() });
4083
+ if (!result.success) {
4084
+ p.log.error(result.error || "Planning failed");
4085
+ process.exit(1);
4086
+ }
4087
+ } catch (error) {
4088
+ p.log.error(
4089
+ error instanceof Error ? error.message : "An error occurred"
4090
+ );
4091
+ process.exit(1);
4092
+ }
4093
+ });
4094
+ }
4095
+ async function createCliPhaseContext(projectPath, _onUserInput) {
4096
+ let llm;
4097
+ try {
4098
+ const provider = await createProvider("anthropic", {
4099
+ apiKey: process.env.ANTHROPIC_API_KEY
4100
+ });
4101
+ llm = {
4102
+ async chat(messages) {
4103
+ const adapted = messages.map((m) => ({
4104
+ role: m.role,
4105
+ content: m.content
4106
+ }));
4107
+ const response = await provider.chat(adapted);
4108
+ return {
4109
+ content: response.content,
4110
+ usage: response.usage
4111
+ };
4112
+ },
4113
+ async chatWithTools(messages, tools) {
4114
+ const adapted = messages.map((m) => ({
4115
+ role: m.role,
4116
+ content: m.content
4117
+ }));
4118
+ const adaptedTools = tools.map((t) => ({
4119
+ name: t.name,
4120
+ description: t.description,
4121
+ input_schema: t.parameters
4122
+ }));
4123
+ const response = await provider.chatWithTools(adapted, { tools: adaptedTools });
4124
+ return {
4125
+ content: response.content,
4126
+ usage: response.usage,
4127
+ toolCalls: response.toolCalls?.map((tc) => ({
4128
+ name: tc.name,
4129
+ arguments: tc.input
4130
+ }))
4131
+ };
4132
+ }
4133
+ };
4134
+ } catch {
4135
+ llm = {
4136
+ async chat() {
4137
+ return { content: "{}", usage: { inputTokens: 0, outputTokens: 0 } };
4138
+ },
4139
+ async chatWithTools() {
4140
+ return { content: "{}", usage: { inputTokens: 0, outputTokens: 0 } };
4141
+ }
4142
+ };
4143
+ }
4144
+ return {
4145
+ projectPath,
4146
+ config: {
4147
+ quality: {
4148
+ minScore: 85,
4149
+ minCoverage: 80,
4150
+ maxIterations: 10,
4151
+ convergenceThreshold: 2
4152
+ },
4153
+ timeouts: {
4154
+ phaseTimeout: 36e5,
4155
+ taskTimeout: 6e5,
4156
+ llmTimeout: 12e4
4157
+ }
4158
+ },
4159
+ state: {
4160
+ artifacts: [],
4161
+ progress: 0,
4162
+ checkpoint: null
4163
+ },
4164
+ tools: {
4165
+ file: {
4166
+ async read(path5) {
4167
+ const fs5 = await import('fs/promises');
4168
+ return fs5.readFile(path5, "utf-8");
4169
+ },
4170
+ async write(path5, content) {
4171
+ const fs5 = await import('fs/promises');
4172
+ const nodePath = await import('path');
4173
+ await fs5.mkdir(nodePath.dirname(path5), { recursive: true });
4174
+ await fs5.writeFile(path5, content, "utf-8");
4175
+ },
4176
+ async exists(path5) {
4177
+ const fs5 = await import('fs/promises');
4178
+ try {
4179
+ await fs5.access(path5);
4180
+ return true;
4181
+ } catch {
4182
+ return false;
4183
+ }
4184
+ },
4185
+ async glob(pattern) {
4186
+ const { glob } = await import('glob');
4187
+ return glob(pattern, { cwd: projectPath });
4188
+ }
4189
+ },
4190
+ bash: {
4191
+ async exec(command, options = {}) {
4192
+ const { execa } = await import('execa');
4193
+ try {
4194
+ const result = await execa(command, {
4195
+ shell: true,
4196
+ cwd: options.cwd || projectPath,
4197
+ timeout: options.timeout,
4198
+ env: options.env
4199
+ });
4200
+ return {
4201
+ stdout: result.stdout,
4202
+ stderr: result.stderr,
4203
+ exitCode: result.exitCode ?? 0
4204
+ };
4205
+ } catch (error) {
4206
+ const err = error;
4207
+ return {
4208
+ stdout: err.stdout || "",
4209
+ stderr: err.stderr || "",
4210
+ exitCode: err.exitCode || 1
4211
+ };
4212
+ }
4213
+ }
4214
+ },
4215
+ git: {
4216
+ async status() {
4217
+ return { branch: "main", clean: true, staged: [], unstaged: [], untracked: [] };
4218
+ },
4219
+ async commit() {
4220
+ },
4221
+ async push() {
4222
+ }
4223
+ },
4224
+ test: {
4225
+ async run() {
4226
+ return { passed: 0, failed: 0, skipped: 0, duration: 0, failures: [] };
4227
+ },
4228
+ async coverage() {
4229
+ return { lines: 0, branches: 0, functions: 0, statements: 0 };
4230
+ }
4231
+ },
4232
+ quality: {
4233
+ async lint() {
4234
+ return { errors: 0, warnings: 0, issues: [] };
4235
+ },
4236
+ async complexity() {
4237
+ return { averageComplexity: 0, maxComplexity: 0, files: [] };
4238
+ },
4239
+ async security() {
4240
+ return { vulnerabilities: 0, issues: [] };
4241
+ }
4242
+ }
4243
+ },
4244
+ llm
4245
+ };
4246
+ }
4247
+ async function runPlan(options = {}) {
4248
+ const cwd = options.cwd || process.cwd();
4249
+ const configPath = await findConfigPath(cwd);
4250
+ if (!configPath) {
4251
+ return {
4252
+ success: false,
4253
+ error: 'No Corbat-Coco config found. Run "coco init" first.'
4254
+ };
4255
+ }
4256
+ await loadConfig(configPath);
4257
+ if (!options.auto) {
4258
+ p.intro(chalk5.cyan("Corbat-Coco Planning"));
4259
+ }
4260
+ const onUserInput = options.auto ? void 0 : async (prompt, opts) => {
4261
+ if (opts && opts.length > 0) {
4262
+ const result = await p.select({
4263
+ message: prompt,
4264
+ options: opts.map((o) => ({ value: o, label: o }))
4265
+ });
4266
+ if (p.isCancel(result)) {
4267
+ throw new Error("Cancelled by user");
4268
+ }
4269
+ return result;
4270
+ } else {
4271
+ const result = await p.text({ message: prompt });
4272
+ if (p.isCancel(result)) {
4273
+ throw new Error("Cancelled by user");
4274
+ }
4275
+ return result;
4276
+ }
4277
+ };
4278
+ let convergeResult;
4279
+ if (!options.skipDiscovery) {
4280
+ if (!options.auto) {
4281
+ const shouldProceed = await p.confirm({
4282
+ message: "Ready to start planning. Continue?"
4283
+ });
4284
+ if (p.isCancel(shouldProceed) || shouldProceed === false) {
4285
+ return { success: false, error: "Planning cancelled by user" };
4286
+ }
4287
+ p.log.info("Phase 1: Converge - Understanding your requirements");
4288
+ }
4289
+ const convergeExecutor = createConvergeExecutor({
4290
+ onUserInput,
4291
+ onProgress: (step, progress, message) => {
4292
+ if (!options.auto) {
4293
+ p.log.step(`[${step}] ${progress}% - ${message}`);
4294
+ }
4295
+ }
4296
+ });
4297
+ const context2 = await createCliPhaseContext(cwd);
4298
+ convergeResult = await convergeExecutor.execute(context2);
4299
+ if (!convergeResult.success) {
4300
+ return {
4301
+ success: false,
4302
+ error: convergeResult.error || "CONVERGE phase failed"
4303
+ };
4304
+ }
4305
+ } else {
4306
+ convergeResult = { phase: "converge", success: true, artifacts: [] };
4307
+ }
4308
+ if (!options.auto) {
4309
+ p.log.info("Phase 2: Orchestrate - Creating development plan");
4310
+ }
4311
+ const orchestrateExecutor = createOrchestrateExecutor();
4312
+ const context = await createCliPhaseContext(cwd);
4313
+ const orchestrateResult = await orchestrateExecutor.execute(context);
4314
+ if (!orchestrateResult.success) {
4315
+ return {
4316
+ success: false,
4317
+ error: orchestrateResult.error || "ORCHESTRATE phase failed"
4318
+ };
4319
+ }
4320
+ if (!options.auto) {
4321
+ console.log("\n" + chalk5.bold("Planning Summary:"));
4322
+ console.log(
4323
+ chalk5.dim(" Artifacts generated: ") + orchestrateResult.artifacts.length
4324
+ );
4325
+ for (const artifact of orchestrateResult.artifacts) {
4326
+ console.log(chalk5.dim(` - ${artifact.type}: ${artifact.description}`));
4327
+ }
4328
+ p.outro(
4329
+ chalk5.green("Planning complete! Run 'coco build' to start development.")
4330
+ );
4331
+ }
4332
+ return {
4333
+ success: true
4334
+ };
4335
+ }
4336
+ function registerBuildCommand(program2) {
4337
+ program2.command("build").description("Execute tasks and build the project").option("-t, --task <task-id>", "Build a specific task only").option("-s, --sprint <sprint-id>", "Build a specific sprint only").option("--no-review", "Skip self-review iterations").option("--max-iterations <n>", "Maximum iterations per task", "10").option("--min-quality <n>", "Minimum quality score", "85").action(async (options) => {
4338
+ await runBuild(options);
4339
+ });
4340
+ }
4341
+ async function runBuild(options) {
4342
+ p.intro(chalk5.cyan("Corbat-Coco Build"));
4343
+ const projectState = await checkProjectState();
4344
+ if (!projectState.hasProject) {
4345
+ p.log.error("No Corbat-Coco project found. Run 'coco init' first.");
4346
+ process.exit(1);
4347
+ }
4348
+ if (!projectState.hasPlan) {
4349
+ p.log.error("No development plan found. Run 'coco plan' first.");
4350
+ process.exit(1);
4351
+ }
4352
+ const maxIterations = parseInt(options.maxIterations || "10", 10);
4353
+ const minQuality = parseInt(options.minQuality || "85", 10);
4354
+ p.log.step(`Phase 3: Complete - Building with quality threshold ${minQuality}`);
4355
+ const tasks = await loadTasks();
4356
+ p.log.info(`Found ${tasks.length} tasks to complete`);
4357
+ for (let i = 0; i < tasks.length; i++) {
4358
+ const task = tasks[i];
4359
+ if (!task) continue;
4360
+ console.log("\n" + chalk5.bold(`Task ${i + 1}/${tasks.length}: ${task.title}`));
4361
+ await executeTask(task, {
4362
+ maxIterations,
4363
+ minQuality,
4364
+ skipReview: !options.review
4365
+ });
4366
+ }
4367
+ p.outro(chalk5.green("Build complete!"));
4368
+ }
4369
+ async function executeTask(_task, options) {
4370
+ const spinner4 = p.spinner();
4371
+ let iteration = 1;
4372
+ let score = 0;
4373
+ let previousScore = 0;
4374
+ do {
4375
+ spinner4.start(`Iteration ${iteration}: Generating code...`);
4376
+ await simulateDelay(2e3);
4377
+ spinner4.stop(`Iteration ${iteration}: Code generated.`);
4378
+ spinner4.start(`Iteration ${iteration}: Running tests...`);
4379
+ await simulateDelay(1500);
4380
+ spinner4.stop(`Iteration ${iteration}: Tests passed.`);
4381
+ spinner4.start(`Iteration ${iteration}: Evaluating quality...`);
4382
+ await simulateDelay(1e3);
4383
+ previousScore = score;
4384
+ score = Math.min(100, 70 + iteration * 8 + Math.random() * 5);
4385
+ spinner4.stop(`Iteration ${iteration}: Quality score: ${score.toFixed(0)}/100`);
4386
+ if (score >= options.minQuality) {
4387
+ const converged = Math.abs(score - previousScore) < 2;
4388
+ if (converged || options.skipReview) {
4389
+ p.log.success(`Task completed in ${iteration} iterations with score ${score.toFixed(0)}`);
4390
+ break;
4391
+ }
4392
+ }
4393
+ if (iteration < options.maxIterations) {
4394
+ spinner4.start(`Iteration ${iteration}: Analyzing improvements...`);
4395
+ await simulateDelay(1e3);
4396
+ spinner4.stop(`Iteration ${iteration}: Improvements identified.`);
4397
+ }
4398
+ iteration++;
4399
+ } while (iteration <= options.maxIterations);
4400
+ if (score < options.minQuality) {
4401
+ p.log.warn(`Task completed with score ${score.toFixed(0)} (below threshold ${options.minQuality})`);
4402
+ }
4403
+ }
4404
+ async function loadTasks(_options) {
4405
+ return [
4406
+ { id: "task-001", title: "Create user entity", description: "Create the User entity with validation" },
4407
+ { id: "task-002", title: "Implement registration", description: "Create registration endpoint" },
4408
+ { id: "task-003", title: "Add authentication", description: "Implement JWT authentication" }
4409
+ ];
4410
+ }
4411
+ async function checkProjectState() {
4412
+ const fs5 = await import('fs/promises');
4413
+ let hasProject = false;
4414
+ let hasPlan = false;
4415
+ try {
4416
+ await fs5.access(".coco");
4417
+ hasProject = true;
4418
+ } catch {
4419
+ }
4420
+ try {
4421
+ await fs5.access(".coco/planning/backlog.json");
4422
+ hasPlan = true;
4423
+ } catch {
4424
+ }
4425
+ return { hasProject, hasPlan };
4426
+ }
4427
+ function simulateDelay(ms) {
4428
+ return new Promise((resolve) => setTimeout(resolve, ms));
4429
+ }
4430
+ function registerStatusCommand(program2) {
4431
+ program2.command("status").description("Show current project status and progress").option("-d, --detailed", "Show detailed status").option("-v, --verbose", "Show verbose output including checkpoints").option("--json", "Output as JSON").action(async (options) => {
4432
+ try {
4433
+ await runStatus({ ...options, cwd: process.cwd() });
4434
+ } catch (error) {
4435
+ p.log.error(
4436
+ error instanceof Error ? error.message : "An error occurred"
4437
+ );
4438
+ process.exit(1);
4439
+ }
4440
+ });
4441
+ }
4442
+ async function runStatus(options = {}) {
4443
+ const cwd = options.cwd || process.cwd();
4444
+ const configPath = await findConfigPath(cwd);
4445
+ if (!configPath) {
4446
+ p.log.warning("No project found. Run 'coco init' first.");
4447
+ return {
4448
+ project: "",
4449
+ phase: "idle",
4450
+ progress: 0,
4451
+ checkpoints: []
4452
+ };
4453
+ }
4454
+ const config = await loadConfig(configPath);
4455
+ const state = await loadProjectState(cwd, config);
4456
+ if (options.json) {
4457
+ console.log(JSON.stringify(state, null, 2));
4458
+ return {
4459
+ project: state.name,
4460
+ phase: state.currentPhase,
4461
+ progress: state.progress,
4462
+ sprint: state.sprint,
4463
+ metrics: state.metrics,
4464
+ checkpoints: state.checkpoints
4465
+ };
4466
+ }
4467
+ p.log.info(chalk5.bold(`Project: ${state.name}`));
4468
+ p.log.info(`Phase: ${formatPhaseStatus(state.currentPhase, getPhaseStatusForPhase(state.currentPhase))}`);
4469
+ p.log.info(`Progress: ${formatProgress(state.progress)}`);
4470
+ if (state.sprint) {
4471
+ p.log.info(`Sprint: ${state.sprint.name} (${state.sprint.completed}/${state.sprint.total} tasks)`);
4472
+ }
4473
+ if (options.verbose && state.metrics) {
4474
+ p.log.info(`Average Quality: ${state.metrics.averageQuality}/100`);
4475
+ p.log.info(`Test Coverage: ${state.metrics.testCoverage}%`);
4476
+ p.log.info(`Security Issues: ${state.metrics.securityIssues}`);
4477
+ }
4478
+ if (options.verbose && state.checkpoints.length > 0) {
4479
+ p.log.info(`Checkpoints: ${state.checkpoints.length} available (checkpoint: ${state.checkpoints[0]})`);
4480
+ }
4481
+ return {
4482
+ project: state.name,
4483
+ phase: state.currentPhase,
4484
+ progress: state.progress,
4485
+ sprint: state.sprint,
4486
+ metrics: state.metrics,
4487
+ checkpoints: state.checkpoints
4488
+ };
4489
+ }
4490
+ function formatPhaseStatus(phase, status) {
4491
+ const icons = {
4492
+ pending: "\u25CB",
4493
+ in_progress: "\u2192",
4494
+ completed: "\u2713",
4495
+ failed: "\u2717"
4496
+ };
4497
+ const colors = {
4498
+ pending: chalk5.gray,
4499
+ in_progress: chalk5.yellow,
4500
+ completed: chalk5.green,
4501
+ failed: chalk5.red
4502
+ };
4503
+ const icon = icons[status];
4504
+ const color = colors[status];
4505
+ const phaseName = phase.charAt(0).toUpperCase() + phase.slice(1);
4506
+ return color(`${icon} ${phaseName}`);
4507
+ }
4508
+ function formatProgress(progress) {
4509
+ const percentage = Math.round(progress * 100);
4510
+ const barLength = 20;
4511
+ const filled = Math.round(barLength * progress);
4512
+ const empty = barLength - filled;
4513
+ const bar = chalk5.green("\u2588".repeat(filled)) + chalk5.dim("\u2591".repeat(empty));
4514
+ return `[${bar}] ${percentage}%`;
4515
+ }
4516
+ function getPhaseStatusForPhase(phase) {
4517
+ if (phase === "idle") return "pending";
4518
+ return "in_progress";
4519
+ }
4520
+ async function loadProjectState(cwd, config) {
4521
+ const fs5 = await import('fs/promises');
4522
+ const path5 = await import('path');
4523
+ const statePath = path5.join(cwd, ".coco", "state.json");
4524
+ const backlogPath = path5.join(cwd, ".coco", "planning", "backlog.json");
4525
+ const checkpointDir = path5.join(cwd, ".coco", "checkpoints");
4526
+ let currentPhase = "idle";
4527
+ let metrics;
4528
+ let sprint;
4529
+ let checkpoints = [];
4530
+ try {
4531
+ const stateContent = await fs5.readFile(statePath, "utf-8");
4532
+ const stateData = JSON.parse(stateContent);
4533
+ currentPhase = stateData.currentPhase || "idle";
4534
+ metrics = stateData.metrics;
4535
+ } catch {
4536
+ }
4537
+ try {
4538
+ const backlogContent = await fs5.readFile(backlogPath, "utf-8");
4539
+ const backlogData = JSON.parse(backlogContent);
4540
+ if (backlogData.currentSprint) {
4541
+ const tasks = backlogData.tasks || [];
4542
+ const completedTasks2 = tasks.filter((t) => t.status === "completed");
4543
+ sprint = {
4544
+ id: backlogData.currentSprint.id,
4545
+ name: backlogData.currentSprint.name,
4546
+ completed: completedTasks2.length,
4547
+ total: tasks.length,
4548
+ avgQuality: 0,
4549
+ tasks: tasks.map((t) => ({
4550
+ id: t.id,
4551
+ title: t.title || t.id,
4552
+ status: t.status,
4553
+ score: t.score
4554
+ }))
4555
+ };
4556
+ }
4557
+ } catch {
4558
+ }
4559
+ try {
4560
+ const files = await fs5.readdir(checkpointDir);
4561
+ checkpoints = files.filter((f) => f.endsWith(".json")).sort().reverse();
4562
+ } catch {
4563
+ }
4564
+ const totalTasks = sprint?.total || 0;
4565
+ const completedTasks = sprint?.completed || 0;
4566
+ const progress = totalTasks > 0 ? completedTasks / totalTasks : 0;
4567
+ return {
4568
+ name: config.project?.name || "my-project",
4569
+ currentPhase,
4570
+ progress,
4571
+ sprint,
4572
+ metrics,
4573
+ checkpoints,
4574
+ lastCheckpoint: checkpoints.length > 0 ? {
4575
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
4576
+ canResume: true
4577
+ } : void 0
4578
+ };
4579
+ }
4580
+ function registerResumeCommand(program2) {
4581
+ program2.command("resume").description("Resume from the last checkpoint after an interruption").option("-c, --checkpoint <id>", "Resume from a specific checkpoint").option("--list", "List available checkpoints").option("--force", "Force resume even if state is inconsistent").action(async (options) => {
4582
+ await runResume(options);
4583
+ });
4584
+ }
4585
+ async function runResume(options) {
4586
+ p.intro(chalk5.cyan("Corbat-Coco Resume"));
4587
+ const hasProject = await checkProjectExists();
4588
+ if (!hasProject) {
4589
+ p.log.error("No Corbat-Coco project found.");
4590
+ process.exit(1);
4591
+ }
4592
+ if (options.list) {
4593
+ await listCheckpoints();
4594
+ return;
4595
+ }
4596
+ const checkpoint = options.checkpoint ? await loadCheckpoint(options.checkpoint) : await findLatestCheckpoint();
4597
+ if (!checkpoint) {
4598
+ p.log.error("No checkpoint found to resume from.");
4599
+ process.exit(1);
4600
+ }
4601
+ console.log(chalk5.bold("\nCheckpoint Information:"));
4602
+ console.log(chalk5.dim(" ID: ") + checkpoint.id);
4603
+ console.log(chalk5.dim(" Created: ") + checkpoint.timestamp);
4604
+ console.log(chalk5.dim(" Phase: ") + checkpoint.phase);
4605
+ console.log(chalk5.dim(" Task: ") + (checkpoint.currentTask || "None"));
4606
+ const validation = await validateCheckpoint();
4607
+ if (!validation.valid && !options.force) {
4608
+ p.log.error("Checkpoint validation failed:");
4609
+ for (const issue of validation.issues) {
4610
+ console.log(chalk5.red(" - " + issue));
4611
+ }
4612
+ console.log(chalk5.dim("\nUse --force to resume anyway (may cause issues)."));
4613
+ process.exit(1);
4614
+ }
4615
+ const shouldResume = await p.confirm({
4616
+ message: `Resume from checkpoint ${checkpoint.id}?`
4617
+ });
4618
+ if (p.isCancel(shouldResume) || !shouldResume) {
4619
+ p.cancel("Resume cancelled.");
4620
+ process.exit(0);
4621
+ }
4622
+ const spinner4 = p.spinner();
4623
+ spinner4.start("Restoring state from checkpoint...");
4624
+ try {
4625
+ await restoreFromCheckpoint(checkpoint);
4626
+ spinner4.stop("State restored successfully.");
4627
+ } catch (error) {
4628
+ spinner4.stop("Failed to restore state.");
4629
+ throw error;
4630
+ }
4631
+ p.log.success(`Resuming from phase: ${checkpoint.phase}`);
4632
+ p.outro(chalk5.green("Ready to continue. Run 'coco build' to proceed."));
4633
+ }
4634
+ async function listCheckpoints() {
4635
+ const checkpoints = [
4636
+ {
4637
+ id: "cp-2024-01-15-001",
4638
+ timestamp: "2024-01-15T10:30:00Z",
4639
+ phase: "complete",
4640
+ currentTask: "task-003",
4641
+ completedTasks: ["task-001", "task-002"],
4642
+ canResume: true
4643
+ },
4644
+ {
4645
+ id: "cp-2024-01-15-002",
4646
+ timestamp: "2024-01-15T11:00:00Z",
4647
+ phase: "complete",
4648
+ currentTask: "task-003",
4649
+ completedTasks: ["task-001", "task-002"],
4650
+ canResume: true
4651
+ }
4652
+ ];
4653
+ console.log(chalk5.bold("\nAvailable Checkpoints:"));
4654
+ console.log("");
4655
+ for (const cp of checkpoints) {
4656
+ const status = cp.canResume ? chalk5.green("") : chalk5.red("");
4657
+ console.log(` ${status} ${chalk5.cyan(cp.id)}`);
4658
+ console.log(chalk5.dim(` Created: ${cp.timestamp}`));
4659
+ console.log(chalk5.dim(` Phase: ${cp.phase}`));
4660
+ console.log(chalk5.dim(` Task: ${cp.currentTask || "None"}`));
4661
+ console.log("");
4662
+ }
4663
+ }
4664
+ async function loadCheckpoint(id) {
4665
+ return {
4666
+ id,
4667
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
4668
+ phase: "complete",
4669
+ currentTask: "task-003",
4670
+ completedTasks: ["task-001", "task-002"],
4671
+ canResume: true
4672
+ };
4673
+ }
4674
+ async function findLatestCheckpoint() {
4675
+ return {
4676
+ id: "cp-2024-01-15-002",
4677
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
4678
+ phase: "complete",
4679
+ currentTask: "task-003",
4680
+ completedTasks: ["task-001", "task-002"],
4681
+ canResume: true
4682
+ };
4683
+ }
4684
+ async function validateCheckpoint(_checkpoint) {
4685
+ const issues = [];
4686
+ return {
4687
+ valid: issues.length === 0,
4688
+ issues
4689
+ };
4690
+ }
4691
+ async function restoreFromCheckpoint(_checkpoint) {
4692
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
4693
+ }
4694
+ async function checkProjectExists() {
4695
+ try {
4696
+ const fs5 = await import('fs/promises');
4697
+ await fs5.access(".coco");
4698
+ return true;
4699
+ } catch {
4700
+ return false;
4701
+ }
4702
+ }
4703
+ function registerConfigCommand(program2) {
4704
+ const configCmd = program2.command("config").description("Manage Corbat-Coco configuration");
4705
+ configCmd.command("get <key>").description("Get a configuration value").action(async (key) => {
4706
+ await runConfigGet(key);
4707
+ });
4708
+ configCmd.command("set <key> <value>").description("Set a configuration value").action(async (key, value) => {
4709
+ await runConfigSet(key, value);
4710
+ });
4711
+ configCmd.command("list").description("List all configuration values").option("--json", "Output as JSON").action(async (options) => {
4712
+ await runConfigList(options);
4713
+ });
4714
+ configCmd.command("init").description("Initialize configuration interactively").action(async () => {
4715
+ await runConfigInit();
4716
+ });
4717
+ }
4718
+ async function runConfigGet(key) {
4719
+ const config = await loadConfig2();
4720
+ const value = getNestedValue(config, key);
4721
+ if (value === void 0) {
4722
+ p.log.error(`Configuration key '${key}' not found.`);
4723
+ process.exit(1);
4724
+ }
4725
+ console.log(typeof value === "object" ? JSON.stringify(value, null, 2) : String(value));
4726
+ }
4727
+ async function runConfigSet(key, value) {
4728
+ const config = await loadConfig2();
4729
+ let parsedValue;
4730
+ try {
4731
+ parsedValue = JSON.parse(value);
4732
+ } catch {
4733
+ parsedValue = value;
4734
+ }
4735
+ setNestedValue(config, key, parsedValue);
4736
+ await saveConfig(config);
4737
+ p.log.success(`Set ${key} = ${value}`);
4738
+ }
4739
+ async function runConfigList(options) {
4740
+ const config = await loadConfig2();
4741
+ if (options.json) {
4742
+ console.log(JSON.stringify(config, null, 2));
4743
+ return;
4744
+ }
4745
+ console.log(chalk5.bold("\nCorbat-Coco Configuration:\n"));
4746
+ printConfig(config, "");
4747
+ }
4748
+ async function runConfigInit() {
4749
+ p.intro(chalk5.cyan("Corbat-Coco Configuration Setup"));
4750
+ const apiKey = await p.text({
4751
+ message: "Enter your Anthropic API key:",
4752
+ placeholder: "sk-ant-...",
4753
+ validate: (value) => {
4754
+ if (!value) return "API key is required";
4755
+ if (!value.startsWith("sk-ant-")) return "Invalid API key format";
4756
+ return void 0;
4757
+ }
4758
+ });
4759
+ if (p.isCancel(apiKey)) {
4760
+ p.cancel("Configuration cancelled.");
4761
+ process.exit(0);
4762
+ }
4763
+ const model = await p.select({
4764
+ message: "Select the default model:",
4765
+ options: [
4766
+ { value: "claude-sonnet-4-20250514", label: "Claude Sonnet 4", hint: "Recommended for coding" },
4767
+ { value: "claude-opus-4-20250514", label: "Claude Opus 4", hint: "Most capable" },
4768
+ { value: "claude-3-5-sonnet-20241022", label: "Claude 3.5 Sonnet", hint: "Fast and capable" }
4769
+ ]
4770
+ });
4771
+ if (p.isCancel(model)) {
4772
+ p.cancel("Configuration cancelled.");
4773
+ process.exit(0);
4774
+ }
4775
+ const quality = await p.text({
4776
+ message: "Minimum quality score (0-100):",
4777
+ placeholder: "85",
4778
+ initialValue: "85",
4779
+ validate: (value) => {
4780
+ const num = parseInt(value, 10);
4781
+ if (isNaN(num) || num < 0 || num > 100) return "Must be a number between 0 and 100";
4782
+ return void 0;
4783
+ }
4784
+ });
4785
+ if (p.isCancel(quality)) {
4786
+ p.cancel("Configuration cancelled.");
4787
+ process.exit(0);
4788
+ }
4789
+ const config = {
4790
+ provider: {
4791
+ type: "anthropic",
4792
+ apiKey,
4793
+ model
4794
+ },
4795
+ quality: {
4796
+ minScore: parseInt(quality, 10),
4797
+ minCoverage: 80,
4798
+ maxIterations: 10
4799
+ }
4800
+ };
4801
+ await saveConfig(config);
4802
+ p.outro(chalk5.green("Configuration saved to .coco/config.json"));
4803
+ }
4804
+ async function loadConfig2() {
4805
+ return {
4806
+ provider: {
4807
+ type: "anthropic",
4808
+ model: "claude-sonnet-4-20250514"
4809
+ },
4810
+ quality: {
4811
+ minScore: 85,
4812
+ minCoverage: 80,
4813
+ maxIterations: 10
4814
+ },
4815
+ persistence: {
4816
+ checkpointInterval: 3e5,
4817
+ maxCheckpoints: 50
4818
+ }
4819
+ };
4820
+ }
4821
+ async function saveConfig(config) {
4822
+ const fs5 = await import('fs/promises');
4823
+ await fs5.mkdir(".coco", { recursive: true });
4824
+ await fs5.writeFile(".coco/config.json", JSON.stringify(config, null, 2));
4825
+ }
4826
+ function getNestedValue(obj, path5) {
4827
+ const keys = path5.split(".");
4828
+ let current = obj;
4829
+ for (const key of keys) {
4830
+ if (current === null || current === void 0 || typeof current !== "object") {
4831
+ return void 0;
4832
+ }
4833
+ current = current[key];
4834
+ }
4835
+ return current;
4836
+ }
4837
+ function setNestedValue(obj, path5, value) {
4838
+ const keys = path5.split(".");
4839
+ let current = obj;
4840
+ for (let i = 0; i < keys.length - 1; i++) {
4841
+ const key = keys[i];
4842
+ if (!key) continue;
4843
+ if (!(key in current) || typeof current[key] !== "object") {
4844
+ current[key] = {};
4845
+ }
4846
+ current = current[key];
4847
+ }
4848
+ const lastKey = keys[keys.length - 1];
4849
+ if (lastKey) {
4850
+ current[lastKey] = value;
4851
+ }
4852
+ }
4853
+ function printConfig(obj, prefix) {
4854
+ if (obj === null || obj === void 0) {
4855
+ console.log(chalk5.dim(prefix) + chalk5.yellow("null"));
4856
+ return;
4857
+ }
4858
+ if (typeof obj !== "object") {
4859
+ console.log(chalk5.dim(prefix) + String(obj));
4860
+ return;
4861
+ }
4862
+ for (const [key, value] of Object.entries(obj)) {
4863
+ const fullKey = prefix ? `${prefix}.${key}` : key;
4864
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) {
4865
+ printConfig(value, fullKey);
4866
+ } else {
4867
+ const displayValue = typeof value === "string" && value.startsWith("sk-") ? chalk5.dim("[hidden]") : chalk5.cyan(JSON.stringify(value));
4868
+ console.log(` ${chalk5.dim(fullKey + ":")} ${displayValue}`);
4869
+ }
4870
+ }
4871
+ }
4872
+
4873
+ // src/cli/index.ts
4874
+ var program = new Command();
4875
+ program.name("coco").description("Corbat-Coco: Autonomous Coding Agent with Self-Review and Quality Convergence").version(VERSION, "-v, --version", "Output the current version");
4876
+ registerInitCommand(program);
4877
+ registerPlanCommand(program);
4878
+ registerBuildCommand(program);
4879
+ registerStatusCommand(program);
4880
+ registerResumeCommand(program);
4881
+ registerConfigCommand(program);
4882
+ program.parseAsync(process.argv).catch((error) => {
4883
+ console.error("Fatal error:", error);
4884
+ process.exit(1);
4885
+ });
4886
+ //# sourceMappingURL=index.js.map
4887
+ //# sourceMappingURL=index.js.map