up-cli 0.1.1__py3-none-any.whl → 0.5.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.
Files changed (55) hide show
  1. up/__init__.py +1 -1
  2. up/ai_cli.py +229 -0
  3. up/cli.py +75 -4
  4. up/commands/agent.py +521 -0
  5. up/commands/bisect.py +343 -0
  6. up/commands/branch.py +350 -0
  7. up/commands/dashboard.py +248 -0
  8. up/commands/init.py +195 -6
  9. up/commands/learn.py +1741 -0
  10. up/commands/memory.py +545 -0
  11. up/commands/new.py +108 -10
  12. up/commands/provenance.py +267 -0
  13. up/commands/review.py +239 -0
  14. up/commands/start.py +1124 -0
  15. up/commands/status.py +360 -0
  16. up/commands/summarize.py +122 -0
  17. up/commands/sync.py +317 -0
  18. up/commands/vibe.py +304 -0
  19. up/context.py +421 -0
  20. up/core/__init__.py +69 -0
  21. up/core/checkpoint.py +479 -0
  22. up/core/provenance.py +364 -0
  23. up/core/state.py +678 -0
  24. up/events.py +512 -0
  25. up/git/__init__.py +37 -0
  26. up/git/utils.py +270 -0
  27. up/git/worktree.py +331 -0
  28. up/learn/__init__.py +155 -0
  29. up/learn/analyzer.py +227 -0
  30. up/learn/plan.py +374 -0
  31. up/learn/research.py +511 -0
  32. up/learn/utils.py +117 -0
  33. up/memory.py +1096 -0
  34. up/parallel.py +551 -0
  35. up/summarizer.py +407 -0
  36. up/templates/__init__.py +70 -2
  37. up/templates/config/__init__.py +502 -20
  38. up/templates/docs/SKILL.md +28 -0
  39. up/templates/docs/__init__.py +341 -0
  40. up/templates/docs/standards/HEADERS.md +24 -0
  41. up/templates/docs/standards/STRUCTURE.md +18 -0
  42. up/templates/docs/standards/TEMPLATES.md +19 -0
  43. up/templates/learn/__init__.py +567 -14
  44. up/templates/loop/__init__.py +546 -27
  45. up/templates/mcp/__init__.py +474 -0
  46. up/templates/projects/__init__.py +786 -0
  47. up/ui/__init__.py +14 -0
  48. up/ui/loop_display.py +650 -0
  49. up/ui/theme.py +137 -0
  50. up_cli-0.5.0.dist-info/METADATA +519 -0
  51. up_cli-0.5.0.dist-info/RECORD +55 -0
  52. up_cli-0.1.1.dist-info/METADATA +0 -186
  53. up_cli-0.1.1.dist-info/RECORD +0 -14
  54. {up_cli-0.1.1.dist-info → up_cli-0.5.0.dist-info}/WHEEL +0 -0
  55. {up_cli-0.1.1.dist-info → up_cli-0.5.0.dist-info}/entry_points.txt +0 -0
@@ -1,6 +1,7 @@
1
1
  """Product-loop system templates."""
2
2
 
3
3
  from pathlib import Path
4
+ from datetime import date
4
5
 
5
6
 
6
7
  def create_loop_system(target_dir: Path, ai_target: str, force: bool = False) -> None:
@@ -15,6 +16,8 @@ def create_loop_system(target_dir: Path, ai_target: str, force: bool = False) ->
15
16
 
16
17
  # Create files
17
18
  _create_skill_md(skill_dir, force)
19
+ _create_circuit_breaker(skill_dir, force)
20
+ _create_state_manager(skill_dir, force)
18
21
  _create_loop_state(target_dir, force)
19
22
 
20
23
 
@@ -22,6 +25,7 @@ def _write_file(path: Path, content: str, force: bool) -> None:
22
25
  """Write file if it doesn't exist or force is True."""
23
26
  if path.exists() and not force:
24
27
  return
28
+ path.parent.mkdir(parents=True, exist_ok=True)
25
29
  path.write_text(content)
26
30
 
27
31
 
@@ -32,21 +36,25 @@ name: product-loop
32
36
  description: Resilient development with SESRC principles
33
37
  user-invocable: true
34
38
  allowed-tools: Read, Edit, Write, Bash, Grep, Glob, TodoWrite
39
+ version: "1.0.0"
40
+ min-claude-version: "2024.01"
35
41
  ---
36
42
 
37
43
  # Resilient Product Loop
38
44
 
45
+ Autonomous product development with **built-in resilience patterns** for production-grade reliability.
46
+
39
47
  ## SESRC Principles
40
48
 
41
49
  | Principle | Implementation |
42
50
  |-----------|----------------|
43
- | **Stable** | Graceful degradation |
44
- | **Efficient** | Token budgets |
45
- | **Safe** | Input validation |
46
- | **Reliable** | Timeouts, rollback |
47
- | **Cost-effective** | Early termination |
51
+ | **Stable** | Graceful degradation, fallback modes |
52
+ | **Efficient** | Token budgets, incremental testing |
53
+ | **Safe** | Input validation, path whitelisting |
54
+ | **Reliable** | Timeouts, idempotency, rollback |
55
+ | **Cost-effective** | Early termination, ROI threshold |
48
56
 
49
- ## Loop
57
+ ## Core Loop
50
58
 
51
59
  ```
52
60
  OBSERVE → CHECKPOINT → EXECUTE → VERIFY → COMMIT
@@ -54,38 +62,549 @@ OBSERVE → CHECKPOINT → EXECUTE → VERIFY → COMMIT
54
62
 
55
63
  ## Commands
56
64
 
57
- - `/product-loop` - Start loop
58
- - `/product-loop resume` - Resume from checkpoint
59
- - `/product-loop status` - Show state
60
- - `/product-loop rollback` - Rollback last change
65
+ ### Skill Commands (for AI)
66
+ | Command | Description |
67
+ |---------|-------------|
68
+ | `/product-loop` | Start the development loop |
69
+ | `/product-loop resume` | Resume from last checkpoint |
70
+ | `/product-loop status` | Show current state |
71
+
72
+ ### CLI Commands (for users)
73
+ | Command | Description |
74
+ |---------|-------------|
75
+ | `up start` | Start the product loop |
76
+ | `up start --resume` | Resume from checkpoint |
77
+ | `up start --parallel` | Run tasks in parallel |
78
+ | `up save` | Create checkpoint |
79
+ | `up reset` | Rollback to checkpoint |
80
+ | `up status` | Show current state |
81
+
82
+ ---
61
83
 
62
84
  ## Circuit Breaker
63
85
 
64
- Max 3 failures before circuit opens.
86
+ Prevents infinite loops on persistent failures.
87
+
88
+ | State | Description |
89
+ |-------|-------------|
90
+ | CLOSED | Normal operation, failures counted |
91
+ | HALF_OPEN | Testing after cooldown |
92
+ | OPEN | Halted - requires intervention |
93
+
94
+ **Thresholds:**
95
+ - Max 3 consecutive failures → circuit opens
96
+ - Reset after 5 minutes cooldown
97
+ - Requires 2 successes to close
98
+
99
+ ---
100
+
101
+ ## Phase Details
102
+
103
+ ### Phase 1: OBSERVE
104
+
105
+ Read task sources in priority order:
106
+ 1. `.up/state.json` - Resume interrupted task (unified state)
107
+ 2. `prd.json` - Structured user stories
108
+ 3. `TODO.md` - Feature backlog
109
+
110
+ ### Phase 2: CHECKPOINT
111
+
112
+ Before risky operations:
113
+ - Create git checkpoint via `up save`
114
+ - Record modified files
115
+ - Save state to `.up/state.json`
116
+
117
+ ### Phase 3: EXECUTE
118
+
119
+ Execute task with circuit breaker:
120
+ - Check circuit state before operation
121
+ - Record success/failure
122
+ - Open circuit on repeated failures
123
+
124
+ ### Phase 4: VERIFY
125
+
126
+ Run verification suite:
127
+ 1. Syntax check (fast)
128
+ 2. Import check
129
+ 3. Unit tests
130
+ 4. Type check
131
+ 5. Lint
132
+
133
+ ### Phase 5: COMMIT
134
+
135
+ On success:
136
+ - Update state file
137
+ - Update TODO status
138
+ - Git commit if milestone complete
139
+
140
+ ---
141
+
142
+ ## State File: `.up/state.json`
143
+
144
+ The unified state file stores loop state, context budget, agent state, and metrics:
145
+
146
+ ```json
147
+ {
148
+ "version": "2.0",
149
+ "loop": {
150
+ "iteration": 5,
151
+ "phase": "VERIFY",
152
+ "current_task": "US-003",
153
+ "tasks_completed": ["US-001", "US-002"],
154
+ "tasks_failed": [],
155
+ "last_checkpoint": "cp-20260204-123456"
156
+ },
157
+ "circuit_breakers": {
158
+ "task": {"failures": 0, "state": "CLOSED"}
159
+ },
160
+ "metrics": {
161
+ "total_tasks": 15,
162
+ "total_rollbacks": 1,
163
+ "success_rate": 0.93
164
+ }
165
+ }
166
+ ```
167
+
168
+ ---
169
+
170
+ ## Recovery Strategies
171
+
172
+ | Error Type | Recovery |
173
+ |------------|----------|
174
+ | Syntax error | Auto-fix with linter |
175
+ | Test failure | Rollback, retry |
176
+ | Build error | Rollback, mark blocked |
177
+ | Circuit open | Wait or notify user |
178
+
179
+ ---
180
+
181
+ ## Budget Controls
182
+
183
+ ```
184
+ max_iterations: 20
185
+ max_retries_per_task: 3
186
+ max_total_rollbacks: 5
187
+ timeout_per_operation: 120s
188
+ ```
189
+
190
+ ---
191
+
192
+ ## Quick Start
193
+
194
+ 1. Ensure `prd.json` or `TODO.md` exists with tasks
195
+ 2. Run `/product-loop` or `up start`
196
+ 3. Loop will:
197
+ - Pick highest priority task
198
+ - Create checkpoint
199
+ - Execute with circuit breaker
200
+ - Verify changes
201
+ - Commit on success
202
+
203
+ ---
65
204
 
66
- ## State File
205
+ ## Context Budget Integration
67
206
 
68
- `.loop_state.json` tracks progress.
207
+ This skill respects context budget:
208
+ - Checks `.claude/context_budget.json` before operations
209
+ - Warns when approaching limits
210
+ - Creates handoff at critical threshold
211
+
212
+ ---
213
+
214
+ ## Implementation Note
215
+
216
+ The actual implementation is in `src/up/core/`:
217
+ - `state.py` - Unified state management with circuit breaker
218
+ - `checkpoint.py` - Git checkpoint operations
219
+ - `provenance.py` - AI operation tracking
220
+
221
+ The Python files in this skill folder (`circuit_breaker.py`, `state_manager.py`) are **reference implementations** for understanding the patterns. The CLI uses the implementations in `src/up/core/`.
222
+
223
+ ---
224
+
225
+ ## Status Output Format
226
+
227
+ ```
228
+ ═══════════════════════════════════════════
229
+ PRODUCT LOOP - Iteration #5
230
+ ═══════════════════════════════════════════
231
+ Health: ✅ HEALTHY
232
+ Circuit: task=CLOSED
233
+ Task: US-003 Add authentication
234
+ Status: ✅ COMPLETE
235
+ ───────────────────────────────────────────
236
+ Tests: ✅ 42/42 passing
237
+ Progress: [████████░░] 80%
238
+ ═══════════════════════════════════════════
239
+ ```
69
240
  """
70
241
  _write_file(skill_dir / "SKILL.md", content, force)
71
242
 
72
243
 
244
+ def _create_circuit_breaker(skill_dir: Path, force: bool) -> None:
245
+ """Create circuit breaker implementation."""
246
+ content = '''#!/usr/bin/env python3
247
+ """
248
+ Circuit Breaker for Product Loop
249
+
250
+ Prevents runaway loops by tracking failures and opening circuit.
251
+ """
252
+
253
+ import json
254
+ from dataclasses import dataclass
255
+ from datetime import datetime
256
+ from enum import Enum
257
+ from pathlib import Path
258
+ from typing import Optional
259
+
260
+
261
+ class CircuitState(Enum):
262
+ CLOSED = "CLOSED"
263
+ HALF_OPEN = "HALF_OPEN"
264
+ OPEN = "OPEN"
265
+
266
+
267
+ @dataclass
268
+ class CircuitConfig:
269
+ max_failures: int = 3
270
+ reset_timeout_seconds: int = 300
271
+ half_open_success_threshold: int = 2
272
+
273
+
274
+ class CircuitBreaker:
275
+ """Circuit breaker pattern implementation."""
276
+
277
+ def __init__(self, name: str, state_dir: Optional[Path] = None, config: Optional[CircuitConfig] = None):
278
+ self.name = name
279
+ self.state_dir = state_dir or Path.cwd()
280
+ self.config = config or CircuitConfig()
281
+ self.state_file = self.state_dir / f".circuit_{name}.json"
282
+ self._load()
283
+
284
+ def _load(self) -> None:
285
+ if self.state_file.exists():
286
+ try:
287
+ data = json.loads(self.state_file.read_text())
288
+ self.state = CircuitState(data.get("state", "CLOSED"))
289
+ self.failures = data.get("failures", 0)
290
+ self.successes = data.get("successes", 0)
291
+ self.last_failure = data.get("last_failure")
292
+ except (json.JSONDecodeError, ValueError):
293
+ self._reset()
294
+ else:
295
+ self._reset()
296
+
297
+ def _reset(self) -> None:
298
+ self.state = CircuitState.CLOSED
299
+ self.failures = 0
300
+ self.successes = 0
301
+ self.last_failure = None
302
+
303
+ def _save(self) -> None:
304
+ self.state_file.parent.mkdir(parents=True, exist_ok=True)
305
+ self.state_file.write_text(json.dumps({
306
+ "name": self.name,
307
+ "state": self.state.value,
308
+ "failures": self.failures,
309
+ "successes": self.successes,
310
+ "last_failure": self.last_failure,
311
+ "updated_at": datetime.now().isoformat(),
312
+ }, indent=2))
313
+
314
+ def can_execute(self) -> bool:
315
+ """Check if operation can proceed."""
316
+ if self.state == CircuitState.OPEN:
317
+ # Check if cooldown period has passed
318
+ if self.last_failure:
319
+ try:
320
+ last = datetime.fromisoformat(self.last_failure)
321
+ elapsed = (datetime.now() - last).total_seconds()
322
+ if elapsed >= self.config.reset_timeout_seconds:
323
+ self.state = CircuitState.HALF_OPEN
324
+ self.successes = 0
325
+ self._save()
326
+ return True
327
+ except ValueError:
328
+ pass
329
+ return False
330
+ return True
331
+
332
+ def record_success(self) -> None:
333
+ """Record successful operation."""
334
+ self.failures = 0
335
+
336
+ if self.state == CircuitState.HALF_OPEN:
337
+ self.successes += 1
338
+ if self.successes >= self.config.half_open_success_threshold:
339
+ self.state = CircuitState.CLOSED
340
+
341
+ self._save()
342
+
343
+ def record_failure(self) -> CircuitState:
344
+ """Record failed operation. Returns new state."""
345
+ self.failures += 1
346
+ self.last_failure = datetime.now().isoformat()
347
+
348
+ if self.state == CircuitState.HALF_OPEN:
349
+ # Immediate open on failure in half-open
350
+ self.state = CircuitState.OPEN
351
+ elif self.failures >= self.config.max_failures:
352
+ self.state = CircuitState.OPEN
353
+
354
+ self._save()
355
+ return self.state
356
+
357
+ def reset(self) -> None:
358
+ """Manually reset circuit breaker."""
359
+ self._reset()
360
+ self._save()
361
+
362
+ def get_status(self) -> dict:
363
+ return {
364
+ "name": self.name,
365
+ "state": self.state.value,
366
+ "failures": self.failures,
367
+ "can_execute": self.can_execute(),
368
+ }
369
+
370
+
371
+ if __name__ == "__main__":
372
+ import sys
373
+
374
+ name = sys.argv[1] if len(sys.argv) > 1 else "default"
375
+ cb = CircuitBreaker(name)
376
+
377
+ if len(sys.argv) > 2:
378
+ cmd = sys.argv[2]
379
+ if cmd == "success":
380
+ cb.record_success()
381
+ print(f"Recorded success. State: {cb.state.value}")
382
+ elif cmd == "failure":
383
+ new_state = cb.record_failure()
384
+ print(f"Recorded failure. State: {new_state.value}")
385
+ elif cmd == "reset":
386
+ cb.reset()
387
+ print("Circuit reset to CLOSED")
388
+ else:
389
+ print(f"Unknown command: {cmd}")
390
+ else:
391
+ print(json.dumps(cb.get_status(), indent=2))
392
+ '''
393
+ _write_file(skill_dir / "circuit_breaker.py", content, force)
394
+
395
+
396
+ def _create_state_manager(skill_dir: Path, force: bool) -> None:
397
+ """Create state manager for loop."""
398
+ content = '''#!/usr/bin/env python3
399
+ """
400
+ State Manager for Product Loop
401
+
402
+ Manages loop state, checkpoints, and progress tracking.
403
+ """
404
+
405
+ import json
406
+ from dataclasses import dataclass, field, asdict
407
+ from datetime import datetime
408
+ from pathlib import Path
409
+ from typing import Optional
410
+
411
+
412
+ @dataclass
413
+ class LoopState:
414
+ """Current state of the product loop."""
415
+ version: str = "1.0"
416
+ iteration: int = 0
417
+ phase: str = "INIT"
418
+ current_task: Optional[str] = None
419
+ tasks_completed: list[str] = field(default_factory=list)
420
+ tasks_remaining: list[str] = field(default_factory=list)
421
+ checkpoints: list[dict] = field(default_factory=list)
422
+ metrics: dict = field(default_factory=lambda: {
423
+ "total_edits": 0,
424
+ "total_rollbacks": 0,
425
+ "success_rate": 1.0,
426
+ })
427
+ last_updated: str = field(default_factory=lambda: datetime.now().isoformat())
428
+
429
+
430
+ class StateManager:
431
+ """Manages product loop state."""
432
+
433
+ def __init__(self, workspace: Optional[Path] = None):
434
+ self.workspace = workspace or Path.cwd()
435
+ self.state_file = self.workspace / ".loop_state.json"
436
+ self.state = self._load()
437
+
438
+ def _load(self) -> LoopState:
439
+ if self.state_file.exists():
440
+ try:
441
+ data = json.loads(self.state_file.read_text())
442
+ return LoopState(
443
+ version=data.get("version", "1.0"),
444
+ iteration=data.get("iteration", 0),
445
+ phase=data.get("phase", "INIT"),
446
+ current_task=data.get("current_task"),
447
+ tasks_completed=data.get("tasks_completed", []),
448
+ tasks_remaining=data.get("tasks_remaining", []),
449
+ checkpoints=data.get("checkpoints", []),
450
+ metrics=data.get("metrics", {}),
451
+ last_updated=data.get("last_updated", datetime.now().isoformat()),
452
+ )
453
+ except (json.JSONDecodeError, KeyError):
454
+ pass
455
+ return LoopState()
456
+
457
+ def save(self) -> None:
458
+ self.state.last_updated = datetime.now().isoformat()
459
+ self.state_file.write_text(json.dumps(asdict(self.state), indent=2))
460
+
461
+ def start_iteration(self) -> None:
462
+ self.state.iteration += 1
463
+ self.state.phase = "OBSERVE"
464
+ self.save()
465
+
466
+ def set_phase(self, phase: str) -> None:
467
+ self.state.phase = phase
468
+ self.save()
469
+
470
+ def set_current_task(self, task_id: str) -> None:
471
+ self.state.current_task = task_id
472
+ self.save()
473
+
474
+ def complete_task(self, task_id: str) -> None:
475
+ if task_id not in self.state.tasks_completed:
476
+ self.state.tasks_completed.append(task_id)
477
+ if task_id in self.state.tasks_remaining:
478
+ self.state.tasks_remaining.remove(task_id)
479
+ if self.state.current_task == task_id:
480
+ self.state.current_task = None
481
+ self.save()
482
+
483
+ def add_checkpoint(self, description: str, files: list[str]) -> str:
484
+ checkpoint_id = f"cp-{datetime.now().strftime('%Y%m%d%H%M%S')}"
485
+ self.state.checkpoints.append({
486
+ "id": checkpoint_id,
487
+ "timestamp": datetime.now().isoformat(),
488
+ "description": description,
489
+ "files": files,
490
+ })
491
+ # Keep only last 10 checkpoints
492
+ self.state.checkpoints = self.state.checkpoints[-10:]
493
+ self.save()
494
+ return checkpoint_id
495
+
496
+ def record_edit(self) -> None:
497
+ self.state.metrics["total_edits"] = self.state.metrics.get("total_edits", 0) + 1
498
+ self._update_success_rate()
499
+ self.save()
500
+
501
+ def record_rollback(self) -> None:
502
+ self.state.metrics["total_rollbacks"] = self.state.metrics.get("total_rollbacks", 0) + 1
503
+ self._update_success_rate()
504
+ self.save()
505
+
506
+ def _update_success_rate(self) -> None:
507
+ edits = self.state.metrics.get("total_edits", 0)
508
+ rollbacks = self.state.metrics.get("total_rollbacks", 0)
509
+ if edits > 0:
510
+ self.state.metrics["success_rate"] = round((edits - rollbacks) / edits, 2)
511
+
512
+ def get_summary(self) -> dict:
513
+ return {
514
+ "iteration": self.state.iteration,
515
+ "phase": self.state.phase,
516
+ "current_task": self.state.current_task,
517
+ "completed": len(self.state.tasks_completed),
518
+ "remaining": len(self.state.tasks_remaining),
519
+ "success_rate": self.state.metrics.get("success_rate", 1.0),
520
+ "checkpoints": len(self.state.checkpoints),
521
+ }
522
+
523
+ def reset(self) -> None:
524
+ self.state = LoopState()
525
+ self.save()
526
+
527
+
528
+ if __name__ == "__main__":
529
+ import sys
530
+
531
+ manager = StateManager()
532
+
533
+ if len(sys.argv) > 1:
534
+ cmd = sys.argv[1]
535
+ if cmd == "summary":
536
+ print(json.dumps(manager.get_summary(), indent=2))
537
+ elif cmd == "reset":
538
+ manager.reset()
539
+ print("State reset")
540
+ elif cmd == "full":
541
+ print(json.dumps(asdict(manager.state), indent=2))
542
+ else:
543
+ print(f"Unknown command: {cmd}")
544
+ else:
545
+ print(json.dumps(manager.get_summary(), indent=2))
546
+ '''
547
+ _write_file(skill_dir / "state_manager.py", content, force)
548
+
549
+
73
550
  def _create_loop_state(target_dir: Path, force: bool) -> None:
74
- """Create initial loop state file."""
75
- content = """{
76
- "version": "1.0",
77
- "iteration": 0,
78
- "phase": "INIT",
79
- "circuit_breaker": {
80
- "test": {"failures": 0, "state": "CLOSED"},
81
- "build": {"failures": 0, "state": "CLOSED"}
82
- },
551
+ """Create initial unified state file in .up/ directory."""
552
+ today = date.today().isoformat()
553
+
554
+ # Create .up directory
555
+ up_dir = target_dir / ".up"
556
+ up_dir.mkdir(parents=True, exist_ok=True)
557
+
558
+ # Unified state file
559
+ content = f"""{{
560
+ "version": "2.0",
561
+ "loop": {{
562
+ "iteration": 0,
563
+ "phase": "INIT",
564
+ "current_task": null,
565
+ "tasks_completed": [],
566
+ "tasks_failed": [],
567
+ "last_checkpoint": null,
568
+ "doom_loop_threshold": 3
569
+ }},
570
+ "context": {{
571
+ "budget": 100000,
572
+ "used": 0,
573
+ "warning_threshold": 0.8,
574
+ "critical_threshold": 0.9
575
+ }},
576
+ "parallel": {{
577
+ "active": false,
578
+ "max_workers": 3,
579
+ "active_worktrees": []
580
+ }},
83
581
  "checkpoints": [],
84
- "metrics": {
85
- "total_edits": 0,
582
+ "circuit_breakers": {{
583
+ "task": {{"failures": 0, "state": "CLOSED", "last_failure": null}}
584
+ }},
585
+ "metrics": {{
586
+ "total_tasks": 0,
86
587
  "total_rollbacks": 0,
87
- "success_rate": 1.0
88
- }
588
+ "success_rate": 1.0,
589
+ "session_start": "{today}"
590
+ }},
591
+ "created_at": "{today}",
592
+ "updated_at": "{today}"
593
+ }}
594
+ """
595
+ _write_file(up_dir / "state.json", content, force)
596
+
597
+ # Also create config.json with defaults
598
+ config_content = """{
599
+ "doom_loop_threshold": 3,
600
+ "circuit_breaker_cooldown_minutes": 5,
601
+ "circuit_breaker_failure_threshold": 3,
602
+ "checkpoint_retention_count": 50,
603
+ "context_budget": 100000,
604
+ "context_warning_threshold": 0.8,
605
+ "context_critical_threshold": 0.9,
606
+ "ai_timeout_seconds": 600,
607
+ "parallel_max_workers": 3
89
608
  }
90
609
  """
91
- _write_file(target_dir / ".loop_state.json", content, force)
610
+ _write_file(up_dir / "config.json", config_content, force)