okstra 0.21.1 → 0.22.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.
- package/docs/kr/architecture.md +1 -0
- package/docs/project-structure-overview.md +53 -0
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/bin/okstra-trace-cleanup.sh +44 -11
- package/runtime/prompts/profiles/_common-contract.md +12 -0
- package/runtime/prompts/profiles/release-handoff.md +2 -1
- package/runtime/python/okstra_ctl/index.py +2 -1
- package/runtime/python/okstra_ctl/pr_template.py +126 -0
- package/runtime/python/okstra_ctl/render.py +3 -0
- package/runtime/python/okstra_ctl/run.py +41 -2
- package/runtime/python/okstra_ctl/seeding.py +19 -0
- package/runtime/python/okstra_ctl/workers.py +20 -0
- package/runtime/skills/okstra-run/SKILL.md +27 -8
- package/runtime/skills/okstra-run/templates/pr-body.template.md +41 -0
- package/runtime/templates/reports/final-report.template.md +1 -0
- package/runtime/templates/reports/settings.template.json +4 -2
- package/src/render-bundle.mjs +2 -1
package/docs/kr/architecture.md
CHANGED
|
@@ -838,6 +838,7 @@ Claude가 작성하는 최종 보고서는 brief에 더 구체적인 형식이
|
|
|
838
838
|
- `scripts/okstra-codex-exec.sh`, `scripts/okstra-gemini-exec.sh` 는 dispatch 마다 prompt path 옆에 `<prompt>.log` sidecar 를 만들고 stdout 을 거기로 mirror 합니다 (`tee`, `PIPESTATUS[0]` 로 종료코드 보존). stderr 은 같은 파일에 append (subagent stderr 캡처 contract 보존), 매 dispatch 시 truncate. 호출 subagent 의 `BashOutput` 폴링은 60s 간격이라 long-running run (analysis 의 large-codebase scan, implementation 의 cargo / pytest) 동안 사용자가 stalled state 를 탐지할 수 없는 문제를 해소합니다.
|
|
839
839
|
- `$TMUX` 가 셋팅된 lead 환경이면 wrapper 가 sibling pane 을 자동 분할해 `tail -F <log-path>` 를 띄웁니다. pane title 은 `<cli>-<role>-trace` (e.g. `codex-worker-trace`, `gemini-worker-trace`); role 은 wrapper 의 5번째 optional positional 인자이며, 누락 시 기본값 `worker` 로 떨어집니다. caller 가 다른 라벨(예: `executor`)을 원하면 5번째 인자로 명시해야 합니다. focus 는 caller pane 으로 복귀하고, CLI 종료 후 pane 은 유지돼 스크롤백 가능. `$TMUX` 미설정, split 실패, 구버전 tmux 등 모든 경로는 silent degrade.
|
|
840
840
|
- **Claude `/exit` 시 자동 정리**: trace pane 의 `tail -F` 는 tmux 셸의 자식이라 Claude 가 종료돼도 살아남는 문제를 막기 위해, wrapper 는 spawn 한 pane id 를 caller `$TMUX_PANE` 으로 키된 registry (`${TMPDIR:-/tmp}/okstra-trace-panes/<caller-pane>.list`) 에 append 합니다. `templates/reports/settings.template.json` 의 `hooks.SessionEnd` 가 `$HOME/.okstra/bin/okstra-trace-cleanup.sh` 를 호출해 자신의 caller pane registry 만 읽어 `tmux kill-pane` 합니다. caller pane 단위로 scope 가 잡혀 있어 같은 tmux 세션에 Claude 인스턴스가 여러 개 떠 있어도 서로의 trace pane 을 죽이지 않습니다. tmux 가 없거나 stale pane id 인 경우 silent degrade.
|
|
841
|
+
- **Phase 종료 시 사용자 확인**: 매 phase 의 마지막 단계로 lead 가 `okstra-trace-cleanup.sh --list` 로 등록된 pane 목록을 출력한 뒤 사용자에게 "모두 닫기 / 그대로 두기" 양자택일을 묻고 응답대로 처리합니다 (`prompts/profiles/_common-contract.md` 의 *Phase wrap-up* 항목). `$TMUX_PANE` 미설정 환경에서는 단계 자체가 silent-skip. `--list` 모드는 pane 을 죽이지 않고 `<pane_id>\t<pane_title>` 만 출력하므로 사용자가 무엇이 닫힐지 시각적으로 확인할 수 있습니다.
|
|
841
842
|
- 디스크 누적은 `okstra-logs` skill 이 read-only 로 인벤토리 + cleanup 명령을 제안합니다 (실행은 사용자 copy-paste).
|
|
842
843
|
|
|
843
844
|
### Linked-worktree `.git/` write 권한 (codex / gemini)
|
|
@@ -385,4 +385,57 @@ okstra/
|
|
|
385
385
|
|
|
386
386
|
---
|
|
387
387
|
|
|
388
|
+
## 부록 A. 보고서 row ID prefix 용어집
|
|
389
|
+
|
|
390
|
+
okstra 가 산출하는 final-report 와 worker-result 의 표·리스트는 섹션별 두-글자 prefix 로 행을 식별합니다. 같은 prefix 는 어느 task / phase / project 에서도 의미가 동일하므로, 한 run 의 `C-005` 를 후속 run 이나 follow-up task 에서 그대로 인용해 cross-reference 할 수 있습니다.
|
|
391
|
+
|
|
392
|
+
### A.1 Final-report 행 ID
|
|
393
|
+
|
|
394
|
+
| Prefix | 의미 | 정의 위치 (template / contract) |
|
|
395
|
+
|--------|------|----------------------------------|
|
|
396
|
+
| `P-NNN` | Problem / Verification Target — 본 run 이 해결하려는 문제·검증 대상 한 줄 요약 | `## Summary of the Problem or Verification Target` |
|
|
397
|
+
| `C-NNN` | Consensus — 모든 worker 가 합의한 결론 | `### 1.1 Consensus` |
|
|
398
|
+
| `D-NNN` | Differences — worker 간 의미 있는 불일치 | `### 1.2 Differences` |
|
|
399
|
+
| `E-NNN` | Primary Evidence — 본 verdict 의 1차 근거 | `### 3.1 Primary Evidence` |
|
|
400
|
+
| `S-NNN` | Secondary Evidence / Alternate Interpretations — 보조 근거·대안 해석 | `### 3.2 Secondary Evidence or Alternate Interpretations` |
|
|
401
|
+
| `R-NNN` | Missing Information / Risks — 누락 정보·인지된 위험 | `## 4. Missing Information and Risks` |
|
|
402
|
+
| `RR-NNN` | Residual Risk — verdict 를 막진 않지만 추적해야 하는 잔여 위험 | `### 4.2 Residual Risk` |
|
|
403
|
+
| `OF-NNN` | Option File-structure rows — implementation-planning 의 옵션별 파일 책임 | `### 4.5.1 Option Candidates` |
|
|
404
|
+
| `DM-NNN` | Dependency / Migration risk — 순서·백필·feature-flag 선행 조건 | `### 4.5.5 Dependency / Migration Risk` |
|
|
405
|
+
| `RB-NNN` | Rollback step — revert 경로·트리거 신호 | `### 4.5.7 Rollback Strategy` |
|
|
406
|
+
| `OQ-NNN` | Open Question — pre-planning 에서 발견된 모호점 | `### 4.5.9 Open Questions` |
|
|
407
|
+
| `FU-NNN` | Follow-up Task — 본 run 범위 밖이지만 후속 처리해야 하는 항목 | `## 7. Follow-up Tasks` |
|
|
408
|
+
| `FU-VNN` | Verifier-harness Follow-up — verifier 환경 결함을 잡는 후속 (okstra 본체 fix) | `## 7.` (lead synthesis, `Origin = manual` 표기) |
|
|
409
|
+
| `A1, A2, …` | Additional materials requested — 다음 run 시작 전 사용자가 채워야 할 자료 요청. **run 사이에 ID 유지**, 답변 완료 시 `Status` 만 갱신 | `### 5.1 추가 자료 요청` |
|
|
410
|
+
| `Q1, Q2, …` | Questions for the user — 동일 규칙으로 run 간 ID 유지 | `### 5.2 사용자 확인 질문` |
|
|
411
|
+
|
|
412
|
+
### A.2 Worker-result 행 ID (worker 가 자기 output 안에서 매기는 ID)
|
|
413
|
+
|
|
414
|
+
`okstra-team-contract` 가 정의하는 worker output 6개 섹션 중 1, 2 섹션이 prefix 를 갖습니다.
|
|
415
|
+
|
|
416
|
+
| Prefix | 의미 | Worker output 섹션 |
|
|
417
|
+
|--------|------|-------------------|
|
|
418
|
+
| `F-NNN` | Finding — worker 의 핵심 발견 | §1 Findings |
|
|
419
|
+
| `M-NNN` | Missing Information / Assumption — worker 가 인지한 누락 정보·가정 | §2 Missing Information or Assumptions |
|
|
420
|
+
|
|
421
|
+
§3 Safe Areas, §4 Uncertain Points, §5 Recommended Next Actions, §6 Specialization Lens 는 표 형태가 아니라 별도 prefix 가 없습니다.
|
|
422
|
+
|
|
423
|
+
### A.3 Phase / 입력 도메인 약어
|
|
424
|
+
|
|
425
|
+
| 약어 | 의미 |
|
|
426
|
+
|------|------|
|
|
427
|
+
| `AC` | Acceptance Criteria — `final-verification-input.template.md` 의 `## Acceptance Criteria` 섹션에 사용자가 적어두는 합격 기준 (예: `AC1`, `AC2`, … `AC10`). Verifier 는 한 AC 당 한 줄로 `covered` / `not covered` 판정 |
|
|
428
|
+
| `OQ` | Open Question — implementation-planning 의 `### 4.5.9` 행 ID. final-report 의 다른 섹션에서 `OQ-A`, `OQ-D` 처럼 알파벳 인덱스로도 인용됨 |
|
|
429
|
+
| `FU-V` | Follow-up — Verifier harness 카테고리. `okstra-codex-exec.sh`, worktree provisioner 등 okstra 본체의 verifier 환경 결함을 잡는 후속 task |
|
|
430
|
+
|
|
431
|
+
### A.4 Ticket / Verdict 토큰
|
|
432
|
+
|
|
433
|
+
| 토큰 | 위치 | 허용 값 |
|
|
434
|
+
|------|------|---------|
|
|
435
|
+
| `Verdict Token` | `## 2. Final Verdict` | `accepted`, `conditional-accept`, `blocked`, `not-applicable` (final-verification 만 의미 있음, release-handoff 가 진입 gate 로 사용) |
|
|
436
|
+
| `Direction` | `## 2. Final Verdict` | `continue-investigation`, `begin-implementation`, `approve`, `reject`, `hold` |
|
|
437
|
+
| `Ticket ID` | 모든 행 표에 컬럼으로 등장 | brief 의 `Issue / Ticket` 값 → 비어 있으면 `Task ID` → 둘 다 없으면 `unknown` |
|
|
438
|
+
|
|
439
|
+
---
|
|
440
|
+
|
|
388
441
|
*작성일: 2026-05-14 · 분석 대상: `claude/objective-einstein-250add` 브랜치 기준 okstra v0.20.1*
|
package/package.json
CHANGED
package/runtime/BUILD.json
CHANGED
|
@@ -1,26 +1,48 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
#
|
|
3
|
-
# okstra-trace-cleanup.sh —
|
|
3
|
+
# okstra-trace-cleanup.sh — manage tmux trace panes spawned by okstra worker
|
|
4
4
|
# wrappers (`okstra-codex-exec.sh`, `okstra-gemini-exec.sh`) for the current
|
|
5
5
|
# Claude Code session.
|
|
6
6
|
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
7
|
+
# Two modes:
|
|
8
|
+
# (default) kill every registered pane and remove the registry file.
|
|
9
|
+
# Used by the `SessionEnd` hook in
|
|
10
|
+
# `templates/reports/settings.template.json` so panes do not
|
|
11
|
+
# outlive Claude `/exit`. Also callable manually by the lead at
|
|
12
|
+
# phase-end (see `_common-contract.md`).
|
|
13
|
+
# --list print one line per registered pane (`<pane_id>\t<pane_title>`)
|
|
14
|
+
# so the lead can show the user what *would* be closed before
|
|
15
|
+
# asking. Exits 0 with empty stdout when nothing is tracked.
|
|
13
16
|
#
|
|
14
|
-
#
|
|
15
|
-
# instances
|
|
16
|
-
# trace panes.
|
|
17
|
+
# Pane registry is keyed by the caller's `$TMUX_PANE` — the pane Claude
|
|
18
|
+
# itself is attached to. Multiple Claude instances in the same tmux session
|
|
19
|
+
# therefore do not stomp each other's trace panes.
|
|
17
20
|
#
|
|
18
21
|
# Failures are tolerated silently — a stale pane id, missing $TMUX, or a
|
|
19
22
|
# locked tmux client must never prevent Claude from exiting cleanly.
|
|
20
23
|
|
|
21
24
|
set -u
|
|
22
25
|
|
|
23
|
-
|
|
26
|
+
MODE="kill"
|
|
27
|
+
case "${1:-}" in
|
|
28
|
+
"") MODE="kill" ;;
|
|
29
|
+
--list) MODE="list" ;;
|
|
30
|
+
--dry-run) MODE="list" ;; # alias
|
|
31
|
+
-h|--help)
|
|
32
|
+
cat <<'USAGE'
|
|
33
|
+
usage: okstra-trace-cleanup.sh [--list]
|
|
34
|
+
|
|
35
|
+
(no args) kill every pane registered for $TMUX_PANE; remove registry file.
|
|
36
|
+
--list print "<pane_id>\t<pane_title>" per registered pane; no kill.
|
|
37
|
+
--dry-run alias for --list.
|
|
38
|
+
USAGE
|
|
39
|
+
exit 0 ;;
|
|
40
|
+
*)
|
|
41
|
+
printf 'okstra-trace-cleanup.sh: unknown option: %s\n' "$1" >&2
|
|
42
|
+
exit 2 ;;
|
|
43
|
+
esac
|
|
44
|
+
|
|
45
|
+
# No tmux pane context → nothing to clean / list.
|
|
24
46
|
if [[ -z "${TMUX_PANE:-}" ]]; then
|
|
25
47
|
exit 0
|
|
26
48
|
fi
|
|
@@ -33,6 +55,17 @@ if [[ ! -f "$registry_file" ]]; then
|
|
|
33
55
|
exit 0
|
|
34
56
|
fi
|
|
35
57
|
|
|
58
|
+
if [[ "$MODE" == "list" ]]; then
|
|
59
|
+
while IFS= read -r pane_id; do
|
|
60
|
+
[[ -n "$pane_id" ]] || continue
|
|
61
|
+
# `display-message -p` resolves a *live* pane's title; for stale ids
|
|
62
|
+
# tmux exits non-zero — fall back to an empty title rather than failing.
|
|
63
|
+
title=$(tmux display-message -p -t "$pane_id" '#{pane_title}' 2>/dev/null || true)
|
|
64
|
+
printf '%s\t%s\n' "$pane_id" "$title"
|
|
65
|
+
done < "$registry_file"
|
|
66
|
+
exit 0
|
|
67
|
+
fi
|
|
68
|
+
|
|
36
69
|
while IFS= read -r pane_id; do
|
|
37
70
|
[[ -n "$pane_id" ]] || continue
|
|
38
71
|
tmux kill-pane -t "$pane_id" 2>/dev/null || true
|
|
@@ -20,6 +20,18 @@ profile document.
|
|
|
20
20
|
- This rule does NOT relax any phase-specific Forbidden actions list; safety rules in the per-profile document remain in force regardless of the user's authority.
|
|
21
21
|
- Anti-escalation rule (shared):
|
|
22
22
|
- treating "다음 단계 진행해" or equivalent user phrases as authorisation to start a *different* lifecycle phase is forbidden. The next phase begins only in a separate okstra run launched with the new `--task-type`. Per-profile documents may further restrict this within their own scope.
|
|
23
|
+
- Phase wrap-up — worker trace pane disposition (shared, MUST be the *last* step before returning control to the user):
|
|
24
|
+
- Codex / Gemini worker wrappers spawn `tail -F` trace panes in the lead's tmux session (`codex-<role>-trace`, `gemini-<role>-trace`). They survive every worker invocation by design so the operator can scroll back through the final output, but accumulate across phases and clutter the screen.
|
|
25
|
+
- When `$TMUX_PANE` is set, after the final-report file has been written and the routing recommendation has been issued, the lead MUST run `$HOME/.okstra/bin/okstra-trace-cleanup.sh --list` exactly once. The output is a tab-separated `<pane_id>\t<pane_title>` list of every trace pane registered for this Claude session.
|
|
26
|
+
- If the list is empty, skip the question — there is nothing to ask about.
|
|
27
|
+
- Otherwise the lead MUST present the user with a strict binary choice **before** declaring the phase complete. Use one prompt of this shape (Korean preferred, English acceptable if the rest of the run is in English):
|
|
28
|
+
> 현재 phase 종료 시점입니다. 다음 worker trace pane 이 열려 있습니다 — 닫을까요?
|
|
29
|
+
> <인용된 `--list` 출력>
|
|
30
|
+
> (예) 모두 닫기 / (아니오) 그대로 두기
|
|
31
|
+
- On `예` / `y` / `close` → run `$HOME/.okstra/bin/okstra-trace-cleanup.sh` (no args) and report the kill count back in one sentence.
|
|
32
|
+
- On `아니오` / `n` / `keep` → leave the panes intact; remind the user that they will be cleaned up automatically when Claude `/exit` fires the `SessionEnd` hook.
|
|
33
|
+
- The question MUST be a clean yes/no — do NOT offer "close some / keep some" partial answers, do NOT propose alternatives like "close only codex panes". The whole-set decision keeps the wrap-up predictable.
|
|
34
|
+
- This step is mandatory for every phase (`requirements-discovery`, `error-analysis`, `implementation-planning`, `implementation`, `final-verification`, `release-handoff`). It is silent-skipped when `$TMUX_PANE` is unset (lead running outside tmux); the lead MUST NOT fabricate a synthetic pane list in that case.
|
|
23
35
|
- Clarification request policy (shared — applies whenever a profile uses `## 5. Clarification Requests for the Next Run`):
|
|
24
36
|
- section 5 MUST be split into two distinct sub-sections per `final-report-template.md` — `5.1 추가 자료 요청 (Additional Materials Requested)` for files/logs/screenshots/links the user must attach, and `5.2 사용자 확인 질문 (Questions for the User)` for decisions or facts only the user can confirm. Never mix material requests and decision questions in the same row or list.
|
|
25
37
|
- write every entry in full, descriptive sentences that a non-developer can act on without further context. Avoid abbreviations and internal jargon. For each material request, state *why* it is needed, *where* the user can find it, and *where* to place it. For each question, state *why* the answer changes the next step, *what* is being asked in a complete sentence, and *what shape of answer* is expected (예/아니오, 보기 중 하나, 숫자/날짜, 짧은 서술 등); supply concrete option choices when applicable.
|
|
@@ -34,9 +34,10 @@
|
|
|
34
34
|
- `cancel` — end the run without executing push or PR commands; record the cancellation in the final report.
|
|
35
35
|
- Inline drafting rules (Claude lead):
|
|
36
36
|
- read the run brief, the cited final-verification report, `git log --oneline <base>..HEAD`, and `git diff <base>..HEAD --stat` to ground the drafted text in actual committed changes.
|
|
37
|
+
- **PR body template** — the run context exposes `PR_TEMPLATE_PATH` (resolved by the prepare step in priority order: per-run override → `<project_root>/.project-docs/okstra/project.json` `prTemplatePath` → `~/.okstra/config.json` `prTemplatePath` → bundled default at `~/.claude/skills/okstra-run/templates/pr-body.template.md`) along with `PR_TEMPLATE_SOURCE` indicating which scope was used. The lead MUST `Read` this file verbatim, strip HTML comments, then fill in the placeholders. Do NOT hard-code a section list — the template is the source of truth for the structure. If the resolved file is missing at draft time, abort the run with a clear error rather than inventing a structure.
|
|
37
38
|
- produce **two artifacts** before showing them to the user:
|
|
38
39
|
1. **PR title** — by default the subject of the most recent implementation commit, or a concise Conventional Commits-style summary of the committed range.
|
|
39
|
-
2. **PR body** — markdown
|
|
40
|
+
2. **PR body** — markdown filled from `PR_TEMPLATE_PATH`. The user-confirmation step's diff (Q3 `edit then proceed`) is computed against the filled template, not against the raw template file.
|
|
40
41
|
- Allowed actions during the run (Claude lead only):
|
|
41
42
|
- read-only inspection: `git status`, `git status --short`, `git diff`, `git log`, `git rev-parse`, `git ls-remote --heads origin <name>`, `gh pr list --head <branch>`, `gh pr view <url>`.
|
|
42
43
|
- feature-branch push (only when the user picked `push + PR`): `git push -u origin <current-branch>`. The pushed ref MUST be the feature branch — never the chosen base branch.
|
|
@@ -57,6 +57,7 @@ def record_start(home: Path, *, project_id: str, project_root: str,
|
|
|
57
57
|
run_dir_rel: str, final_report_rel: str,
|
|
58
58
|
argv: list, cwd: str,
|
|
59
59
|
env_overrides: dict,
|
|
60
|
+
okstra_version: str = "",
|
|
60
61
|
brief_sha256: str = "",
|
|
61
62
|
initial_status: str = "running",
|
|
62
63
|
final_status_rel: str = "") -> str:
|
|
@@ -122,7 +123,7 @@ def record_start(home: Path, *, project_id: str, project_root: str,
|
|
|
122
123
|
_replace_or_append_project_row(home, project_id, run_id, row)
|
|
123
124
|
save_invocation(home, project_id, task_group, task_id, task_type, run_seq, {
|
|
124
125
|
"runId": run_id,
|
|
125
|
-
"okstraVersion": os.environ.get("OKSTRA_SCRIPT_VERSION", ""),
|
|
126
|
+
"okstraVersion": okstra_version or os.environ.get("OKSTRA_SCRIPT_VERSION", ""),
|
|
126
127
|
"invokedAt": when,
|
|
127
128
|
"cwd": cwd,
|
|
128
129
|
"argv": argv,
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""PR body 템플릿 경로 해석.
|
|
2
|
+
|
|
3
|
+
release-handoff 단계에서 lead 가 PR 본문을 작성할 때 사용하는 마크다운
|
|
4
|
+
템플릿의 경로를 결정한다. 우선순위:
|
|
5
|
+
|
|
6
|
+
1. per-run override (okstra-run Step 6 에서 입력)
|
|
7
|
+
2. project: <project_root>/.project-docs/okstra/project.json 의 ``prTemplatePath``
|
|
8
|
+
3. global: ~/.okstra/config.json 의 ``prTemplatePath``
|
|
9
|
+
4. default: 스킬 설치 디렉터리의 ``okstra-run/templates/pr-body.template.md``
|
|
10
|
+
|
|
11
|
+
경로는 절대경로 또는 ``~`` 시작 경로를 권장한다. 상대경로일 경우 project
|
|
12
|
+
스코프는 ``project_root`` 기준, override 는 호출자 cwd 기준으로 해석한다.
|
|
13
|
+
global 스코프에서 상대경로는 모호하므로 거절한다.
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import os
|
|
19
|
+
from dataclasses import dataclass
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
|
|
22
|
+
_DEFAULT_FILENAME = "pr-body.template.md"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class PrTemplateError(Exception):
|
|
26
|
+
"""invalid PR template configuration — surface to user."""
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass(frozen=True)
|
|
30
|
+
class ResolvedPrTemplate:
|
|
31
|
+
path: Path
|
|
32
|
+
source: str # "override" | "project" | "global" | "default"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _okstra_home() -> Path:
|
|
36
|
+
override = os.environ.get("OKSTRA_HOME", "").strip()
|
|
37
|
+
return Path(override) if override else Path.home() / ".okstra"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _default_template_candidates() -> list[Path]:
|
|
41
|
+
"""디폴트 템플릿 후보 경로들 (우선순위 순)."""
|
|
42
|
+
out: list[Path] = []
|
|
43
|
+
env_dir = os.environ.get("OKSTRA_SKILLS_DIR", "").strip()
|
|
44
|
+
if env_dir:
|
|
45
|
+
out.append(Path(env_dir) / "okstra-run" / "templates" / _DEFAULT_FILENAME)
|
|
46
|
+
out.append(
|
|
47
|
+
Path.home() / ".claude" / "skills" / "okstra-run" / "templates" / _DEFAULT_FILENAME
|
|
48
|
+
)
|
|
49
|
+
return out
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _read_json_field(path: Path, field: str) -> str:
|
|
53
|
+
if not path.is_file():
|
|
54
|
+
return ""
|
|
55
|
+
try:
|
|
56
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
57
|
+
except (OSError, json.JSONDecodeError):
|
|
58
|
+
return ""
|
|
59
|
+
if not isinstance(data, dict):
|
|
60
|
+
return ""
|
|
61
|
+
return str(data.get(field) or "").strip()
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _resolve_project_relative(value: str, project_root: Path) -> Path:
|
|
65
|
+
p = Path(value).expanduser()
|
|
66
|
+
return p if p.is_absolute() else (Path(project_root) / p).resolve()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def resolve_pr_template_path(
|
|
70
|
+
project_root: Path,
|
|
71
|
+
override_path: str = "",
|
|
72
|
+
) -> ResolvedPrTemplate:
|
|
73
|
+
"""우선순위에 따라 PR 템플릿 경로를 해석한다.
|
|
74
|
+
|
|
75
|
+
Raises:
|
|
76
|
+
PrTemplateError: 설정된 경로가 존재하지 않거나, 디폴트조차 찾지 못한 경우.
|
|
77
|
+
"""
|
|
78
|
+
project_root = Path(project_root)
|
|
79
|
+
|
|
80
|
+
# 1) per-run override
|
|
81
|
+
ov = (override_path or "").strip()
|
|
82
|
+
if ov:
|
|
83
|
+
p = _resolve_project_relative(ov, project_root)
|
|
84
|
+
if not p.is_file():
|
|
85
|
+
raise PrTemplateError(f"override PR template not found: {p}")
|
|
86
|
+
return ResolvedPrTemplate(path=p, source="override")
|
|
87
|
+
|
|
88
|
+
# 2) project.json
|
|
89
|
+
pj_path = Path(project_root) / ".project-docs" / "okstra" / "project.json"
|
|
90
|
+
pj_val = _read_json_field(pj_path, "prTemplatePath")
|
|
91
|
+
if pj_val:
|
|
92
|
+
p = _resolve_project_relative(pj_val, project_root)
|
|
93
|
+
if not p.is_file():
|
|
94
|
+
raise PrTemplateError(
|
|
95
|
+
f"project.json prTemplatePath points to missing file: {p} "
|
|
96
|
+
f"(configured in {pj_path})"
|
|
97
|
+
)
|
|
98
|
+
return ResolvedPrTemplate(path=p, source="project")
|
|
99
|
+
|
|
100
|
+
# 3) global config
|
|
101
|
+
gc_path = _okstra_home() / "config.json"
|
|
102
|
+
gv = _read_json_field(gc_path, "prTemplatePath")
|
|
103
|
+
if gv:
|
|
104
|
+
p = Path(gv).expanduser()
|
|
105
|
+
if not p.is_absolute():
|
|
106
|
+
raise PrTemplateError(
|
|
107
|
+
f"global config prTemplatePath must be absolute or start with '~/': got {gv!r} "
|
|
108
|
+
f"(configured in {gc_path})"
|
|
109
|
+
)
|
|
110
|
+
if not p.is_file():
|
|
111
|
+
raise PrTemplateError(
|
|
112
|
+
f"global prTemplatePath missing: {p} (configured in {gc_path})"
|
|
113
|
+
)
|
|
114
|
+
return ResolvedPrTemplate(path=p, source="global")
|
|
115
|
+
|
|
116
|
+
# 4) default
|
|
117
|
+
for cand in _default_template_candidates():
|
|
118
|
+
if cand.is_file():
|
|
119
|
+
return ResolvedPrTemplate(path=cand, source="default")
|
|
120
|
+
|
|
121
|
+
raise PrTemplateError(
|
|
122
|
+
"no PR template available: default skill template not found. "
|
|
123
|
+
f"Searched: {', '.join(str(c) for c in _default_template_candidates())}. "
|
|
124
|
+
"Reinstall okstra (`npx okstra install`) or set prTemplatePath in "
|
|
125
|
+
"project.json / ~/.okstra/config.json."
|
|
126
|
+
)
|
|
@@ -694,6 +694,7 @@ def render_run_manifest(run_manifest_path: str, ctx: dict) -> None:
|
|
|
694
694
|
workflow = task_manifest.get("workflow", {}) if isinstance(task_manifest.get("workflow"), dict) else {}
|
|
695
695
|
payload = {
|
|
696
696
|
"schemaVersion": "1.0",
|
|
697
|
+
"okstraVersion": ctx.get("OKSTRA_VERSION", ""),
|
|
697
698
|
"projectId": ctx.get("PROJECT_ID", ""),
|
|
698
699
|
"taskGroup": ctx.get("TASK_GROUP", ""),
|
|
699
700
|
"taskId": ctx.get("TASK_ID", ""),
|
|
@@ -907,6 +908,7 @@ def render_task_index(template_path: str, output_path: str, ctx: dict) -> None:
|
|
|
907
908
|
"{{RELATED_TASKS_INLINE}}": ctx.get("RELATED_TASKS_INLINE", "None"),
|
|
908
909
|
"{{RECOMMENDED_ANALYSERS}}": ", ".join(task_manifest.get("recommendedWorkers", [])),
|
|
909
910
|
"{{LEAD_MODEL}}": rc.get("leadModel", ctx.get("LEAD_MODEL_DISPLAY", "")),
|
|
911
|
+
"{{OKSTRA_VERSION}}": ctx.get("OKSTRA_VERSION", ""),
|
|
910
912
|
"{{LATEST_RUN_RELATIVE_PATH}}": task_manifest.get("latestRunPath", ctx.get("LATEST_RUN_RELATIVE_PATH", "")),
|
|
911
913
|
"{{LATEST_REPORT_RELATIVE_PATH}}": task_manifest.get("latestReportPath", ctx.get("LATEST_REPORT_RELATIVE_PATH", "")),
|
|
912
914
|
"{{TEAM_STATE_RELATIVE_PATH}}": task_manifest.get("teamStatePath", ctx.get("TEAM_STATE_RELATIVE_PATH", "")),
|
|
@@ -1161,6 +1163,7 @@ def render_template_file(template_path: str, output_path: str, ctx: dict) -> Non
|
|
|
1161
1163
|
"{{REPORT_WRITER_WORKER_ERRORS_SIDECAR_RELATIVE_PATH}}": ctx.get("REPORT_WRITER_WORKER_ERRORS_SIDECAR_RELATIVE_PATH", ""),
|
|
1162
1164
|
"{{LEAD_MODEL}}": lead_model,
|
|
1163
1165
|
"{{LEAD_MODEL_EXECUTION_VALUE}}": lead_model_execution,
|
|
1166
|
+
"{{OKSTRA_VERSION}}": ctx.get("OKSTRA_VERSION", ""),
|
|
1164
1167
|
"{{CLAUDE_WORKER_MODEL}}": ctx.get("CLAUDE_WORKER_MODEL_DISPLAY", ""),
|
|
1165
1168
|
"{{CLAUDE_WORKER_MODEL_EXECUTION_VALUE}}": ctx.get("CLAUDE_WORKER_MODEL_EXECUTION_VALUE", ""),
|
|
1166
1169
|
"{{CODEX_WORKER_MODEL}}": ctx.get("CODEX_WORKER_MODEL_DISPLAY", ""),
|
|
@@ -49,6 +49,7 @@ from .seeding import (
|
|
|
49
49
|
SettingsLinkError,
|
|
50
50
|
cleanup_obsolete_generated_docs,
|
|
51
51
|
ensure_project_settings_symlink,
|
|
52
|
+
installed_version,
|
|
52
53
|
verify_installation,
|
|
53
54
|
)
|
|
54
55
|
from .session import (
|
|
@@ -56,7 +57,12 @@ from .session import (
|
|
|
56
57
|
resolve_inproc_lead_session_id,
|
|
57
58
|
write_claude_resume_command_file,
|
|
58
59
|
)
|
|
59
|
-
from .
|
|
60
|
+
from .pr_template import PrTemplateError, resolve_pr_template_path
|
|
61
|
+
from .workers import (
|
|
62
|
+
normalize_workers,
|
|
63
|
+
resolve_profile_workers,
|
|
64
|
+
validate_workers_against_profile,
|
|
65
|
+
)
|
|
60
66
|
from .workflow import compute_workflow_state
|
|
61
67
|
from .worktree import provision_task_worktree
|
|
62
68
|
|
|
@@ -102,6 +108,9 @@ class PrepareInputs:
|
|
|
102
108
|
base_ref: str = ""
|
|
103
109
|
approved_plan_path: str = ""
|
|
104
110
|
clarification_response_path: str = "" # absolute or empty
|
|
111
|
+
# release-handoff 전용: PR 본문 템플릿 1회성 override. 빈 문자열이면
|
|
112
|
+
# project.json → global config → 스킬 디폴트 순으로 해석된다.
|
|
113
|
+
pr_template_path: str = ""
|
|
105
114
|
render_only: bool = False
|
|
106
115
|
refresh_assets: bool = False
|
|
107
116
|
approve_plan_ack: bool = False
|
|
@@ -310,6 +319,7 @@ def _record_start(
|
|
|
310
319
|
argv=canonical_argv,
|
|
311
320
|
cwd=cwd,
|
|
312
321
|
env_overrides={},
|
|
322
|
+
okstra_version=ctx.get("OKSTRA_VERSION", ""),
|
|
313
323
|
initial_status=initial_status,
|
|
314
324
|
brief_sha256=brief_sha256,
|
|
315
325
|
)
|
|
@@ -495,12 +505,28 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
|
|
|
495
505
|
if inp.task_type == "release-handoff":
|
|
496
506
|
workers: list[str] = []
|
|
497
507
|
else:
|
|
498
|
-
|
|
508
|
+
profile_workers = resolve_profile_workers(profile_file)
|
|
509
|
+
profile_workers_csv = ",".join(profile_workers)
|
|
499
510
|
workers = normalize_workers(inp.workers_override or profile_workers_csv)
|
|
511
|
+
if inp.workers_override.strip():
|
|
512
|
+
validate_workers_against_profile(workers, profile_workers)
|
|
500
513
|
if not workers:
|
|
501
514
|
raise PrepareError(f"no workers resolved for profile: {inp.task_type}")
|
|
502
515
|
selected_reviewers = ",".join(workers)
|
|
503
516
|
|
|
517
|
+
# ---- PR template resolution (release-handoff only) ----
|
|
518
|
+
pr_template_path_str = ""
|
|
519
|
+
pr_template_source = ""
|
|
520
|
+
if inp.task_type == "release-handoff":
|
|
521
|
+
try:
|
|
522
|
+
resolved_tpl = resolve_pr_template_path(
|
|
523
|
+
Path(inp.project_root), inp.pr_template_path
|
|
524
|
+
)
|
|
525
|
+
except PrTemplateError as exc:
|
|
526
|
+
raise PrepareError(f"PR template resolution failed: {exc}") from exc
|
|
527
|
+
pr_template_path_str = str(resolved_tpl.path)
|
|
528
|
+
pr_template_source = resolved_tpl.source
|
|
529
|
+
|
|
504
530
|
# ---- model assignments ----
|
|
505
531
|
lead_default = _default("OKSTRA_DEFAULT_LEAD_MODEL", "opus")
|
|
506
532
|
claude_default = _default("OKSTRA_DEFAULT_CLAUDE_MODEL", "sonnet")
|
|
@@ -634,6 +660,8 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
|
|
|
634
660
|
ctx.update({
|
|
635
661
|
"REVIEW_PROFILE": inp.task_type,
|
|
636
662
|
"SELECTED_REVIEWERS": selected_reviewers,
|
|
663
|
+
"PR_TEMPLATE_PATH": pr_template_path_str,
|
|
664
|
+
"PR_TEMPLATE_SOURCE": pr_template_source,
|
|
637
665
|
"CLAUDE_SESSION_ID": claude_session_id,
|
|
638
666
|
"CLARIFICATION_RESPONSE_PATH": inp.clarification_response_path,
|
|
639
667
|
"CLARIFICATION_RESPONSE_FILE": inp.clarification_response_path,
|
|
@@ -666,6 +694,7 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
|
|
|
666
694
|
"LATEST_REPORT_PATH": "",
|
|
667
695
|
"LATEST_REPORT_RELATIVE_PATH": "",
|
|
668
696
|
"RENDER_ONLY": "true" if inp.render_only else "false",
|
|
697
|
+
"OKSTRA_VERSION": installed_version(),
|
|
669
698
|
**workflow_state,
|
|
670
699
|
})
|
|
671
700
|
|
|
@@ -853,6 +882,15 @@ def main(argv: list[str]) -> int:
|
|
|
853
882
|
),
|
|
854
883
|
)
|
|
855
884
|
p.add_argument("--clarification-response", default="", dest="clarification_response_path")
|
|
885
|
+
p.add_argument(
|
|
886
|
+
"--pr-template-path",
|
|
887
|
+
default="",
|
|
888
|
+
dest="pr_template_path",
|
|
889
|
+
help=(
|
|
890
|
+
"release-handoff 전용 1회성 PR 본문 템플릿 경로. 빈 값이면 "
|
|
891
|
+
"project.json → ~/.okstra/config.json → 스킬 디폴트 순으로 해석."
|
|
892
|
+
),
|
|
893
|
+
)
|
|
856
894
|
p.add_argument("--render-only", action="store_true", dest="render_only")
|
|
857
895
|
p.add_argument("--refresh-assets", action="store_true", dest="refresh_assets")
|
|
858
896
|
p.add_argument(
|
|
@@ -917,6 +955,7 @@ def main(argv: list[str]) -> int:
|
|
|
917
955
|
base_ref=args.base_ref,
|
|
918
956
|
approved_plan_path=args.approved_plan_path,
|
|
919
957
|
clarification_response_path=clarification_abs,
|
|
958
|
+
pr_template_path=args.pr_template_path,
|
|
920
959
|
render_only=args.render_only,
|
|
921
960
|
refresh_assets=args.refresh_assets,
|
|
922
961
|
approve_plan_ack=args.approve_plan_ack,
|
|
@@ -23,6 +23,25 @@ class SettingsLinkError(Exception):
|
|
|
23
23
|
"""`<project>/.claude/settings.local.json` symlink provisioning 실패."""
|
|
24
24
|
|
|
25
25
|
|
|
26
|
+
def installed_version() -> str:
|
|
27
|
+
"""Read the version stamp written by `okstra install` to `~/.okstra/version`.
|
|
28
|
+
|
|
29
|
+
Returns an empty string if the stamp is missing or unreadable. Callers use
|
|
30
|
+
the result to label generated artifacts (run manifests, final reports) so
|
|
31
|
+
that consumers can tell which okstra release produced a given run — and
|
|
32
|
+
so that report readers can distinguish behaviour drift across upgrades
|
|
33
|
+
without having to dig through git history.
|
|
34
|
+
|
|
35
|
+
The stamp lives at `_okstra_home() / "version"`. `OKSTRA_HOME` overrides
|
|
36
|
+
the home directory for tests.
|
|
37
|
+
"""
|
|
38
|
+
version_file = _okstra_home() / "version"
|
|
39
|
+
try:
|
|
40
|
+
return version_file.read_text(encoding="utf-8").strip()
|
|
41
|
+
except OSError:
|
|
42
|
+
return ""
|
|
43
|
+
|
|
44
|
+
|
|
26
45
|
def required_install_paths() -> list[Path]:
|
|
27
46
|
"""okstra install 이 채워야 하는 최소 자산 경로."""
|
|
28
47
|
okstra_home = Path.home() / ".okstra"
|
|
@@ -68,3 +68,23 @@ def normalize_workers(value: str) -> list[str]:
|
|
|
68
68
|
seen.add(v)
|
|
69
69
|
out.append(v)
|
|
70
70
|
return out
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def validate_workers_against_profile(
|
|
74
|
+
workers: list[str], profile_workers: list[str]
|
|
75
|
+
) -> None:
|
|
76
|
+
"""프로파일이 `Required workers:` 로 로스터를 선언했다면, 사용자
|
|
77
|
+
override 가 그 부분집합인지 검증한다.
|
|
78
|
+
|
|
79
|
+
`profile_workers` 가 비어 있으면(프로파일이 로스터를 선언하지 않은
|
|
80
|
+
구버전) 검증을 건너뛴다 — 하위 호환을 위해.
|
|
81
|
+
"""
|
|
82
|
+
if not profile_workers:
|
|
83
|
+
return
|
|
84
|
+
allowed = set(profile_workers)
|
|
85
|
+
extras = [w for w in workers if w not in allowed]
|
|
86
|
+
if extras:
|
|
87
|
+
raise WorkersError(
|
|
88
|
+
"workers not allowed by profile roster: "
|
|
89
|
+
f"{','.join(extras)} (profile allows: {','.join(profile_workers)})"
|
|
90
|
+
)
|
|
@@ -208,19 +208,37 @@ Do NOT ask for `workers_override` in implementation — the profile's required r
|
|
|
208
208
|
|
|
209
209
|
### 6b. Other phases (`requirements-discovery`, `error-analysis`, `implementation-planning`, `final-verification`, `release-handoff`)
|
|
210
210
|
|
|
211
|
-
|
|
211
|
+
**Before asking any worker/model question, resolve the profile's allowed roster:**
|
|
212
212
|
|
|
213
|
-
|
|
213
|
+
```python
|
|
214
|
+
from okstra_ctl.workers import resolve_profile_workers
|
|
215
|
+
profile_workers = resolve_profile_workers(Path("<OKSTRA_PROMPTS_PROFILES_DIR>/<task-type>.md"))
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
This is the **only** set of worker IDs you may show or ask about. Never offer
|
|
219
|
+
workers outside this list. Special cases:
|
|
220
|
+
|
|
221
|
+
- If `profile_workers` is empty (e.g., `release-handoff` is lead-only with no
|
|
222
|
+
`- Required workers:` block), **skip the worker question and all
|
|
223
|
+
worker-model questions entirely** — only ask lead model, directive, related,
|
|
224
|
+
clarification. The backend forces `workers=[]` for these profiles.
|
|
225
|
+
- Otherwise, the worker question must enumerate **only** `profile_workers` —
|
|
226
|
+
do NOT show `claude, codex, gemini, report-writer` blindly.
|
|
227
|
+
|
|
228
|
+
Ask each in turn (model prompts use `AskUserQuestion` with the option lists above; others are free text). Skip any worker-model prompt whose worker is not in `profile_workers`.
|
|
229
|
+
|
|
230
|
+
1. (only when `profile_workers` is non-empty) `AskUserQuestion` `"참여 워커 목록 (쉼표 구분, 빈 칸 = 프로필 기본값 <profile_workers_csv>). 선택지: <profile_workers_csv>"` (free text) → `workers_override`. Validate the answer is a subset of `profile_workers`; re-ask on failure. (Backend will also reject violations with `WorkersError`.)
|
|
214
231
|
2. `AskUserQuestion` `"리더(Claude lead) 모델?"` (Claude options) → `lead_model`
|
|
215
|
-
3. `AskUserQuestion` `"claude 워커 모델?"` (Claude options) → `claude_model`
|
|
216
|
-
4. `AskUserQuestion` `"codex 워커 모델?"` (Codex options) → `codex_model`
|
|
217
|
-
5. `AskUserQuestion` `"gemini 워커 모델?"` (Gemini options) → `gemini_model`
|
|
218
|
-
6. `AskUserQuestion` `"리포트 작성자 모델?"` (Claude options) → `report_writer_model`
|
|
232
|
+
3. (only if `claude` ∈ resolved workers) `AskUserQuestion` `"claude 워커 모델?"` (Claude options) → `claude_model`
|
|
233
|
+
4. (only if `codex` ∈ resolved workers) `AskUserQuestion` `"codex 워커 모델?"` (Codex options) → `codex_model`
|
|
234
|
+
5. (only if `gemini` ∈ resolved workers) `AskUserQuestion` `"gemini 워커 모델?"` (Gemini options) → `gemini_model`
|
|
235
|
+
6. (only if `report-writer` ∈ resolved workers) `AskUserQuestion` `"리포트 작성자 모델?"` (Claude options) → `report_writer_model`
|
|
219
236
|
7. `AskUserQuestion` `"추가 directive (선택, 빈 칸 가능)"` (free text) → `directive`
|
|
220
237
|
8. `AskUserQuestion` `"관련 task id 목록, 쉼표 구분 (선택, 빈 칸 가능)"` (free text) → `related_tasks_raw`
|
|
221
238
|
9. `AskUserQuestion` `"clarification-response 파일 경로 (follow-up 시에만, 빈 칸 가능)"` (free text) → `clarification_response_path`
|
|
239
|
+
10. (only when `task_type == "release-handoff"`) `AskUserQuestion` `"PR 본문 템플릿 경로 1회성 override (빈 칸 = project.json → ~/.okstra/config.json → 스킬 디폴트 순으로 자동 해석)"` (free text) → `pr_template_path`. The backend (`okstra_ctl.pr_template.resolve_pr_template_path`) validates the file exists and surfaces `PrTemplateError` on failure. If the user wants to persist the choice instead of a one-shot override, tell them to set `prTemplatePath` in `<project_root>/.project-docs/okstra/project.json` (project scope) or `~/.okstra/config.json` (global scope).
|
|
222
240
|
|
|
223
|
-
For prompts whose target worker is NOT in the resolved workers list (after override),
|
|
241
|
+
For prompts whose target worker is NOT in the resolved workers list (after override), present a single confirmation line such as `gemini-model 생략 (workers에 gemini 없음)` so the user can see why the question was skipped.
|
|
224
242
|
|
|
225
243
|
## Step 6.5: Confirm selections before rendering
|
|
226
244
|
|
|
@@ -267,7 +285,8 @@ okstra render-bundle \
|
|
|
267
285
|
--lead-model "..." --claude-model "..." --codex-model "..." \
|
|
268
286
|
--gemini-model "..." --report-writer-model "..." \
|
|
269
287
|
--related-tasks "..." \
|
|
270
|
-
--clarification-response "<clarification-or-empty>"
|
|
288
|
+
--clarification-response "<clarification-or-empty>" \
|
|
289
|
+
--pr-template-path "<pr-template-override-or-empty; release-handoff only>"
|
|
271
290
|
```
|
|
272
291
|
|
|
273
292
|
Stdout prints `okstra task root:`, `okstra instruction-set:`, and the full
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
okstra release-handoff 기본 PR 본문 템플릿.
|
|
3
|
+
|
|
4
|
+
이 파일은 사용자 정의 PR 템플릿이 없을 때 사용됩니다. 우선순위:
|
|
5
|
+
1. okstra-run Step 6 에서 입력한 per-run override 경로
|
|
6
|
+
2. <project-root>/.project-docs/okstra/project.json 의 `prTemplatePath`
|
|
7
|
+
3. ~/.okstra/config.json 의 `prTemplatePath`
|
|
8
|
+
4. 이 디폴트 파일
|
|
9
|
+
|
|
10
|
+
프로젝트 또는 전역 설정으로 자체 템플릿을 쓰려면 위 경로 중 하나에
|
|
11
|
+
`prTemplatePath` 키를 추가하세요. (절대경로 또는 project_root 기준 상대경로)
|
|
12
|
+
|
|
13
|
+
플레이스홀더는 release-handoff 의 Claude lead 가 다음 입력을 근거로
|
|
14
|
+
직접 채웁니다:
|
|
15
|
+
- run brief 의 의도/스코프
|
|
16
|
+
- 인용된 final-verification 리포트의 verdict 근거
|
|
17
|
+
- `git log --oneline <base>..HEAD` 의 commit 범위
|
|
18
|
+
- `git diff <base>..HEAD --stat` 의 변경 파일 통계
|
|
19
|
+
|
|
20
|
+
빈 섹션은 그대로 두지 말고 통째로 삭제합니다. HTML 주석은 PR 생성 전에
|
|
21
|
+
모두 제거됩니다.
|
|
22
|
+
-->
|
|
23
|
+
|
|
24
|
+
## Summary
|
|
25
|
+
|
|
26
|
+
<!-- 1–3 bullets. 변경의 동기(WHY)와 결과(WHAT changed at a high level). -->
|
|
27
|
+
|
|
28
|
+
## Changes
|
|
29
|
+
|
|
30
|
+
<!-- 영역별로 묶은 구체적 변경 목록. 파일/모듈 단위 그룹화 권장. -->
|
|
31
|
+
|
|
32
|
+
## Test plan
|
|
33
|
+
|
|
34
|
+
<!-- 리뷰어가 따라 할 수 있는 검증 절차. 자동/수동 모두 가능. -->
|
|
35
|
+
|
|
36
|
+
- [ ] <검증 항목>
|
|
37
|
+
- [ ] <검증 항목>
|
|
38
|
+
|
|
39
|
+
## Linked issues
|
|
40
|
+
|
|
41
|
+
<!-- `Refs: TICKET-123`, `Closes #N`, 관련 PR 링크 등. 없으면 섹션 통째로 삭제. -->
|
|
@@ -16,6 +16,7 @@ project-id: "{{PROJECT_ID}}"
|
|
|
16
16
|
- Task Type: {{TASK_TYPE}}
|
|
17
17
|
- Report Owner: `Claude lead`
|
|
18
18
|
- Lead Model: `{{LEAD_MODEL}}`
|
|
19
|
+
- Okstra Version: `{{OKSTRA_VERSION}}`
|
|
19
20
|
- Clarification Response Carried In: `{{CLARIFICATION_RESPONSE_RELATIVE_PATH}}`
|
|
20
21
|
|
|
21
22
|
## User Approval Request (사용자 승인 게이트)
|
|
@@ -131,11 +131,13 @@
|
|
|
131
131
|
"Bash(codex exec:*)",
|
|
132
132
|
"Bash(okstra)",
|
|
133
133
|
"Bash(okstra:*)",
|
|
134
|
-
"Bash($HOME/.okstra/bin
|
|
134
|
+
"Bash($HOME/.okstra/bin/:*)",
|
|
135
135
|
|
|
136
136
|
"Bash(gemini)",
|
|
137
137
|
"Bash(gemini:*)",
|
|
138
|
-
|
|
138
|
+
|
|
139
|
+
"Bash($HOME/.okstra/bin/okstra-trace-cleanup.sh)",
|
|
140
|
+
"Bash($HOME/.okstra/bin/okstra-trace-cleanup.sh:*)",
|
|
139
141
|
|
|
140
142
|
"Bash(claude)",
|
|
141
143
|
"Bash(claude:*)",
|
package/src/render-bundle.mjs
CHANGED
|
@@ -17,7 +17,8 @@ Usage:
|
|
|
17
17
|
[--lead-model <m>] [--claude-model <m>] [--codex-model <m>] \\
|
|
18
18
|
[--gemini-model <m>] [--report-writer-model <m>] \\
|
|
19
19
|
[--related-tasks <list>] [--base-ref <ref>] \\
|
|
20
|
-
[--clarification-response <path>] [--work-category <cat>]
|
|
20
|
+
[--clarification-response <path>] [--work-category <cat>] \\
|
|
21
|
+
[--pr-template-path <path>] # release-handoff only
|
|
21
22
|
|
|
22
23
|
All flags pass through unchanged to \`python3 -m okstra_ctl.run\`. The
|
|
23
24
|
shim auto-supplies \`--workspace-root\` (from \`okstra paths --field workspace\`)
|