ouroboros-ai 0.2.3__py3-none-any.whl → 0.3.0__py3-none-any.whl

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.

Potentially problematic release.


This version of ouroboros-ai might be problematic. Click here for more details.

@@ -1,7 +1,7 @@
1
1
  """Interactive interview engine for requirement clarification.
2
2
 
3
3
  This module implements the interview protocol that refines vague ideas into
4
- clear requirements through iterative questioning (max 10 rounds).
4
+ clear requirements through iterative questioning. Users control when to stop.
5
5
  """
6
6
 
7
7
  from collections.abc import Iterator
@@ -52,10 +52,17 @@ def _file_lock(file_path: Path, exclusive: bool = True) -> Iterator[None]:
52
52
 
53
53
  log = structlog.get_logger()
54
54
 
55
- MAX_INTERVIEW_ROUNDS = 10
55
+ # Interview round constants
56
+ MIN_ROUNDS_BEFORE_EARLY_EXIT = 3 # Must complete at least 3 rounds
57
+ SOFT_LIMIT_WARNING_THRESHOLD = 15 # Warn about diminishing returns after this
58
+ DEFAULT_INTERVIEW_ROUNDS = 10 # Reference value for prompts (not enforced)
59
+
56
60
  # Default model moved to config.models.ClarificationConfig.default_model
57
61
  _FALLBACK_MODEL = "openrouter/google/gemini-2.0-flash-001"
58
62
 
63
+ # Legacy alias for backward compatibility
64
+ MAX_INTERVIEW_ROUNDS = DEFAULT_INTERVIEW_ROUNDS
65
+
59
66
 
60
67
  class InterviewStatus(StrEnum):
61
68
  """Status of the interview process."""
@@ -69,13 +76,13 @@ class InterviewRound(BaseModel):
69
76
  """A single round of interview questions and responses.
70
77
 
71
78
  Attributes:
72
- round_number: 1-based round number (1 to MAX_INTERVIEW_ROUNDS).
79
+ round_number: 1-based round number (no upper limit - user controls).
73
80
  question: The question asked by the system.
74
81
  user_response: The user's response (None if not yet answered).
75
82
  timestamp: When this round was created.
76
83
  """
77
84
 
78
- round_number: int = Field(ge=1, le=MAX_INTERVIEW_ROUNDS)
85
+ round_number: int = Field(ge=1) # No upper limit - user decides when to stop
79
86
  question: str
80
87
  user_response: str | None = None
81
88
  timestamp: datetime = Field(default_factory=lambda: datetime.now(UTC))
@@ -107,11 +114,8 @@ class InterviewState(BaseModel):
107
114
 
108
115
  @property
109
116
  def is_complete(self) -> bool:
110
- """Check if interview has reached max rounds or is marked complete."""
111
- return (
112
- self.status == InterviewStatus.COMPLETED
113
- or len(self.rounds) >= MAX_INTERVIEW_ROUNDS
114
- )
117
+ """Check if interview is marked complete (user-controlled)."""
118
+ return self.status == InterviewStatus.COMPLETED
115
119
 
116
120
  def mark_updated(self) -> None:
117
121
  """Update the updated_at timestamp."""
@@ -321,14 +325,8 @@ class InterviewEngine:
321
325
  response_length=len(user_response),
322
326
  )
323
327
 
324
- # Check if we've reached max rounds
325
- if len(state.rounds) >= MAX_INTERVIEW_ROUNDS:
326
- state.status = InterviewStatus.COMPLETED
327
- log.info(
328
- "interview.max_rounds_reached",
329
- interview_id=state.interview_id,
330
- total_rounds=len(state.rounds),
331
- )
328
+ # Note: No auto-complete on round limit. User controls when to stop.
329
+ # CLI handles prompting user to continue after each round.
332
330
 
333
331
  return Result.ok(state)
334
332
 
@@ -437,7 +435,7 @@ class InterviewEngine:
437
435
  Returns:
438
436
  The system prompt.
439
437
  """
440
- round_info = f"Round {state.current_round_number} of {MAX_INTERVIEW_ROUNDS}"
438
+ round_info = f"Round {state.current_round_number}"
441
439
 
442
440
  return f"""You are an expert requirements engineer conducting an interview to refine vague ideas into clear, executable requirements.
443
441
 
@@ -5,6 +5,7 @@ Supports both LiteLLM (external API) and Claude Code (Max Plan) modes.
5
5
  """
6
6
 
7
7
  import asyncio
8
+ from enum import Enum, auto
8
9
  from pathlib import Path
9
10
  from typing import Annotated
10
11
 
@@ -12,13 +13,28 @@ from rich.prompt import Confirm, Prompt
12
13
  import typer
13
14
 
14
15
  from ouroboros.bigbang.ambiguity import AmbiguityScorer
15
- from ouroboros.bigbang.interview import MAX_INTERVIEW_ROUNDS, InterviewEngine, InterviewState
16
+ from ouroboros.bigbang.interview import (
17
+ MIN_ROUNDS_BEFORE_EARLY_EXIT,
18
+ SOFT_LIMIT_WARNING_THRESHOLD,
19
+ InterviewEngine,
20
+ InterviewState,
21
+ InterviewStatus,
22
+ )
16
23
  from ouroboros.bigbang.seed_generator import SeedGenerator
17
24
  from ouroboros.cli.formatters import console
18
25
  from ouroboros.cli.formatters.panels import print_error, print_info, print_success, print_warning
19
26
  from ouroboros.providers.base import LLMAdapter
20
27
  from ouroboros.providers.litellm_adapter import LiteLLMAdapter
21
28
 
29
+
30
+ class SeedGenerationResult(Enum):
31
+ """Result of seed generation attempt."""
32
+
33
+ SUCCESS = auto()
34
+ CANCELLED = auto()
35
+ CONTINUE_INTERVIEW = auto()
36
+
37
+
22
38
  app = typer.Typer(
23
39
  name="init",
24
40
  help="Start interactive interview to refine requirements.",
@@ -43,62 +59,30 @@ def _get_adapter(use_orchestrator: bool) -> LLMAdapter:
43
59
  return LiteLLMAdapter()
44
60
 
45
61
 
46
- async def _run_interview(
47
- initial_context: str,
48
- resume_id: str | None = None,
49
- state_dir: Path | None = None,
50
- use_orchestrator: bool = False,
51
- ) -> None:
52
- """Run the interview process.
62
+ async def _run_interview_loop(
63
+ engine: InterviewEngine,
64
+ state: InterviewState,
65
+ ) -> InterviewState:
66
+ """Run the interview question loop until completion or user exit.
53
67
 
54
- Args:
55
- initial_context: Initial context or idea for the interview.
56
- resume_id: Optional interview ID to resume.
57
- state_dir: Optional custom state directory.
58
- use_orchestrator: If True, use Claude Code (Max Plan) instead of LiteLLM.
59
- """
60
- # Initialize components
61
- llm_adapter = _get_adapter(use_orchestrator)
62
- engine = InterviewEngine(
63
- llm_adapter=llm_adapter,
64
- state_dir=state_dir or Path.home() / ".ouroboros" / "data",
65
- )
68
+ Implements tiered confirmation:
69
+ - Rounds 1-3: Auto-continue (minimum context)
70
+ - Rounds 4-15: Ask "Continue?" after each round
71
+ - Rounds 16+: Ask "Continue?" with diminishing returns warning
66
72
 
67
- # Load or start interview
68
- if resume_id:
69
- print_info(f"Resuming interview: {resume_id}")
70
- state_result = await engine.load_state(resume_id)
71
- if state_result.is_err:
72
- print_error(f"Failed to load interview: {state_result.error.message}")
73
- raise typer.Exit(code=1)
74
- state = state_result.value
75
- else:
76
- print_info("Starting new interview session...")
77
- state_result = await engine.start_interview(initial_context)
78
- if state_result.is_err:
79
- print_error(f"Failed to start interview: {state_result.error.message}")
80
- raise typer.Exit(code=1)
81
- state = state_result.value
82
-
83
- console.print()
84
- console.print(
85
- f"[bold cyan]Interview Session: {state.interview_id}[/]",
86
- )
87
- console.print(f"[muted]Max rounds: {MAX_INTERVIEW_ROUNDS}[/]")
88
- console.print()
73
+ Args:
74
+ engine: Interview engine instance.
75
+ state: Current interview state.
89
76
 
90
- # Interview loop
77
+ Returns:
78
+ Updated interview state.
79
+ """
91
80
  while not state.is_complete:
92
81
  current_round = state.current_round_number
93
- console.print(
94
- f"[bold]Round {current_round}/{MAX_INTERVIEW_ROUNDS}[/]",
95
- )
82
+ console.print(f"[bold]Round {current_round}[/]")
96
83
 
97
84
  # Generate question
98
- with console.status(
99
- "[cyan]Generating question...[/]",
100
- spinner="dots",
101
- ):
85
+ with console.status("[cyan]Generating question...[/]", spinner="dots"):
102
86
  question_result = await engine.ask_next_question(state)
103
87
 
104
88
  if question_result.is_err:
@@ -130,15 +114,22 @@ async def _run_interview(
130
114
 
131
115
  state = record_result.value
132
116
 
133
- # Save state
117
+ # Save state immediately after recording
134
118
  save_result = await engine.save_state(state)
135
119
  if save_result.is_err:
136
120
  print_error(f"Warning: Failed to save state: {save_result.error.message}")
137
121
 
138
122
  console.print()
139
123
 
140
- # Check if user wants to continue or finish early
141
- if not state.is_complete and current_round >= 3:
124
+ # Tiered confirmation logic
125
+ if current_round >= MIN_ROUNDS_BEFORE_EARLY_EXIT:
126
+ # Show warning for rounds beyond soft limit
127
+ if current_round >= SOFT_LIMIT_WARNING_THRESHOLD:
128
+ print_warning(
129
+ f"Round {current_round}: Diminishing returns expected. "
130
+ "Consider generating Seed to check ambiguity score."
131
+ )
132
+
142
133
  should_continue = Confirm.ask(
143
134
  "Continue with more questions?",
144
135
  default=True,
@@ -147,40 +138,104 @@ async def _run_interview(
147
138
  complete_result = await engine.complete_interview(state)
148
139
  if complete_result.is_ok:
149
140
  state = complete_result.value
150
- await engine.save_state(state)
141
+ await engine.save_state(state)
151
142
  break
152
143
 
153
- # Interview complete
154
- console.print()
155
- print_success("Interview completed!")
156
- console.print(f"[muted]Total rounds: {len(state.rounds)}[/]")
157
- console.print(f"[muted]Interview ID: {state.interview_id}[/]")
144
+ return state
158
145
 
159
- # Save final state
160
- save_result = await engine.save_state(state)
161
- if save_result.is_ok:
162
- console.print(f"[muted]State saved to: {save_result.value}[/]")
163
146
 
164
- console.print()
147
+ async def _run_interview(
148
+ initial_context: str,
149
+ resume_id: str | None = None,
150
+ state_dir: Path | None = None,
151
+ use_orchestrator: bool = False,
152
+ ) -> None:
153
+ """Run the interview process.
165
154
 
166
- # Ask if user wants to proceed to Seed generation
167
- should_generate_seed = Confirm.ask(
168
- "[bold cyan]Proceed to generate Seed specification?[/]",
169
- default=True,
155
+ Args:
156
+ initial_context: Initial context or idea for the interview.
157
+ resume_id: Optional interview ID to resume.
158
+ state_dir: Optional custom state directory.
159
+ use_orchestrator: If True, use Claude Code (Max Plan) instead of LiteLLM.
160
+ """
161
+ # Initialize components
162
+ llm_adapter = _get_adapter(use_orchestrator)
163
+ engine = InterviewEngine(
164
+ llm_adapter=llm_adapter,
165
+ state_dir=state_dir or Path.home() / ".ouroboros" / "data",
170
166
  )
171
167
 
172
- if not should_generate_seed:
173
- console.print(
174
- "[muted]You can resume later with:[/] "
175
- f"[bold]ouroboros init start --resume {state.interview_id}[/]"
168
+ # Load or start interview
169
+ if resume_id:
170
+ print_info(f"Resuming interview: {resume_id}")
171
+ state_result = await engine.load_state(resume_id)
172
+ if state_result.is_err:
173
+ print_error(f"Failed to load interview: {state_result.error.message}")
174
+ raise typer.Exit(code=1)
175
+ state = state_result.value
176
+ else:
177
+ print_info("Starting new interview session...")
178
+ state_result = await engine.start_interview(initial_context)
179
+ if state_result.is_err:
180
+ print_error(f"Failed to start interview: {state_result.error.message}")
181
+ raise typer.Exit(code=1)
182
+ state = state_result.value
183
+
184
+ console.print()
185
+ console.print(f"[bold cyan]Interview Session: {state.interview_id}[/]")
186
+ console.print("[muted]No round limit - you decide when to stop[/]")
187
+ console.print()
188
+
189
+ # Run initial interview loop
190
+ state = await _run_interview_loop(engine, state)
191
+
192
+ # Outer loop for retry on high ambiguity
193
+ while True:
194
+ # Interview complete
195
+ console.print()
196
+ print_success("Interview completed!")
197
+ console.print(f"[muted]Total rounds: {len(state.rounds)}[/]")
198
+ console.print(f"[muted]Interview ID: {state.interview_id}[/]")
199
+
200
+ # Save final state
201
+ save_result = await engine.save_state(state)
202
+ if save_result.is_ok:
203
+ console.print(f"[muted]State saved to: {save_result.value}[/]")
204
+
205
+ console.print()
206
+
207
+ # Ask if user wants to proceed to Seed generation
208
+ should_generate_seed = Confirm.ask(
209
+ "[bold cyan]Proceed to generate Seed specification?[/]",
210
+ default=True,
176
211
  )
177
- return
178
212
 
179
- # Generate Seed
180
- seed_path = await _generate_seed_from_interview(state, llm_adapter)
213
+ if not should_generate_seed:
214
+ console.print(
215
+ "[muted]You can resume later with:[/] "
216
+ f"[bold]ouroboros init start --resume {state.interview_id}[/]"
217
+ )
218
+ return
181
219
 
182
- if seed_path is None:
183
- return
220
+ # Generate Seed
221
+ seed_path, result = await _generate_seed_from_interview(state, llm_adapter)
222
+
223
+ if result == SeedGenerationResult.CONTINUE_INTERVIEW:
224
+ # Re-open interview for more questions
225
+ console.print()
226
+ print_info("Continuing interview to reduce ambiguity...")
227
+ state.status = InterviewStatus.IN_PROGRESS
228
+ await engine.save_state(state) # Save status change immediately
229
+
230
+ # Continue interview loop (reusing the same helper)
231
+ state = await _run_interview_loop(engine, state)
232
+ continue
233
+
234
+ if result == SeedGenerationResult.CANCELLED:
235
+ return
236
+
237
+ # Success - proceed to workflow
238
+ break
184
239
 
185
240
  # Ask if user wants to start workflow
186
241
  console.print()
@@ -196,7 +251,7 @@ async def _run_interview(
196
251
  async def _generate_seed_from_interview(
197
252
  state: InterviewState,
198
253
  llm_adapter: LLMAdapter,
199
- ) -> Path | None:
254
+ ) -> tuple[Path | None, SeedGenerationResult]:
200
255
  """Generate Seed from completed interview.
201
256
 
202
257
  Args:
@@ -204,7 +259,7 @@ async def _generate_seed_from_interview(
204
259
  llm_adapter: LLM adapter for scoring and generation.
205
260
 
206
261
  Returns:
207
- Path to generated seed file, or None if failed.
262
+ Tuple of (path to generated seed file or None, result status).
208
263
  """
209
264
  console.print()
210
265
  console.print("[bold cyan]Generating Seed specification...[/]")
@@ -216,7 +271,7 @@ async def _generate_seed_from_interview(
216
271
 
217
272
  if score_result.is_err:
218
273
  print_error(f"Failed to calculate ambiguity: {score_result.error.message}")
219
- return None
274
+ return None, SeedGenerationResult.CANCELLED
220
275
 
221
276
  ambiguity_score = score_result.value
222
277
  console.print(f"[muted]Ambiguity score: {ambiguity_score.overall_score:.2f}[/]")
@@ -226,12 +281,24 @@ async def _generate_seed_from_interview(
226
281
  f"Ambiguity score ({ambiguity_score.overall_score:.2f}) is too high. "
227
282
  "Consider more interview rounds to clarify requirements."
228
283
  )
229
- should_force = Confirm.ask(
230
- "[yellow]Generate Seed anyway?[/]",
231
- default=False,
284
+ console.print()
285
+ console.print("[bold]What would you like to do?[/]")
286
+ console.print(" [cyan]1[/] - Continue interview with more questions")
287
+ console.print(" [cyan]2[/] - Generate Seed anyway (force)")
288
+ console.print(" [cyan]3[/] - Cancel")
289
+ console.print()
290
+
291
+ choice = Prompt.ask(
292
+ "[yellow]Select option[/]",
293
+ choices=["1", "2", "3"],
294
+ default="1",
232
295
  )
233
- if not should_force:
234
- return None
296
+
297
+ if choice == "1":
298
+ return None, SeedGenerationResult.CONTINUE_INTERVIEW
299
+ elif choice == "3":
300
+ return None, SeedGenerationResult.CANCELLED
301
+ # choice == "2" falls through to generate anyway
235
302
 
236
303
  # Step 2: Generate Seed
237
304
  with console.status("[cyan]Generating Seed from interview...[/]", spinner="dots"):
@@ -240,18 +307,20 @@ async def _generate_seed_from_interview(
240
307
  if ambiguity_score.is_ready_for_seed:
241
308
  seed_result = await generator.generate(state, ambiguity_score)
242
309
  else:
243
- # Create a modified score that passes threshold for forced generation
310
+ # TODO: Add force=True parameter to SeedGenerator.generate() instead of this hack
311
+ # Creating a modified score to bypass threshold check
244
312
  from ouroboros.bigbang.ambiguity import AmbiguityScore as AmbScore
245
313
 
314
+ FORCED_SCORE_VALUE = 0.19 # Just under threshold (0.2)
246
315
  forced_score = AmbScore(
247
- overall_score=0.19, # Just under threshold
316
+ overall_score=FORCED_SCORE_VALUE,
248
317
  breakdown=ambiguity_score.breakdown,
249
318
  )
250
319
  seed_result = await generator.generate(state, forced_score)
251
320
 
252
321
  if seed_result.is_err:
253
322
  print_error(f"Failed to generate Seed: {seed_result.error.message}")
254
- return None
323
+ return None, SeedGenerationResult.CANCELLED
255
324
 
256
325
  seed = seed_result.value
257
326
 
@@ -261,10 +330,10 @@ async def _generate_seed_from_interview(
261
330
 
262
331
  if save_result.is_err:
263
332
  print_error(f"Failed to save Seed: {save_result.error.message}")
264
- return None
333
+ return None, SeedGenerationResult.CANCELLED
265
334
 
266
335
  print_success(f"Seed generated: {seed_path}")
267
- return seed_path
336
+ return seed_path, SeedGenerationResult.SUCCESS
268
337
 
269
338
 
270
339
  async def _start_workflow(seed_path: Path, use_orchestrator: bool = False) -> None:
@@ -344,21 +413,17 @@ def start(
344
413
  """
345
414
  # Get initial context if not provided
346
415
  if not resume and not context:
347
- console.print(
348
- "[bold cyan]Welcome to Ouroboros Interview![/]",
349
- )
416
+ console.print("[bold cyan]Welcome to Ouroboros Interview![/]")
350
417
  console.print()
351
418
  console.print(
352
419
  "This interactive process will help refine your ideas into clear requirements.",
353
420
  )
354
421
  console.print(
355
- f"You'll be asked up to {MAX_INTERVIEW_ROUNDS} questions to reduce ambiguity.",
422
+ "You control when to stop - no arbitrary round limit.",
356
423
  )
357
424
  console.print()
358
425
 
359
- context = Prompt.ask(
360
- "[bold]What would you like to build?[/]",
361
- )
426
+ context = Prompt.ask("[bold]What would you like to build?[/]")
362
427
 
363
428
  if not resume and not context:
364
429
  print_error("Initial context is required when not resuming.")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ouroboros-ai
3
- Version: 0.2.3
3
+ Version: 0.3.0
4
4
  Summary: Self-Improving AI Workflow System
5
5
  Author-email: Q00 <jqyu.lee@gmail.com>
6
6
  License-File: LICENSE
@@ -41,18 +41,26 @@ Description-Content-Type: text/markdown
41
41
  <em>The serpent that devours itself to be reborn anew.</em>
42
42
  </p>
43
43
 
44
+ <p align="center">
45
+ <a href="https://pypi.org/project/ouroboros-ai/"><img src="https://img.shields.io/pypi/v/ouroboros-ai?color=blue" alt="PyPI"></a>
46
+ <a href="https://python.org"><img src="https://img.shields.io/badge/python-3.14+-blue" alt="Python"></a>
47
+ <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green" alt="License"></a>
48
+ </p>
49
+
44
50
  <p align="center">
45
51
  <a href="#-philosophy">Philosophy</a> •
46
52
  <a href="#-the-six-phases">Phases</a> •
47
53
  <a href="#-architecture">Architecture</a> •
54
+ <a href="#-prerequisites">Prerequisites</a> •
48
55
  <a href="#-quick-start">Start</a> •
49
- <a href="#-the-personas-lateral-thinking-agents">Personas</a>
56
+ <a href="#-roadmap">Roadmap</a>
57
+ <a href="#-contributing">Contributing</a>
50
58
  </p>
51
59
 
52
60
  <br/>
53
61
 
54
62
  <p align="center">
55
- <code>74 modules</code> · <code>1,341 tests</code> · <code>97%+ coverage</code>
63
+ <code>75 modules</code> · <code>1,341 tests</code> · <code>97%+ coverage</code> · <code>v0.2.x</code>
56
64
  </p>
57
65
 
58
66
  <br/>
@@ -352,7 +360,7 @@ src/ouroboros/
352
360
 
353
361
  <br/>
354
362
 
355
- **작동 방식**: Stagnation Detection (4 patterns) → Persona Rotation → 새로운 관점으로 재시도
363
+ **How it works**: Stagnation Detection (4 patterns) → Persona Rotation → Retry with fresh perspective
356
364
 
357
365
  <br/>
358
366
 
@@ -415,6 +423,22 @@ not to restart, but to **re-crystallize** with new understanding.
415
423
 
416
424
  <br/>
417
425
 
426
+ ## ◈ Prerequisites
427
+
428
+ <br/>
429
+
430
+ | Requirement | Description |
431
+ |-------------|-------------|
432
+ | **Python 3.14+** | Required (uses latest language features) |
433
+ | **Claude Code Max Plan** | For orchestrator mode (no API key needed) |
434
+ | **OR API Key** | OpenRouter, Anthropic, or OpenAI for LiteLLM mode |
435
+
436
+ <br/>
437
+
438
+ ---
439
+
440
+ <br/>
441
+
418
442
  ## ◈ Installation
419
443
 
420
444
  <br/>
@@ -476,7 +500,7 @@ uv run ouroboros init start "I want to build a task management CLI"
476
500
  uv run ouroboros status health
477
501
  ```
478
502
 
479
- > 📖 **[Full Guide: Running with Claude Code](docs/running-with-claude-code.md)**
503
+ > 📖 **[Full Guide: Running with Claude Code](docs/running-with-claude-code.md)** | **[CLI Reference](docs/cli-reference.md)**
480
504
 
481
505
  <br/>
482
506
 
@@ -563,22 +587,49 @@ uv run ruff format src/
563
587
 
564
588
  <br/>
565
589
 
590
+ ## ◈ Contributing
591
+
592
+ <br/>
593
+
594
+ Contributions are welcome! Please see:
595
+
596
+ - **Issues**: [GitHub Issues](https://github.com/Q00/ouroboros/issues) for bugs and feature requests
597
+ - **Discussions**: [GitHub Discussions](https://github.com/Q00/ouroboros/discussions) for questions and ideas
598
+
599
+ <br/>
600
+
601
+ ---
602
+
603
+ <br/>
604
+
566
605
  ## ◈ Roadmap
567
606
 
568
607
  <br/>
569
608
 
609
+ ### Completed
610
+
570
611
  ```
571
- [■■■■■■■■■■] Epic 0 Foundation
572
- [■■■■■■■■■■] Epic 1 Big Bang
573
- [■■■■■■■■■■] Epic 2 PAL Router
574
- [■■■■■■■■■■] Epic 3 Double Diamond
575
- [■■■■■■■■■■] Epic 4 Resilience
576
- [■■■■■■■■■■] Epic 5 Evaluation
577
- [■■■■■■■■■■] Epic 6 Drift Control
578
- [■■■■■■■■■■] Epic 7 Secondary Loop
579
- [■■■■■■■■■■] Epic 8 Orchestrator Complete
612
+ [■■■■■■■■■■] Epic 0 Foundation
613
+ [■■■■■■■■■■] Epic 1 Big Bang
614
+ [■■■■■■■■■■] Epic 2 PAL Router
615
+ [■■■■■■■■■■] Epic 3 Double Diamond
616
+ [■■■■■■■■■■] Epic 4 Resilience
617
+ [■■■■■■■■■■] Epic 5 Evaluation
618
+ [■■■■■■■■■■] Epic 6 Drift Control
619
+ [■■■■■■■■■■] Epic 7 Secondary Loop
620
+ [■■■■■■■■■■] Epic 8 Orchestrator
580
621
  ```
581
622
 
623
+ ### Upcoming
624
+
625
+ | Feature | Description | Status |
626
+ |---------|-------------|--------|
627
+ | **Worker MCP** | MCP server for distributed task execution | Planned |
628
+ | **TUI Enhancement** | Rich terminal UI with real-time progress | Planned |
629
+ | **AC Tree Visualization** | Interactive acceptance criteria graph | Planned |
630
+ | **Plugin System** | Custom evaluators and personas | Planned |
631
+ | **Web Dashboard** | Execution monitoring and analytics | Planned |
632
+
582
633
  <br/>
583
634
 
584
635
  ---
@@ -3,13 +3,13 @@ ouroboros/__main__.py,sha256=f_qnL0zPJwh9kfQqynX5adpqzj8ilj94zW5Q2loqGxE,168
3
3
  ouroboros/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  ouroboros/bigbang/__init__.py,sha256=9xGqOYwMKBifb7QVwonc_wndNLMZb7ZH7xgMHaz_70A,951
5
5
  ouroboros/bigbang/ambiguity.py,sha256=5KM8xjATknjLZguVa90Yii6o3pzXE4PU4BJIP6Ii938,17955
6
- ouroboros/bigbang/interview.py,sha256=zm1VrDNqE8ouGG62h8qnNkIpnUf3HHv4NjzMKDIaWcY,17147
6
+ ouroboros/bigbang/interview.py,sha256=ku1MVppSmIS9OZeiqC208el4ZqoYYQwT78ycLenwID4,17200
7
7
  ouroboros/bigbang/seed_generator.py,sha256=7MY9a7Eua_zVGDWIVDlzOZJjeAwz0DRatXJg0PvMgiY,20082
8
8
  ouroboros/cli/__init__.py,sha256=CRpxsqJadZL7bCS-yrULWC51tqPKfPsxQLgt0JiwP4g,225
9
9
  ouroboros/cli/main.py,sha256=ldvqtVpw2xZwE8G7M34qY_7qg0RuNiydjdmmU-hdJvM,1485
10
10
  ouroboros/cli/commands/__init__.py,sha256=LZpEvU80R4Cq0LwgkwOluEGNsmmJ9K7roeDQ6bsbbDc,193
11
11
  ouroboros/cli/commands/config.py,sha256=kcqi0Wo09oo1MMyZIX4k2IDICV1SAX6HzAXZaIJGdKY,2100
12
- ouroboros/cli/commands/init.py,sha256=HmXwTLyso6p8Df5aAguxh-XTIYZGkzGltGXqJvDxI78,13536
12
+ ouroboros/cli/commands/init.py,sha256=y2hlHnqFkc9-44_mJUs3GmQvX4Tg6yTIyb0e0KCPl30,16130
13
13
  ouroboros/cli/commands/run.py,sha256=DnxfbSdATDIaNYJXLcwAcR9NqNVGkVlHgYJImaSVn4I,6328
14
14
  ouroboros/cli/commands/status.py,sha256=Bnqpj1UkqhpBPYA11DV-Z63Bz8pjrebhlzeMKwz3_Ps,2217
15
15
  ouroboros/cli/formatters/__init__.py,sha256=-Ik7KXajaIExBxSAp5iYp8gO9SfXudGjyDe2nm2_msw,691
@@ -75,8 +75,8 @@ ouroboros/routing/tiers.py,sha256=QhBQUOo2-h5Z3dEtC0lcOzkRnqTi2W7Jl46750AVNig,73
75
75
  ouroboros/secondary/__init__.py,sha256=kYQ7C4bnBzwDlPrU8qZrOPr2ZuTBaftGktOXl5WZl5Q,1123
76
76
  ouroboros/secondary/scheduler.py,sha256=sPVVWJ1q0yewRAM-Rm1j_HMerSe4cavIvP9z4xlUuL4,13737
77
77
  ouroboros/secondary/todo_registry.py,sha256=4W3C9Uro29VrVLCPKUlpH_BYpzQSbRNW1oMnDYyEhEw,13880
78
- ouroboros_ai-0.2.3.dist-info/METADATA,sha256=pAjfUYPmqTUzuLJoNQcoJx88R8yZwj_ALVniBc6jLGg,19661
79
- ouroboros_ai-0.2.3.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
80
- ouroboros_ai-0.2.3.dist-info/entry_points.txt,sha256=MoETHup6rVkR6AsyjoRzAgIuvVtYYm3Jw40itV3_VyI,53
81
- ouroboros_ai-0.2.3.dist-info/licenses/LICENSE,sha256=n2X-q26TqpXnoBo0t_WouhFxWw663_q5FmbYDZayoHo,1060
82
- ouroboros_ai-0.2.3.dist-info/RECORD,,
78
+ ouroboros_ai-0.3.0.dist-info/METADATA,sha256=J5my7Xvr9PRNCn081N7t6ER3cvRP7AFwWilWjcf5fUo,21321
79
+ ouroboros_ai-0.3.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
80
+ ouroboros_ai-0.3.0.dist-info/entry_points.txt,sha256=MoETHup6rVkR6AsyjoRzAgIuvVtYYm3Jw40itV3_VyI,53
81
+ ouroboros_ai-0.3.0.dist-info/licenses/LICENSE,sha256=n2X-q26TqpXnoBo0t_WouhFxWw663_q5FmbYDZayoHo,1060
82
+ ouroboros_ai-0.3.0.dist-info/RECORD,,