ocak 0.2.0 → 0.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f4aa86d60189e50486edaba37ce3a2763d52a28b817818ac5b6466c952087cff
4
- data.tar.gz: 4a1d662842ef9f7e88fe5b6deeb30c51f061a6a598f948e1e622bf60e05abc70
3
+ metadata.gz: f3ade72c57a02f3647218543b060295454aec639d90ba261b4fe9b88fa05f1bf
4
+ data.tar.gz: 86decb5ce7099d43214859b82f3b12fc7d55b5cdf3d1d57a30ed38dc7c74a957
5
5
  SHA512:
6
- metadata.gz: 14787c35edcd0b30fd87b1aaf3e9537e091778a457ab43c33304b037dcbd79efa03d3f1dbdfa5d7d4256431cea8e7067d2c66ea9e5fcbaa43f86cfdef1802105
7
- data.tar.gz: 8d0c0b8a7ad9a8ed47d9b2864d9238b6a6705df00d6580db7296d67fab284198001c41ed9cc024edb6288344183ef07059788f536392ba7b7b386baa52106e7e
6
+ metadata.gz: 6ba522815876568e13b9453cdbd943a873c2ad28cee59a6c92d99b6dc62ccfeeadac57a2a2815b95f834b7c7173ffb2baf40107dcdfabc2cc74cf3d04446ad2d
7
+ data.tar.gz: 9b4d7d602aa8c2c213d64bbf75a5d9c5dfecb01952d327140c268f32474aaa02e3664f6b29cd60c9eee45fa1ac97e6f5012b9bbac51d54b1de68d40c34291a64
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  *Ocak (pronounced "oh-JAHK") is Turkish for "forge" or "hearth" — the place where raw material meets fire and becomes something useful. Also: let 'em cook.*
4
4
 
5
- Multi-agent pipeline that processes GitHub issues autonomously with Claude Code. You write an issue, label it, and ocak runs it through implement -> review -> fix -> security review -> document -> audit -> merge. Each issue gets its own worktree so they can run in parallel.
5
+ Multi-agent pipeline that processes GitHub issues autonomously with Claude Code. You write an issue, label it, and ocak runs it through implement review fix security review document audit merge. Each issue gets its own worktree so they can run in parrallel.
6
6
 
7
7
  ## Quick Start
8
8
 
@@ -19,61 +19,168 @@ ocak init
19
19
  ocak run --once --watch
20
20
 
21
21
  # Or run a single issue:
22
- ocak run --single 42 --watch
22
+ ocak run 42 --watch
23
23
  ```
24
24
 
25
25
  ## How It Works
26
26
 
27
- ### The Pipeline
27
+ ### Pipeline Flow
28
28
 
29
+ ```mermaid
30
+ flowchart LR
31
+ A["/design<br>Create issue"] --> B["Label<br>auto-ready"]
32
+ B --> C["Planner<br>Batch & classify"]
33
+ C --> D["Worktree<br>per issue"]
34
+ D --> E["Pipeline<br>Steps"]
35
+ E --> F["Rebase<br>& merge"]
29
36
  ```
30
- /design Label Pipeline
31
- ┌──────┐ ┌──────────┐ ┌───────────────────────────────────────────┐
32
- │ User │───>│auto-ready│───>│ implement → review → fix → security → │
33
- │ idea │ │ label │ │ document → audit → merge PR │
34
- └──────┘ └──────────┘ └───────────────────────────────────────────┘
35
- │ │ │
36
- ▼ ▼ ▼
37
- worktree read-only sequential
38
- per issue reviews rebase+merge
37
+
38
+ ### Pipeline Steps
39
+
40
+ ```mermaid
41
+ flowchart TD
42
+ IMP["1. Implementer<br>(opus)"] --> REV["2. Reviewer<br>(sonnet)"]
43
+ REV -->|"🔴 findings"| FIX1["3. Fix<br>(opus)"]
44
+ REV -->|"no findings"| SEC
45
+ FIX1 --> VER["4. Verify<br>(sonnet)"]
46
+ VER --> SEC["5. Security Review<br>(sonnet)"]
47
+ SEC -->|"🔴 findings"| FIX2["6. Security Fix<br>(opus)"]
48
+ SEC -->|"no findings"| DOC
49
+ FIX2 --> DOC["7. Documenter<br>(sonnet)"]
50
+ DOC --> AUD["8. Auditor<br>(sonnet)"]
51
+ AUD --> MRG["9. Merger<br>(sonnet)"]
52
+ ```
53
+
54
+ Steps 5-8 are tagged `complexity: full` and get skipped for simple issues. `--fast` forces everything to simple complexity so a typo fix doesnt burn through the whole pipeline.
55
+
56
+ Conditional steps only run when needed:
57
+ - **Fix** only runs if the reviewer flagged 🔴 blocking issues
58
+ - **Verify** only runs if fixes were actually applied
59
+ - **Merge** gets skipped in `--manual-review` mode or when the auditor BLOCKs
60
+
61
+ ### Label State Machine
62
+
63
+ ```mermaid
64
+ stateDiagram-v2
65
+ [*] --> auto_ready
66
+ auto_ready --> auto_doing: Pipeline picks up
67
+ auto_doing --> completed: Success
68
+ auto_doing --> pipeline_failed: Failure
69
+ auto_doing --> auto_pending_human: --manual-review / audit BLOCK
70
+ auto_doing --> auto_ready: Ctrl+C interrupt
71
+ pipeline_failed --> auto_doing: ocak resume
72
+ auto_pending_human --> auto_reready: Human labels for re-review
73
+ auto_reready --> auto_pending_human: Feedback addressed
74
+
75
+ state "auto-ready" as auto_ready
76
+ state "auto-doing" as auto_doing
77
+ state "completed" as completed
78
+ state "pipeline-failed" as pipeline_failed
79
+ state "auto-pending-human" as auto_pending_human
80
+ state "auto-reready" as auto_reready
81
+ ```
82
+
83
+ ### Complexity Classification
84
+
85
+ The planner classifes each issue as `simple` or `full`:
86
+ - **Simple** — skips security review, second fix pass, documenter, and auditor
87
+ - **Full** — runs the whole thing
88
+
89
+ `--fast` forces all issues to `simple`, giving you: implement → review → fix (if needed) → verify (if fixed) → merge.
90
+
91
+ ### Merge Flow
92
+
93
+ After all pipeline steps complete:
94
+
95
+ ```mermaid
96
+ flowchart LR
97
+ A["Commit<br>changes"] --> B["Rebase<br>onto main"]
98
+ B -->|conflict| C["Implementer<br>resolves"]
99
+ C --> D
100
+ B -->|clean| D["Run tests"]
101
+ D --> E["Push branch"]
102
+ E --> F["Merger agent<br>create PR + merge"]
39
103
  ```
40
104
 
41
- 1. **Design**`/design` in Claude Code walks you through creating an issue thats detailed enough for agents to work from
42
- 2. **Label** — slap the `auto-ready` label on it
43
- 3. **Plan** — planner agent figures out which issues can safely run in parallel
44
- 4. **Execute** — each issue gets a worktree, runs through the pipeline steps
45
- 5. **Merge** — completed work gets rebased, tested, and merged sequentially
105
+ Merging is sequential one at a time so you dont get conflicts between parallel worktrees.
46
106
 
47
- ### Agents
107
+ ## Agents
48
108
 
49
109
  8 agents, each with scoped tool permisions:
50
110
 
51
111
  | Agent | Role | Tools | Model |
52
112
  |-------|------|-------|-------|
53
- | **implementer** | Write code and tests | Read, Write, Edit, Bash | opus |
54
- | **reviewer** | Check patterns, tests, quality | Read, Grep, Glob (read-only) | sonnet |
55
- | **security-reviewer** | OWASP Top 10, auth, injection | Read, Grep, Glob, Bash | sonnet |
56
- | **auditor** | Pre-merge gate on changed files | Read, Grep, Glob (read-only) | sonnet |
57
- | **documenter** | Add missing docs | Read, Write, Edit | sonnet |
58
- | **merger** | Create PR, merge, close issue | Read, Grep, Bash | sonnet |
59
- | **planner** | Determine safe parallelization | Read, Grep, Glob (read-only) | sonnet |
60
- | **pipeline** | Self-contained orchestrator | All tools | opus |
113
+ | **implementer** | Write code and tests | Read, Write, Edit, Bash, Task | opus |
114
+ | **reviewer** | Check patterns, tests, quality | Read, Grep, Glob, Bash (read-only) | sonnet |
115
+ | **security-reviewer** | OWASP Top 10, auth, injection | Read, Grep, Glob, Bash (read-only) | sonnet |
116
+ | **auditor** | Pre-merge security gate | Read, Grep, Glob, Bash (read-only) | sonnet |
117
+ | **documenter** | Add missing docs (skips if not needed) | Read, Write, Edit, Bash | sonnet |
118
+ | **merger** | Create PR, merge, close issue | Read, Glob, Grep, Bash | sonnet |
119
+ | **planner** | Batch issues, classify complexity | Read, Glob, Grep, Bash (read-only) | haiku |
120
+ | **pipeline** | Self-contained single-agent mode | All tools | opus |
61
121
 
62
- ### Skills
122
+ Review agents (reviewer, security-reviewer, auditor) have no Write/Edit access — they can only read and report stuff back.
63
123
 
64
- Interactive skills for when you want to be in the loop:
124
+ ## Skills
65
125
 
66
- - `/design` walks through your codebase, asks questions, produces a detailed issue
67
- - `/audit [scope]` — codebase sweep for security, patterns, tests, data, dependencies
68
- - `/scan-file <path>` — deep single-file analysis with test coverage check
69
- - `/debt` — tech debt tracker with risk scoring
126
+ Interactive skills for use inside Claude Code:
70
127
 
71
- ### Label State Machine
128
+ | Skill | Description |
129
+ |-------|-------------|
130
+ | `/design [description]` | Researches your codebase, asks clarifying questions, produces a detailed implementation-ready issue |
131
+ | `/audit [scope]` | Codebase sweep — scopes: `security`, `errors`, `patterns`, `tests`, `data`, `dependencies`, or `all` |
132
+ | `/scan-file <path>` | Deep single-file analysis with test coverage check, scored 1-10 per method |
133
+ | `/debt` | Tech debt tracker with risk scoring (churn, coverage, suppressions, age, blast radius) |
134
+
135
+ ## Modes
72
136
 
137
+ ### Full Pipeline — `ocak run`
138
+
139
+ The default. Polls for `auto-ready` issues, plans batches, runs the full step sequence in parallel worktrees, merges sequentally.
140
+
141
+ ```bash
142
+ ocak run --once --watch # Process current batch and exit
143
+ ocak run 42 --watch # Single issue, full pipeline
144
+ ocak run --manual-review --watch # Create PRs without auto-merge
145
+ ocak run --audit --watch # Auditor as merge gate
146
+ ocak run --fast --watch # Skip security/docs/audit steps
147
+ ```
148
+
149
+ ### Fast Mode — `ocak hiz`
150
+
151
+ Lightweight alternative for quick PRs you'll review youself:
152
+
153
+ ```mermaid
154
+ flowchart LR
155
+ A["Implementer<br>(sonnet)"] --> B["Reviewer (haiku)<br>+<br>Security (sonnet)<br>in parallel"]
156
+ B --> C["Verify<br>tests + lint"]
157
+ C --> D["Create PR<br>(no merge)"]
73
158
  ```
74
- auto-ready ──→ in-progress ──→ completed
75
-
76
- └──→ pipeline-failed
159
+
160
+ Runs in your current checkout (no worktree), uses cheaper models, creates a PR without merging. Rougly 5-10x cheaper than the full pipeline.
161
+
162
+ ```bash
163
+ ocak hiz 42 --watch
164
+ ```
165
+
166
+ ### Re-review Flow
167
+
168
+ When `--manual-review` is enabled, PRs sit open for human review. After leaving feedback, slap the `auto-reready` label on the PR and ocak will:
169
+
170
+ 1. Check out the PR branch
171
+ 2. Run the implementer against the review comments
172
+ 3. Push with `--force-with-lease`
173
+ 4. Remove the `auto-reready` label
174
+ 5. Comment "Feedback addressed. Please re-review."
175
+
176
+ ### Graceful Shutdown
177
+
178
+ `Ctrl+C` once — current agent step finishes, then the pipeline stops. WIP gets committed, labels reset to `auto-ready`, and resume commands are printed.
179
+
180
+ `Ctrl+C` twice — kills active subprocesses immediatley (SIGTERM → wait → SIGKILL), then same cleanup runs.
181
+
182
+ ```bash
183
+ ocak resume 42 --watch # Pick up from where it stopped
77
184
  ```
78
185
 
79
186
  ## Configuration
@@ -87,23 +194,35 @@ stack:
87
194
  framework: rails
88
195
  test_command: "bundle exec rspec"
89
196
  lint_command: "bundle exec rubocop -A"
197
+ setup_command: "bundle install"
90
198
  security_commands:
91
199
  - "bundle exec brakeman -q"
92
200
  - "bundle exec bundler-audit check"
93
201
 
94
202
  # Pipeline settings
95
203
  pipeline:
96
- max_parallel: 3 # Concurrent worktrees
97
- poll_interval: 60 # Seconds between polls
204
+ max_parallel: 5
205
+ poll_interval: 60
98
206
  worktree_dir: ".claude/worktrees"
99
207
  log_dir: "logs/pipeline"
208
+ cost_budget: 5.0 # Max USD per issue (kills pipeline if exceeded)
209
+ manual_review: false # Create PRs without auto-merge
210
+ audit_mode: false # Run auditor as merge gate
211
+
212
+ # Safety controls
213
+ safety:
214
+ allowed_authors: [] # Restrict to GitHub usernames (empty = current gh user)
215
+ require_comment: false # Require confirmation comment before processing
216
+ max_issues_per_run: 5 # Cap issues per polling cycle
100
217
 
101
218
  # GitHub labels
102
219
  labels:
103
220
  ready: "auto-ready"
104
- in_progress: "in-progress"
221
+ in_progress: "auto-doing"
105
222
  completed: "completed"
106
223
  failed: "pipeline-failed"
224
+ reready: "auto-reready"
225
+ awaiting_review: "auto-pending-human"
107
226
 
108
227
  # Pipeline steps — add, remove, reorder as you like
109
228
  steps:
@@ -113,19 +232,23 @@ steps:
113
232
  role: review
114
233
  - agent: implementer
115
234
  role: fix
116
- condition: has_findings # Only runs if reviewer found issues
235
+ condition: has_findings
117
236
  - agent: reviewer
118
237
  role: verify
119
- condition: had_fixes # Only runs if fixes were made
120
- - agent: security_reviewer
238
+ condition: had_fixes
239
+ - agent: security-reviewer
121
240
  role: security
241
+ complexity: full
122
242
  - agent: implementer
123
243
  role: fix
124
244
  condition: has_findings
245
+ complexity: full
125
246
  - agent: documenter
126
247
  role: document
248
+ complexity: full
127
249
  - agent: auditor
128
250
  role: audit
251
+ complexity: full
129
252
  - agent: merger
130
253
  role: merge
131
254
 
@@ -189,6 +312,43 @@ steps:
189
312
  role: custom_step
190
313
  ```
191
314
 
315
+ ### Per-Step Model Override
316
+
317
+ Override the default model for any step:
318
+
319
+ ```yaml
320
+ steps:
321
+ - agent: implementer
322
+ role: implement
323
+ model: sonnet # Use sonnet instead of opus for cheaper runs
324
+ ```
325
+
326
+ ## Stack Detection
327
+
328
+ `ocak init` auto-detects your project stack and generates tailored agent templates. For anything else you get generic agents that you can cusomize.
329
+
330
+ | Language | Frameworks | Test | Lint | Security |
331
+ |----------|-----------|------|------|----------|
332
+ | Ruby | Rails, Sinatra, Hanami | rspec | rubocop | brakeman, bundler-audit |
333
+ | TypeScript/JS | Next, Remix, Nuxt, Svelte, React, Vue, Express | vitest, jest | biome, eslint | npm audit |
334
+ | Python | Django, Flask, FastAPI | pytest | ruff, flake8 | bandit, safety |
335
+ | Rust | Actix, Axum, Rocket | cargo test | cargo clippy | cargo audit |
336
+ | Go | Gin, Echo, Fiber, Chi | go test | golangci-lint | gosec |
337
+ | Java | — | gradle test | — | — |
338
+ | Elixir | Phoenix | mix test | mix credo | — |
339
+
340
+ Monorepo detection: npm/pnpm workspaces, Cargo workspaces, Go workspaces, Lerna, and convention-based (`packages/`, `apps/`, `services/`).
341
+
342
+ ## Run Reports
343
+
344
+ Pipeline runs generate JSON reports in `.ocak/reports/`:
345
+
346
+ ```bash
347
+ ocak status --report
348
+ ```
349
+
350
+ Shows per-run stats (cost, duration, steps completed, failures) and aggregates across recent runs (avg cost, avg duration, success rate, slowest step, most-skipped step). Handy for figuring out where all your money went.
351
+
192
352
  ## Writing Good Issues
193
353
 
194
354
  The `/design` skill produces issues formatted for zero-context agents. Think of it as writing a ticket for a contractor who's never seen your codebase — everthing they need should be in the issue body. The key sections:
@@ -204,50 +364,66 @@ The `/design` skill produces issues formatted for zero-context agents. Think of
204
364
  ## CLI Reference
205
365
 
206
366
  ```
207
- ocak init [--force] [--no-ai] Set up pipeline in current project
208
- ocak run [options] Run the pipeline
209
- --watch Stream agent activity with color
210
- --single N Run one issue, no worktrees
211
- --dry-run Show plan without executing
212
- --once Process current batch and exit
213
- --max-parallel N Limit concurrency (default: 3)
214
- --poll-interval N Seconds between polls (default: 60)
215
- ocak status Show pipeline state
216
- ocak clean Remove stale worktrees
217
- ocak design [description] Launch issue design session
218
- ocak audit [scope] Run codebase audit
219
- ocak debt Track technical debt
367
+ ocak init [--force] [--no-ai] Set up pipeline in current project
368
+ [--config-only] Only generate config, hooks, settings
369
+ [--skip-agents] Skip agent generation
370
+ [--skip-skills] Skip skill generation
371
+
372
+ ocak run [ISSUE] [options] Run the pipeline
373
+ --watch Stream agent activity with color
374
+ --dry-run Show plan without executing
375
+ --once Process current batch and exit
376
+ --fast Skip security/docs/audit (simple complexity)
377
+ --max-parallel N Limit concurrency (default: 5)
378
+ --poll-interval N Seconds between polls (default: 60)
379
+ --manual-review Create PRs without auto-merge
380
+ --audit Run auditor as post-pipeline gate
381
+ --verbose / --quiet Control output verbosity
382
+
383
+ ocak hiz ISSUE [options] Fast mode: implement + parallel review
384
+ --watch Stream agent activity
385
+ --dry-run Show plan without executing
386
+ --verbose / --quiet Control output verbosity
387
+
388
+ ocak resume ISSUE [options] Resume from last successful step
389
+ --watch Stream agent activity
390
+ --dry-run Show which steps would re-run
391
+ --verbose / --quiet Control output verbosity
392
+
393
+ ocak status Show pipeline state
394
+ --report Show run reports (cost, duration, stats)
395
+
396
+ ocak clean Remove stale worktrees
397
+ --logs Clean log files, state, and reports
398
+ --all Clean worktrees and logs
399
+ --keep N Only remove artifacts older than N days
400
+
401
+ ocak design [DESCRIPTION] Launch interactive issue design session
402
+ ocak audit [SCOPE] Print instructions for /audit skill
403
+ ocak debt Print instructions for /debt skill
220
404
  ```
221
405
 
222
406
  ## FAQ
223
407
 
224
408
  **How much does it cost?**
225
409
 
226
- Depends on the issue. Simple stuff is $2-5, complex issues can be $10-15. The implementer runs on opus which is the expensive part, reviews on sonnet are pretty cheap. You can see costs in the `--watch` output.
410
+ Depends on the issue. Simple stuff is $2-5, complex issues can be $10-15. The implementer runs on opus which is the expensive part, reviews on sonnet are pretty cheap. Use `ocak hiz` for ~5-10x cheaper runs. Set `cost_budget` in config to cap spend per issue.
227
411
 
228
412
  **Is it safe?**
229
413
 
230
- Reasonably. Review agents are read-only (no Write/Edit tools), merging is sequential so you don't get conflicts, and failed piplines get labeled and logged. You can always `--dry-run` first to see what it would do.
414
+ Reasonably. Review agents are read-only (no Write/Edit tools), merging is sequential so you dont get conflicts, and failed piplines get labeled and logged. You can always `--dry-run` first to see what it would do.
231
415
 
232
416
  **What if it breaks?**
233
417
 
234
- Issues get labeled `pipeline-failed` with a comment explaining what went wrong. Worktrees get cleaned up automatically. Run `ocak clean` to remove any stragglers, and check `logs/pipeline/` for detailed logs.
235
-
236
- **Can I run one issue manually?**
237
-
238
- ```bash
239
- ocak run --single 42 --watch
240
- ```
241
-
242
- Runs the full pipeline for issue #42 in your current checkout (no worktree).
418
+ Issues get labeled `pipeline-failed` with a comment explaining what went wrong. Worktrees get cleaned up automaticaly. Run `ocak clean` to remove any stragglers, and check `logs/pipeline/` for detailed logs.
243
419
 
244
420
  **How do I pause it?**
245
421
 
246
- Kill the `ocak run` process. Issues that are `in-progress` will keep their label remove it manually or let the next run pick them back up.
422
+ `Ctrl+C` once lets the current step finish, commits WIP changes, resets labels, and prints resume commands. `Ctrl+C` twice kills subprocesses immediately. Exit code 130 either way.
247
423
 
248
- **What languages does it support?**
249
-
250
- `ocak init` auto-detects Ruby, TypeScript/JavaScript, Python, Rust, Go, Java, and Elixir. Agents get generated with stack-specific instructions. For anything else you get generic agents that you can customize.
424
+ ```bash
425
+ ocak resume 42 --watch # pick up from where it stopped
426
+ ```
251
427
 
252
428
  ## Development
253
429
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'erb'
4
4
  require 'fileutils'
5
+ require 'open3'
5
6
 
6
7
  module Ocak
7
8
  class AgentGenerator
@@ -89,6 +90,7 @@ module Ocak
89
90
  monorepo = @stack.respond_to?(:monorepo) ? @stack.monorepo : false
90
91
  packages = @stack.respond_to?(:packages) ? (@stack.packages || []) : []
91
92
  project_dir = @project_dir
93
+ max_parallel = 5
92
94
 
93
95
  binding
94
96
  end
@@ -14,9 +14,8 @@ module Ocak
14
14
  def warnings? = output.to_s.include?("\u{1F7E1}")
15
15
  end
16
16
 
17
- FailedStatus = Struct.new(:success?) do
18
- def self.instance = new(false)
19
- end
17
+ # Backwards-compat alias — FailedStatus now lives in ProcessRunner.
18
+ FailedStatus = ProcessRunner::FailedStatus
20
19
 
21
20
  AGENT_TOOLS = {
22
21
  'implementer' => 'Read,Write,Edit,Glob,Grep,Bash',
@@ -53,10 +52,11 @@ module Ocak
53
52
  /overloaded/i
54
53
  ].freeze
55
54
 
56
- def initialize(config:, logger:, watch: nil)
55
+ def initialize(config:, logger:, watch: nil, registry: nil)
57
56
  @config = config
58
57
  @logger = logger
59
58
  @watch = watch
59
+ @registry = registry
60
60
  end
61
61
 
62
62
  def run_agent(agent_name, prompt, chdir: nil, model: nil, retries: MAX_RETRIES)
@@ -119,7 +119,8 @@ module Ocak
119
119
  end
120
120
 
121
121
  def build_agent_result(parser, status, stderr, agent_name)
122
- output = parser.result_text || ''
122
+ full = parser.full_output
123
+ output = full.empty? ? (parser.result_text || '') : full
123
124
  exit_ok = status.respond_to?(:success?) && status.success?
124
125
  success = parser.success? && exit_ok
125
126
 
@@ -153,7 +154,7 @@ module Ocak
153
154
  cmd.push('--model', model) if model
154
155
  cmd.push('--', prompt)
155
156
 
156
- ProcessRunner.run(cmd, chdir: chdir, timeout: TIMEOUT, on_line: on_line)
157
+ ProcessRunner.run(cmd, chdir: chdir, timeout: TIMEOUT, on_line: on_line, registry: @registry)
157
158
  end
158
159
 
159
160
  def extract_result_from_stream(raw)
@@ -1,17 +1,34 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'fileutils'
3
4
  require_relative '../config'
4
5
  require_relative '../worktree_manager'
5
6
 
6
7
  module Ocak
7
8
  module Commands
8
9
  class Clean < Dry::CLI::Command
9
- desc 'Remove stale worktrees and prune git worktree list'
10
+ desc 'Remove stale worktrees, logs, and reports'
10
11
 
11
- def call(**)
12
+ option :logs, type: :boolean, default: false, desc: 'Clean log files, state files, and reports'
13
+ option :all, type: :boolean, default: false, desc: 'Clean worktrees and logs'
14
+ option :keep, type: :integer, desc: 'Only remove artifacts older than N days'
15
+
16
+ def call(**options)
12
17
  config = Config.load
13
- manager = WorktreeManager.new(config: config)
18
+ do_worktrees = !options[:logs] || options[:all]
19
+ do_logs = options[:logs] || options[:all]
20
+
21
+ clean_worktrees(config) if do_worktrees
22
+ clean_logs(config, keep_days: options[:keep]) if do_logs
23
+ rescue Config::ConfigNotFound => e
24
+ warn "Error: #{e.message}"
25
+ exit 1
26
+ end
27
+
28
+ private
14
29
 
30
+ def clean_worktrees(config)
31
+ manager = WorktreeManager.new(config: config)
15
32
  puts 'Cleaning stale worktrees...'
16
33
  removed = manager.clean_stale
17
34
 
@@ -21,9 +38,53 @@ module Ocak
21
38
  removed.each { |path| puts " Removed: #{path}" }
22
39
  puts "Cleaned #{removed.size} worktree(s)."
23
40
  end
24
- rescue Config::ConfigNotFound => e
25
- warn "Error: #{e.message}"
26
- exit 1
41
+ end
42
+
43
+ def clean_logs(config, keep_days:)
44
+ cutoff = keep_days ? Time.now - (keep_days * 86_400) : nil
45
+ puts keep_days ? "Cleaning logs older than #{keep_days} days..." : 'Cleaning logs...'
46
+
47
+ log_dir = File.join(config.project_dir, config.log_dir)
48
+ reports_dir = File.join(config.project_dir, '.ocak', 'reports')
49
+
50
+ artifacts = collect_artifacts(log_dir, reports_dir)
51
+ removed = remove_artifacts(artifacts, cutoff)
52
+
53
+ if removed.empty?
54
+ puts 'No artifacts to clean.'
55
+ else
56
+ puts "Cleaned #{removed.size} artifact(s)."
57
+ end
58
+ end
59
+
60
+ def collect_artifacts(log_dir, reports_dir)
61
+ artifacts = []
62
+
63
+ if Dir.exist?(log_dir)
64
+ artifacts += Dir.glob(File.join(log_dir, '*.log'))
65
+ artifacts += Dir.glob(File.join(log_dir, 'issue-*-state.json'))
66
+ artifacts += Dir.glob(File.join(log_dir, 'issue-*/')).select { |f| File.directory?(f) }
67
+ end
68
+
69
+ artifacts += Dir.glob(File.join(reports_dir, '*.json')) if Dir.exist?(reports_dir)
70
+
71
+ artifacts
72
+ end
73
+
74
+ def remove_artifacts(artifacts, cutoff)
75
+ removed = []
76
+ artifacts.each do |path|
77
+ next if cutoff && File.mtime(path) >= cutoff
78
+
79
+ if File.directory?(path)
80
+ FileUtils.rm_rf(path)
81
+ else
82
+ FileUtils.rm_f(path)
83
+ end
84
+ puts " Removed: #{path}"
85
+ removed << path
86
+ end
87
+ removed
27
88
  end
28
89
  end
29
90
  end
@@ -15,18 +15,10 @@ module Ocak
15
15
  exit 1
16
16
  end
17
17
 
18
- puts 'Starting interactive design session...'
19
- puts 'This will open Claude Code with the /design skill.'
20
- puts ''
21
-
22
18
  if description
23
19
  exec('claude', '--skill', skill_path, '--', description)
24
20
  else
25
- puts 'Run this inside Claude Code:'
26
- puts ' /design <description of what you want to build>'
27
- puts ''
28
- puts 'Or provide a description directly:'
29
- puts ' ocak design "add user authentication with OAuth"'
21
+ exec('claude', '--skill', skill_path)
30
22
  end
31
23
  end
32
24
  end