okstra 0.11.0 → 0.13.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/README.md +1 -1
- package/docs/kr/architecture.md +1 -1
- package/docs/kr/cli.md +1 -1
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/SKILL.md +11 -8
- package/runtime/prompts/launch.template.md +12 -28
- package/runtime/prompts/profiles/implementation.md +8 -8
- package/runtime/prompts/profiles/release-handoff.md +22 -22
- package/runtime/python/okstra_ctl/render.py +48 -0
- package/runtime/python/okstra_ctl/run.py +19 -11
- package/runtime/python/okstra_ctl/workflow.py +3 -3
- package/runtime/python/okstra_ctl/worktree.py +212 -90
- package/runtime/python/okstra_ctl/worktree_registry.py +211 -0
- package/runtime/python/okstra_project/resolver.py +9 -6
- package/runtime/skills/okstra-report-writer/SKILL.md +3 -1
- package/runtime/skills/okstra-run/SKILL.md +52 -8
- package/runtime/skills/okstra-setup/SKILL.md +26 -0
package/README.md
CHANGED
|
@@ -164,7 +164,7 @@ Notable flags added in 0.7.0 / 0.8.0:
|
|
|
164
164
|
|
|
165
165
|
Recent workflow additions (post-0.8.0, on `main`):
|
|
166
166
|
|
|
167
|
-
- **Isolated
|
|
167
|
+
- **Isolated task worktree for every task-type** — prepare automatically runs `git worktree add ~/.okstra/worktrees/<project>/<group>/<task>/` on a fresh branch `<work-category-prefix>-<task-id-segment>` branched from the main worktree's `HEAD` the first time a task-key is seen. Every subsequent phase of the same task-key (`requirements-discovery` → `error-analysis` → `implementation-planning` → `implementation`) reuses the same path and branch, so phase N inherits the working-tree state phase N-1 left behind. A global registry at `~/.okstra/worktrees/registry.json` (flock-guarded) reserves task-keys and branches across concurrent runs; all path/branch segments are sanitised (`/`, `:`, etc. → `-`). The worktree is preserved after every run for follow-up phases, PR authoring, and rollback. Skip paths: when the caller is already inside another worktree or `project_root` is not a git repo, provisioning no-ops. Manual cleanup: `git worktree remove <path>` → `git branch -D <branch>` plus removing the task-key entry from the registry. Details: [`docs/kr/architecture.md`](docs/kr/architecture.md) (*Task type* section) and [`docs/kr/cli.md#--executor`](docs/kr/cli.md#--executor).
|
|
168
168
|
- **`release-handoff` lifecycle phase** — runs after `final-verification` returns `verdict=accepted`. The lead drafts a commit message and PR body via a Claude worker, then prompts the user with `AskUserQuestion` for three choices: action (`commit only` / `commit + PR` / `skip`), PR base branch (`staging` / `preprod` / `prod` / `main` / `dev` / free-form), and message handling (`use as-is` / `edit then proceed` / `cancel`). Only user-selected mutating git/gh commands run. Force-push, base-branch direct push, hook bypass (`--no-verify`), and release publishing (`gh release`, `npm publish`, ...) are forbidden. Source code is not edited in this phase. Profile: [`prompts/profiles/release-handoff.md`](prompts/profiles/release-handoff.md).
|
|
169
169
|
|
|
170
170
|
### 3.5 Ops commands
|
package/docs/kr/architecture.md
CHANGED
|
@@ -321,7 +321,7 @@ Claude launch prompt 본문은 항상 `prompts/launch.template.md` 템플릿에
|
|
|
321
321
|
공통 제약:
|
|
322
322
|
|
|
323
323
|
- `implementation`을 제외한 모든 phase는 source code edit, build, migration, deployment, 그 밖의 state-mutating 명령을 금지합니다(`final-verification`은 read-only 테스트 명령만 허용). `implementation`은 승인된 plan의 파일 목록 안에서만 edit/commit이 허용되며, `git push`·publish·deploy·실제 migration·third-party write API는 여전히 금지됩니다.
|
|
324
|
-
-
|
|
324
|
+
- **모든 task-type 격리 worktree (BLOCKING)**: 모든 task-type 의 첫 번째 phase prepare 단계에서 `okstra-ctl` 이 자동으로 task-key 단위 `git worktree` 를 생성하고, 같은 task-key 의 이후 phase (`requirements-discovery` → `error-analysis` → `implementation-planning` → `implementation`) 는 동일한 worktree·브랜치를 재사용합니다. 위치는 `~/.okstra/worktrees/<project-id>/<task-group-segment>/<task-id-segment>/` (segment 의 `/`·`:` 등 특수문자는 `-` 로 정규화) 이고, 브랜치 이름은 `<work-category-prefix>-<task-id-segment>` (예: `feat-dev-9436`, `fix-dev-7311`) 입니다. base ref 는 첫 phase prepare 시점의 main worktree `HEAD`. `~/.okstra/worktrees/registry.json` (flock-guarded) 가 task-key → path/branch 매핑을 전역 관리해 동시 실행 시 path·branch 충돌을 방지합니다. `.project-docs/`, `.scratch/`, `graphify-out/` 은 main worktree 에서 symlink 로 연결되어 모든 task 가 동일한 shared state 를 봅니다. caller 가 이미 다른 worktree 안에 있거나 project_root 가 git repo 가 아니면 provisioning 은 skip 되고 executor 는 project_root 에서 그대로 작업합니다. worktree 는 run 종료 후 자동 삭제되지 않으며 후속 phase·PR 작성·rollback 검증의 권위 artefact 입니다. 수동 cleanup: `git -C <main-worktree> worktree remove <path>` → `git -C <main-worktree> branch -D <branch>` + registry 항목 삭제. 자세한 동작은 `prompts/profiles/implementation.md` 의 *Task worktree* 블록과 `agents/SKILL.md` 의 *Task worktree (BLOCKING for every task-type)* 섹션 참고.
|
|
325
325
|
- `implementation` 과 `release-handoff` 를 제외한 모든 phase 는 source code edit, build, migration, deployment, 그 밖의 state-mutating 명령을 금지합니다 (`final-verification` 은 read-only 테스트 명령만 허용). `implementation` 은 승인된 plan 의 파일 목록 안에서만 edit/commit 이 허용되며, `git push`·publish·deploy·실제 migration·third-party write API 는 여전히 금지됩니다. `release-handoff` 는 source code 자체는 수정하지 않고, 사용자가 메뉴로 선택한 commit / push / PR 명령만 실행합니다 (force push, base 브랜치 직접 push, hook bypass, release publish 는 여전히 금지).
|
|
326
326
|
- 사용자가 "다음 단계 진행해" 같은 표현을 보내도, 그 발화만으로 다음 phase가 자동 시작되지 않습니다. 다음 phase는 새 `okstra.sh` 실행으로만 시작합니다.
|
|
327
327
|
- **Authority & permissions assumption (HARD RULE — 모든 task-type 및 `okstra-schedule` 공통)**: 사용자(및 팀)는 예상되는 모든 작업에 대해 완전한 권한·승인 권한을 보유한다고 가정합니다. 외부 승인, 서드파티 액세스, 역할/IAM 권한, 조직적 sign-off, 법무·보안 검토, 벤더 협의, "권한 보유 여부 확인" 같은 항목을 routing 결정·missing inputs·clarification questions·risk·dependency·open questions·effort/day 추정에 포함하지 않습니다. okstra 내부 phase 핸드오프(`User Approval Request` 등)는 사용자 본인이 즉시 승인 가능한 내부 게이트이므로 영향 없으며, `implementation`의 forbidden actions(`git push`, prod deploy, shared-DB migration 등)도 권한 사유가 아닌 **안전 사유**로 계속 적용됩니다.
|
package/docs/kr/cli.md
CHANGED
|
@@ -288,7 +288,7 @@ fallback 기본값은 아래와 같습니다.
|
|
|
288
288
|
- Executor 의 모델은 provider 별 worker 모델 플래그를 그대로 재사용합니다. 즉 `--executor codex` 이면 Executor 의 모델은 `--codex-model` (기본 `gpt-5.5`), `--executor gemini` 이면 `--gemini-model` (기본 `auto`) 가 됩니다.
|
|
289
289
|
- Claude/Codex/Gemini 세 verifier 는 executor provider 와 관계없이 항상 dispatch 됩니다. Executor 와 같은 provider 라도 별도 CLI 세션으로 verifier 가 호출되어 context 가 분리되므로 self-review 안전장치는 유지됩니다.
|
|
290
290
|
- 실제 파일 변경은 Codex/Gemini 의 경우 각 CLI 의 auto-edit 모드 (예: `codex exec --full-auto`) 를 통해 일어나며, Claude-side Edit/Write tool 을 거치지 않습니다.
|
|
291
|
-
- **
|
|
291
|
+
- **Task worktree (모든 task-type 자동 격리)**: 모든 task-type 의 첫 번째 phase prepare 단계에서 `okstra-ctl` 이 `~/.okstra/worktrees/<project-id>/<task-group-segment>/<task-id-segment>/` 에 `git worktree` 를 생성하고, 브랜치 `<work-category-prefix>-<task-id-segment>` 를 main worktree `HEAD` 에서 분기합니다. 같은 task-key 의 이후 phase 는 동일한 path/branch 를 재사용하므로 status 가 `reused` 로 기록됩니다 (run-prep 시점에 새 `git worktree add` 가 일어나지 않음). 모든 segment 의 `/`·`:` 등 특수문자는 `-` 로 정규화되며, `~/.okstra/worktrees/registry.json` 가 task-key → path/branch 매핑을 전역 관리합니다 (flock-guarded). Executor 의 Edit/Write/build/test/commit, verifier 의 read 는 모두 이 worktree 안에서 수행됩니다. caller 가 이미 다른 worktree 안에 있거나 project_root 가 git repo 가 아니면 provisioning 은 skip 되고 status 가 `skipped-in-worktree` / `skipped-not-git` 로 기록됩니다. 경로·브랜치 충돌은 `PrepareError` 로 즉시 실패시키며, run 종료 후 worktree 는 자동 삭제하지 않습니다 (수동: `git worktree remove` → `git branch -D` + registry 항목 삭제).
|
|
292
292
|
|
|
293
293
|
예:
|
|
294
294
|
|
package/package.json
CHANGED
package/runtime/BUILD.json
CHANGED
package/runtime/agents/SKILL.md
CHANGED
|
@@ -107,18 +107,21 @@ Lead MUST dispatch Edit/Write-bearing work only through the `workerAgent` declar
|
|
|
107
107
|
|
|
108
108
|
Executor is chosen at run-prep time via `--executor <claude|codex|gemini>` (or `OKSTRA_DEFAULT_EXECUTOR`, fallback `claude`); the model used by the executor is taken from the corresponding worker model flag (`--claude-model` / `--codex-model` / `--gemini-model`). For Codex/Gemini executors, the underlying file mutation happens inside the executor CLI's own auto-edit mode (e.g. `codex exec --full-auto`), not through Claude-side Edit/Write tools.
|
|
109
109
|
|
|
110
|
-
####
|
|
110
|
+
#### Task worktree (BLOCKING for every task-type)
|
|
111
111
|
|
|
112
|
-
|
|
112
|
+
`okstra-ctl` provisions a dedicated `git worktree` per task-key at run-prep time of the **first** phase, and every subsequent phase of the same task-key reuses the same worktree on the same branch. Lead, the Executor, and every verifier MUST treat the provisioned worktree as the canonical working directory regardless of task-type.
|
|
113
113
|
|
|
114
|
-
-
|
|
115
|
-
-
|
|
116
|
-
-
|
|
117
|
-
-
|
|
114
|
+
- One worktree per task-key — `requirements-discovery`, `error-analysis`, `implementation-planning`, and `implementation` all share the same path and branch so phase N inherits the working-tree state phase N-1 left behind.
|
|
115
|
+
- Location: `~/.okstra/worktrees/<project-id>/<task-group-segment>/<task-id-segment>/` (override `OKSTRA_HOME` only for tests). All segments are sanitised — `/`, `:`, and other special chars collapse to `-`.
|
|
116
|
+
- Branch: `<work-category-prefix>-<task-id-segment>` (e.g. `feat-dev-9436`, `fix-dev-7311`). Branched from `HEAD` of the repo's **main** worktree at the first phase's prep time; base SHA is recorded in `EXECUTOR_WORKTREE_BASE_REF`.
|
|
117
|
+
- A global registry at `~/.okstra/worktrees/registry.json` (flock-guarded) maps each task-key to its path + branch and prevents concurrent runs from colliding. Branch names are globally unique across task-keys on this machine.
|
|
118
|
+
- Sync dirs (`.project-docs`, `.scratch`, `graphify-out` by default; override with `OKSTRA_WORKTREE_SYNC_DIRS`) are symlinked from the **main worktree** so every task observes the same shared state irrespective of which checkout invoked okstra.
|
|
119
|
+
- The path, branch, base ref, and provisioning status (`created` | `reused` | `skipped-in-worktree` | `skipped-not-git`) are exposed through the launch prompt's `## Executor Worktree` section and the implementation profile's worktree block.
|
|
120
|
+
- **Skip conditions** (worktree provisioning is a no-op; task uses `project_root` directly):
|
|
118
121
|
- `project_root` is already inside a non-main worktree (the run reuses the caller's worktree to avoid nesting).
|
|
119
122
|
- `project_root` is not inside a git repository at all.
|
|
120
|
-
- **Failure mode**: any other error during `git worktree add` (path collision, branch collision, detached-HEAD with no SHA) raises `PrepareError`. Re-run after manually removing the stale path/branch — the worktree is intentionally not garbage-collected.
|
|
121
|
-
- **Lifecycle**: kept after the run for manual PR authoring, rollback verification, and
|
|
123
|
+
- **Failure mode**: any other error during `git worktree add` (on-disk path collision not tracked by the registry, branch collision with a different task-key, detached-HEAD with no SHA) raises `PrepareError`. Re-run after manually removing the stale path/branch — the worktree is intentionally not garbage-collected.
|
|
124
|
+
- **Lifecycle**: kept after the run for follow-up phases, manual PR authoring, rollback verification, and `final-verification`. Manual cleanup: `git -C <main-worktree> worktree remove <path>` then `git -C <main-worktree> branch -D <branch>`; remove the corresponding registry entry by hand or via `okstra worktree release <task-key>` (when available).
|
|
122
125
|
|
|
123
126
|
## Phase 1: Task-bundle intake and required reading order
|
|
124
127
|
|
|
@@ -14,31 +14,7 @@ Invoke the `okstra` skill now. Read the manifests below for all task metadata, p
|
|
|
14
14
|
- Phase advancement requires a new okstra invocation launched with `--task-type {{WORKFLOW_NEXT_RECOMMENDED_PHASE}}` after this run's final report is written and approved. The lead must not write source code, run builds/migrations/deployments, or otherwise produce artifacts of a different phase from inside this run.
|
|
15
15
|
- See `Lifecycle Phase Boundaries` in the okstra skill (`agents/SKILL.md`) for the canonical rules and the phase-transition checklist.
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
Before any `Agent` dispatch for workers, you MUST perform Phase 3 of the
|
|
20
|
-
`okstra` skill (`agents/SKILL.md` → "Phase 3 — Team creation"). Skipping
|
|
21
|
-
this gate silently degrades the run to in-process background dispatch and
|
|
22
|
-
loses the Teams split-pane observability surface, even though worker
|
|
23
|
-
outputs may still appear correct on disk.
|
|
24
|
-
|
|
25
|
-
Required actions, in order, regardless of how many workers are selected
|
|
26
|
-
for this run (roster comes from `resultContract.requiredWorkerRoles` in
|
|
27
|
-
`task-manifest.json` — it may be 1, 2, 3, or more workers):
|
|
28
|
-
|
|
29
|
-
1. Invoke the `okstra-team-contract` skill and verify the selected worker
|
|
30
|
-
roster against `task-manifest.json`'s `resultContract.requiredWorkerRoles`.
|
|
31
|
-
2. Call `TeamCreate(team_name: "okstra-{{TASK_KEY}}", description: ...)`.
|
|
32
|
-
3. Record the outcome in team-state under
|
|
33
|
-
`teamCreate: { attempted: true, status: "ok" | "error", error?: <msg> }`
|
|
34
|
-
BEFORE any `Agent(...)` worker dispatch.
|
|
35
|
-
4. Only after `teamCreate` is persisted may you dispatch workers — with
|
|
36
|
-
`team_name` on success, or with `run_in_background: true` and no
|
|
37
|
-
`team_name` ONLY when `teamCreate.status == "error"` was recorded.
|
|
38
|
-
|
|
39
|
-
If the Agent tool rejects a dispatch with `"team must be created first"` /
|
|
40
|
-
`"team을 먼저 생성하거나 team_name 없이 호출해야 합니다"`, the correct
|
|
41
|
-
response is to go back to step 2 — NOT to strip `team_name` and retry.
|
|
17
|
+
{{TEAM_CREATION_GATE}}
|
|
42
18
|
|
|
43
19
|
## Project Root
|
|
44
20
|
|
|
@@ -46,6 +22,13 @@ response is to go back to step 2 — NOT to strip `team_name` and retry.
|
|
|
46
22
|
- All other paths in this prompt are relative to this root unless they begin with `/`.
|
|
47
23
|
- When dispatching workers, you MUST include this absolute root in every worker prompt header so that workers do not depend on inherited cwd to resolve relative paths.
|
|
48
24
|
|
|
25
|
+
## Worker Prompt Files (not pre-rendered)
|
|
26
|
+
|
|
27
|
+
- The `runs/<phase>/prompts/<worker>-worker-prompt-*.md` files referenced in `task-manifest.json → artifacts.workerPromptPathByWorkerId` are **NOT** rendered by the okstra runtime. Those paths are the *assigned* locations where the prompt history MUST be written at dispatch time.
|
|
28
|
+
- Therefore: at the start of every phase the prompts/ directory is normally empty (or contains only previously-dispatched workers' files). This is expected. Do NOT narrate it as "missing", "누락", or "not yet rendered" — it just means dispatch has not happened yet.
|
|
29
|
+
- Before dispatching any required worker, **you (the lead) construct the worker prompt and persist it to the assigned absolute path using `Write`** (per `okstra-team-contract` rule 6). Only after persisting do you call the worker subagent (Agent tool / Codex / Gemini wrapper). The wrapper subagents will also re-write the same file on their end; the double-write is intentional and idempotent.
|
|
30
|
+
- Do not "check if the file exists and skip dispatch" — file presence is not a signal to skip. Worker selection and skipping rules come from team-state, never from prompts/ directory contents.
|
|
31
|
+
|
|
49
32
|
## Manifests
|
|
50
33
|
|
|
51
34
|
- Task manifest: `{{TASK_MANIFEST_RELATIVE_PATH}}`
|
|
@@ -70,9 +53,10 @@ response is to go back to step 2 — NOT to strip `team_name` and retry.
|
|
|
70
53
|
- Branch: `{{EXECUTOR_WORKTREE_BRANCH}}`
|
|
71
54
|
- Base ref: `{{EXECUTOR_WORKTREE_BASE_REF}}`
|
|
72
55
|
- Note: `{{EXECUTOR_WORKTREE_NOTE}}`
|
|
73
|
-
- For
|
|
74
|
-
-
|
|
75
|
-
-
|
|
56
|
+
- For any task-type with status `created` or `reused`, every role (lead, executor, verifier) MUST anchor reads and writes to the working tree path above — phase N inherits the working-tree state phase N-1 left behind. Verifiers read from the SAME path so they observe the exact diff produced by the Executor.
|
|
57
|
+
- Branch and path are globally reserved per task-key via `~/.okstra/worktrees/registry.json`; concurrent okstra runs on this machine cannot collide.
|
|
58
|
+
- The worktree is preserved after every run (no automatic cleanup) and is reused by every subsequent phase of the same task-key. Manual cleanup when fully done: `git worktree remove <path>` → `git branch -D <branch>` + remove the task-key entry from the registry.
|
|
59
|
+
- For status `skipped-in-worktree` or `skipped-not-git`, the run operates directly in `{{PROJECT_ROOT}}` and this section is informational only.
|
|
76
60
|
|
|
77
61
|
## Available MCP Servers
|
|
78
62
|
|
|
@@ -27,17 +27,17 @@
|
|
|
27
27
|
- **CLI ack** — the user runs `okstra ... --task-type implementation --approved-plan <path> --approve`. The CLI invocation itself is modelled as the user's act of approval; the runtime (`okstra_ctl.run._apply_cli_approval`) flips the checkbox in the report file and appends an audit line `- 승인 일시 (CLI ack): <ISO8601> — recorded by \`okstra --approve\`` before the standard regex validation runs. Use this when running unattended or when you want a single command to both approve and launch the next phase.
|
|
28
28
|
- The `--approve` flag is **only meaningful with `--task-type implementation` and `--approved-plan <path>`**. Passing it with any other task-type causes `PrepareError` (the runtime refuses to silently ignore approval signals). It is also a no-op if the file already carries a valid approval marker (idempotent — only an audit line is appended, the marker is not re-toggled).
|
|
29
29
|
- the file's `Recommended option` and its bite-sized step list become the authoritative scope for this run; any deviation must be justified in the final report and routed back to a new `implementation-planning` run instead of being silently expanded.
|
|
30
|
-
-
|
|
31
|
-
- Status: `{{EXECUTOR_WORKTREE_STATUS}}` (one of: `created` | `skipped-in-worktree` | `skipped-not-git`)
|
|
32
|
-
- Working tree path: `{{EXECUTOR_WORKTREE_PATH}}` — when status is `created`, this is
|
|
33
|
-
- Branch: `{{EXECUTOR_WORKTREE_BRANCH}}` — empty when status is `skipped-*`. The branch name encodes `<work-category-prefix>-<task-id-segment
|
|
34
|
-
- Base ref: `{{EXECUTOR_WORKTREE_BASE_REF}}` — commit SHA the worktree was branched from; canonical `<base>` for every `git diff` / `git log` in this run.
|
|
30
|
+
- Task worktree (provisioned by `okstra-ctl` at the first phase's run-prep time, reused for every subsequent phase of this task-key):
|
|
31
|
+
- Status: `{{EXECUTOR_WORKTREE_STATUS}}` (one of: `created` | `reused` | `skipped-in-worktree` | `skipped-not-git`)
|
|
32
|
+
- Working tree path: `{{EXECUTOR_WORKTREE_PATH}}` — when status is `created` or `reused`, this is the task's `git worktree` rooted at `~/.okstra/worktrees/<project>/<task-group>/<task-id>/` (segments sanitised — `/` `:` → `-`). When skipped, this is the caller's `project_root`.
|
|
33
|
+
- Branch: `{{EXECUTOR_WORKTREE_BRANCH}}` — empty when status is `skipped-*`. The branch name encodes `<work-category-prefix>-<task-id-segment>` and is globally unique across task-keys via `~/.okstra/worktrees/registry.json`.
|
|
34
|
+
- Base ref: `{{EXECUTOR_WORKTREE_BASE_REF}}` — commit SHA the worktree was branched from at the first phase; canonical `<base>` for every `git diff` / `git log` in this run.
|
|
35
35
|
- Provisioning note: `{{EXECUTOR_WORKTREE_NOTE}}`
|
|
36
|
-
- **Executor behaviour**: when status is `created`, the Executor MUST run every Edit / Write / build / test / commit command with the working tree path above as cwd. Treat it as `project_root` for the duration of this run. Do NOT mutate the caller's original checkout. Do NOT `cd` out of the worktree to reach files; if a file outside the worktree is needed, the dependency is a planning gap — record it in `Out-of-plan edits` and continue.
|
|
36
|
+
- **Executor behaviour**: when status is `created` or `reused`, the Executor MUST run every Edit / Write / build / test / commit command with the working tree path above as cwd. Treat it as `project_root` for the duration of this run. Do NOT mutate the caller's original checkout. Do NOT `cd` out of the worktree to reach files; if a file outside the worktree is needed, the dependency is a planning gap — record it in `Out-of-plan edits` and continue.
|
|
37
37
|
- **Verifier behaviour**: all three verifier roles read from the SAME working tree path so they observe the exact diff the Executor produced. Verifiers remain strictly read-only there.
|
|
38
|
-
- **Lifecycle**: the worktree is kept after the run completes (no automatic cleanup)
|
|
38
|
+
- **Lifecycle**: the worktree is kept after the run completes (no automatic cleanup) and is reused by every subsequent phase of the same task-key. Cleanup, when the task is fully done, is manual: `git -C <main-worktree> worktree remove <path>` followed by `git -C <main-worktree> branch -D <branch>`, plus removing the task-key entry from `~/.okstra/worktrees/registry.json`.
|
|
39
39
|
- **Skipped paths**: when status is `skipped-in-worktree` or `skipped-not-git`, the executor operates in `project_root` as before. Cite the status in the final report's metadata header so reviewers know which path was taken.
|
|
40
|
-
- **Synced state directories (symlinks into the
|
|
40
|
+
- **Synced state directories (symlinks into the MAIN worktree)**: at provision time `okstra-ctl` symlinks `.project-docs/`, `.scratch/`, and `graphify-out/` from the repo's **main worktree** into the task worktree (override via `OKSTRA_WORKTREE_SYNC_DIRS`; empty string disables). These are NOT independent copies — writes through them land in the main worktree. Inside this run the executor MUST confine writes under these paths to its own task scope (i.e. only `.project-docs/okstra/tasks/<this-task-id>/...`). Reading from elsewhere under the symlinks (other tasks, `graphify-out/GRAPH_REPORT.md`, `.scratch/` issues) is allowed and expected for context.
|
|
41
41
|
- Pre-implementation context exploration (executor before first edit):
|
|
42
42
|
- **Mandatory skill invocation — `tdd`**: BEFORE the first `Edit` or `Write` call, the executor MUST invoke the `tdd` skill via the `Skill` tool and follow its red-green-refactor loop for every code change in this run. This is a hard requirement, not a recommendation; skipping it is a `contract-violated` outcome. The skill governs HOW each step is executed (failing test first → minimal implementation → refactor); it does not override the approved plan's WHAT/file scope.
|
|
43
43
|
- Order of operations per plan step: (1) write/extend the test that captures the step's acceptance criterion and confirm it fails for the right reason, (2) commit the failing test (`test(<scope>): ...`), (3) implement the minimum change to make it pass, (4) commit the implementation (`feat|fix(<scope>): ...`), (5) refactor without changing behaviour and commit separately if any cleanup is made (`refactor(<scope>): ...`). The failing-then-passing transition between steps (2) and (4) is the `TDD evidence` required by the final report.
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
# Release Handoff Profile
|
|
2
2
|
|
|
3
3
|
- Purpose: take an `accepted` final-verification verdict and turn it into a delivered commit and/or pull request, with explicit user selection at every mutating step
|
|
4
|
-
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
4
|
+
- **Execution model: single-lead, no worker dispatch.** This phase is a thin orchestrator over `git` / `gh`; it does NOT run team-mode, does NOT call `TeamCreate`, does NOT dispatch analysis or drafter sub-agents, and does NOT run convergence. The Claude lead performs every step inline (drafting commit/PR text, asking the user, running git, writing the final report) — see "Lead-only contract" below.
|
|
5
|
+
- Required workers: *(none — this profile intentionally has no `- Required workers:` block; the run is executed entirely by the Claude lead)*
|
|
6
|
+
- Lead-only contract (replaces the shared team contract for this phase):
|
|
7
|
+
- The Claude lead is the sole agent for this run. No `Agent(...)` worker dispatch, no `TeamCreate`, no parallel sub-agents, no convergence loop.
|
|
8
|
+
- The lead drafts the commit message and PR body **inline** by reading the run brief, the cited final-verification report, and `git diff <base>..HEAD --stat`. No drafter worker is dispatched.
|
|
9
|
+
- The lead authors the final-report file directly (no `Report writer worker` dispatch). The report still conforms to the standard `okstra-final-report.template.md` structure, including the `## 4.6 Release Handoff Deliverables` section.
|
|
10
|
+
- The shared anti-escalation rule from the common contract still applies: do not start any other lifecycle phase from inside this run.
|
|
11
|
+
- The shared "authority & permissions assumption" rule from the common contract still applies: assume the user holds every permission needed; do not block on hypothetical approvals.
|
|
12
|
+
- The shared "MCP read-only" rule still applies if the brief lists MCP servers, though most release-handoff runs do not use MCP.
|
|
12
13
|
- Pre-handoff entry gate (mandatory — refuse to start if any item fails):
|
|
13
14
|
- the task brief MUST cite the originating `final-verification` final-report path under `## Source Verification Report`. The lead opens that file and confirms section `## 2. Final Verdict` contains exactly the token `accepted`.
|
|
14
15
|
- if the verdict is `conditional-accept`, `blocked`, or any other token (including ambiguous phrasing like "looks good"), the run MUST end immediately with status `blocked` and a routing recommendation back to `error-analysis` or `implementation-planning`. Do NOT prompt the user; do NOT run any git command.
|
|
@@ -28,18 +29,17 @@
|
|
|
28
29
|
- `dev`
|
|
29
30
|
- `직접 입력` (free-form branch name; lead validates the name exists on origin via `git ls-remote --heads origin <name>` and re-asks on failure)
|
|
30
31
|
The chosen base MUST NOT equal the feature branch. If it does, re-ask.
|
|
31
|
-
3. **Commit message + PR body confirmation** — show the
|
|
32
|
-
- `use as-is` — proceed with the
|
|
32
|
+
3. **Commit message + PR body confirmation** — show the lead's inline draft verbatim and capture one of:
|
|
33
|
+
- `use as-is` — proceed with the drafted text.
|
|
33
34
|
- `edit then proceed` — accept inline edits from the user, then proceed with the edited text.
|
|
34
35
|
- `cancel` — end the run without executing any git command; record the cancellation in the final report.
|
|
35
|
-
-
|
|
36
|
-
-
|
|
37
|
-
-
|
|
38
|
-
1. **Commit message
|
|
39
|
-
2. **PR body
|
|
40
|
-
- is
|
|
41
|
-
|
|
42
|
-
- Allowed actions during the run (Claude lead only — workers stay read-only):
|
|
36
|
+
- Inline drafting rules (Claude lead):
|
|
37
|
+
- read the run brief, the cited final-verification report, and `git diff <base>..HEAD --stat` to ground the drafted text in actual changes.
|
|
38
|
+
- produce **two artifacts** before showing them to the user:
|
|
39
|
+
1. **Commit message** — a single message in Conventional Commits style (`<type>(<scope>): <subject>` + optional body + optional footer). When `commit + PR` will be opened against a `release-please`-managed repo, the type MUST match a configured changelog section (`feat` / `fix` / `perf` / `revert` / `deps` / `docs` / `refactor` / `build` / `ci` / `chore` / `test`).
|
|
40
|
+
2. **PR body** — markdown with sections `## Summary`, `## Changes`, `## Test plan`, `## Linked issues` (omit a section only if it is genuinely empty).
|
|
41
|
+
- if the diff is empty or no commit can be produced (working tree already matches the base), record "no staged changes; commit skipped" in the final report and skip `git commit` while still proceeding to the PR step if requested.
|
|
42
|
+
- Allowed actions during the run (Claude lead only):
|
|
43
43
|
- 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>`.
|
|
44
44
|
- local commit: `git add -- <path>...` (prefer explicit file paths over `git add -A` / `git add .`), `git commit -m "<message>"`. Re-use the user-confirmed message exactly.
|
|
45
45
|
- feature-branch push (only when the user picked `commit + PR`): `git push -u origin <current-branch>`. The pushed ref MUST be the feature branch — never the chosen base branch.
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
- source-code edits, refactors, or any modification to files outside the run's own artifact directories (`reports/`, `prompts/`, `state/`, `manifests/`, `worker-results/`, `status/`, `sessions/`). The diff being shipped MUST be exactly what the prior `implementation` run produced; release-handoff packages it, it does not re-author it.
|
|
60
60
|
- executing any mutating command the user did NOT select. Examples: opening a PR when the user picked `commit only`; running `git commit` when the user picked `skip`; switching the PR base branch silently after the user already chose one.
|
|
61
61
|
- retrying a failed git / gh command with weaker safety flags. If `git push` fails with non-fast-forward, the lead MUST stop, explain the failure to the user, and ask for instructions — it MUST NOT add `--force`.
|
|
62
|
-
-
|
|
62
|
+
- `TeamCreate`, `Agent(...)` dispatch of any kind, or any other parallel sub-agent fan-out. This phase runs entirely under the Claude lead.
|
|
63
63
|
- silently treating an unrecognised user reply as one of the menu options. If the user's answer does not match a presented choice, re-ask the question verbatim.
|
|
64
64
|
- Required deliverable shape (final report, in addition to the standard sections):
|
|
65
65
|
- **Source Verification Report**: relative path of the originating `final-verification` final-report file plus the literal quoted `## 2. Final Verdict` line that read `accepted`.
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
- **User Selections**: a block recording each prompt and the user's verbatim answer.
|
|
68
68
|
- Q1 action: `commit only` | `commit + PR` | `skip`.
|
|
69
69
|
- Q2 PR base (if applicable): the chosen branch and how it was selected (menu pick vs free-form input).
|
|
70
|
-
- Q3 message/body: `use as-is` | `edit then proceed` (with a diff between the
|
|
70
|
+
- Q3 message/body: `use as-is` | `edit then proceed` (with a diff between the lead's draft and the final text) | `cancel`.
|
|
71
71
|
- **Executed Commands**: every git / gh command the lead actually ran, with its exit code and a one-line stdout/stderr summary. Read-only inspection commands MAY be summarised; mutating commands MUST be listed verbatim.
|
|
72
72
|
- **Commit List**: each commit SHA (short and full), its subject line, and the files it touched. If no commit was produced (idempotent no-op), state `- No commit was produced (working tree had no staged changes).`
|
|
73
73
|
- **Pull Request Outcome**: one of
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
- `- PR reused: <url>` when an existing PR was found via `gh pr list`
|
|
77
77
|
- `- PR creation skipped: <reason>` for any user-driven cancellation
|
|
78
78
|
- **Routing recommendation**: explicit `done` token, since release-handoff is the terminal lifecycle phase. If the run ended in `skip` or `cancel`, the recommendation MUST also state whether re-entry into release-handoff is appropriate.
|
|
79
|
-
- Self-review pass before finalising the report (`Claude lead` runs this
|
|
79
|
+
- Self-review pass before finalising the report (`Claude lead` runs this):
|
|
80
80
|
1. **Entry-gate audit** — section 2 cites the originating final-verification report path and the literal `accepted` verdict line. If either is missing, the run is invalid and MUST be re-routed to `final-verification`.
|
|
81
81
|
2. **User-selection traceability** — every executed mutating command maps to a user selection captured in the report. Any mutating command without a corresponding user answer is a contract violation.
|
|
82
82
|
3. **Forbidden-action audit** — scan the run's session transcripts (`git`, `gh` invocations) for every entry in the Forbidden actions list above. Any occurrence means the run has crossed into unsafe territory and MUST be flagged as `contract-violated`.
|
|
@@ -86,4 +86,4 @@
|
|
|
86
86
|
- re-litigating the final-verification verdict — release-handoff trusts the cited `accepted` verdict and does not reopen acceptance checks.
|
|
87
87
|
- opening additional PRs, releases, or deployments beyond the single PR the user chose to create.
|
|
88
88
|
- merging the PR. Merging is a separate, manual step performed by the user (or by repo automation) after release-handoff ends; the lead MUST NOT call `gh pr merge`.
|
|
89
|
-
- escalating beyond the menu choices on user phrasing — every mutating action requires an explicit menu selection
|
|
89
|
+
- escalating beyond the menu choices on user phrasing — every mutating action requires an explicit menu selection.
|
|
@@ -986,7 +986,55 @@ def render_template_file(template_path: str, output_path: str, ctx: dict) -> Non
|
|
|
986
986
|
else "- `Gemini worker` is not selected for this run, so no Gemini attempt is required."
|
|
987
987
|
)
|
|
988
988
|
|
|
989
|
+
# Team creation gate block: phase-conditional.
|
|
990
|
+
# - release-handoff (and any other phase that renders with no workers
|
|
991
|
+
# selected) is single-lead and MUST NOT call `TeamCreate`. Emit a
|
|
992
|
+
# short notice instead of the BLOCKING gate.
|
|
993
|
+
# - All other phases keep the full team-creation contract.
|
|
994
|
+
task_type = ctx.get("ANALYSIS_TYPE", "")
|
|
995
|
+
if task_type == "release-handoff" or not selected:
|
|
996
|
+
team_creation_gate_block = (
|
|
997
|
+
"## Single-Lead Phase (no team creation)\n"
|
|
998
|
+
"\n"
|
|
999
|
+
"This run is single-lead. There is no worker roster, no\n"
|
|
1000
|
+
"`TeamCreate` call, no `Agent(...)` worker dispatch, and no\n"
|
|
1001
|
+
"convergence loop. The Claude lead performs every step inline\n"
|
|
1002
|
+
"(reading inputs, drafting commit / PR text when applicable,\n"
|
|
1003
|
+
"asking the user, running git / gh, and writing the final\n"
|
|
1004
|
+
"report). Do NOT call `TeamCreate` or dispatch any sub-agent\n"
|
|
1005
|
+
"from this run — that would be a contract violation."
|
|
1006
|
+
)
|
|
1007
|
+
else:
|
|
1008
|
+
team_creation_gate_block = (
|
|
1009
|
+
"## Team Creation Gate (BLOCKING)\n"
|
|
1010
|
+
"\n"
|
|
1011
|
+
"Before any `Agent` dispatch for workers, you MUST perform Phase 3 of the\n"
|
|
1012
|
+
"`okstra` skill (`agents/SKILL.md` → \"Phase 3 — Team creation\"). Skipping\n"
|
|
1013
|
+
"this gate silently degrades the run to in-process background dispatch and\n"
|
|
1014
|
+
"loses the Teams split-pane observability surface, even though worker\n"
|
|
1015
|
+
"outputs may still appear correct on disk.\n"
|
|
1016
|
+
"\n"
|
|
1017
|
+
"Required actions, in order, regardless of how many workers are selected\n"
|
|
1018
|
+
"for this run (roster comes from `resultContract.requiredWorkerRoles` in\n"
|
|
1019
|
+
"`task-manifest.json` — it may be 1, 2, 3, or more workers):\n"
|
|
1020
|
+
"\n"
|
|
1021
|
+
"1. Invoke the `okstra-team-contract` skill and verify the selected worker\n"
|
|
1022
|
+
" roster against `task-manifest.json`'s `resultContract.requiredWorkerRoles`.\n"
|
|
1023
|
+
f"2. Call `TeamCreate(team_name: \"okstra-{ctx.get('TASK_KEY', '')}\", description: ...)`.\n"
|
|
1024
|
+
"3. Record the outcome in team-state under\n"
|
|
1025
|
+
" `teamCreate: { attempted: true, status: \"ok\" | \"error\", error?: <msg> }`\n"
|
|
1026
|
+
" BEFORE any `Agent(...)` worker dispatch.\n"
|
|
1027
|
+
"4. Only after `teamCreate` is persisted may you dispatch workers — with\n"
|
|
1028
|
+
" `team_name` on success, or with `run_in_background: true` and no\n"
|
|
1029
|
+
" `team_name` ONLY when `teamCreate.status == \"error\"` was recorded.\n"
|
|
1030
|
+
"\n"
|
|
1031
|
+
"If the Agent tool rejects a dispatch with `\"team must be created first\"` /\n"
|
|
1032
|
+
"`\"team을 먼저 생성하거나 team_name 없이 호출해야 합니다\"`, the correct\n"
|
|
1033
|
+
"response is to go back to step 2 — NOT to strip `team_name` and retry."
|
|
1034
|
+
)
|
|
1035
|
+
|
|
989
1036
|
mapping = {
|
|
1037
|
+
"{{TEAM_CREATION_GATE}}": team_creation_gate_block,
|
|
990
1038
|
"{{PROJECT_ID}}": ctx.get("PROJECT_ID", ""),
|
|
991
1039
|
"{{TASK_GROUP}}": ctx.get("TASK_GROUP", ""),
|
|
992
1040
|
"{{TASK_ID}}": ctx.get("TASK_ID", ""),
|
|
@@ -58,7 +58,7 @@ from .session import (
|
|
|
58
58
|
)
|
|
59
59
|
from .workers import normalize_workers, resolve_profile_workers
|
|
60
60
|
from .workflow import compute_workflow_state
|
|
61
|
-
from .worktree import
|
|
61
|
+
from .worktree import provision_task_worktree
|
|
62
62
|
|
|
63
63
|
APPROVED_PLAN_PATTERN = re.compile(
|
|
64
64
|
r"^[ \t]*(?:[-*+][ \t]+)?(APPROVED([ \t]|:|$)|\[x\][ \t]*Approved|"
|
|
@@ -457,10 +457,17 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
|
|
|
457
457
|
raise PrepareError(f"project.json upsert failed for {project_root}: {exc}") from exc
|
|
458
458
|
|
|
459
459
|
# ---- workers resolution ----
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
460
|
+
# release-handoff is intentionally single-lead (no worker dispatch, no
|
|
461
|
+
# TeamCreate, no convergence). The profile has no `- Required workers:`
|
|
462
|
+
# block; force an empty roster regardless of any user override so the
|
|
463
|
+
# rendered task bundle stays consistent with the profile contract.
|
|
464
|
+
if inp.task_type == "release-handoff":
|
|
465
|
+
workers: list[str] = []
|
|
466
|
+
else:
|
|
467
|
+
profile_workers_csv = ",".join(resolve_profile_workers(profile_file))
|
|
468
|
+
workers = normalize_workers(inp.workers_override or profile_workers_csv)
|
|
469
|
+
if not workers:
|
|
470
|
+
raise PrepareError(f"no workers resolved for profile: {inp.task_type}")
|
|
464
471
|
selected_reviewers = ",".join(workers)
|
|
465
472
|
|
|
466
473
|
# ---- model assignments ----
|
|
@@ -517,21 +524,22 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
|
|
|
517
524
|
task_type=inp.task_type, run_seq_override=run_seq_override,
|
|
518
525
|
)
|
|
519
526
|
|
|
520
|
-
# ----
|
|
521
|
-
#
|
|
522
|
-
#
|
|
527
|
+
# ---- task worktree provisioning (every phase, every task-type) ----
|
|
528
|
+
# One worktree per task-key: requirements-discovery, error-analysis,
|
|
529
|
+
# implementation-planning and implementation phases of the same task
|
|
530
|
+
# all share this directory and branch. The global registry handles
|
|
531
|
+
# reservation across concurrent runs.
|
|
523
532
|
try:
|
|
524
|
-
worktree =
|
|
533
|
+
worktree = provision_task_worktree(
|
|
525
534
|
task_type=inp.task_type,
|
|
526
535
|
project_root=project_root,
|
|
527
536
|
project_id=inp.project_id,
|
|
528
537
|
task_group_segment=ctx["TASK_GROUP_SEGMENT"],
|
|
529
538
|
task_id_segment=ctx["TASK_ID_SEGMENT"],
|
|
530
|
-
run_seq=int(ctx["RUN_REPORTS_SEQ"]),
|
|
531
539
|
work_category=inp.work_category,
|
|
532
540
|
)
|
|
533
541
|
except RuntimeError as exc:
|
|
534
|
-
raise PrepareError(f"
|
|
542
|
+
raise PrepareError(f"task worktree provisioning failed: {exc}") from exc
|
|
535
543
|
|
|
536
544
|
ctx.update({
|
|
537
545
|
"EXECUTOR_WORKTREE_PATH": worktree.path,
|
|
@@ -127,11 +127,11 @@ PHASE_RULES: dict[str, dict[str, str]] = {
|
|
|
127
127
|
" - entering this phase only when the cited final-verification report's verdict is exactly `accepted`\n"
|
|
128
128
|
" - asking the user (via `AskUserQuestion` / interactive prompt) which delivery action to take: `commit only`, `commit + PR`, or `skip` (end the run)\n"
|
|
129
129
|
" - asking the user to pick a PR base branch from `staging` | `preprod` | `prod` | `main` | `dev` | a user-supplied branch name\n"
|
|
130
|
-
" -
|
|
130
|
+
" - drafting commit message(s) and PR body **inline as the Claude lead** (no drafter worker, no `Agent` dispatch); the lead reviews its own draft with the user before any git command runs\n"
|
|
131
131
|
" - local git operations: `git status`, `git diff`, `git log`, `git add`, `git commit -m`\n"
|
|
132
132
|
" - pushing the current feature branch to its origin remote via `git push -u origin <current-branch>` (the feature branch only — NEVER the base branch)\n"
|
|
133
133
|
" - creating a pull request via `gh pr create --base <chosen-base> --head <current-branch>`; if a PR with the same head already exists, surface its URL and skip creation\n"
|
|
134
|
-
" -
|
|
134
|
+
" - the lead writes the final report directly (no `Report writer worker` dispatch); the report still conforms to the standard final-report template"
|
|
135
135
|
),
|
|
136
136
|
"forbidden": (
|
|
137
137
|
" - entering this phase when the cited final-verification verdict is `conditional-accept` or `blocked`, or when no final-verification report is cited\n"
|
|
@@ -141,7 +141,7 @@ PHASE_RULES: dict[str, dict[str, str]] = {
|
|
|
141
141
|
" - bypassing git hooks (`--no-verify`, `-n`), bypassing GPG signing, or otherwise disabling repo-configured safeguards\n"
|
|
142
142
|
" - release-publishing commands: `gh release`, `npm publish`, `cargo publish`, `pip publish`, `docker push`, `terraform apply`, `kubectl apply` against non-local clusters\n"
|
|
143
143
|
" - executing any command the user did NOT select (e.g. if the user picked `commit only`, opening a PR is forbidden; if the user picked `skip`, the run ends without git commands)\n"
|
|
144
|
-
" -
|
|
144
|
+
" - `TeamCreate`, `Agent(...)` worker dispatch, parallel sub-agent fan-out, or any team-mode orchestration — this phase runs single-lead\n"
|
|
145
145
|
" - silently retrying a failed git/gh command with weaker flags (e.g. retrying `git push` with `--force` after a non-fast-forward rejection)"
|
|
146
146
|
),
|
|
147
147
|
},
|