okstra 0.103.0 → 0.105.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/contributor-change-matrix.md +3 -0
- package/docs/for-ai/README.md +3 -1
- package/docs/for-ai/skills/okstra-manager.md +62 -0
- package/docs/kr/architecture/storage-model.md +29 -8
- package/docs/kr/architecture.md +5 -4
- package/docs/kr/cli.md +3 -3
- package/docs/project-structure-overview.md +34 -7
- package/docs/task-process/README.md +8 -6
- package/docs/task-process/common-flow.md +12 -3
- package/docs/task-process/final-verification.md +37 -26
- package/docs/task-process/implementation.md +28 -14
- package/docs/task-process/release-handoff.md +23 -9
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/python/okstra_ctl/container_registry.py +10 -31
- package/runtime/python/okstra_ctl/json_registry.py +43 -0
- package/runtime/python/okstra_ctl/manager_launch.py +63 -5
- package/runtime/python/okstra_ctl/manager_paths.py +7 -4
- package/runtime/python/okstra_ctl/manager_store.py +3 -1
- package/runtime/python/okstra_ctl/manager_sync.py +15 -7
- package/runtime/python/okstra_ctl/worktree_registry.py +6 -32
|
@@ -5,6 +5,9 @@ Use this matrix before changing high-risk repo contracts. Update the source file
|
|
|
5
5
|
| Change | Must update | Tests |
|
|
6
6
|
|---|---|---|
|
|
7
7
|
| Add CLI flag | `src/`, `scripts/okstra_ctl/run.py`, `docs/kr/cli.md`, `prompts/wizard/` | JS CLI tests and pytest CLI contracts |
|
|
8
|
+
| Add Node subcommand | `src/cli-registry.mjs`, `src/commands/`, `docs/kr/cli.md`, `docs/project-structure-overview.md` | `tests-js/cli-registry.test.mjs` plus command-specific JS/Python tests |
|
|
9
|
+
| Add public skill | `src/lib/skill-catalog.mjs`, `.claude-plugin/plugin.json`, `skills/<name>/SKILL.md`, `docs/for-ai/README.md`, `docs/project-structure-overview.md`, `README*` | `tests-js/skill-catalog.test.mjs`, `tests/contract/test_docs_runtime_contract.py` |
|
|
10
|
+
| Change manager contract | `scripts/okstra_ctl/manager_*.py`, `src/commands/manager.mjs`, `skills/okstra-manager/SKILL.md`, `docs/for-ai/skills/okstra-manager.md`, `docs/kr/cli.md`, `docs/kr/architecture/storage-model.md` | `tests-js/manager.test.mjs`, `tests/test_okstra_manager_*.py` |
|
|
8
11
|
| Add phase | `scripts/okstra_ctl/workflow.py`, `prompts/profiles/`, `validators/`, `tests/` | workflow and validation contract tests |
|
|
9
12
|
| Change worker roster | `prompts/profiles/*.md`, `scripts/okstra_ctl/workers.py`, `tests/contract/test_repo_contracts.py` | worker roster contract tests |
|
|
10
13
|
| Change report section | `schemas/final-report-v1.0.schema.json`, `templates/reports/final-report.template.md`, `scripts/okstra_ctl/render_final_report.py`, `validators/validate-run.py` | final-report schema, renderer, and validator tests |
|
package/docs/for-ai/README.md
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
| 새 프로젝트나 새 머신에서 okstra 설치/초기화 | `okstra-setup` | [`skills/okstra-setup.md`](skills/okstra-setup.md) |
|
|
20
20
|
| 요구사항, 티켓, 링크, 코드베이스 스캔, error-zip을 okstra 입력 brief로 변환 | `okstra-brief` | [`skills/okstra-brief.md`](skills/okstra-brief.md) |
|
|
21
21
|
| 현재 Claude Code 세션에서 okstra run 시작 또는 다음 phase 실행 | `okstra-run` | [`skills/okstra-run.md`](skills/okstra-run.md) |
|
|
22
|
+
| 여러 프로젝트에 걸친 okstra task 묶음, 할당, sync snapshot, child launch packet 관리 | `okstra-manager` | [`skills/okstra-manager.md`](skills/okstra-manager.md) |
|
|
22
23
|
| 상태, history, report, time, logs, cost, errors, error-zip, recap 확인 | `okstra-inspect` | [`skills/okstra-inspect.md`](skills/okstra-inspect.md) |
|
|
23
24
|
| task-group 전체의 클라이언트용 작업 일정 생성 | `okstra-schedule` | [`skills/okstra-schedule.md`](skills/okstra-schedule.md) |
|
|
24
25
|
| 대화/결정/선호/요구사항을 전역 Memory Book에 저장 또는 검색 | `okstra-memory` | [`skills/okstra-memory.md`](skills/okstra-memory.md) |
|
|
@@ -43,11 +44,12 @@
|
|
|
43
44
|
|
|
44
45
|
## 공개 스킬 목록
|
|
45
46
|
|
|
46
|
-
`src/lib/skill-catalog.mjs` 기준 공개 스킬은 다음
|
|
47
|
+
`src/lib/skill-catalog.mjs` 기준 공개 스킬은 다음 8개다.
|
|
47
48
|
|
|
48
49
|
- `okstra-setup`
|
|
49
50
|
- `okstra-brief`
|
|
50
51
|
- `okstra-run`
|
|
52
|
+
- `okstra-manager`
|
|
51
53
|
- `okstra-memory`
|
|
52
54
|
- `okstra-inspect`
|
|
53
55
|
- `okstra-schedule`
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# okstra-manager
|
|
2
|
+
|
|
3
|
+
여러 프로젝트에 걸친 okstra task 를 하나의 manager-owned context 로 묶을 때 사용한다. 원문 계약은 [`skills/okstra-manager/SKILL.md`](../../../skills/okstra-manager/SKILL.md), CLI 구현은 [`src/commands/manager.mjs`](../../../src/commands/manager.mjs) 와 [`scripts/okstra_ctl/manager_cli.py`](../../../scripts/okstra_ctl/manager_cli.py) 이다.
|
|
4
|
+
|
|
5
|
+
## 언제 쓰나
|
|
6
|
+
|
|
7
|
+
- 사용자가 "여러 프로젝트", "cross-project", "okstra manager", "프로젝트 묶어서" 같은 의도를 말한다.
|
|
8
|
+
- manager 아래에 프로젝트를 등록하거나 후보 프로젝트를 찾는다.
|
|
9
|
+
- 공통 task-group/task-id 아래 child project task 를 계획하고 역할/지시를 배정한다.
|
|
10
|
+
- child project `.okstra` 상태를 manager snapshot 으로 동기화하거나 status 를 본다.
|
|
11
|
+
- 특정 child task 실행을 위한 launch packet 과 manager child context 를 준비한다.
|
|
12
|
+
|
|
13
|
+
## 실행 규칙
|
|
14
|
+
|
|
15
|
+
1. 모든 명령은 literal `okstra` 로 시작한다. 셸 변수, `$(...)`, `&&`, `eval`, 선행 env assignment 로 감싸지 않는다.
|
|
16
|
+
2. JSON stdout 이 source of truth 다. manager 상태나 child launch args 를 문서/메모리에서 재구성하지 않는다.
|
|
17
|
+
3. `--workspace-root` 는 Node wrapper 가 소유한다. 사용자가 넘기면 CLI 가 거부한다.
|
|
18
|
+
4. `new project` 의 `--project-root` 는 이미 존재하는 디렉터리여야 한다. 그 안의 `.okstra/project.json` 이 없을 때만 setup-equivalent registration 을 수행한다.
|
|
19
|
+
5. public child task identity 는 `project-id:task-group:task-id` 이다. `new task --task` 예시는 full key form 을 우선 보여준다.
|
|
20
|
+
6. 같은 manager task id 아래 실제 child task id 가 다르면 `task assign` 과 `task run` 에 `--child-task-id <id>` 를 같이 넘긴다.
|
|
21
|
+
|
|
22
|
+
## 명령 표면
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
okstra manager init --manager-id <manager-id> --json
|
|
26
|
+
okstra manager discover-projects --json
|
|
27
|
+
okstra manager new project --manager-id <manager-id> --project-id <project-id> --project-root <abs-path> [--role <role>] [--tag <tag>] --json
|
|
28
|
+
okstra manager new task-group --manager-id <manager-id> --task-group <task-group> --json
|
|
29
|
+
okstra manager new task --manager-id <manager-id> --task-group <task-group> --task-id <task-id> --task <project-id:task-group:task-id> --json
|
|
30
|
+
okstra manager task assign --manager-id <manager-id> --task-group <task-group> --task-id <task-id> --project-id <project-id> [--child-task-id <child-task-id>] [--role <role>] [--tag <tag>] [--assignment <text>] --json
|
|
31
|
+
okstra manager task note --manager-id <manager-id> --task-group <task-group> --task-id <task-id> --scope <shared|project> [--project-id <project-id>] --body <text> --json
|
|
32
|
+
okstra manager task sync --manager-id <manager-id> --task-group <task-group> --task-id <task-id> --json
|
|
33
|
+
okstra manager task status --manager-id <manager-id> --task-group <task-group> --task-id <task-id> --json
|
|
34
|
+
okstra manager task run --manager-id <manager-id> --project-id <project-id> --task-group <task-group> --task-id <task-id> [--child-task-id <child-task-id>] --json
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## 저장 모델
|
|
38
|
+
|
|
39
|
+
Manager state 는 `~/.okstra/managers/<manager-id>/` 아래에 저장된다.
|
|
40
|
+
|
|
41
|
+
- `manager.json`: manager id / schema / createdAt
|
|
42
|
+
- `projects.json`: 등록된 projectId, projectRoot, role, tags
|
|
43
|
+
- `task-groups/<safe-group>/<safe-task>/manifest.json`: manager task objective, common brief, progress mode
|
|
44
|
+
- `children.json`: child task plan, assignment, launch metadata
|
|
45
|
+
- `directives.jsonl`: shared/project directive rows
|
|
46
|
+
- `snapshots.json`: `task sync` 가 project-local `.okstra` 에서 읽어온 read-side snapshot
|
|
47
|
+
- `events.jsonl`: `task-created`, `child-launch-prepared` 같은 manager event
|
|
48
|
+
- `child-context/<safe-project>-<safe-task>.md`: `task run` 이 만든 child lead context
|
|
49
|
+
|
|
50
|
+
비 ASCII task-group/task-id 처럼 slug 가 비는 segment 는 `u-<sha1-prefix>` path segment 를 사용하지만, manifest 와 child `taskKey` 는 원래 입력값을 보존한다.
|
|
51
|
+
|
|
52
|
+
## Child launch
|
|
53
|
+
|
|
54
|
+
`task run` 은 child 작업을 직접 실행하지 않고 launch packet 을 준비한다. 반환 packet 의 핵심 필드는:
|
|
55
|
+
|
|
56
|
+
- `backend`: `$TMUX` 가 있으면 `tmux-child-lead`, 없으면 `subagent-child-lead`
|
|
57
|
+
- `workerDispatchBackend`: v1 에서는 항상 `subagent`
|
|
58
|
+
- `projectRoot`: child project root
|
|
59
|
+
- `contextPath`: manager child context markdown
|
|
60
|
+
- `runArgs`: host launcher 가 사용할 `okstra run ... --directive "Read manager child context: ..."` 인자
|
|
61
|
+
|
|
62
|
+
packet 생성이 성공하면 `children.json` 의 해당 child launch 상태가 `prepared` 로 갱신되고, `events.jsonl` 에 `child-launch-prepared` 가 append 된다. 실패해도 project-local task state 는 수정하지 않는다.
|
|
@@ -93,20 +93,13 @@ worker prompt history는 `/tmp`가 아니라 항상 현재 run의 `prompts/` 아
|
|
|
93
93
|
같은 task-type으로 다시 실행하면 동일한 `runs/<task-type>/` 폴더를 재사용하지만, 유형별 하위 폴더 아래에서 run-level 파일명이 `-<task-type>-<seq>` suffix로 분리되므로 기존 산출물을 덮어쓰지 않습니다. `<seq>`는 카테고리 디렉토리(`manifests/`, `prompts/`, `reports/`, `status/`, `state/`, `sessions/`, `worker-results/`)별로 독립 스캔되므로 같은 run에서도 카테고리별 값이 다를 수 있습니다.
|
|
94
94
|
이전 flat legacy artifact가 task-type run 폴더 최상위에 남아 있으면 다음 실행 시 해당 유형별 하위 폴더로 자동 정리합니다.
|
|
95
95
|
|
|
96
|
-
### 3. Project-level discovery and skill
|
|
96
|
+
### 3. Project-level discovery and installed skill assets
|
|
97
97
|
프로젝트 공용 discovery pointer는 아래 경로에 생성합니다.
|
|
98
98
|
|
|
99
99
|
```text
|
|
100
100
|
<target-project>/.okstra/discovery/latest-task.json
|
|
101
101
|
<target-project>/.okstra/discovery/task-catalog.json
|
|
102
102
|
```
|
|
103
|
-
scripts/okstra.sh workflow가 사용하는 project-local Claude assets는 아래 경로들에 seed합니다.
|
|
104
|
-
|
|
105
|
-
```text
|
|
106
|
-
<target-project>/.claude/skills/okstra/SKILL.md
|
|
107
|
-
<target-project>/.claude/skills/<sub-skill>/SKILL.md
|
|
108
|
-
<target-project>/.claude/agents/<agent>.md
|
|
109
|
-
```
|
|
110
103
|
|
|
111
104
|
역할 구분:
|
|
112
105
|
|
|
@@ -123,6 +116,34 @@ scripts/okstra.sh workflow가 사용하는 project-local Claude assets는 아래
|
|
|
123
116
|
- `.project-docs/ai/okstra/okstra-guide.md`
|
|
124
117
|
- `.project-docs/ai/okstra/worker-catalog.md`
|
|
125
118
|
|
|
119
|
+
### 4. Manager-owned cross-project state
|
|
120
|
+
|
|
121
|
+
`okstra manager` 는 project-local `.okstra` 정본을 대체하지 않고, 여러 project-local task 를 묶는 전역 manager state 를 사용자 홈에 저장합니다.
|
|
122
|
+
|
|
123
|
+
```text
|
|
124
|
+
~/.okstra/managers/<manager-id>/
|
|
125
|
+
├── manager.json
|
|
126
|
+
├── projects.json
|
|
127
|
+
└── task-groups/<safe-task-group>/<safe-task-id>/
|
|
128
|
+
├── manifest.json
|
|
129
|
+
├── children.json
|
|
130
|
+
├── directives.jsonl
|
|
131
|
+
├── snapshots.json
|
|
132
|
+
├── events.jsonl
|
|
133
|
+
└── child-context/<safe-project-id>-<safe-child-task-id>.md
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
저장 권위:
|
|
137
|
+
|
|
138
|
+
- `manager.json`: manager identity 와 schema version.
|
|
139
|
+
- `projects.json`: manager 에 등록된 projectId / projectRoot / role / tags. `projectRoot` 는 이미 존재하는 디렉터리여야 하며, project-local `.okstra/project.json` 이 없을 때만 setup-equivalent registration 을 수행합니다.
|
|
140
|
+
- `manifest.json`, `children.json`, `directives.jsonl`: manager-owned plan, child assignment, shared/project directive.
|
|
141
|
+
- `snapshots.json`: `task sync` 가 child project `.okstra` 에서 읽어온 read-side snapshot. project state 의 source of truth 가 아닙니다.
|
|
142
|
+
- `events.jsonl`: `task-created`, `child-launch-prepared` 같은 manager event.
|
|
143
|
+
- `child-context/*.md`: `task run` 이 준비한 child lead context. sibling project report/snapshot 은 read-side source material 로만 전달합니다.
|
|
144
|
+
|
|
145
|
+
Path segment 는 slug 로 정규화합니다. 비 ASCII 값처럼 slug 가 비는 경우에는 `u-<sha1-prefix>` fallback segment 를 쓰지만, `manifest.json` 과 child `taskKey` 는 원래 입력값(`project-id:task-group:task-id`)을 보존합니다.
|
|
146
|
+
|
|
126
147
|
## Task manifest contract
|
|
127
148
|
|
|
128
149
|
`task-manifest.json`은 Claude가 task continuity를 이해할 때 기준이 되는 canonical metadata 파일입니다.
|
package/docs/kr/architecture.md
CHANGED
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
|
|
78
78
|
okstra 의 prepare 책임은 단일 python 진입점 [`okstra_ctl.run.prepare_task_bundle`](../../scripts/okstra_ctl/run.py) 에 모여 있습니다. 이 함수가 다음을 한 트랜잭션으로 수행합니다.
|
|
79
79
|
|
|
80
|
-
- okstra 설치 자산(`~/.claude/skills/okstra
|
|
80
|
+
- okstra 설치 자산(`~/.agents/skills/okstra-*`, optional `~/.claude/skills/okstra-*` / `~/.claude/agents/*-worker.md`, `~/.okstra/bin/...`) 존재 확인
|
|
81
81
|
- `<PROJECT_ROOT>/.okstra/project.json` self-registration (또는 projectId 일치 검증)
|
|
82
82
|
- task type → `prompts/profiles/<task-type>.md` 로드 + 권장 workers 추출
|
|
83
83
|
- 사용자 worker/model 오버라이드 정규화 (Claude/Codex/Antigravity/Report-writer 각각 display ↔ execution-value 매핑)
|
|
@@ -122,6 +122,7 @@ okstra 의 prepare 책임은 단일 python 진입점 [`okstra_ctl.run.prepare_ta
|
|
|
122
122
|
- [`okstra_ctl.session`](../../scripts/okstra_ctl/session.py) · [`okstra_ctl.seeding`](../../scripts/okstra_ctl/seeding.py) — Claude session id / resume command / 설치 검증 / runtime settings.
|
|
123
123
|
- [`okstra_ctl.{ids,index,invocation,jsonl,project_meta,reconcile,resolver,sequence,batch,backfill,listing,locks,tmux}`](../../scripts/okstra_ctl/) — 중앙 인덱스 (`~/.okstra`) 의 기존 모듈군.
|
|
124
124
|
- [`okstra_project.{resolver,state}`](../../scripts/okstra_project/) — PROJECT_ROOT 해석 + project.json upsert + task-catalog/manifest reader.
|
|
125
|
+
- [`okstra_ctl.manager_cli`](../../scripts/okstra_ctl/manager_cli.py), [`manager_store`](../../scripts/okstra_ctl/manager_store.py), [`manager_sync`](../../scripts/okstra_ctl/manager_sync.py), [`manager_launch`](../../scripts/okstra_ctl/manager_launch.py), [`manager_paths`](../../scripts/okstra_ctl/manager_paths.py) — `okstra manager` 의 cross-project manager state, one-way project snapshot sync, child launch packet/context 생성.
|
|
125
126
|
|
|
126
127
|
### Bash entry points (thin)
|
|
127
128
|
|
|
@@ -146,9 +147,9 @@ okstra 의 prepare 책임은 단일 python 진입점 [`okstra_ctl.run.prepare_ta
|
|
|
146
147
|
- [`prompts/lead/okstra-lead-contract.md`](../../prompts/lead/okstra-lead-contract.md) — main okstra lead contract. 런타임 리소스(`~/.okstra/prompts/lead/`)이며 agent skill 이 아닙니다.
|
|
147
148
|
- [`skills/okstra-setup/SKILL.md`](../../skills/okstra-setup/SKILL.md) — **첫 실행 부트스트랩**. `okstra install` + `project.json` 생성.
|
|
148
149
|
- [`skills/okstra-run/SKILL.md`](../../skills/okstra-run/SKILL.md) — **현재 claude 세션 안에서 okstra task 를 시작**하는 in-session 진입점. `prepare_task_bundle` 직접 호출.
|
|
149
|
-
- 사용자 호출 가능 스킬은 `skills/okstra-setup/SKILL.md`, `skills/okstra-brief/SKILL.md`, `skills/okstra-run/SKILL.md`, `skills/okstra-memory/SKILL.md`, `skills/okstra-inspect/SKILL.md`, `skills/okstra-schedule/SKILL.md`, `skills/okstra-container-build/SKILL.md`
|
|
150
|
+
- 사용자 호출 가능 스킬은 `skills/okstra-setup/SKILL.md`, `skills/okstra-brief/SKILL.md`, `skills/okstra-run/SKILL.md`, `skills/okstra-manager/SKILL.md`, `skills/okstra-memory/SKILL.md`, `skills/okstra-inspect/SKILL.md`, `skills/okstra-schedule/SKILL.md`, `skills/okstra-container-build/SKILL.md` 8종뿐이며, 이것만 agent skill home 으로 복사됩니다 — brief 작성, phase 진행, cross-project manager task 조정, 전역 Memory Book 저장/검색, status/history/report/time/logs/cost/errors/recap read-side, schedule 보조, 그리고 `okstra-container-build` 는 검증 완료된 task 코드를 docker compose 그룹으로 배포하고 컨테이너별 watcher 로 감시하는 비선형(PHASE_SEQUENCE 외부) 컨테이너 배포/감시 스킬. `okstra-manager` 는 `okstra manager` CLI JSON/launch packet 을 source of truth 로 사용하며, manager-owned plan/assignment/directive/snapshot/event 는 `~/.okstra/managers/<manager-id>/` 아래에 저장합니다. `okstra-inspect` 의 read-side facet 은 `skills/okstra-inspect/SKILL.md` 의 sub-command 표가 정본인 8종입니다. `okstra-inspect logs` 는 codex/antigravity wrapper 가 매 dispatch 마다 `runs/<task-type>/prompts/<worker>-prompt-<phase>-<seq>.log` 로 남기는 live-log sidecar 의 인벤토리·정리 안내(read-only), `okstra-inspect cost` 는 `okstra context-cost` 결과 요약, `okstra-inspect errors` 는 task 의 okstra-run 에러 로그를 타임스탬프 markdown error-report 로 모아 렌더하고 요약을 출력, `okstra-inspect recap` 은 task 의 run 간 phase 전·후 요약에 더해 `.okstra` 산출물에 대한 자유 Q&A 까지 답합니다.
|
|
150
151
|
- 내부 운영 계약 — `context-loader` / `team-contract` / `convergence` / `report-writer` 와 lead 계약 — 은 `prompts/lead/*.md` 로, 구현/검증 워커의 언어별 coding preflight 는 `prompts/coding-preflight/*` (overview 라우터 + clean-code + languages/frameworks/architectures 3단계 선택) 로 이동했습니다. 모두 `~/.okstra/prompts/` 에 설치되는 런타임 리소스이며 skill discovery 대상이 아닙니다. generated launch prompt 가 lead 에게 절대 경로를 제공하고, 재설치 시 과거 `okstra-context-loader` / `okstra-team-contract` / `okstra-convergence` / `okstra-report-writer` / `okstra-coding-preflight` / `okstra` skill 디렉터리는 exact-name prune 됩니다.
|
|
151
|
-
- 플러그인 매니페스트: [`../../.claude-plugin/plugin.json`](../../.claude-plugin/plugin.json) — `npx skills@latest add Devonshin/okstra` 보조 채널이 참조. 일반 셋업에는 `npx okstra@latest install` 을 사용한다. 플러그인 매니페스트는 사용자 진입점
|
|
152
|
+
- 플러그인 매니페스트: [`../../.claude-plugin/plugin.json`](../../.claude-plugin/plugin.json) — `npx skills@latest add Devonshin/okstra` 보조 채널이 참조. 일반 셋업에는 `npx okstra@latest install` 을 사용한다. 플러그인 매니페스트는 사용자 진입점 8개(`okstra-setup`, `okstra-brief`, `okstra-run`, `okstra-manager`, `okstra-memory`, `okstra-inspect`, `okstra-schedule`, `okstra-container-build`)만 노출한다.
|
|
152
153
|
- 설치 위치: `~/.claude/skills/<name>/SKILL.md` 또는 `~/.agents/skills/<name>/SKILL.md`.
|
|
153
154
|
- 릴리스 절차: [`../../RELEASING.md`](../../RELEASING.md) — npm publish 흐름과 release-please / manual fallback.
|
|
154
155
|
|
|
@@ -175,7 +176,7 @@ okstra 의 prepare 단계는 디스크 권위 + 단일 python 진입점 모델
|
|
|
175
176
|
│ ┌────────────┴────────────────┐ │
|
|
176
177
|
│ ▼ ▼ │
|
|
177
178
|
│ on-disk authority ~/.okstra (central index) │
|
|
178
|
-
│ <PROJECT_ROOT>/.
|
|
179
|
+
│ <PROJECT_ROOT>/.okstra/ per-task mutex + record_start │
|
|
179
180
|
└────────────────────────────────────────────────────────────────┘
|
|
180
181
|
```
|
|
181
182
|
|
package/docs/kr/cli.md
CHANGED
|
@@ -620,7 +620,7 @@ chmod +x ~/.local/bin/okstra-ctl
|
|
|
620
620
|
|
|
621
621
|
### `okstra` Node CLI — admin / introspection subcommands
|
|
622
622
|
|
|
623
|
-
`okstra` Node CLI (`bin/okstra`) 는 installer/admin 명령과 skill / agent 가 사용하는 introspection 명령을 함께 제공합니다. Python 런타임을 직접 호출하지 않고 Node wrapper를 통하므로 `PYTHONPATH` wiring은 `src/
|
|
623
|
+
`okstra` Node CLI (`bin/okstra`) 는 installer/admin 명령과 skill / agent 가 사용하는 introspection 명령을 함께 제공합니다. Python 런타임을 직접 호출하지 않고 Node wrapper를 통하므로 `PYTHONPATH` wiring은 `src/lib/python-helper.mjs`가 처리합니다.
|
|
624
624
|
|
|
625
625
|
| 명령 | 용도 |
|
|
626
626
|
|---|---|
|
|
@@ -633,7 +633,7 @@ chmod +x ~/.local/bin/okstra-ctl
|
|
|
633
633
|
| `okstra check-project [--json]` | 현재 프로젝트가 등록되었는지 검증 |
|
|
634
634
|
| `okstra config <get\|set\|unset\|show> [key] [value] [--scope project\|global\|all]` | 영구 설정 관리 (예: `pr-template-path`). 원자적 JSON 쓰기 |
|
|
635
635
|
| `okstra memory <add\|list\|search\|show\|archive>` | `~/.okstra/memory-book` 전역 대화 메모리 관리. 프로젝트 `.okstra/` 와 분리된 user-home 저장소이며, `okstra 에 정리해서 보관해` 자연어 스킬의 CLI 기반 |
|
|
636
|
-
| `okstra manager <init\|discover-projects\|new\|task>` | 여러 프로젝트의 okstra task 를 manager-owned context 로 묶는 공개 CLI. `new project`, `new task-group`, `new task` 로 manager 계획을 만들고, `task assign` / `task note` / `task sync` / `task status` / `task run` 으로 프로젝트별 할당과 snapshot 을 관리한다. child task key 는 공개 문서에서 `project-id:task-group:task-id` 전체 형식을
|
|
636
|
+
| `okstra manager <init\|discover-projects\|new\|task>` | 여러 프로젝트의 okstra task 를 manager-owned context 로 묶는 공개 CLI. `new project`, `new task-group`, `new task` 로 manager 계획을 만들고, `task assign` / `task note` / `task sync` / `task status` / `task run` 으로 프로젝트별 할당과 snapshot 을 관리한다. `new project --project-root` 는 이미 존재하는 디렉터리만 받으며, 그 안의 `.okstra/project.json` 이 없을 때만 setup-equivalent registration 을 수행한다. child task key 는 공개 문서에서 `project-id:task-group:task-id` 전체 형식을 사용하고, 같은 manager task 안에서 child task id 가 다르면 `--child-task-id` 로 exact child 를 지정한다. `task run` 은 child lead 를 직접 실행하지 않고 `prepared` launch metadata/event 와 child launch context packet 을 JSON 으로 반환한다 |
|
|
637
637
|
| `okstra migrate [--apply] [--cwd <dir>] [--quiet]` | 한 번만 실행: 프로젝트 산출물 루트를 `.project-docs/okstra/` → `.okstra/` 로 이동. 기본 dry-run, `--apply` 로 실제 실행. `git mv` (git worktree) + 빈 `.project-docs/` 제거 + `<PROJECT>/CLAUDE.md` import 라인 + `.gitignore` + `~/.okstra/{recent,active}.jsonl` + `~/.okstra/worktrees/registry.json` 의 해당 프로젝트 row 동기화. 이미 `.okstra/` 가 존재하거나 레거시 디렉토리가 없으면 exit 1 로 거부. v0.x 말까지 유지 후 제거 예정 |
|
|
638
638
|
| `okstra task-list [--project-root <path>]` | `list_project_tasks` + `read_latest_task` 결과를 합쳐 task 카탈로그 + 최근 task 를 JSON 으로 반환 |
|
|
639
639
|
| `okstra task-show <task-key> [--project-root <path>]` | task-manifest.json 의 workflow / phase / status 요약 |
|
|
@@ -647,7 +647,7 @@ chmod +x ~/.local/bin/okstra-ctl
|
|
|
647
647
|
| `okstra wizard <init\|step\|render-args\|confirmation> --state-file <path>` | okstra-run 인터랙티브 입력 상태머신 (`okstra_ctl.wizard`). `init` 으로 state file 을 시드한 뒤 skill 이 `step --answer <val>` 을 반복 호출하면 다음 `Prompt` JSON 을 받음. `--answer` 는 **필수**. 응답을 주지 않고 다음 prompt 만 미리 보고 싶다면 `--no-submit` 으로 peek. `render-args` 는 최종 `render-bundle` 인자 맵, `confirmation` 은 사용자 echo 블록을 반환. `implementation` task type 에서는 `approved_plan_pick` 직후 `stage_pick` 단계가 추가되어 실행할 stage 를 선택하고, `executor_pick` 으로 넘어갑니다. brief 단계는 entry task-type(requirements-discovery / error-analysis / improvement-discovery)에서만 나오며, downstream 은 manifest 의 brief 를 자동 carry-in 하고(미등록 시 `brief_carry` 3-옵션 fallback), `release-handoff` 는 brief 없이 `handoff_stage_pick` 멀티선택(eligible stage 묶음 / 전체 task)으로 진입합니다 |
|
|
648
648
|
| `okstra token-usage ...` | 설치된 `okstra-token-usage.py` 를 감싸 run token usage 수집/치환을 수행. 세션 jsonl 은 기본적으로 `$OKSTRA_HOME/cache/token-usage/` 의 byte cursor 캐시로 증분 스캔하며, `--no-cache` 로 캐시를 우회해 전체 재스캔을 강제할 수 있음(정확성 폴백) |
|
|
649
649
|
|
|
650
|
-
> 모든 subcommand 는 `bin/okstra` 가 spawn 하는 python 헬퍼 (`src/
|
|
650
|
+
> 모든 subcommand 는 `bin/okstra` 가 spawn 하는 python 헬퍼 (`src/lib/python-helper.mjs`) 가 `PYTHONPATH` 와 `~/.okstra/lib/python` 을 wire 합니다. 직접 `python3 -m okstra_ctl.*` 으로 호출하면 `PYTHONPATH` 를 사용자가 직접 셋업해야 합니다.
|
|
651
651
|
|
|
652
652
|
### Live-log sidecar
|
|
653
653
|
|
|
@@ -153,17 +153,29 @@ Runtime/install asset changes follow this checklist:
|
|
|
153
153
|
| `setup` | `src/commands/lifecycle/setup.mjs` | Create/update `<PROJECT_ROOT>/.okstra/project.json` |
|
|
154
154
|
| `check-project` | `src/commands/lifecycle/check-project.mjs` | Verify project registration |
|
|
155
155
|
| `config` | `src/commands/lifecycle/config.mjs` | Read/write project/global settings such as PR template path |
|
|
156
|
+
| `migrate` | `src/commands/lifecycle/migrate.mjs` | One-shot legacy `.project-docs/okstra` → `.okstra` migration helper |
|
|
157
|
+
| `git-reconcile` | `src/commands/execute/git-reconcile.mjs` | Reconcile stale stage SHAs after external git history changes |
|
|
158
|
+
| `handoff` | `src/commands/execute/handoff.mjs` | Stage-group release-handoff eligibility / assemble / record helpers |
|
|
159
|
+
| `integrate-stages` | `src/commands/execute/integrate-stages.mjs` | Merge verified stages into the task worktree and clean stage worktrees |
|
|
156
160
|
| `task-list`, `task-show` | `src/commands/inspect/task-list.mjs`, `src/commands/inspect/task-show.mjs` | Task/run introspection for skills |
|
|
161
|
+
| `resolve-task-key` | `src/commands/inspect/resolve-task-key.mjs` | Resolve a bare task-id to candidate task-keys from the project catalog |
|
|
162
|
+
| `time-report`, `log-report`, `error-report`, `error-zip` | `src/commands/inspect/*.mjs` | Read-side task runtime, wrapper log, and error aggregation helpers |
|
|
157
163
|
| `context-cost` | `src/commands/inspect/context-cost.mjs` | Estimate task bundle file/read context cost |
|
|
158
164
|
| `worktree-lookup` | `src/commands/execute/worktree-lookup.mjs` | Look up a task-key's registered worktree |
|
|
159
165
|
| `plan-validate` | `src/commands/execute/plan-validate.mjs` | Check approved-plan approval marker |
|
|
160
166
|
| `render-bundle` | `src/commands/execute/render-bundle.mjs` | Preview `prepare_task_bundle(render_only=True)` |
|
|
167
|
+
| `run` | `src/commands/execute/run.mjs` | Host-aware execution front door (`auto` → Claude/Codex/external path selection) |
|
|
168
|
+
| `codex-run`, `codex-dispatch` | `src/commands/execute/codex-*.mjs` | Codex lead dry-run bundle preparation and CLI-backed worker dispatch |
|
|
169
|
+
| `team` | `src/commands/execute/team.mjs` | External lead tmux-pane worker dispatch / await / teardown |
|
|
161
170
|
| `render-views` | `src/commands/report/render-views.mjs` | Generate slim MD + self-contained HTML report views |
|
|
171
|
+
| `render-final-report`, `inject-report-index` | `src/commands/report/*.mjs` | Render final-report Markdown from data.json and inject top-of-report index anchors |
|
|
162
172
|
| `wizard` | `src/commands/execute/wizard.mjs` | Drive the `okstra-run` interactive state machine |
|
|
163
173
|
| `token-usage` | `src/commands/execute/token-usage.mjs` | Wrap installed Python token usage CLI |
|
|
174
|
+
| `spawn-followups`, `error-log` | `src/commands/execute/*.mjs` | Follow-up task bundle creation and run error-log append helpers |
|
|
164
175
|
| `memory` | `src/commands/memory/memory.mjs` | Store/find global conversation memory under `~/.okstra/memory-book` |
|
|
165
176
|
| `recap` | `src/commands/inspect/recap.mjs` | `okstra recap <assemble\|record>` Node wrapper backing the okstra-inspect `recap` facet |
|
|
166
177
|
| `container` | `src/commands/inspect/container.mjs` | `bin okstra container` thin shim into `scripts/okstra_ctl/container.py` for the okstra-container-build skill |
|
|
178
|
+
| `manager` | `src/commands/manager.mjs` | Thin shim into `scripts/okstra_ctl/manager_cli.py` for cross-project manager state and child launch packets |
|
|
167
179
|
|
|
168
180
|
`src/lib/python-helper.mjs` centralizes Node → Python execution so command modules do not duplicate subprocess wiring.
|
|
169
181
|
|
|
@@ -176,7 +188,7 @@ Top-level scripts:
|
|
|
176
188
|
| `okstra.sh` | Bash CLI wrapper around `prepare_task_bundle`, optionally launches `claude` |
|
|
177
189
|
| `okstra-ctl.sh` | Bash control center for list/show/open/rerun/reconcile/project commands |
|
|
178
190
|
| `okstra-central.sh` | Central run index writer / reconciler entrypoint |
|
|
179
|
-
| `okstra-codex-exec.sh`, `okstra-antigravity-exec.sh` | Worker CLI wrappers with log/status sidecars |
|
|
191
|
+
| `okstra-claude-exec.sh`, `okstra-codex-exec.sh`, `okstra-antigravity-exec.sh` | Worker CLI wrappers with log/status sidecars |
|
|
180
192
|
| `okstra-wrapper-status.py` | Heartbeat sidecar writer used by worker wrappers |
|
|
181
193
|
| `okstra-token-usage.py` | Token usage CLI entrypoint |
|
|
182
194
|
| `okstra-render-final-report.py` | Render final-report Markdown from data.json |
|
|
@@ -227,6 +239,11 @@ Important modules:
|
|
|
227
239
|
| `container.py` | okstra-container-build 공개 스킬의 `okstra container` 수렴 entrypoint — `provision_container_group` + `up`/`status`/`logs`/`stop-watcher`/`down` 디스패치, env override 합성, compose argv 조립, 컨테이너별 watcher 기동 |
|
|
228
240
|
| `container_registry.py` | flock-guarded 보조 인덱스 — 컨테이너 그룹별 tmux session/pane 및 watcher findings 추적 |
|
|
229
241
|
| `plan_run_root.py` | `approved_plan_path` → `plan_run_root` 파생 + task-key 역추적 공유 헬퍼 |
|
|
242
|
+
| `manager_cli.py` | `okstra manager` Python entrypoint — manager init/discover/new/task subcommands and JSON output |
|
|
243
|
+
| `manager_paths.py` | Manager state path SSOT under `~/.okstra/managers/<manager-id>/`; slug fallback uses `u-<sha1-prefix>` when a safe segment would be empty |
|
|
244
|
+
| `manager_store.py` | Manager-owned state mutation — project membership, task planning, assignment, directives, event append |
|
|
245
|
+
| `manager_sync.py` | One-way child project `.okstra` snapshot reader; corrupt child state becomes row-level `error` so other children continue |
|
|
246
|
+
| `manager_launch.py` | Child launch packet and manager child context renderer; records `prepared` launch metadata/events without changing project-local task state |
|
|
230
247
|
|
|
231
248
|
### 4.4 `scripts/okstra_project/`
|
|
232
249
|
|
|
@@ -326,17 +343,17 @@ The lead operating contract moved out of `agents/` to `prompts/lead/okstra-lead-
|
|
|
326
343
|
1. Resolve project root and verify/upsert `project.json`.
|
|
327
344
|
2. Resolve profile, required workers, model assignments, executor provider.
|
|
328
345
|
3. Compute task/run paths and persist run context under `runs/<task-type>/manifests/`.
|
|
329
|
-
4. Provision or reuse the task-key worktree.
|
|
346
|
+
4. Provision or reuse the task-key worktree, or the selected implementation stage worktree for stage-isolated runs.
|
|
330
347
|
5. Materialize `instruction-set/` files and lead prompt snapshot.
|
|
331
348
|
6. Persist run inputs, team state, task manifest, task index, run manifest, timeline, discovery pointers.
|
|
332
349
|
7. Record the run in `~/.okstra/{active,recent}.jsonl` and project index.
|
|
333
350
|
|
|
334
351
|
### 5.2 Worktree model
|
|
335
352
|
|
|
336
|
-
|
|
353
|
+
Non-`implementation` phases share one task-key worktree:
|
|
337
354
|
|
|
338
355
|
```text
|
|
339
|
-
~/.okstra/worktrees/<project-id>/<task-group>/<task-id>/
|
|
356
|
+
~/.okstra/worktrees/<project-id>/<task-group-segment>/<task-id-segment>/
|
|
340
357
|
```
|
|
341
358
|
|
|
342
359
|
Branch name:
|
|
@@ -345,7 +362,16 @@ Branch name:
|
|
|
345
362
|
<work-category-prefix>-<task-id-segment>
|
|
346
363
|
```
|
|
347
364
|
|
|
348
|
-
|
|
365
|
+
That task-key worktree is reused for `requirements-discovery` through `implementation-planning`, and by whole-task verification / handoff flows after implementation stages have been integrated. `worktreeSyncDirs`, `worktreeSyncFiles`, and `worktreeSnapshotFiles` provide filesystem continuity only; they do not expand okstra's context/write boundary.
|
|
366
|
+
|
|
367
|
+
`implementation` is stage-isolated: one run owns one `stage-<N>` worktree and branch, with stage-key reservation in `worktree_registry.py`.
|
|
368
|
+
|
|
369
|
+
```text
|
|
370
|
+
~/.okstra/worktrees/<project-id>/<task-group-segment>/<task-id-segment>/stage-<N>/
|
|
371
|
+
<work-category-prefix>-<task-id-segment>-s<N>
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
Single-stage `final-verification --stage <N>` reuses the matching implementation stage worktree read-only, while run artifacts are isolated under `runs/final-verification/stage-<N>/`. Whole-task final-verification keeps the flat `runs/final-verification/` shape.
|
|
349
375
|
|
|
350
376
|
### 5.3 Final report model
|
|
351
377
|
|
|
@@ -368,13 +394,14 @@ The Markdown is derived, not the authoring source. The schema is the contract.
|
|
|
368
394
|
~/.okstra/
|
|
369
395
|
├── version
|
|
370
396
|
├── lib/python/{okstra_ctl,okstra_project,okstra_token_usage,okstra_vendor,lib/}
|
|
371
|
-
├── bin/{okstra.sh,okstra-codex-exec.sh,okstra-antigravity-exec.sh,...}
|
|
397
|
+
├── bin/{okstra.sh,okstra-claude-exec.sh,okstra-codex-exec.sh,okstra-antigravity-exec.sh,...}
|
|
372
398
|
├── templates/
|
|
373
399
|
├── installed-skills.json
|
|
374
400
|
├── installed-agents.json
|
|
375
401
|
├── active.jsonl
|
|
376
402
|
├── recent.jsonl
|
|
377
403
|
├── projects/<project-id>/{meta.json,index.jsonl}
|
|
404
|
+
├── managers/<manager-id>/{manager.json,projects.json,task-groups/...}
|
|
378
405
|
├── worktrees/{registry.json,registry.lock,<project>/<group>/<task>/}
|
|
379
406
|
├── archive/
|
|
380
407
|
└── .locks/
|
|
@@ -481,4 +508,4 @@ Clarifications now live in the unified `## 1. Clarification Items` table. Deprec
|
|
|
481
508
|
|
|
482
509
|
---
|
|
483
510
|
|
|
484
|
-
*Updated: 2026-06-
|
|
511
|
+
*Updated: 2026-06-27 · Source of truth checked against `package.json`, `bin/okstra`, `src/cli-registry.mjs`, `src/lib/skill-catalog.mjs`, `tools/build.mjs`, `scripts/`, `skills/`, `agents/`, `templates/`, `schemas/`, `validators/`, and tests.*
|
|
@@ -42,7 +42,7 @@ flowchart TD
|
|
|
42
42
|
| `error-analysis` | [error-analysis.md](error-analysis.md) | 증상과 evidence로 원인 후보와 검증 경로를 찾는다. |
|
|
43
43
|
| `implementation-planning` | [implementation-planning.md](implementation-planning.md) | 구현 옵션, 검증, rollback, approval gate가 있는 계획을 만든다. |
|
|
44
44
|
| `implementation` | [implementation.md](implementation.md) | 승인된 계획을 executor가 구현하고 verifier가 독립 검증한다. |
|
|
45
|
-
| `final-verification` | [final-verification.md](final-verification.md) | 구현
|
|
45
|
+
| `final-verification` | [final-verification.md](final-verification.md) | 구현 결과의 whole-task 또는 single-stage acceptance를 판정한다. |
|
|
46
46
|
| `release-handoff` | [release-handoff.md](release-handoff.md) | accepted verdict 뒤 push/PR handoff를 lead-only로 수행한다. |
|
|
47
47
|
|
|
48
48
|
## 4. 핵심 코드 위치
|
|
@@ -52,10 +52,13 @@ flowchart TD
|
|
|
52
52
|
| okstra-run skill 절차 | [`skills/okstra-run/SKILL.md`](../../skills/okstra-run/SKILL.md) |
|
|
53
53
|
| wizard state machine | [`scripts/okstra_ctl/wizard.py`](../../scripts/okstra_ctl/wizard.py) |
|
|
54
54
|
| wizard prompt text | [`prompts/wizard/prompts.ko.json`](../../prompts/wizard/prompts.ko.json) |
|
|
55
|
-
| render-bundle Node shim | [`src/render-bundle.mjs`](../../src/render-bundle.mjs) |
|
|
55
|
+
| render-bundle Node shim | [`src/commands/execute/render-bundle.mjs`](../../src/commands/execute/render-bundle.mjs) |
|
|
56
56
|
| bundle 생성 단일 진입점 | [`scripts/okstra_ctl/run.py`](../../scripts/okstra_ctl/run.py) |
|
|
57
|
+
| implementation stage 선택/provision | [`scripts/okstra_ctl/implementation_stage.py`](../../scripts/okstra_ctl/implementation_stage.py) |
|
|
58
|
+
| stage target/base/verification 정책 | [`scripts/okstra_ctl/stage_targets.py`](../../scripts/okstra_ctl/stage_targets.py) |
|
|
57
59
|
| phase boundary | [`scripts/okstra_ctl/workflow.py`](../../scripts/okstra_ctl/workflow.py) |
|
|
58
60
|
| task worktree | [`scripts/okstra_ctl/worktree.py`](../../scripts/okstra_ctl/worktree.py) |
|
|
61
|
+
| stage-group handoff | [`scripts/okstra_ctl/handoff.py`](../../scripts/okstra_ctl/handoff.py) |
|
|
59
62
|
| worker roster parser | [`scripts/okstra_ctl/workers.py`](../../scripts/okstra_ctl/workers.py) |
|
|
60
63
|
| lead operating contract | [`prompts/lead/okstra-lead-contract.md`](../../prompts/lead/okstra-lead-contract.md) |
|
|
61
64
|
| phase profiles | [`prompts/profiles/`](../../prompts/profiles/) |
|
|
@@ -68,7 +71,6 @@ flowchart TD
|
|
|
68
71
|
| `requirements-discovery` | 공통 질문만 | profile/brief/base-ref 존재 | multi-worker analysis, convergence 1 round default | `pending-routing-decision` |
|
|
69
72
|
| `error-analysis` | 공통 질문만 | profile/brief/base-ref 존재 | multi-worker analysis, convergence 2 rounds default | `implementation-planning` |
|
|
70
73
|
| `implementation-planning` | 공통 질문만 | profile/brief/base-ref 존재 | multi-worker analysis + Phase 6 plan-body verification | `implementation` |
|
|
71
|
-
| `implementation` | approved plan, stage, executor | approved marker, stage map, QA command deny-list | executor writes, verifiers read-only | `final-verification` |
|
|
72
|
-
| `final-verification` |
|
|
73
|
-
| `release-handoff` | PR template override/scope |
|
|
74
|
-
|
|
74
|
+
| `implementation` | approved plan, stage multi-pick, executor | approved marker, stage map, stage-key reservation, QA command deny-list | one run = one stage; executor writes in isolated stage worktree, verifiers read-only | `final-verification` |
|
|
75
|
+
| `final-verification` | approved plan, stage pick(whole-task or single-stage) | `VERIFICATION_TARGET` 해소; whole-task 자동 통합/teardown 또는 single-stage worktree reuse | whole-task may integrate stages first; analyser verification itself is read-only | `pending-release-handoff` |
|
|
76
|
+
| `release-handoff` | handoff scope(stage-group or whole-task), PR template override/scope | accepted verification eligibility, generated `release-handoff-input.md`, empty worker roster | single-lead; whole-task PR or stage-group collector branch/PR | `done-or-follow-up` |
|
|
@@ -93,7 +93,7 @@ sequenceDiagram
|
|
|
93
93
|
Skill->>FS: read claude-execution-prompt.md
|
|
94
94
|
```
|
|
95
95
|
|
|
96
|
-
`render-bundle
|
|
96
|
+
`render-bundle`의 Node shim은 [`src/commands/execute/render-bundle.mjs`](../../src/commands/execute/render-bundle.mjs)다. 이 shim이 `--workspace-root`, `--render-only`, runtime resolution 인자를 직접 붙인다. 따라서 okstra-run 경로의 initial run status는 `prepared`, task status는 `instruction-set-generated`로 시작한다.
|
|
97
97
|
|
|
98
98
|
## 5. Claude lead phase 1-7
|
|
99
99
|
|
|
@@ -131,8 +131,15 @@ flowchart TD
|
|
|
131
131
|
Runs --> Reports[reports/final-report-*.md<br/>reports/*.data.json]
|
|
132
132
|
Runs --> State[state/*.json]
|
|
133
133
|
Runs --> Status[status/final-status-*.json]
|
|
134
|
+
Runs --> ImplStage[implementation/stage-N/...]
|
|
135
|
+
Runs --> FvStage[final-verification/stage-N/...]
|
|
136
|
+
Runs --> Carry[implementation/carry/stage-N.json]
|
|
137
|
+
Runs --> Consumers[implementation-planning/consumers.jsonl]
|
|
138
|
+
Root --> Handoff[release-handoff-input.md]
|
|
134
139
|
```
|
|
135
140
|
|
|
141
|
+
`implementation`과 단일-stage `final-verification`은 run 산출물을 `stage-<N>/` 아래에 격리한다. `implementation`의 carry sidecar와 `consumers.jsonl`은 stage 간 조율 ledger라 phase root에 남는다. `release-handoff`는 brief를 받지 않고 prepare가 `release-handoff-input.md`를 생성한다.
|
|
142
|
+
|
|
136
143
|
`runtime/`은 build output이므로 이 흐름을 고칠 때는 source인 `scripts/`, `skills/`, `agents/`, `prompts/`, `templates/`, `validators/`를 수정하고 `npm run build`로 갱신한다.
|
|
137
144
|
|
|
138
145
|
## 7. 공통 분기 규칙
|
|
@@ -157,10 +164,12 @@ flowchart TD
|
|
|
157
164
|
|
|
158
165
|
중요한 점은 `Use defaults`가 model 기본값을 뜻한다는 것이다. non-implementation task-type에서는 defaults를 골라도 worker roster 질문은 계속 나온다. 반대로 `implementation`은 worker override 질문이 없다. profile default roster와 executor binding을 사용한다.
|
|
159
166
|
|
|
167
|
+
Worktree 규칙은 phase별로 다르다. `requirements-discovery`부터 `implementation-planning`까지는 task-key worktree를 재사용한다. `implementation`은 task-key worktree를 anchor로 삼되 실제 실행은 stage-key(`stage-<N>`) worktree와 `runs/implementation/stage-<N>/` 산출물에서 한 stage씩 격리한다. `final-verification --stage N`은 해당 implementation stage worktree를 읽기 대상으로 재사용하고, whole-task 모드는 task-key worktree에 stage commit을 자동 통합한 뒤 검증 target을 만든다.
|
|
168
|
+
|
|
160
169
|
## 8. 주의할 불일치 지점
|
|
161
170
|
|
|
162
171
|
- `okstra-run` wizard 경로는 implementation approved plan에 이미 approval marker가 있어야 통과한다. `scripts/okstra.sh`에는 `--approve`가 있어 checkbox를 CLI ack로 flip할 수 있지만, 현재 wizard `render_args()`에는 `approve` flag가 없다.
|
|
163
|
-
- `release-handoff
|
|
172
|
+
- `release-handoff`는 brief가 없다. prepare가 accepted final-verification report를 인용해 `release-handoff-input.md`를 만들고, wizard의 `handoff_stage_pick` 또는 CLI `--stages`가 whole-task/stage-group 범위를 고정한다.
|
|
173
|
+
- `release-handoff`도 task worktree provisioning 대상이다. 정상적인 handoff는 같은 task-key의 implementation/final-verification 결과를 재사용하는 흐름이 안전하다. 새 task로 시작하면 새 worktree가 만들어지고, profile의 "implementation commit이 있어야 함" entry gate에서 막힐 수 있다.
|
|
164
174
|
- `release-handoff`는 profile에 `Required workers:` block이 없다. runtime도 worker roster를 강제로 empty로 만든다.
|
|
165
175
|
- 모든 task-type은 한 run 안에서 다음 lifecycle phase를 시작하지 않는다. "다음 단계 진행해"는 현재 phase 산출을 마무리하라는 뜻으로만 해석한다.
|
|
166
|
-
|
|
@@ -21,10 +21,10 @@
|
|
|
21
21
|
flowchart TD
|
|
22
22
|
Start[/okstra-run/] --> Common[공통 task identity flow]
|
|
23
23
|
Common --> Type[task-type = final-verification]
|
|
24
|
-
Type -->
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
Type --> PlanPick[approved plan pick]
|
|
25
|
+
PlanPick --> Approved[approval marker confirm]
|
|
26
|
+
Approved --> Stage[stage pick<br/>whole-task or stage number]
|
|
27
|
+
Stage --> Defaults[Use defaults / Customize]
|
|
28
28
|
Defaults -->|defaults| Workers[worker roster pick<br/>claude/codex/report-writer default]
|
|
29
29
|
Defaults -->|customize| Models[lead + worker model prompts]
|
|
30
30
|
Models --> Extras[directive, related tasks, clarification]
|
|
@@ -35,38 +35,47 @@ flowchart TD
|
|
|
35
35
|
|
|
36
36
|
`final-verification`은 analyser roster가 있는 non-implementation phase다. 따라서 `Use defaults`를 골라도 worker roster prompt는 계속 나온다. 기본 required workers는 `claude`, `codex`, `report-writer`이고, `antigravity`는 opt-in이다.
|
|
37
37
|
|
|
38
|
+
이 phase는 `base-ref`를 직접 묻지 않는다. wizard는 approved plan의 Stage Map을 기준으로 whole-task 또는 단일 stage를 선택하고, prepare가 registry/`consumers.jsonl`/git 상태에서 `VERIFICATION_TARGET`을 해소한다.
|
|
39
|
+
|
|
38
40
|
## 3. entry gate
|
|
39
41
|
|
|
40
42
|
```mermaid
|
|
41
43
|
sequenceDiagram
|
|
42
|
-
participant
|
|
43
|
-
participant
|
|
44
|
-
participant
|
|
44
|
+
participant P as prepare_task_bundle
|
|
45
|
+
participant C as consumers.jsonl
|
|
46
|
+
participant Reg as worktree registry
|
|
45
47
|
participant Git as verification worktree
|
|
48
|
+
participant Lead as Claude lead
|
|
46
49
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
50
|
+
P->>C: backfill carry and read done rows
|
|
51
|
+
P->>Reg: resolve task or stage worktree
|
|
52
|
+
alt whole-task
|
|
53
|
+
P->>Git: merge done stage commits into task worktree
|
|
54
|
+
P->>Git: teardown clean stage worktrees/registry keys
|
|
55
|
+
else single-stage
|
|
56
|
+
P->>Git: reuse selected stage worktree
|
|
57
|
+
end
|
|
58
|
+
P->>Git: resolve base/head/diff stat
|
|
59
|
+
P-->>Lead: inject VERIFICATION_TARGET
|
|
60
|
+
Lead->>Lead: verify against injected target only
|
|
55
61
|
```
|
|
56
62
|
|
|
57
|
-
|
|
63
|
+
prepare는 worker dispatch 전에 verification target을 고정한다. 다음 중 하나라도 실패하면 lead prompt가 시작되지 않고 `PrepareError`로 멈춘다.
|
|
64
|
+
|
|
65
|
+
- approved plan의 Stage Map을 읽을 수 없다.
|
|
66
|
+
- whole-task인데 Stage Map의 모든 stage가 `done`이 아니다.
|
|
67
|
+
- whole-task 자동 통합 중 merge conflict가 난다.
|
|
68
|
+
- single-stage인데 선택 stage의 implementation stage worktree가 없거나 dirty다.
|
|
69
|
+
- verification worktree가 `.okstra/` 밖 dirty state다.
|
|
58
70
|
|
|
59
|
-
|
|
60
|
-
- 인용된 implementation final report가 없거나 필수 section이 빠져 있다.
|
|
61
|
-
- report가 named worktree를 가리키는데 lead가 다른 checkout을 보고 있다.
|
|
62
|
-
- implementation report의 commit list / diff summary와 현재 checkout이 맞지 않는다.
|
|
63
|
-
- delivered code change commit이 없다.
|
|
71
|
+
시작된 lead는 `VERIFICATION_TARGET`을 권위로 취급한다. worktree/base/head/stage/source report를 brief에서 다시 고르지 않는다.
|
|
64
72
|
|
|
65
73
|
## 4. 검증 실행 흐름
|
|
66
74
|
|
|
67
75
|
```mermaid
|
|
68
76
|
flowchart TD
|
|
69
|
-
Gate[entry gate passed] -->
|
|
77
|
+
Gate[entry gate passed] --> Target[injected VERIFICATION_TARGET]
|
|
78
|
+
Target --> Lead[Claude lead confirms target snapshot]
|
|
70
79
|
Lead --> CW[Claude verifier<br/>read-only]
|
|
71
80
|
Lead --> XW[Codex verifier<br/>read-only]
|
|
72
81
|
Lead --> GW{Antigravity opt-in?}
|
|
@@ -110,7 +119,7 @@ flowchart TD
|
|
|
110
119
|
|
|
111
120
|
`## 7. Final Verdict`에는 `Verdict Token` field가 정확히 하나 들어가야 하며 값은 다음 셋 중 하나다.
|
|
112
121
|
|
|
113
|
-
- `accepted`: release-handoff
|
|
122
|
+
- `accepted`: whole-task이면 plain `release-handoff`, single-stage이면 `release-handoff(stage-group)` 후보가 되는 상태
|
|
114
123
|
- `conditional-accept`: 조건을 모두 명시해야 하며, 조건이 gate면 다음 phase를 막는다
|
|
115
124
|
- `blocked`: acceptance blocker가 있어 release-handoff로 갈 수 없는 상태
|
|
116
125
|
|
|
@@ -132,6 +141,7 @@ flowchart TD
|
|
|
132
141
|
final report에는 최소 다음이 필요하다.
|
|
133
142
|
|
|
134
143
|
- originating implementation final-report path
|
|
144
|
+
- verification scope(`whole-task` 또는 `single-stage`)와 injected `VERIFICATION_TARGET`
|
|
135
145
|
- inspected worktree path
|
|
136
146
|
- implementation base ref와 run start head SHA
|
|
137
147
|
- quoted commit list / diff summary
|
|
@@ -146,15 +156,16 @@ final report에는 최소 다음이 필요하다.
|
|
|
146
156
|
|
|
147
157
|
```mermaid
|
|
148
158
|
flowchart TD
|
|
149
|
-
FV[final-verification] -->
|
|
159
|
+
FV[final-verification] --> Runtime[prepare-owned whole-task integration<br/>or single-stage target resolution]
|
|
160
|
+
FV --> Allowed[lead read-only inspect/test/validate]
|
|
150
161
|
FV -. forbidden .-> Edit[source edit or config edit]
|
|
151
162
|
FV -. forbidden .-> Fix[in-run bug fix]
|
|
152
|
-
FV -. forbidden .-> Mutate[mutating command]
|
|
163
|
+
FV -. forbidden .-> Mutate[lead-owned mutating command]
|
|
153
164
|
FV -. forbidden .-> Expand[scope expansion]
|
|
154
165
|
FV -. forbidden .-> Hide[hide verifier dissent]
|
|
155
166
|
```
|
|
156
167
|
|
|
157
|
-
source edit, follow-up fix, scope expansion은 전부 금지다. 결함을 발견하면 현재 run 안에서 고치지 않고 final report에 blocker로 기록한 뒤 새 `error-analysis` 또는 `implementation-planning` 입력으로 넘긴다.
|
|
168
|
+
whole-task mode의 stage merge/teardown은 prepare가 수행하는 runtime-owned 통합 단계다. 그 이후 lead 검증은 read-only다. source edit, follow-up fix, scope expansion은 전부 금지다. 결함을 발견하면 현재 run 안에서 고치지 않고 final report에 blocker로 기록한 뒤 새 `error-analysis` 또는 `implementation-planning` 입력으로 넘긴다.
|
|
158
169
|
|
|
159
170
|
## 8. 확인한 코드
|
|
160
171
|
|
|
@@ -27,8 +27,9 @@ flowchart TD
|
|
|
27
27
|
BaseRef --> PlanPick
|
|
28
28
|
PlanPick --> Approved{APPROVED marker present?}
|
|
29
29
|
Approved -->|no| Retry[re-prompt same step]
|
|
30
|
-
Approved -->|yes| Stage[stage pick<br/>
|
|
31
|
-
Stage -->
|
|
30
|
+
Approved -->|yes| Stage[stage multi-pick<br/>ready/active markers]
|
|
31
|
+
Stage --> Chain[render-args<br/>stage + chain-stages]
|
|
32
|
+
Chain --> Executor[executor pick<br/>claude/codex/antigravity]
|
|
32
33
|
Executor --> Defaults[Use defaults / Customize]
|
|
33
34
|
Defaults -->|defaults| Confirm
|
|
34
35
|
Defaults -->|customize| Models[lead + executor model + report-writer model]
|
|
@@ -37,7 +38,9 @@ flowchart TD
|
|
|
37
38
|
Confirm --> Render[render-bundle]
|
|
38
39
|
```
|
|
39
40
|
|
|
40
|
-
`implementation`은 worker override 질문이 없다. wizard `render_args()`가 `workers`를 빈 문자열로 내보내고, runtime이 profile default roster를 사용한다.
|
|
41
|
+
`implementation`은 worker override 질문이 없다. wizard `render_args()`가 `workers`를 빈 문자열로 내보내고, runtime이 profile default roster를 사용한다. `stage_pick`은 완료/진행중/준비됨/대기 상태를 보여주는 multi-pick이다. 선택한 stage 집합은 의존성 closure와 위상정렬을 거쳐 `chain-stages` CSV가 되고, 각 실제 run은 그중 한 stage만 실행한다.
|
|
42
|
+
|
|
43
|
+
현재 okstra-run wizard 경로는 승인 checkbox를 직접 flip하는 `--approve`를 노출하지 않는다. plan file에는 이미 recognized approval marker가 있어야 한다.
|
|
41
44
|
|
|
42
45
|
## 3. runtime gate
|
|
43
46
|
|
|
@@ -47,16 +50,21 @@ sequenceDiagram
|
|
|
47
50
|
participant P as prepare_task_bundle
|
|
48
51
|
participant Plan as approved final-report
|
|
49
52
|
participant QA as project.json qaCommands
|
|
50
|
-
participant Stage as stage
|
|
53
|
+
participant Stage as stage target policy
|
|
54
|
+
participant Reg as worktree registry
|
|
55
|
+
participant WT as stage worktree
|
|
51
56
|
|
|
52
57
|
W->>P: task-type=implementation, approved-plan, stage, executor
|
|
53
58
|
P->>Plan: file exists?
|
|
54
59
|
P->>Plan: approval marker regex matches?
|
|
55
60
|
P->>Plan: unresolved Blocks=approval rows?
|
|
56
|
-
P->>Stage:
|
|
61
|
+
P->>Stage: parse Stage Map and recover carry/done rows
|
|
62
|
+
P->>Reg: read active stage-key reservations
|
|
63
|
+
P->>Stage: select exactly one ready stage
|
|
64
|
+
P->>WT: provision stage-N worktree + branch
|
|
57
65
|
P->>QA: validate qaCommands deny-list
|
|
58
66
|
P->>P: executor provider in resolved roster?
|
|
59
|
-
P->>P:
|
|
67
|
+
P->>P: namespace run artifacts under stage-N
|
|
60
68
|
P-->>W: prepared implementation prompt or PrepareError
|
|
61
69
|
```
|
|
62
70
|
|
|
@@ -86,16 +94,21 @@ Executor만 project file을 mutate할 수 있다. verifier는 같은 worktree에
|
|
|
86
94
|
```mermaid
|
|
87
95
|
flowchart LR
|
|
88
96
|
Plan[approved plan<br/>Stage Map] --> Parse[parse stage map]
|
|
89
|
-
Parse --> Done[read consumers.jsonl
|
|
90
|
-
Done -->
|
|
91
|
-
|
|
97
|
+
Parse --> Done[backfill carry<br/>read consumers.jsonl]
|
|
98
|
+
Done --> Reserved[read registry<br/>active stage reservations]
|
|
99
|
+
Reserved --> Resolve{stage arg}
|
|
100
|
+
Resolve -->|auto| Next[lowest ready<br/>not done/started/reserved]
|
|
92
101
|
Resolve -->|number| Forced[selected stage]
|
|
93
|
-
Next -->
|
|
94
|
-
Forced -->
|
|
95
|
-
|
|
102
|
+
Next --> Base[resolve stage base commit]
|
|
103
|
+
Forced --> Base
|
|
104
|
+
Base --> WT[create/reuse stage worktree]
|
|
105
|
+
WT --> Started[append consumer status=started]
|
|
106
|
+
Started --> Run[implementation executes one selected stage]
|
|
96
107
|
```
|
|
97
108
|
|
|
98
|
-
stage 선택은 `auto` 또는 숫자다. 다른 task-type에서 `--stage`가 오면 `PrepareError`다.
|
|
109
|
+
stage 선택은 `auto` 또는 숫자다. 다른 task-type에서 `--stage`가 오면 `PrepareError`다. runtime은 `consumers.jsonl`의 `done`/`started`와 registry의 active stage-key를 함께 보고 점유 stage를 제외한다.
|
|
110
|
+
|
|
111
|
+
stage worktree base는 dependency 형태로 정한다. 독립 stage는 첫 implementation 진입 때 고정한 task-key worktree HEAD를 anchor로 삼고, 단일 의존 stage는 선행 stage의 done `head_commit`에서 분기한다. 다중 의존 stage는 모든 선행 done commit이 task-key worktree HEAD의 ancestor인지 확인한 뒤 그 HEAD에서 분기한다.
|
|
99
112
|
|
|
100
113
|
## 6. 산출물
|
|
101
114
|
|
|
@@ -113,12 +126,14 @@ flowchart TD
|
|
|
113
126
|
final report에는 최소 다음이 필요하다.
|
|
114
127
|
|
|
115
128
|
- approved plan path와 quoted approval marker
|
|
129
|
+
- selected stage, isolated stage worktree path, run artifact path(`runs/implementation/stage-<N>/`)
|
|
116
130
|
- commit SHA, message, plan step mapping
|
|
117
131
|
- diff summary와 per-file summary
|
|
118
132
|
- out-of-plan edits block
|
|
119
133
|
- plan validation command의 실제 stdout/stderr와 exit code
|
|
120
134
|
- TDD failing-then-passing evidence
|
|
121
135
|
- verifier별 independent validation rerun result
|
|
136
|
+
- `carry/stage-<N>.json` evidence sidecar와 `consumers.jsonl` started/done row
|
|
122
137
|
- rollback verification
|
|
123
138
|
- follow-up tasks table
|
|
124
139
|
|
|
@@ -146,4 +161,3 @@ flowchart TD
|
|
|
146
161
|
- [`validators/validate-implementation-plan-stages.py`](../../validators/validate-implementation-plan-stages.py)
|
|
147
162
|
- [`scripts/okstra_ctl/qa_commands.py`](../../scripts/okstra_ctl/qa_commands.py)
|
|
148
163
|
- [`prompts/lead/okstra-lead-contract.md`](../../prompts/lead/okstra-lead-contract.md)
|
|
149
|
-
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
## 1. 목적
|
|
16
16
|
|
|
17
|
-
`release-handoff`는 `accepted` verdict가 나온 already-committed implementation
|
|
17
|
+
`release-handoff`는 `accepted` verdict가 나온 already-committed implementation 결과를 push하거나 PR로 넘기는 terminal phase다. whole-task mode는 검증된 task branch를 그대로 포장한다. stage-group mode는 선택 stage들을 collector branch에 assemble해 한 PR로 묶을 수 있으며, 이때 생성되는 merge commit은 `okstra handoff assemble`만 만든다.
|
|
18
18
|
|
|
19
19
|
이 phase는 worker dispatch가 없다. `claude`, `codex`, `report-writer` roster를 쓰지 않으며 Claude lead가 inline으로 git/gh inspection, 사용자 질문, PR draft, final report 작성을 모두 수행한다.
|
|
20
20
|
|
|
@@ -24,7 +24,9 @@
|
|
|
24
24
|
flowchart TD
|
|
25
25
|
Start[/okstra-run/] --> Common[공통 task identity flow]
|
|
26
26
|
Common --> Type[task-type = release-handoff]
|
|
27
|
-
Type -->
|
|
27
|
+
Type --> Plan[approved plan auto/pick]
|
|
28
|
+
Plan --> Scope[handoff stage pick<br/>whole-task or eligible stages]
|
|
29
|
+
Scope --> Worktree{active task worktree?}
|
|
28
30
|
Worktree -->|yes| Defaults[Use defaults / Customize]
|
|
29
31
|
Worktree -->|no| BaseRef[base-ref pick/text]
|
|
30
32
|
BaseRef --> Defaults
|
|
@@ -32,14 +34,14 @@ flowchart TD
|
|
|
32
34
|
Defaults -->|customize| Models[lead model prompt]
|
|
33
35
|
Models --> Extras[directive, related tasks, clarification]
|
|
34
36
|
Extras --> Template[PR template override?]
|
|
35
|
-
Template -->
|
|
36
|
-
|
|
37
|
+
Template --> TemplateScope[save template to project/global?]
|
|
38
|
+
TemplateScope --> Confirm
|
|
37
39
|
Confirm --> Render[render-bundle]
|
|
38
40
|
```
|
|
39
41
|
|
|
40
|
-
`release-handoff`는 worker roster prompt가 없다. wizard의 `render_args()`는 `pr-template-path`를 release-handoff일 때만 포함하고, runtime은 worker list를 강제로 empty로 만든다.
|
|
42
|
+
`release-handoff`는 worker roster prompt가 없다. wizard의 `render_args()`는 `pr-template-path`를 release-handoff일 때만 포함하고, runtime은 worker list를 강제로 empty로 만든다. scope 선택은 prepare 전에 끝난다. whole-task는 accepted whole-task verification report가 있어야 하고, stage-group은 accepted single-stage verification으로 `verified` 처리됐지만 아직 `pr`로 덮이지 않은 stage만 후보가 된다.
|
|
41
43
|
|
|
42
|
-
주의할 점은 이 phase도 task worktree provisioning 대상이라는 것이다. 정상 흐름은 같은 task-key의 implementation/final-verification
|
|
44
|
+
주의할 점은 이 phase도 task worktree provisioning 대상이라는 것이다. 정상 흐름은 같은 task-key의 implementation/final-verification 결과를 재사용한다. 새 task로 시작하면 새 branch가 만들어질 수 있고, entry gate의 "implementation commit exists" 조건에서 막힐 가능성이 높다.
|
|
43
45
|
|
|
44
46
|
## 3. prepare 단계
|
|
45
47
|
|
|
@@ -72,7 +74,10 @@ flowchart TD
|
|
|
72
74
|
Source -->|no| Block[blocked<br/>route final-verification]
|
|
73
75
|
Source -->|yes| Verdict{Verdict Token == accepted?}
|
|
74
76
|
Verdict -->|no| Block
|
|
75
|
-
Verdict -->|yes|
|
|
77
|
+
Verdict -->|yes| Mode{HANDOFF_MODE}
|
|
78
|
+
Mode -->|stage-group| Eligible[each selected stage<br/>verified and not in PR]
|
|
79
|
+
Mode -->|whole-task| Status{git status --short clean?}
|
|
80
|
+
Eligible --> Status
|
|
76
81
|
Status -->|no| Dirty[blocked<br/>dirty tree]
|
|
77
82
|
Status -->|yes| Branch{current branch is base branch?}
|
|
78
83
|
Branch -->|yes| BaseBlock[blocked<br/>never operate on base branch]
|
|
@@ -84,7 +89,8 @@ flowchart TD
|
|
|
84
89
|
lead는 사용자에게 push/PR 여부를 묻기 전에 다음을 확인한다.
|
|
85
90
|
|
|
86
91
|
- prepare 가 생성한 input 문서(`release-handoff-input.md`)의 `## Source Verification Report` 가 모드(`HANDOFF_MODE`)와 인용 보고서 표를 담고 있다. brief 는 entry phase 의 입력물이라 release-handoff 에는 없다 — 사용자의 stage 선택은 wizard `handoff_stage_pick` 또는 CLI `--stages` 로 prepare 전에 끝난다.
|
|
87
|
-
-
|
|
92
|
+
- whole-task mode에서는 인용 report가 `verificationScope=whole-task`이고 `Verdict Token = accepted`여야 한다.
|
|
93
|
+
- stage-group mode에서는 인용된 각 single-stage report가 `Verdict Token = accepted`여야 하며, prepare/`okstra handoff assemble`이 eligibility와 dependency closure를 다시 강제한다.
|
|
88
94
|
- working tree가 clean이다.
|
|
89
95
|
- 현재 branch가 `main`, `master`, `prod`, `preprod`, `staging`, `dev` 같은 base branch가 아니다.
|
|
90
96
|
- `<base>..HEAD` commit range가 비어 있지 않다.
|
|
@@ -99,7 +105,11 @@ stateDiagram-v2
|
|
|
99
105
|
Gate --> Q1: action selection
|
|
100
106
|
Q1 --> LocalCheckout: local checkout
|
|
101
107
|
Q1 --> Skip: skip
|
|
102
|
-
Q1 --> Q2: push + PR
|
|
108
|
+
Q1 --> Q2: push + PR whole-task
|
|
109
|
+
Q1 --> G2: push + PR stage-group
|
|
110
|
+
state "base select + stage confirmation / okstra handoff assemble" as Assemble
|
|
111
|
+
G2 --> Assemble
|
|
112
|
+
Assemble --> Q3: collector branch ready
|
|
103
113
|
Q2 --> Probe: choose PR base
|
|
104
114
|
Probe --> Q3: no conflict
|
|
105
115
|
Probe --> Conflict: conflict detected
|
|
@@ -124,6 +134,8 @@ stateDiagram-v2
|
|
|
124
134
|
|
|
125
135
|
`push + PR`에서만 merge-conflict probe를 한다.
|
|
126
136
|
|
|
137
|
+
stage-group mode에서는 `local checkout`을 제공하지 않는다. `push + PR`을 고른 뒤 PR base를 먼저 선택하고, 이미 고정된 `HANDOFF_STAGES`를 확인한 다음 `okstra handoff assemble`이 collector branch를 만든다. 이후 conflict probe와 PR title/body 확인은 collector branch를 head로 삼는다.
|
|
138
|
+
|
|
127
139
|
```mermaid
|
|
128
140
|
flowchart TD
|
|
129
141
|
PushPR[push + PR selected] --> Fetch[git fetch origin chosen-base]
|
|
@@ -170,11 +182,13 @@ flowchart TD
|
|
|
170
182
|
final report에는 최소 다음이 필요하다.
|
|
171
183
|
|
|
172
184
|
- originating final-verification report path와 quoted `accepted` verdict row
|
|
185
|
+
- handoff mode(`whole-task` 또는 `stage-group`)와 selected stages
|
|
173
186
|
- feature branch와 run start `git status --short`
|
|
174
187
|
- 사용자 선택 기록
|
|
175
188
|
- 실행한 모든 git/gh command와 exit code
|
|
176
189
|
- implementation commit list
|
|
177
190
|
- merge-conflict probe 결과
|
|
191
|
+
- stage-group이면 collector branch, merge commit SHA, dependency-closure 결과
|
|
178
192
|
- PR 생성, 재사용, 생략 결과
|
|
179
193
|
- routing recommendation `done`
|
|
180
194
|
|
package/package.json
CHANGED
package/runtime/BUILD.json
CHANGED
|
@@ -4,54 +4,33 @@ docker 라벨은 컨테이너 존재 여부의 기준값(SSOT)이지만 tmux ses
|
|
|
4
4
|
findings 파일 경로 같은 운영 메타데이터는 추적하지 못한다. 이 모듈은 task root
|
|
5
5
|
하위 ``container/registry.json`` 에 그런 보조 정보를 가벼운 dict 로 보관한다.
|
|
6
6
|
|
|
7
|
-
flock + tmp+``os.replace`` 원자 쓰기 idiom 은 ``
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
flock + tmp+``os.replace`` 원자 쓰기 idiom 은 ``json_registry`` 의 공용
|
|
8
|
+
Interface 를 사용하고, lock/registry 경로만 ``paths.container_paths()`` 로
|
|
9
|
+
해소해 전역 ``~/.okstra/worktrees/registry.json`` 과 분리한다. dict 구조는
|
|
10
|
+
``{"services": {<svc>: {session_name, pane_id, findings_path}}}`` 한 가지뿐 —
|
|
11
|
+
worktree_registry 의 dataclass 는 복제하지 않는다.
|
|
12
12
|
"""
|
|
13
13
|
from __future__ import annotations
|
|
14
14
|
|
|
15
|
-
import contextlib
|
|
16
|
-
import fcntl
|
|
17
|
-
import json
|
|
18
|
-
import os
|
|
19
15
|
from pathlib import Path
|
|
20
16
|
|
|
17
|
+
from .json_registry import load_registry_json, registry_lock, save_registry_json
|
|
21
18
|
from . import paths
|
|
22
19
|
|
|
23
20
|
|
|
24
|
-
@contextlib.contextmanager
|
|
25
21
|
def _registry_lock(lock_path: Path):
|
|
26
|
-
"""``container/registry.json.lock`` 에 대한 배타 flock.
|
|
27
|
-
|
|
28
|
-
lock_path.parent.mkdir(parents=True, exist_ok=True)
|
|
29
|
-
if not lock_path.exists():
|
|
30
|
-
lock_path.touch()
|
|
31
|
-
f = lock_path.open("r+")
|
|
32
|
-
try:
|
|
33
|
-
fcntl.flock(f.fileno(), fcntl.LOCK_EX)
|
|
34
|
-
yield
|
|
35
|
-
finally:
|
|
36
|
-
f.close()
|
|
22
|
+
"""``container/registry.json.lock`` 에 대한 배타 flock."""
|
|
23
|
+
return registry_lock(lock_path)
|
|
37
24
|
|
|
38
25
|
|
|
39
26
|
def _load(registry_path: Path) -> dict:
|
|
40
|
-
|
|
41
|
-
return {"services": {}}
|
|
42
|
-
try:
|
|
43
|
-
data = json.loads(registry_path.read_text())
|
|
44
|
-
except (OSError, json.JSONDecodeError):
|
|
45
|
-
return {"services": {}}
|
|
27
|
+
data = load_registry_json(registry_path, lambda: {"services": {}})
|
|
46
28
|
data.setdefault("services", {})
|
|
47
29
|
return data
|
|
48
30
|
|
|
49
31
|
|
|
50
32
|
def _save(registry_path: Path, data: dict) -> None:
|
|
51
|
-
registry_path
|
|
52
|
-
tmp = registry_path.with_suffix(".json.tmp")
|
|
53
|
-
tmp.write_text(json.dumps(data, indent=2, ensure_ascii=False) + "\n")
|
|
54
|
-
os.replace(tmp, registry_path)
|
|
33
|
+
save_registry_json(registry_path, data)
|
|
55
34
|
|
|
56
35
|
|
|
57
36
|
def reserve(
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Shared flock + atomic JSON persistence for small okstra registries."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import contextlib
|
|
5
|
+
import fcntl
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
from collections.abc import Callable, Iterator
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@contextlib.contextmanager
|
|
13
|
+
def registry_lock(lock_path: Path) -> Iterator[None]:
|
|
14
|
+
"""Hold an exclusive flock on ``lock_path``."""
|
|
15
|
+
lock_path.parent.mkdir(parents=True, exist_ok=True)
|
|
16
|
+
if not lock_path.exists():
|
|
17
|
+
lock_path.touch()
|
|
18
|
+
handle = lock_path.open("r+")
|
|
19
|
+
try:
|
|
20
|
+
fcntl.flock(handle.fileno(), fcntl.LOCK_EX)
|
|
21
|
+
yield
|
|
22
|
+
finally:
|
|
23
|
+
fcntl.flock(handle.fileno(), fcntl.LOCK_UN)
|
|
24
|
+
handle.close()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def load_registry_json(path: Path, default_factory: Callable[[], dict]) -> dict:
|
|
28
|
+
"""Load a registry dict, or a fresh default when absent/corrupt."""
|
|
29
|
+
if not path.exists():
|
|
30
|
+
return default_factory()
|
|
31
|
+
try:
|
|
32
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
33
|
+
except (OSError, json.JSONDecodeError):
|
|
34
|
+
return default_factory()
|
|
35
|
+
return data if isinstance(data, dict) else default_factory()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def save_registry_json(path: Path, data: dict) -> None:
|
|
39
|
+
"""Persist registry JSON via temp file + ``os.replace``."""
|
|
40
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
41
|
+
tmp = path.with_suffix(".json.tmp")
|
|
42
|
+
tmp.write_text(json.dumps(data, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
|
|
43
|
+
os.replace(tmp, path)
|
|
@@ -4,17 +4,22 @@ from __future__ import annotations
|
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
from typing import Mapping
|
|
6
6
|
|
|
7
|
-
from okstra_ctl.jsonl import read_jsonl
|
|
7
|
+
from okstra_ctl.jsonl import append_jsonl, read_jsonl
|
|
8
8
|
|
|
9
9
|
from .manager_paths import (
|
|
10
10
|
child_context_path,
|
|
11
11
|
children_json_path,
|
|
12
12
|
directives_jsonl_path,
|
|
13
|
+
events_jsonl_path,
|
|
13
14
|
projects_json_path,
|
|
14
15
|
snapshots_json_path,
|
|
15
16
|
task_manifest_path,
|
|
16
17
|
)
|
|
17
|
-
from .manager_store import ManagerError, _now_iso, _read_json, _read_json_default
|
|
18
|
+
from .manager_store import ManagerError, _now_iso, _read_json, _read_json_default, _write_json
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
WORKER_DISPATCH_BACKEND = "subagent"
|
|
22
|
+
LAUNCH_STATUS_PREPARED = "prepared"
|
|
18
23
|
|
|
19
24
|
|
|
20
25
|
def select_child_lead_backend(env: Mapping[str, str]) -> str:
|
|
@@ -135,6 +140,47 @@ def _render_context(
|
|
|
135
140
|
return "\n".join(lines)
|
|
136
141
|
|
|
137
142
|
|
|
143
|
+
def _record_launch_prepared(
|
|
144
|
+
*,
|
|
145
|
+
home: Path,
|
|
146
|
+
manager_id: str,
|
|
147
|
+
task_group: str,
|
|
148
|
+
task_id: str,
|
|
149
|
+
children: dict,
|
|
150
|
+
child: dict,
|
|
151
|
+
packet: dict,
|
|
152
|
+
created_at: str,
|
|
153
|
+
) -> None:
|
|
154
|
+
launch = child.get("launch")
|
|
155
|
+
if not isinstance(launch, dict):
|
|
156
|
+
launch = {}
|
|
157
|
+
launch.update(
|
|
158
|
+
{
|
|
159
|
+
"status": LAUNCH_STATUS_PREPARED,
|
|
160
|
+
"childLeadBackend": packet["backend"],
|
|
161
|
+
"workerDispatchBackend": WORKER_DISPATCH_BACKEND,
|
|
162
|
+
"taskKey": packet["taskKey"],
|
|
163
|
+
"contextPath": packet["contextPath"],
|
|
164
|
+
"preparedAt": created_at,
|
|
165
|
+
}
|
|
166
|
+
)
|
|
167
|
+
child["launch"] = launch
|
|
168
|
+
_write_json(children_json_path(home, manager_id, task_group, task_id), children)
|
|
169
|
+
append_jsonl(
|
|
170
|
+
events_jsonl_path(home, manager_id, task_group, task_id),
|
|
171
|
+
{
|
|
172
|
+
"event": "child-launch-prepared",
|
|
173
|
+
"createdAt": created_at,
|
|
174
|
+
"projectId": packet["projectId"],
|
|
175
|
+
"taskKey": packet["taskKey"],
|
|
176
|
+
"backend": packet["backend"],
|
|
177
|
+
"workerDispatchBackend": WORKER_DISPATCH_BACKEND,
|
|
178
|
+
"contextPath": packet["contextPath"],
|
|
179
|
+
"runArgs": packet["runArgs"],
|
|
180
|
+
},
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
|
|
138
184
|
def build_launch_packet(
|
|
139
185
|
home: Path,
|
|
140
186
|
manager_id: str,
|
|
@@ -159,6 +205,7 @@ def build_launch_packet(
|
|
|
159
205
|
_validate_child_task_key(child, target_task_key)
|
|
160
206
|
project_root = _validated_project_root(project)
|
|
161
207
|
backend = select_child_lead_backend(env or {})
|
|
208
|
+
created_at = now or _now_iso()
|
|
162
209
|
context_path = child_context_path(home, manager_id, task_group, task_id, project_id, child_task_id_value)
|
|
163
210
|
context_path.parent.mkdir(parents=True, exist_ok=True)
|
|
164
211
|
context_path.write_text(
|
|
@@ -171,14 +218,14 @@ def build_launch_packet(
|
|
|
171
218
|
),
|
|
172
219
|
encoding="utf-8",
|
|
173
220
|
)
|
|
174
|
-
|
|
221
|
+
packet = {
|
|
175
222
|
"managerId": manager_id,
|
|
176
223
|
"taskGroup": task_group,
|
|
177
224
|
"taskId": task_id,
|
|
178
225
|
"projectId": project_id,
|
|
179
226
|
"taskKey": target_task_key,
|
|
180
227
|
"backend": backend,
|
|
181
|
-
"workerDispatchBackend":
|
|
228
|
+
"workerDispatchBackend": WORKER_DISPATCH_BACKEND,
|
|
182
229
|
"projectRoot": project_root,
|
|
183
230
|
"contextPath": str(context_path),
|
|
184
231
|
"runArgs": [
|
|
@@ -194,5 +241,16 @@ def build_launch_packet(
|
|
|
194
241
|
"--directive",
|
|
195
242
|
f"Read manager child context: {context_path}",
|
|
196
243
|
],
|
|
197
|
-
"createdAt":
|
|
244
|
+
"createdAt": created_at,
|
|
198
245
|
}
|
|
246
|
+
_record_launch_prepared(
|
|
247
|
+
home=home,
|
|
248
|
+
manager_id=manager_id,
|
|
249
|
+
task_group=task_group,
|
|
250
|
+
task_id=task_id,
|
|
251
|
+
children=children,
|
|
252
|
+
child=child,
|
|
253
|
+
packet=packet,
|
|
254
|
+
created_at=created_at,
|
|
255
|
+
)
|
|
256
|
+
return packet
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Pure path computation for okstra manager state."""
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
|
+
import hashlib
|
|
4
5
|
from pathlib import Path
|
|
5
6
|
import os
|
|
6
7
|
|
|
@@ -21,9 +22,11 @@ def _require_id(value: str, label: str) -> str:
|
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
def _slug_segment(value: str, label: str) -> str:
|
|
24
|
-
|
|
25
|
+
cleaned = _require_id(value, label)
|
|
26
|
+
segment = slugify(cleaned)
|
|
25
27
|
if not segment:
|
|
26
|
-
|
|
28
|
+
digest = hashlib.sha1(cleaned.encode("utf-8")).hexdigest()[:12]
|
|
29
|
+
return f"u-{digest}"
|
|
27
30
|
return segment
|
|
28
31
|
|
|
29
32
|
|
|
@@ -93,6 +96,6 @@ def child_context_path(
|
|
|
93
96
|
project_id: str,
|
|
94
97
|
child_task_id: str,
|
|
95
98
|
) -> Path:
|
|
96
|
-
safe_project =
|
|
97
|
-
safe_task =
|
|
99
|
+
safe_project = _slug_segment(project_id, "project-id")
|
|
100
|
+
safe_task = _slug_segment(child_task_id, "task-id")
|
|
98
101
|
return task_root(home, manager_id, task_group, task_id) / "child-context" / f"{safe_project}-{safe_task}.md"
|
|
@@ -126,9 +126,11 @@ def link_project(
|
|
|
126
126
|
role: str,
|
|
127
127
|
tags: list[str],
|
|
128
128
|
now: str | None = None,
|
|
129
|
-
) -> dict:
|
|
129
|
+
) -> dict:
|
|
130
130
|
load_manager(home, manager_id)
|
|
131
131
|
root = Path(project_root).resolve()
|
|
132
|
+
if not root.is_dir():
|
|
133
|
+
raise ManagerError(f"projectRoot is not a directory: {root}")
|
|
132
134
|
project_json = project_json_path(root)
|
|
133
135
|
if project_json.is_file():
|
|
134
136
|
try:
|
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
|
|
6
|
-
from okstra_project import TASKS_RELATIVE, parse_task_key, read_task_catalog, read_task_manifest, slugify
|
|
6
|
+
from okstra_project import TASKS_RELATIVE, StateError, parse_task_key, read_task_catalog, read_task_manifest, slugify
|
|
7
7
|
|
|
8
8
|
from .manager_paths import children_json_path, projects_json_path, snapshots_json_path, task_manifest_path
|
|
9
9
|
from .manager_store import _now_iso, _read_json, _read_json_default, _write_json
|
|
@@ -52,9 +52,9 @@ def _resolve_task_root_read_only(project_root: Path, task_key: str) -> Path | No
|
|
|
52
52
|
return None
|
|
53
53
|
|
|
54
54
|
|
|
55
|
-
def
|
|
55
|
+
def _base_child_snapshot(child: dict) -> dict:
|
|
56
56
|
task_key = str(child.get("taskKey") or "")
|
|
57
|
-
|
|
57
|
+
return {
|
|
58
58
|
"projectId": str(child.get("projectId") or ""),
|
|
59
59
|
"taskGroup": str(child.get("taskGroup") or ""),
|
|
60
60
|
"taskId": str(child.get("taskId") or ""),
|
|
@@ -62,10 +62,18 @@ def _snapshot_child(project_root: Path, child: dict) -> dict:
|
|
|
62
62
|
"exists": False,
|
|
63
63
|
"taskRoot": "",
|
|
64
64
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _snapshot_child(project_root: Path, child: dict) -> dict:
|
|
68
|
+
base = _base_child_snapshot(child)
|
|
69
|
+
task_key = base["taskKey"]
|
|
70
|
+
try:
|
|
71
|
+
task_dir = _resolve_task_root_read_only(project_root, task_key)
|
|
72
|
+
if task_dir is None:
|
|
73
|
+
return base
|
|
74
|
+
manifest = read_task_manifest(task_dir) or {}
|
|
75
|
+
except StateError as exc:
|
|
76
|
+
return {**base, "error": str(exc)}
|
|
69
77
|
workflow = _workflow(manifest)
|
|
70
78
|
outcome = _phase_outcome(manifest)
|
|
71
79
|
return {
|
|
@@ -23,10 +23,6 @@ file is useful for debugging.
|
|
|
23
23
|
"""
|
|
24
24
|
from __future__ import annotations
|
|
25
25
|
|
|
26
|
-
import contextlib
|
|
27
|
-
import fcntl
|
|
28
|
-
import json
|
|
29
|
-
import os
|
|
30
26
|
import time
|
|
31
27
|
from dataclasses import dataclass
|
|
32
28
|
from pathlib import Path
|
|
@@ -34,6 +30,8 @@ from typing import Optional
|
|
|
34
30
|
|
|
35
31
|
from okstra_project.dirs import okstra_home
|
|
36
32
|
|
|
33
|
+
from .json_registry import load_registry_json, registry_lock, save_registry_json
|
|
34
|
+
|
|
37
35
|
|
|
38
36
|
REGISTRY_FILENAME = "registry.json"
|
|
39
37
|
LOCK_FILENAME = "registry.lock"
|
|
@@ -77,23 +75,9 @@ class WorktreeEntry:
|
|
|
77
75
|
stages: Optional[list] = None
|
|
78
76
|
|
|
79
77
|
|
|
80
|
-
@contextlib.contextmanager
|
|
81
78
|
def _registry_lock():
|
|
82
|
-
"""Exclusive flock on `<worktrees>/registry.lock`.
|
|
83
|
-
|
|
84
|
-
so we do not serialise unrelated central operations.
|
|
85
|
-
"""
|
|
86
|
-
root = _okstra_worktrees_dir()
|
|
87
|
-
root.mkdir(parents=True, exist_ok=True)
|
|
88
|
-
lockfile = root / LOCK_FILENAME
|
|
89
|
-
if not lockfile.exists():
|
|
90
|
-
lockfile.touch()
|
|
91
|
-
f = lockfile.open("r+")
|
|
92
|
-
try:
|
|
93
|
-
fcntl.flock(f.fileno(), fcntl.LOCK_EX)
|
|
94
|
-
yield
|
|
95
|
-
finally:
|
|
96
|
-
f.close()
|
|
79
|
+
"""Exclusive flock on `<worktrees>/registry.lock`."""
|
|
80
|
+
return registry_lock(_okstra_worktrees_dir() / LOCK_FILENAME)
|
|
97
81
|
|
|
98
82
|
|
|
99
83
|
def _registry_path() -> Path:
|
|
@@ -101,24 +85,14 @@ def _registry_path() -> Path:
|
|
|
101
85
|
|
|
102
86
|
|
|
103
87
|
def _load() -> dict:
|
|
104
|
-
|
|
105
|
-
if not p.exists():
|
|
106
|
-
return {"tasks": {}, "branches": {}}
|
|
107
|
-
try:
|
|
108
|
-
data = json.loads(p.read_text())
|
|
109
|
-
except (OSError, json.JSONDecodeError):
|
|
110
|
-
return {"tasks": {}, "branches": {}}
|
|
88
|
+
data = load_registry_json(_registry_path(), lambda: {"tasks": {}, "branches": {}})
|
|
111
89
|
data.setdefault("tasks", {})
|
|
112
90
|
data.setdefault("branches", {})
|
|
113
91
|
return data
|
|
114
92
|
|
|
115
93
|
|
|
116
94
|
def _save(data: dict) -> None:
|
|
117
|
-
|
|
118
|
-
p.parent.mkdir(parents=True, exist_ok=True)
|
|
119
|
-
tmp = p.with_suffix(".json.tmp")
|
|
120
|
-
tmp.write_text(json.dumps(data, indent=2, ensure_ascii=False) + "\n")
|
|
121
|
-
os.replace(tmp, p)
|
|
95
|
+
save_registry_json(_registry_path(), data)
|
|
122
96
|
|
|
123
97
|
|
|
124
98
|
def lookup(
|