okstra 0.97.1 → 0.98.1

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 (77) hide show
  1. package/README.kr.md +4 -3
  2. package/README.md +4 -3
  3. package/docs/kr/architecture.md +2 -2
  4. package/docs/kr/cli.md +2 -0
  5. package/docs/kr/container.md +122 -0
  6. package/docs/project-structure-overview.md +12 -2
  7. package/docs/superpowers/plans/2026-06-21-okstra-container-local-user-test.md +714 -0
  8. package/docs/superpowers/specs/2026-06-21-okstra-container-local-user-test-design.md +125 -0
  9. package/package.json +1 -1
  10. package/runtime/BUILD.json +2 -2
  11. package/runtime/agents/workers/antigravity-worker.md +2 -18
  12. package/runtime/agents/workers/claude-worker.md +10 -33
  13. package/runtime/agents/workers/codex-worker.md +2 -18
  14. package/runtime/agents/workers/report-writer-worker.md +2 -10
  15. package/runtime/bin/lib/okstra-ctl/cmd-tail.sh +5 -2
  16. package/runtime/bin/okstra-antigravity-exec.sh +6 -1
  17. package/runtime/bin/okstra-claude-exec.sh +6 -1
  18. package/runtime/bin/okstra-codex-exec.sh +6 -1
  19. package/runtime/prompts/profiles/_clarification-recommendation.md +1 -0
  20. package/runtime/prompts/profiles/_coding-conventions-preflight.md +1 -1
  21. package/runtime/prompts/profiles/_common-contract.md +3 -3
  22. package/runtime/prompts/profiles/_coverage-critic.md +17 -0
  23. package/runtime/prompts/profiles/_implementation-deliverable.md +2 -2
  24. package/runtime/prompts/profiles/_implementation-executor.md +2 -2
  25. package/runtime/prompts/profiles/_implementation-self-check.md +2 -1
  26. package/runtime/prompts/profiles/_implementation-verifier.md +3 -3
  27. package/runtime/prompts/profiles/_stage-discipline.md +5 -8
  28. package/runtime/prompts/profiles/error-analysis.md +5 -5
  29. package/runtime/prompts/profiles/final-verification.md +4 -4
  30. package/runtime/prompts/profiles/implementation-planning.md +7 -7
  31. package/runtime/prompts/profiles/implementation.md +5 -5
  32. package/runtime/prompts/profiles/improvement-discovery.md +6 -6
  33. package/runtime/prompts/profiles/release-handoff.md +3 -3
  34. package/runtime/prompts/profiles/requirements-discovery.md +4 -4
  35. package/runtime/prompts/wizard/prompts.ko.json +0 -1
  36. package/runtime/python/okstra_ctl/__init__.py +4 -1
  37. package/runtime/python/okstra_ctl/container.py +970 -0
  38. package/runtime/python/okstra_ctl/container_registry.py +93 -0
  39. package/runtime/python/okstra_ctl/error_zip.py +4 -4
  40. package/runtime/python/okstra_ctl/handoff.py +7 -1
  41. package/runtime/python/okstra_ctl/ids.py +40 -1
  42. package/runtime/python/okstra_ctl/listing.py +3 -5
  43. package/runtime/python/okstra_ctl/log_report.py +102 -0
  44. package/runtime/python/okstra_ctl/pane_reclaim.py +5 -4
  45. package/runtime/python/okstra_ctl/paths.py +40 -0
  46. package/runtime/python/okstra_ctl/plan_run_root.py +78 -0
  47. package/runtime/python/okstra_ctl/reconcile.py +4 -2
  48. package/runtime/python/okstra_ctl/resolve_task_key.py +54 -0
  49. package/runtime/python/okstra_ctl/run.py +48 -30
  50. package/runtime/python/okstra_ctl/stage_integrate.py +8 -2
  51. package/runtime/python/okstra_ctl/stage_targets.py +79 -0
  52. package/runtime/python/okstra_ctl/time_report.py +200 -0
  53. package/runtime/python/okstra_ctl/tmux.py +67 -0
  54. package/runtime/python/okstra_ctl/wizard.py +35 -20
  55. package/runtime/python/okstra_project/__init__.py +2 -0
  56. package/runtime/python/okstra_project/state.py +50 -2
  57. package/runtime/skills/okstra-brief/SKILL.md +17 -7
  58. package/runtime/skills/okstra-container/SKILL.md +169 -0
  59. package/runtime/skills/okstra-inspect/SKILL.md +64 -178
  60. package/runtime/skills/okstra-memory/SKILL.md +8 -6
  61. package/runtime/skills/okstra-run/SKILL.md +4 -4
  62. package/runtime/skills/okstra-schedule/SKILL.md +9 -16
  63. package/runtime/skills/okstra-setup/SKILL.md +10 -9
  64. package/runtime/templates/reports/brief.template.md +1 -1
  65. package/runtime/templates/reports/final-report.template.md +1 -1
  66. package/runtime/templates/reports/settings.template.json +3 -1
  67. package/runtime/validators/validate-implementation-plan-stages.py +11 -3
  68. package/src/cli-registry.mjs +28 -0
  69. package/src/commands/inspect/container.mjs +27 -0
  70. package/src/commands/inspect/log-report.mjs +26 -0
  71. package/src/commands/inspect/resolve-task-key.mjs +26 -0
  72. package/src/commands/inspect/task-list.mjs +26 -15
  73. package/src/commands/inspect/time-report.mjs +26 -0
  74. package/src/commands/lifecycle/check-project.mjs +7 -1
  75. package/src/commands/lifecycle/doctor.mjs +16 -0
  76. package/src/lib/skill-catalog.mjs +1 -0
  77. package/runtime/agents/TODO.md +0 -226
@@ -119,11 +119,51 @@ def find_task_root(project_root: Path, task_key: str) -> Optional[Path]:
119
119
  return None
120
120
 
121
121
 
122
- def list_project_tasks(project_root: Path) -> list[dict]:
122
+ def resolve_task_id(
123
+ project_root: Path,
124
+ task_id: str,
125
+ *,
126
+ task_group: Optional[str] = None,
127
+ ) -> list[dict]:
128
+ """bare task-id(옵션 task-group)로 catalog 후보 entry 목록을 돌려준다.
129
+
130
+ full task-key 해석(find_task_root)이 다루지 않는 한 겹: skill markdown 이
131
+ 복제하던 "catalog 를 case-insensitive 로 훑어 taskId(필요시 taskGroup)가
132
+ 일치하는 entry 를 모은다" 를 단일 SSOT 로 노출한다. 매칭은 find_task_root
133
+ 의 taskKey 비교와 동일한 .lower() 케이스무시 규칙. entry dict 를 그대로
134
+ 반환하므로 호출자가 분기·경로조립에 바로 쓴다(0개=없음, 1개=확정, N개=모호).
135
+ """
136
+ wanted_id = (task_id or "").strip().lower()
137
+ if not wanted_id:
138
+ return []
139
+ wanted_group = task_group.strip().lower() if task_group else None
140
+ matches: list[dict] = []
141
+ for entry in read_task_catalog(Path(project_root)):
142
+ entry_id = entry.get("taskId")
143
+ if not isinstance(entry_id, str) or entry_id.lower() != wanted_id:
144
+ continue
145
+ if wanted_group is not None:
146
+ entry_group = entry.get("taskGroup")
147
+ if not isinstance(entry_group, str) or entry_group.lower() != wanted_group:
148
+ continue
149
+ matches.append(entry)
150
+ return matches
151
+
152
+
153
+ def list_project_tasks(
154
+ project_root: Path,
155
+ *,
156
+ task_type: Optional[str] = None,
157
+ latest_run_status: Optional[str] = None,
158
+ task_group: Optional[str] = None,
159
+ limit: Optional[int] = None,
160
+ ) -> list[dict]:
123
161
  """skill UI 에 보여줄 task 후보 목록을 돌려준다.
124
162
 
125
163
  각 항목은 task-catalog.json 의 entry 그대로 + 디스크 존재 여부 확인.
126
- 존재하지 않는 task root 는 skip(stale catalog 항목)한다.
164
+ 존재하지 않는 task root 는 skip(stale catalog 항목)한다. 선택 필터(taskType /
165
+ latestRunStatus / taskGroup)는 AND 결합, limit 은 카탈로그 순서(updatedAt desc
166
+ 로 저장됨)에서 앞쪽 N개. 모든 필터 인자가 기본값이면 기존 전체 목록과 동일.
127
167
  """
128
168
  project_root = Path(project_root)
129
169
  out = []
@@ -131,10 +171,18 @@ def list_project_tasks(project_root: Path) -> list[dict]:
131
171
  entry_key = entry.get("taskKey") or ""
132
172
  if not isinstance(entry_key, str) or not entry_key:
133
173
  continue
174
+ if task_type and entry.get("taskType") != task_type:
175
+ continue
176
+ if latest_run_status and entry.get("latestRunStatus") != latest_run_status:
177
+ continue
178
+ if task_group and entry.get("taskGroup") != task_group:
179
+ continue
134
180
  root = find_task_root(project_root, entry_key)
135
181
  if root is None:
136
182
  continue
137
183
  out.append({**entry, "_resolvedTaskRoot": str(root)})
184
+ if limit is not None and limit >= 0:
185
+ out = out[:limit]
138
186
  return out
139
187
 
140
188
 
@@ -121,6 +121,12 @@ as source material, and never writes them. Decision files (when raised by
121
121
 
122
122
  Reuse the same literal-token-first rule as `okstra-run`. Each command below is a **separate Bash tool call** starting with the literal token `okstra` so the `Bash(okstra:*)` permission match succeeds. Do **not** wrap any of them in `if`, `eval`, `export`, `$(...)`, `VAR=...`, `||`, or `&&`, and do **not** introduce a `$OKSTRA_CMD` variable or an `npx -y okstra@latest` fallback — those leading tokens defeat the permission match and force a confirmation prompt on every call.
123
123
 
124
+ ```bash
125
+ okstra ensure-installed --runtime claude-code
126
+ ```
127
+
128
+ If this exits non-zero, tell the user: "okstra not installed — run `/okstra-setup` first." Then stop. Do **not** try to invoke `npx -y okstra@latest ...` as a fallback.
129
+
124
130
  ```bash
125
131
  okstra check-project --json
126
132
  ```
@@ -130,7 +136,7 @@ Parse the JSON from stdout:
130
136
  - `ok: true` → use `projectRoot` from the JSON output. Carry it as a literal string into the steps below.
131
137
  - `ok: false` → tell the user: "this project has no okstra setup. Run `/okstra-setup` first." Then stop.
132
138
 
133
- > If the `okstra` binary is not on `PATH` at all, the command above will not run. In that case tell the user verbatim: "okstra not installed — run `/okstra-setup` first." Do **not** try to invoke `npx -y okstra@latest ...` from this skill — `npx` is not on the literal-token allow-list this skill targets and will force a confirmation prompt on every subsequent call.
139
+ > If the `okstra` binary is not on `PATH` at all, the commands above will not run. In that case tell the user verbatim: "okstra not installed — run `/okstra-setup` first." Do **not** try to invoke `npx -y okstra@latest ...` from this skill — `npx` is not on the literal-token allow-list this skill targets and will force a confirmation prompt on every subsequent call.
134
140
 
135
141
  ## Step 1: Choose input source
136
142
 
@@ -328,9 +334,9 @@ If the user picked `Codebase scan`, gather the codebase-scan-specific inputs:
328
334
 
329
335
  1. `AskUserQuestion` (free text):
330
336
  `"Scan scope — comma-separated paths inside the project (e.g. src/foo/, src/bar/baz.py)"` → `scan_scope` (CSV).
331
- 2. `AskUserQuestion` (multi-select, options = the 8 lens enum values from `scripts/okstra_ctl/improvement_lenses.py`'s `LENSES`):
337
+ 2. `AskUserQuestion` (multi-select, options = the lens values from `~/.okstra/lib/python/okstra_ctl/improvement_lenses.py`'s `LENSES` — code source: `scripts/okstra_ctl/improvement_lenses.py`):
332
338
  `"Priority lenses (pick 1–4)"` → `priority_lenses` (1..4 values from the enum).
333
- Enum values: `performance`, `security`, `readability`, `architecture`, `test-coverage`, `dx`, `observability`, `accessibility`.
339
+ Current `LENSES` values (this list mirrors the module — if they diverge, the module wins; Step 6.6 `validate-brief` enforces the subset): `performance`, `security`, `readability`, `architecture`, `test-coverage`, `dx`, `observability`, `accessibility`.
334
340
  3. `AskUserQuestion` (free text):
335
341
  `"Out-of-scope paths (optional, comma-separated)"` → `out_of_scope` (CSV, empty allowed).
336
342
  4. `AskUserQuestion` (free text):
@@ -345,7 +351,7 @@ If the user picked `Codebase scan`, gather the codebase-scan-specific inputs:
345
351
  Validation (before Step 4 sharpening):
346
352
 
347
353
  - `scan_scope` 의 각 path 가 `<PROJECT_ROOT>` 안에 실제 존재하는지 `ls` 로 확인 — 없으면 한 질문으로 정정.
348
- - `priority_lenses` 가 `scripts/okstra_ctl/improvement_lenses.py` 의 `LENSES` 부분집합 (1–4) 인지 확인 — 위반 시 enum 표시 + 재선택.
354
+ - `priority_lenses` 가 `~/.okstra/lib/python/okstra_ctl/improvement_lenses.py` 의 `LENSES` 부분집합 (1–4) 인지 확인 — 위반 시 enum 표시 + 재선택. 최종 enforcement 는 Step 6.6 `validate-brief` (`check_codebase_scope`) 가 수행.
349
355
  - `candidate_cap` 이 1–12 정수인지 확인.
350
356
 
351
357
  Once validation passes, jump to Step 2 (task key).
@@ -604,7 +610,7 @@ rather than the original source.
604
610
 
605
611
  1. **scan-scope 경로 존재 검증.** 각 path 를 `ls` / `Read` 로 확인. 없으면 한 질문으로 정정 (path 의 가장 가까운 후보를 `Recommended:` 로 제시).
606
612
  2. **모호 path narrowing.** `"백엔드"`, `"결제 모듈"` 같은 추상 path 는 구체 디렉토리 1개 이상으로 narrowing. codebase-first 추측 후 한 질문으로 확정.
607
- 3. **priority-lenses 화이트리스트 검증.** `scripts/okstra_ctl/improvement_lenses.py` 의 `LENSES` 부분집합인지 확인. out-of-enum 이면 enum 내 가장 가까운 값을 `Recommended:` 로 제시.
613
+ 3. **priority-lenses 화이트리스트 검증.** `~/.okstra/lib/python/okstra_ctl/improvement_lenses.py` 의 `LENSES` 부분집합인지 확인. out-of-enum 이면 enum 내 가장 가까운 값을 `Recommended:` 로 제시.
608
614
  4. **out-of-scope 일관성.** `out-of-scope` 의 각 path 가 `scan-scope` 의 부분집합인지 확인. 무관한 path 면 한 질문으로 제거 또는 scan-scope 에 흡수.
609
615
  5. **lens 우선순위 근거 1줄.** 각 priority lens 가 *왜* 이 scope 에서 우선인지 brief 본문에 한 줄 없으면, codebase 1차 훑은 결과를 `Recommended:` 로 제시 후 한 질문.
610
616
 
@@ -811,13 +817,17 @@ Inspect the finalized brief and recommend the next `okstra-run` task-type:
811
817
  | Heuristic | Recommendation |
812
818
  |---|---|
813
819
  | `Problem / Symptom` describes a repro / log / stack trace / observable error | `error-analysis` |
820
+ | `Source Material` is okstra error-zip anonymized records (variant 1.C, no `scope`) | `error-analysis` |
814
821
  | `Open Questions` is large or `Desired Outcome` is ambiguous / needs classification | `requirements-discovery` |
815
822
  | `scope: codebase` frontmatter | `improvement-discovery` |
816
823
  | Both / ambiguous | `requirements-discovery` (safe default — the phase itself decides branching) |
817
824
 
818
825
  Write the chosen recommendation into the `Recommended next phase:` header
819
- line of the brief file (Step 5). Do NOT auto-spawn `okstra-run` leave the
820
- trigger to the user.
826
+ line of the brief file (Step 5). The template placeholder may enumerate only a
827
+ subset (e.g. `<requirements-discovery | error-analysis>`); always write the
828
+ value chosen from the full Step 6 enum (`requirements-discovery` |
829
+ `error-analysis` | `improvement-discovery`), even when the placeholder omits it.
830
+ Do NOT auto-spawn `okstra-run` — leave the trigger to the user.
821
831
 
822
832
  ## Step 6.5: Reporter batch confirmation (option B precondition)
823
833
 
@@ -0,0 +1,169 @@
1
+ ---
2
+ name: okstra-container
3
+ description: |
4
+ Use to bring up, inspect, or tear down the okstra user-test container runtime for an implementation task — the docker-compose group okstra deploys from a task's worktree so a human can poke at the running build. Trigger words include "okstra container", "okstra container up", "okstra container down", "컨테이너 띄워", "컨테이너 올려", "컨테이너 내려", "컨테이너 상태", "컨테이너 로그", "유저 테스트 환경", "user test environment", "docker compose 띄워줘", "이 task 컨테이너로 띄워", "watcher 멈춰", "stop watcher", "감시 종료".
5
+ ---
6
+
7
+ # OKSTRA Container
8
+
9
+ Single entry point for the okstra user-test container runtime. okstra provisions a docker-compose group from an `implementation` task's worktree (the `docker-compose.yml` at the worktree root), labels it with the task's run-trace so later sub-commands can find the group, and tails it through a tmux watcher pane. This skill drives that lifecycle. Sub-commands:
10
+
11
+ | Sub-command | What it does |
12
+ |---|---|
13
+ | `up` | Integrate the task's stages into the worktree, run `docker compose up -d`, poll healthchecks, and attach a watcher pane. |
14
+ | `status` | Query the running containers (by run-trace label) plus the watcher metadata for a task-key. |
15
+ | `logs` | Point at the watcher findings dir and per-service watcher state (logs live in the tmux pane, not a file). |
16
+ | `stop-watcher` | Reap the watcher/tail panes only — the containers keep running. |
17
+ | `down` | Tear down the containers (label query) and reap orphan watcher panes; `--all` covers every container group in the project. |
18
+
19
+ ## Step 0: Verify okstra runtime + project setup (shared)
20
+
21
+ Before any sub-command, run each of the following commands as a **separate Bash tool call**. Each command starts with the literal token `okstra` so the `Bash(okstra:*)` permission match succeeds. Do **not** wrap any of them in `if`, `eval`, `export`, `$(...)`, `VAR=...`, `||`, or `&&`, and do **not** introduce a `$OKSTRA_CMD` variable or an `npx -y okstra@latest` fallback — those leading tokens defeat the permission match and force a confirmation prompt on every call. The LLM (you) inspects each command's output and decides what to do next in natural language — never in shell.
22
+
23
+ 1. `okstra ensure-installed --runtime claude-code`
24
+ If this exits non-zero, tell the user: "okstra not installed — run `/okstra-setup` first." Then stop. Do **not** try to invoke `npx -y okstra@latest ...` as a fallback.
25
+
26
+ 2. `okstra check-project --json`
27
+ Reads the project from the current working directory. Parse the JSON from stdout. The shape is `{ok, projectRoot, projectJsonPath, projectId}`.
28
+
29
+ - `ok: false` → tell the user: "this project has no okstra setup. Run `/okstra-setup` first." Then stop.
30
+ - `ok: true` → carry `projectRoot` as a literal string and pass it as `--project-root <projectRoot>` to every `okstra container` call below.
31
+
32
+ Docker itself must be installed and running for `up`/`status`/`down` to work. If a sub-command fails with a docker connection error, tell the user to start Docker (Desktop / daemon) and retry — do not try to start docker yourself.
33
+
34
+ Subsequent `okstra container <subcmd>` calls self-bootstrap their Python path, so this skill never needs `okstra paths --shell` / `export PYTHONPATH=...`.
35
+
36
+ ## Step 1: Dispatch by intent
37
+
38
+ Classify the user's request into one sub-command using the trigger table above.
39
+
40
+ - Clear verbs route directly: "띄워/올려/up" → `up`; "상태/status" → `status`; "로그/logs" → `logs`; "watcher 멈춰/stop-watcher" → `stop-watcher`; "내려/down" → `down`.
41
+ - If the request is ambiguous (e.g. "okstra container 좀 봐줘"), present a numbered picker. Recommend the 1–2 most likely facets first, and make the **last option always "직접 입력"** so the user can name any facet:
42
+
43
+ ```
44
+ 무엇을 할까요?
45
+ 1. status — 실행 중인 컨테이너 / watcher 상태 확인 (추천)
46
+ 2. up — 이 task 의 컨테이너를 띄우기
47
+ 3. 직접 입력 (up / status / logs / stop-watcher / down 중에서)
48
+ ```
49
+
50
+ The full facet list is the `Sub-command | What it does` table above — never hide a facet; if the user picks "직접 입력", let them name any row.
51
+
52
+ When the user chains multiple facets in one message (e.g. "올렸다가 상태 보여줘"), execute them sequentially — Step 0 runs once, each sub-command section runs once.
53
+
54
+ Every sub-command needs a **task-key** (`<project-id>:<task-group>:<task-id>`) — the container group is bound to one implementation task's worktree. Resolve it the same way across sub-commands:
55
+
56
+ 1. If the user gave a full task-key, use it.
57
+ 2. If the user gave only a task-id, resolve it via `okstra resolve-task-key <task-id> --project-root <projectRoot> --json` and branch on the stdout `matches[]`: single → use its `taskKey`; multiple → list candidates and ask (3-option picker); none → report not found.
58
+ 3. `down --all` is the only call that does not need a task-key (see `down`).
59
+
60
+ ---
61
+
62
+ ## up
63
+
64
+ Trigger phrases: "okstra container up", "컨테이너 띄워", "컨테이너 올려", "유저 테스트 환경 띄워", "docker compose 올려줘", "이 task 컨테이너로 띄워".
65
+
66
+ Brings up the task's container group: integrates the implementation stages into the task worktree, synthesizes the compose env override, runs `docker compose up -d`, polls healthchecks, and attaches a tmux watcher pane.
67
+
68
+ **Preconditions** (state them if unmet, do not guess):
69
+ - The task must be an `implementation` task whose worktree is registered in `~/.okstra/worktrees/registry.json`. If `up` fails with "task worktree 가 registry 에 없" → the task has no implementation worktree yet; tell the user to run the `implementation` phase first.
70
+ - The worktree root must contain a `docker-compose.yml`. If `up` fails with "worktree 루트에 ... 가 없습니다" → no compose file shipped with this task; surface the message verbatim.
71
+ - Every stage declared in the approved plan's Stage Map must be `done`. `up` integrates the whole task, so a partially-finished task (e.g. only stage 1 of 3 done) is refused rather than deployed as if complete (gate in `stage_targets.py`, shared with whole-task `final-verification`). If `up` fails with `final-verification(whole-task): stage N not done — run implementation --stage N first`, surface that message verbatim and tell the user to finish the named stage via the `implementation` phase with `--stage N`.
72
+
73
+ Run:
74
+
75
+ ```bash
76
+ okstra container up --project-root <projectRoot> --task-key <task-key>
77
+ ```
78
+
79
+ Parse the stdout JSON and report the provisioned services and the watcher pane. If a service failed its healthcheck, the call surfaces the failing services and the `docker compose ... logs` line to inspect — relay that line; do not invent your own.
80
+
81
+ After a successful `up`, tell the user how to reach the running build (the compose file's published ports) and that `okstra container status <task-key>` / `okstra container down <task-key>` manage it from here.
82
+
83
+ ---
84
+
85
+ ## status
86
+
87
+ Trigger phrases: "okstra container status", "컨테이너 상태", "컨테이너 떠 있어?", "container status".
88
+
89
+ Reports the running containers (queried by run-trace label — the source of truth for "is it up") plus the watcher metadata recorded in the registry.
90
+
91
+ ```bash
92
+ okstra container status --project-root <projectRoot> --task-key <task-key>
93
+ ```
94
+
95
+ Parse the stdout JSON (`{projectName, containers, watchers}`) and report:
96
+
97
+ | Field | Meaning |
98
+ |---|---|
99
+ | `projectName` | the compose project name (label group) |
100
+ | `containers` | running containers found by label — empty array means nothing is up |
101
+ | `watchers` | per-service watcher metadata from the registry (auxiliary; may lag the label query) |
102
+
103
+ If `containers` is empty, say the group is not running and offer `up`. Treat the label query — not the registry `watchers` — as authoritative for whether containers are alive.
104
+
105
+ ---
106
+
107
+ ## logs
108
+
109
+ Trigger phrases: "okstra container logs", "컨테이너 로그", "container logs", "로그 어디서 봐".
110
+
111
+ The live log stream lives in the tmux watcher pane, not in a file. This sub-command points at the watcher findings directory and the per-service watcher state.
112
+
113
+ ```bash
114
+ okstra container logs --project-root <projectRoot> --task-key <task-key>
115
+ ```
116
+
117
+ Add `--service <name>` to scope to one service:
118
+
119
+ ```bash
120
+ okstra container logs --project-root <projectRoot> --task-key <task-key> --service <service>
121
+ ```
122
+
123
+ Parse the stdout JSON (`{watchersDir, watchers}`). Report the `watchersDir` host path (under `~/.okstra/...`-style project artifacts — display the absolute path the CLI returns) and the watcher entries. Explain that the live stream is in the attached tmux pane; to see raw container logs the user runs `docker compose -p <projectName> logs -f <service>` (get `<projectName>` from `status`).
124
+
125
+ ---
126
+
127
+ ## stop-watcher
128
+
129
+ Trigger phrases: "okstra container stop-watcher", "watcher 멈춰", "감시 종료", "stop watcher", "watcher 만 꺼줘".
130
+
131
+ Reaps the watcher/tail tmux panes for the task **without stopping the containers**. Use when the user wants to silence the monitor but keep the build running.
132
+
133
+ ```bash
134
+ okstra container stop-watcher --project-root <projectRoot> --task-key <task-key>
135
+ ```
136
+
137
+ Parse the stdout JSON (`{reapedPanes, note}`). Confirm which panes were reaped and that the containers are still running (the `note` says so). If the user actually wants the containers gone, route to `down`.
138
+
139
+ ---
140
+
141
+ ## down
142
+
143
+ Trigger phrases: "okstra container down", "컨테이너 내려", "컨테이너 종료", "container down", "전부 내려줘".
144
+
145
+ Tears down the container group (removes containers found by the run-trace label) and reaps orphan watcher panes.
146
+
147
+ Single task:
148
+
149
+ ```bash
150
+ okstra container down --project-root <projectRoot> --task-key <task-key>
151
+ ```
152
+
153
+ Every container group in the project (no task-key needed):
154
+
155
+ ```bash
156
+ okstra container down --project-root <projectRoot> --all
157
+ ```
158
+
159
+ Parse the stdout JSON (`{downed, orphanPanesReaped}`). Report each torn-down `projectName` and the orphan panes reaped. `down` removes containers with `docker rm -f` — confirm with the user before running `--all`, since it takes down every okstra container group in the project at once. A single-task `down` is safe to run directly once the task-key is resolved.
160
+
161
+ ---
162
+
163
+ ## Output Rules (shared)
164
+
165
+ - Responses should be concise and written in Korean unless the user requests otherwise.
166
+ - The stdout JSON from each `okstra container` call is the source of truth — do not run raw `docker` commands to second-guess it unless the CLI fails and the user explicitly asks for a manual fallback.
167
+ - Show the resolved `<task-key>` in the heading so the user can confirm disambiguation.
168
+ - Surface failure messages from the CLI verbatim (compose-up failures, missing worktree/compose file) — do not paraphrase the remediation line.
169
+ - Display container/service states as-is from the JSON; do not normalize or remap.