okstra 0.80.0 → 0.81.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.kr.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # okstra
2
2
 
3
- > npm: [`okstra`](https://www.npmjs.com/package/okstra) · 설치: `npx -y okstra@latest install --runtime claude-code`
3
+ > npm: [`okstra`](https://www.npmjs.com/package/okstra) · 설치: `npx -y okstra@latest install`
4
4
  >
5
5
  > English: [`README.md`](README.md)
6
6
 
@@ -74,12 +74,14 @@ okstra/ npm 패키지 = repo 루트
74
74
  ├── archive/ 완료된 run
75
75
  └── .locks/ central/task mutex 파일
76
76
 
77
- ~/.claude/skills/ Claude Code 가 자동 인식
78
- └── okstra-*/SKILL.md 스킬 11종 (§3.3 참조)
77
+ ~/.claude/skills/ Claude Code 가 자동 인식 (`~/.claude` 가 있을 때)
78
+ ~/.codex/skills/ Codex 호환 host 가 인식 (`~/.codex` 가 있을 때)
79
+ ~/.omp/agent/skills/ 둘 다 없을 때 OMP-native fallback skill target
80
+ └── okstra-*/SKILL.md 사용자 노출 스킬 + hidden 내부 지원 스킬
79
81
 
80
- ~/.claude/agents/ Claude Code 가 자동 인식 (subagent 디스커버리 경로)
82
+ ~/.claude/agents/ Claude Code 가 자동 인식 (`~/.claude` 있을 때)
81
83
  └── {claude,codex,gemini,report-writer}-worker.md worker subagent 정의
82
- (multi-agent dispatch 필수)
84
+ (Claude Code multi-agent dispatch 필수)
83
85
 
84
86
  <프로젝트 루트>/.okstra/
85
87
  ├── project.json {projectId, projectRoot, ...} (`/okstra-setup` 이 작성)
@@ -95,8 +97,8 @@ okstra/ npm 패키지 = repo 루트
95
97
  |---|---|---|
96
98
  | 런타임 코드 (python + bash) | `~/.okstra/{lib/python, bin}` | `okstra install` |
97
99
  | agents/prompts/schemas/templates/validators | npm 패키지의 `runtime/` | `okstra` 패키지 자체 (`okstra paths` 로 해석) |
98
- | 스킬 마크다운 | `~/.claude/skills/<name>/SKILL.md` | `okstra install` (`installed-skills.json` 에 트래킹) |
99
- | 워커 에이전트 마크다운 | `~/.claude/agents/<worker>.md` | `okstra install` (`installed-agents.json` 에 트래킹) |
100
+ | 스킬 마크다운 | `~/.claude/skills`, `~/.codex/skills`, 또는 `~/.omp/agent/skills` | `okstra install` (`installed-skills.json` 에 target별 트래킹) |
101
+ | 워커 에이전트 마크다운 | `~/.claude/agents/<worker>.md` | `~/.claude` 가 있을 때 `okstra install` (`installed-agents.json` 에 트래킹) |
100
102
  | 프로젝트 메타데이터 | `<project>/.okstra/` | `/okstra-setup` + 프로젝트 자체 |
101
103
  | Run 인덱스 | `~/.okstra/{recent,active}.jsonl` | `prepare_task_bundle` |
102
104
 
@@ -105,10 +107,10 @@ okstra/ npm 패키지 = repo 루트
105
107
  ### 3.1 최초 셋업 (머신당 1회)
106
108
 
107
109
  ```bash
108
- npx -y okstra@latest install --runtime claude-code
110
+ npx -y okstra@latest install
109
111
  ```
110
112
 
111
- `~/.okstra/{lib/python, bin, templates, version}`, `~/.claude/skills/` 아래 스킬 마크다운 11개, `~/.claude/agents/` 아래 worker agent 4개, `~/.okstra/` 설치 asset manifest 생성합니다. 재실행은 idempotent — 파일별 hash 를 비교하고 바뀐 파일만 갱신합니다.
113
+ `~/.okstra/{lib/python, bin, templates, version}` `~/.okstra/` 설치 asset manifest 를 생성합니다. 스킬 설치는 host home 기준입니다. `~/.claude` 가 있으면 `~/.claude/skills/` 와 `~/.claude/agents/` 설치하고, `~/.codex` 가 있으면 `~/.codex/skills/` 에도 설치합니다. 둘 다 없고 `~/.omp/agent` 있으면 `~/.omp/agent/skills/` 설치합니다. 사용자 진입점 스킬만 목록에 보이고, lead/support 내부 스킬은 hidden 으로 설치되어 런타임이 해석할 수만 있게 둡니다. 재실행은 idempotent — 파일별 hash 를 비교하고 바뀐 파일만 갱신합니다.
112
114
 
113
115
  검증:
114
116
 
@@ -125,10 +127,10 @@ npx -y okstra@latest doctor
125
127
  ```bash
126
128
  npm i -g okstra
127
129
  okstra --version # CLI 가 PATH 에 잡혔는지 확인
128
- okstra install --runtime claude-code # 'npx -y okstra@latest install --runtime claude-code' 동일
130
+ okstra install # 'npx -y okstra@latest install' 동일
129
131
  ```
130
132
 
131
- 글로벌 설치는 Node CLI 를 PATH 에 등록할 뿐입니다. 런타임(`~/.okstra/`) 과 Claude 스킬(`~/.claude/skills/`) 여전히 `okstra install` 이 생성합니다 — `npm i -g` 에 포함되지 않습니다. 실험적 Codex lead adapter 용 공유 runtime 만 설치하려면 `okstra install --runtime codex` 를 사용하세요 (`~/.claude` 자산은 건너뜀). 이후 업그레이드: `npm i -g okstra@latest && okstra install`. 글로벌 바이너리 제거: `npm uninstall -g okstra` (`~/.okstra/` 는 그대로; 그것까지 지우려면 `okstra uninstall`).
133
+ 글로벌 설치는 Node CLI 를 PATH 에 등록할 뿐입니다. 런타임(`~/.okstra/`) 과 host skill directory(`~/.claude/skills/`, `~/.codex/skills/`, 또는 `~/.omp/agent/skills/`) 여전히 `okstra install` 이 생성합니다 — `npm i -g` 에 포함되지 않습니다. 이후 업그레이드: `npm i -g okstra@latest && okstra install`. 글로벌 바이너리 제거: `npm uninstall -g okstra` (`~/.okstra/` 는 그대로; 그것까지 지우려면 `okstra uninstall`).
132
134
 
133
135
  **글로벌 설치 시 스킬 동작.** 모든 okstra 스킬은 PATH 에 잡힌 `okstra` 를 자동 감지하여 `npx -y okstra@latest` 대신 우선 사용합니다. 즉 글로벌 설치를 해두면 매 스킬 호출(`okstra-run`, `okstra-inspect`, `okstra-schedule`, `okstra-setup` Step 2 의 Step 0) 마다 npx 가 패키지 fetch / 버전 체크하던 비용이 사라집니다. 스킬이 본인이 설치한 버전을 그대로 쓰므로 **업그레이드 타이밍은 사용자가 통제** 합니다 — 더 이상 호출마다 `@latest` 가 강제되지 않습니다. 새 릴리스를 받으려면 원하는 시점에 `npm i -g okstra@latest && okstra install` 을 실행하세요. `okstra` 가 PATH 에 없으면 스킬은 자동으로 npx fallback 으로 동작하므로 글로벌 설치가 없는 환경에서도 변경 없이 그대로 동작합니다.
134
136
 
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # okstra
2
2
 
3
- > npm: [`okstra`](https://www.npmjs.com/package/okstra) · install: `npx -y okstra@latest install --runtime claude-code`
3
+ > npm: [`okstra`](https://www.npmjs.com/package/okstra) · install: `npx -y okstra@latest install`
4
4
  >
5
5
  > 한국어 매뉴얼: [`README.kr.md`](README.kr.md)
6
6
 
@@ -73,12 +73,14 @@ okstra/ npm package = repo root
73
73
  ├── archive/ completed runs
74
74
  └── .locks/ central/task mutex files
75
75
 
76
- ~/.claude/skills/ discovered automatically by Claude Code
77
- └── okstra-*/SKILL.md 11 skills total (see §3.3)
76
+ ~/.claude/skills/ discovered automatically by Claude Code (if ~/.claude exists)
77
+ ~/.codex/skills/ discovered by Codex-compatible hosts (if ~/.codex exists)
78
+ ~/.omp/agent/skills/ fallback OMP-native skill target when neither exists
79
+ └── okstra-*/SKILL.md visible user skills + hidden internal support skills
78
80
 
79
- ~/.claude/agents/ discovered automatically by Claude Code
81
+ ~/.claude/agents/ discovered automatically by Claude Code (if ~/.claude exists)
80
82
  └── {claude,codex,gemini,report-writer}-worker.md subagent definitions
81
- (required for multi-agent dispatch)
83
+ (required for Claude Code multi-agent dispatch)
82
84
 
83
85
  <project-root>/.okstra/
84
86
  ├── project.json {projectId, projectRoot, ...} (written by /okstra-setup)
@@ -94,8 +96,8 @@ okstra/ npm package = repo root
94
96
  |---|---|---|
95
97
  | Runtime code (python + bash) | `~/.okstra/{lib/python, bin}` | `okstra install` |
96
98
  | agents/prompts/schemas/templates/validators | npm package's `runtime/` | the `okstra` package (resolved via `okstra paths`) |
97
- | Skill markdown | `~/.claude/skills/<name>/SKILL.md` | `okstra install` (tracked in `installed-skills.json`) |
98
- | Worker agent markdown | `~/.claude/agents/<worker>.md` | `okstra install` (tracked in `installed-agents.json`) |
99
+ | Skill markdown | `~/.claude/skills`, `~/.codex/skills`, or `~/.omp/agent/skills` | `okstra install` (tracked per target in `installed-skills.json`) |
100
+ | Worker agent markdown | `~/.claude/agents/<worker>.md` | `okstra install` when `~/.claude` exists (tracked in `installed-agents.json`) |
99
101
  | Project metadata | `<project>/.okstra/` | `/okstra-setup` + the project itself |
100
102
  | Run index | `~/.okstra/{recent,active}.jsonl` | `prepare_task_bundle` |
101
103
 
@@ -104,10 +106,10 @@ okstra/ npm package = repo root
104
106
  ### 3.1 First-time setup (once per machine)
105
107
 
106
108
  ```bash
107
- npx -y okstra@latest install --runtime claude-code
109
+ npx -y okstra@latest install
108
110
  ```
109
111
 
110
- Provisions `~/.okstra/{lib/python, bin, templates, version}`, the 11 skill markdown files under `~/.claude/skills/`, the 4 worker agents under `~/.claude/agents/`, and the installed-asset manifests in `~/.okstra/`. Re-running is idempotent — per-file hashes are compared and only changed files are touched.
112
+ Provisions `~/.okstra/{lib/python, bin, templates, version}` and the installed-asset manifests in `~/.okstra/`. Skill installation is host-home driven: if `~/.claude` exists, okstra copies skills to `~/.claude/skills/` and worker agents to `~/.claude/agents/`; if `~/.codex` exists, it also copies skills to `~/.codex/skills/`; if neither exists and `~/.omp/agent` exists, it copies skills to `~/.omp/agent/skills/`. User-facing skills are visible; internal lead/support skills are installed hidden so the runtime can resolve them without cluttering the list. Re-running is idempotent — per-file hashes are compared and only changed files are touched.
111
113
 
112
114
  Verify:
113
115
 
@@ -124,10 +126,10 @@ Every example in this README uses `npx -y okstra@latest <cmd>` so you don't need
124
126
  ```bash
125
127
  npm i -g okstra
126
128
  okstra --version # confirm the CLI is on PATH
127
- okstra install --runtime claude-code # same as 'npx -y okstra@latest install --runtime claude-code'
129
+ okstra install # same as 'npx -y okstra@latest install'
128
130
  ```
129
131
 
130
- The global install only registers the Node CLI on your PATH. The runtime (`~/.okstra/`) and the Claude skills (`~/.claude/skills/`) are still provisioned by `okstra install` — they are not part of `npm i -g`. To install only the shared runtime for the experimental Codex lead adapter, use `okstra install --runtime codex` (skips `~/.claude` assets). To upgrade later: `npm i -g okstra@latest && okstra install`. To remove the global binary: `npm uninstall -g okstra` (leaves `~/.okstra/` untouched; remove that with `okstra uninstall`).
132
+ The global install only registers the Node CLI on your PATH. The runtime (`~/.okstra/`) and host skill directories (`~/.claude/skills/`, `~/.codex/skills/`, or `~/.omp/agent/skills/`) are still provisioned by `okstra install` — they are not part of `npm i -g`. To upgrade later: `npm i -g okstra@latest && okstra install`. To remove the global binary: `npm uninstall -g okstra` (leaves `~/.okstra/` untouched; remove that with `okstra uninstall`).
131
133
 
132
134
  **Skill behaviour with a global install.** All okstra skills auto-detect a PATH-resolved `okstra` and prefer it over `npx -y okstra@latest`. That means a global install removes the per-call npx fetch / version-check from every skill invocation (Step 0 of `okstra-run`, `okstra-inspect`, `okstra-schedule`, `okstra-setup` Step 2). Since the skill uses your globally installed version directly, *you* control upgrade timing — `@latest` is no longer forced on each call. Run `npm i -g okstra@latest && okstra install` whenever you want to pull a new release. If `okstra` is not on PATH the skill silently falls back to npx, so machines without a global install keep working unchanged.
133
135
 
@@ -18,7 +18,7 @@
18
18
  - **Single python authority**: 모든 prepare wiring(profile/workers/model 해소, path 계산, render, central record_start)이 [`okstra_ctl.run.prepare_task_bundle()`](../../scripts/okstra_ctl/run.py) 한 함수에 모여 있습니다. `okstra.sh` 와 `okstra-run` skill 은 같은 함수를 호출하는 thin caller 이며, 환경 변수로 상태를 전달하지 않습니다 — task 정체성·경로·workflow 상태는 모두 디스크 권위 파일에서 매번 계산됩니다.
19
19
  - **Claude handoff (두 모드)**: (a) `okstra.sh` 가 새 `claude` 프로세스를 띄우는 전통 방식, (b) `okstra-run` skill 이 현재 claude 세션 안에서 prepare 후 lead 역할을 그대로 인계받는 in-session 모드. 둘 다 `prepare_task_bundle` 의 산출물(instruction-set 등)을 그대로 사용합니다.
20
20
  - **Required team contract**: 각 phase profile의 `Required workers:` 블록이 roster의 권위입니다. 일반 분석 phase는 Claude/Codex analyser + report-writer를 기본으로 하고, Gemini는 profile과 `--workers`가 허용할 때만 포함됩니다. `release-handoff`처럼 lead-only에 가까운 phase는 별도 roster를 가집니다.
21
- - **User-home install + project-local task bundles**: `npx okstra@latest install --runtime claude-code` 한 명령이 런타임(`~/.okstra/{lib/python, bin, templates}`), 스킬 11종(`~/.claude/skills/<name>/SKILL.md`), worker agent 4종(`~/.claude/agents/*-worker.md`) 설치합니다. `--runtime codex` 실험적 Codex lead adapter 공유 runtime 설치하고 `~/.claude` 자산을 건너뜁니다. 전역 대화 메모리는 프로젝트와 분리된 `~/.okstra/memory-book/` 에 저장됩니다. 대상 프로젝트에는 task bundle 과 discovery metadata 가 `.okstra/` 아래 저장되고, **추가로 `<PROJECT_ROOT>/.claude/settings.local.json` 이 `~/.okstra/templates/settings.local.json` 을 가리키는 symlink 로 provisioning** 됩니다 (`okstra setup` 또는 `okstra-ctl` prepare 가 idempotent 하게 관리; 기존에 일반 파일이 있었다면 `.bak.<timestamp>` 로 백업 후 교체). 이 symlink 가 host Claude Code 세션에 자동 로드되어 codex/gemini worker wrapper 호출 권한을 부여하므로, 사용자의 글로벌 `~/.claude/settings.json` 은 건드리지 않습니다. 개발용 `okstra install --link <repo>` 는 설치 파일을 repo source로 symlink합니다.
21
+ - **User-home install + project-local task bundles**: `npx okstra@latest install` 한 명령이 런타임(`~/.okstra/{lib/python, bin, templates}`) 설치하고, 존재하는 agent home 에 skill 을 복사합니다. `~/.claude` 가 있으면 Claude skills + worker agent 4종(`~/.claude/agents/*-worker.md`), `~/.codex` 있으면 Codex skills, 없고 `~/.omp/agent` 있으면 OMP-native skills 를 설치합니다. 사용자 진입점 스킬만 목록에 노출되고 lead/support 내부 스킬은 `hide: true` 설치됩니다. 전역 대화 메모리는 프로젝트와 분리된 `~/.okstra/memory-book/` 에 저장됩니다. 대상 프로젝트에는 task bundle 과 discovery metadata 가 `.okstra/` 아래 저장되고, **추가로 `<PROJECT_ROOT>/.claude/settings.local.json` 이 `~/.okstra/templates/settings.local.json` 을 가리키는 symlink 로 provisioning** 됩니다 (`okstra setup` 또는 `okstra-ctl` prepare 가 idempotent 하게 관리; 기존에 일반 파일이 있었다면 `.bak.<timestamp>` 로 백업 후 교체). 이 symlink 가 host Claude Code 세션에 자동 로드되어 codex/gemini worker wrapper 실행 권한을 제공합니다.
22
22
  - **Resume and clarification**: `--task-key`, `--resume-clarification`, `--clarification-response`로 같은 task 재개와 lead의 추가 질문 응답 흐름을 지원합니다.
23
23
  - **Derived views and telemetry**: final-report data.json → Markdown → slim MD / self-contained HTML view, worker error sidecar, wrapper log sidecar, token usage / cost accounting을 제공합니다.
24
24
 
@@ -152,9 +152,9 @@ okstra 의 prepare 책임은 단일 python 진입점 [`okstra_ctl.run.prepare_ta
152
152
  - [`agents/SKILL.md`](../../agents/SKILL.md) — main okstra lead contract.
153
153
  - [`skills/okstra-setup/SKILL.md`](../../skills/okstra-setup/SKILL.md) — **첫 실행 부트스트랩**. `okstra install` + `project.json` 생성.
154
154
  - [`skills/okstra-run/SKILL.md`](../../skills/okstra-run/SKILL.md) — **현재 claude 세션 안에서 okstra task 를 시작**하는 in-session 진입점. `prepare_task_bundle` 직접 호출.
155
- - `skills/okstra-{brief,run,memory,inspect,schedule,context-loader,team-contract,convergence,report-writer,coding-preflight}/SKILL.md` — brief 작성, phase 진행, 전역 Memory Book 저장/검색, status/history/report/time/log/cost read-side, schedule 보조 skill, 구현/검증 워커의 언어별 coding preflight. `okstra-inspect logs` 는 codex/gemini wrapper 가 매 dispatch 마다 `runs/<task-type>/prompts/<worker>-prompt-<phase>-<seq>.log` 로 남기는 live-log sidecar 의 인벤토리·정리 안내 (read-only, find-delete cleanup 명령 제안만 함). `okstra-inspect cost` 는 `okstra context-cost` CLI 결과를 요약해 task bundle 의 lead/worker/report-writer 읽기 비용을 보여준다.
156
- - 플러그인 매니페스트: [`../../.claude-plugin/plugin.json`](../../.claude-plugin/plugin.json) — `npx skills@latest add Devonshin/okstra` 보조 채널이 참조. 일반 셋업에는 `npx okstra@latest install` 을 사용한다.
157
- - 설치 위치: `~/.claude/skills/<name>/SKILL.md` (`okstra-install.sh` dev 설치, 또는 npx 채널).
155
+ - `skills/okstra-{brief,run,memory,inspect,schedule,context-loader,team-contract,convergence,report-writer,coding-preflight}/SKILL.md` — brief 작성, phase 진행, 전역 Memory Book 저장/검색, status/history/report/time/log/cost read-side, schedule 보조 skill, 구현/검증 워커의 언어별 coding preflight. `context-loader` / `team-contract` / `convergence` / `report-writer` / `coding-preflight` 와 `agents/SKILL.md` lead skill 은 내부 지원 스킬이며 frontmatter `hide: true` 로 설치되어 목록에는 노출되지 않습니다. `okstra-inspect logs` 는 codex/gemini wrapper 가 매 dispatch 마다 `runs/<task-type>/prompts/<worker>-prompt-<phase>-<seq>.log` 로 남기는 live-log sidecar 의 인벤토리·정리 안내 (read-only, find-delete cleanup 명령 제안만 함). `okstra-inspect cost` 는 `okstra context-cost` CLI 결과를 요약해 task bundle 의 lead/worker/report-writer 읽기 비용을 보여준다.
156
+ - 플러그인 매니페스트: [`../../.claude-plugin/plugin.json`](../../.claude-plugin/plugin.json) — `npx skills@latest add Devonshin/okstra` 보조 채널이 참조. 일반 셋업에는 `npx okstra@latest install` 을 사용한다. 플러그인 매니페스트는 사용자 진입점 6개(`okstra-setup`, `okstra-brief`, `okstra-run`, `okstra-memory`, `okstra-inspect`, `okstra-schedule`)만 노출한다.
157
+ - 설치 위치: `~/.claude/skills/<name>/SKILL.md`, `~/.codex/skills/<name>/SKILL.md`, 또는 fallback `~/.omp/agent/skills/<name>/SKILL.md`.
158
158
  - 릴리스 절차: [`../../RELEASING.md`](../../RELEASING.md) — npm publish 흐름과 release-please / manual fallback.
159
159
 
160
160
  ## Architecture: python authority + thin callers
@@ -533,7 +533,7 @@ scripts/okstra.sh workflow가 사용하는 project-local Claude assets는 아래
533
533
  - `.okstra/discovery/latest-task.json`: 현재 프로젝트에서 가장 최근에 준비된 okstra task bundle을 가리키는 current-task convenience pointer
534
534
  - `.okstra/discovery/task-catalog.json`: 현재 프로젝트에 준비된 okstra task bundle 목록을 `taskKey`, `taskGroup`, `taskId` 기준으로 유지하는 canonical project-level catalog
535
535
  - `instruction-set/reference-expectations.md`: 현재 task가 참조해야 할 config files, deployment manifests, expected values를 task-level canonical artifact로 정리한 파일
536
- - `~/.claude/skills/okstra-*/...`, `~/.claude/agents/...`: `okstra install` 이 사용자 홈에 seed하는 Claude asset (project-local 시딩은 더 이상 발생하지 않음 — `okstra install --refresh` 로 갱신)
536
+ - `~/.claude/skills/okstra-*/...`, `~/.codex/skills/okstra-*/...`, `~/.omp/agent/skills/okstra-*/...`, `~/.claude/agents/...`: `okstra install` 이 사용자 홈에 seed하는 skill/agent asset (project-local 시딩은 더 이상 발생하지 않음 — `okstra install --refresh` 로 갱신)
537
537
 
538
538
  이전의 아래 파일들은 더 이상 okstra 생성 대상이 아닙니다.
539
539
 
@@ -1001,8 +1001,8 @@ phase 산출물의 출고 가능 여부를 강제하는 진입점:
1001
1001
  - Executor 별 worktree cwd 주입: codex / gemini executor 는 wrapper(`okstra-codex-exec.sh -C` / `okstra-gemini-exec.sh --include-directories`) 가 CLI layer 에서 cwd 를 worktree 로 고정합니다. Claude executor 는 Bash tool 에 per-call cwd 인자가 없어 cwd 민감 toolchain (`cargo`/`npm`/`pnpm`/`bun`/`pytest`/`make`/`go`) 호출을 같은 Bash invocation 안에서 `cd {{EXECUTOR_WORKTREE_PATH}} && <cmd>` 로 prefix 합니다 — `bash -lc`/`bash -c` 래핑은 금지되며 (`cd` leading token 이 가려져 permission auto-allow 우회 실패), 작업 디렉터리 플래그 (`git -C`, `cargo --manifest-path` 등) 가 있으면 그것을 우선합니다. 자세한 규약은 `prompts/profiles/implementation.md` 의 *Executor Worktree* 블록과 `agents/workers/claude-worker.md` 의 Executor exception 항목 참고.
1002
1002
  - project-level current-task convenience pointer는 `.okstra/discovery/latest-task.json`입니다.
1003
1003
  - project-level canonical task inventory는 `.okstra/discovery/task-catalog.json`입니다.
1004
- - okstra Claude asset은 `~/.claude/skills/`와 `~/.claude/agents/` 아래에 `okstra install` 시점에 seed되며, `okstra install --refresh` 갱신할 있습니다 (per-project seeding은 더 이상 수행되지 않음).
1005
- - seeded okstra Claude assets는 Agent Teams 우선, sequential/background fallback 차선 규칙을 Claude에게 제공합니다.
1004
+ - okstra skill asset은 `okstra install` 시점에 존재하는 agent home 기준으로 seed됩니다: `~/.claude/skills/` + `~/.claude/agents/`, `~/.codex/skills/`, 또는 fallback `~/.omp/agent/skills/` (per-project seeding은 더 이상 수행되지 않음).
1005
+ - seeded okstra Claude assets는 Agent Teams 우선, sequential/background fallback 차선 규칙을 Claude에게 제공합니다. Codex/OMP targets 는 skill markdown 만 받습니다.
1006
1006
  - 최종 판단은 스크립트가 아니라 Claude가 수행합니다.
1007
1007
  - stable task key를 유지해야 이후 bug 추적, 재수정, 재검증이 가능합니다.
1008
1008
  - 워커 에러는 옵션 sidecar `runs/<task-type>/logs/errors-<task-type>-<seq>.jsonl`로 수집되며 lead가 단독 writer입니다. 진입점 helper는 `scripts/okstra-error-log.py`입니다.
package/docs/kr/cli.md CHANGED
@@ -318,7 +318,7 @@ Lead runtime independence boundary:
318
318
  - `codex`: Codex lead adapter 용 runtime marker 입니다. `okstra codex-run` 이 `--render-only --lead-runtime codex` 를 소유해 task bundle 을 준비하고, 준비된 run manifest 는 `okstra codex-dispatch` 로 CLI-backed worker 실행에 넘길 수 있습니다.
319
319
  - `external`: Claude Code Teams 없이 `--render-only --lead-runtime external` 로 task bundle 과 external lead prompt 를 준비하고, lead 는 `okstra team dispatch` / `okstra team await` / `okstra team teardown` 으로 tmux-pane worker lifecycle 을 관리합니다. 이 path 는 `TeamCreate` / `Agent(...)` 를 쓰지 않고 artifact-only accounting 을 사용합니다.
320
320
 
321
- 현재 Claude Code 독립 범위는 external lead prompt + `okstra team *` worker dispatch 입니다. non-render `okstra_ctl.run --lead-runtime external` remains blocked; 완전한 external lead driver 는 아직 별도 구현 대상입니다. `--runtime external` does not install `~/.claude` assets, but selecting a `claude` worker still requires a local Claude CLI wrapper.
321
+ 현재 Claude Code 독립 범위는 external lead prompt + `okstra team *` worker dispatch 입니다. non-render `okstra_ctl.run --lead-runtime external` remains blocked; 완전한 external lead driver 는 아직 별도 구현 대상입니다. `--runtime external` runtime adapter 선택만 의미하며, 스킬 설치 여부는 `~/.claude` / `~/.codex` / `~/.omp/agent` home 존재 여부가 결정합니다. `claude` worker 선택은 여전히 local Claude CLI wrapper 를 필요로 합니다.
322
322
 
323
323
  ### Runtime auto-detection (`auto`)
324
324
 
@@ -328,9 +328,9 @@ Lead runtime independence boundary:
328
328
  - Codex host: `okstra run` 이 `codex-run` -> `codex-dispatch` 를 orchestration 합니다.
329
329
  - generic terminal + tmux: `okstra run` 이 `render-bundle --lead-runtime external` -> `okstra team dispatch` -> `okstra team await` 를 orchestration 합니다.
330
330
 
331
- `okstra install` / `ensure-installed` / `doctor` / `render-bundle` 도 `auto` 를 기본값으로 받지만, host signal 이 없는 plain terminal 에서는 host 지정이 필요합니다. 그래서 Claude Code skill 들은 `--runtime claude-code` / `--lead-runtime claude-code` 를 명시합니다.
331
+ `okstra install` / `ensure-installed` / `doctor` / `render-bundle` 도 `auto` 를 기본값으로 받지만, host signal 이 없는 plain terminal 에서는 host 지정이 필요합니다. 그래서 Claude Code skill 들은 `--runtime claude-code` / `--lead-runtime claude-code` 를 명시합니다. 단, `okstra install` 의 스킬 복사 대상은 runtime resolution 이 아니라 agent home 존재 여부입니다. `~/.claude` 와 `~/.codex` 가 있으면 둘 다 설치하고, 둘 다 없을 때만 `~/.omp/agent` 를 fallback 으로 사용합니다.
332
332
 
333
- `installed-runtimes.json` 은 schemaVersion 2 로 `installRequest`, `runtimeResolution`, `installedRuntimes`, `installedAssets` 를 분리합니다. 이전 `codexAdapter` / `externalAdapter` 는 v1 manifest read 호환 경로에서만 해석됩니다.
333
+ `installed-runtimes.json` 은 schemaVersion 2 로 `installRequest`, `runtimeResolution`, `installedRuntimes`, `installedAssets` 를 분리합니다. `installed-skills.json` 은 schemaVersion 2 target 목록(`provider`, `root`, `skills`)으로 Claude/Codex/OMP skill target 을 기록하며, uninstall 은 이 target 목록을 기준으로 제거합니다. 이전 `codexAdapter` / `externalAdapter` 는 v1 manifest read 호환 경로에서만 해석됩니다.
334
334
 
335
335
  Codex lead dry-run 은 `okstra codex-run <args...>` 를 쓰면 됩니다. 이 명령은 `--render-only --lead-runtime codex` 를 직접 붙이고, worker dispatch 없이 준비된 task bundle 과 lead prompt 를 출력합니다.
336
336
  생성된 team-state / run-manifest / task-manifest 는 `leadEventsPath` 로 `runs/<task-type>/state/lead-events-<task-type>-<seq>.jsonl` 를 가리키며, render 단계는 `bundle-prepared` event 를 기록합니다.
@@ -623,9 +623,9 @@ chmod +x ~/.local/bin/okstra-ctl
623
623
  | 명령 | 용도 |
624
624
  |---|---|
625
625
  | `okstra paths [--field <name>\|--shell]` | package/runtime/home/bin/pythonpath/version 경로 출력 |
626
- | `okstra install [--runtime claude-code\|codex\|external\|all] [--refresh\|--dry-run\|--link <repo>]` | runtime, templates, skills, agents 설치/갱신. 기본값 `claude-code` 기존처럼 Claude skills/agents 까지 설치한다. `codex` / `external` 공유 runtime 설치하고 `~/.claude` 자산을 건드리지 않는다 |
627
- | `okstra ensure-installed [--runtime claude-code\|codex\|external\|all] [-q]` | 설치 상태 확인, stale이면 같은 runtime 으로 재설치. `codex` / `external` runtime 은 Claude skills/agents drift 검사를 건너뛴다 |
628
- | `okstra uninstall [--purge -y]` | 설치 자산 제거. 기본값은 사용자 데이터 보존 |
626
+ | `okstra install [--runtime claude-code\|codex\|external\|all] [--refresh\|--dry-run\|--link <repo>]` | runtime, templates, skills, agents 설치/갱신. 기본 runtime 은 `auto`; 스킬 대상은 runtime 이 아니라 기존 home 기준이다. `~/.claude` 있으면 Claude skills + agents, `~/.codex` 있으면 Codex skills, 없고 `~/.omp/agent` 있으면 OMP skills 를 설치한다 |
627
+ | `okstra ensure-installed [--runtime claude-code\|codex\|external\|all] [-q]` | 설치 상태 확인, stale이면 같은 runtime 으로 재설치. 존재하는 skill target drift 검사한다 |
628
+ | `okstra uninstall [--purge -y]` | 설치 자산 제거. 기본값은 `installed-skills.json` target 목록과 `installed-agents.json` 기준으로 제거하고 사용자 데이터는 보존 |
629
629
  | `okstra doctor [--runtime claude-code\|codex\|external\|all] [--phase <phase>] [--json]` | runtime + Python import + skill/agent 설치 진단. `codex` / `external` runtime 은 Claude skill checks 를 제외한다. `--phase` 는 `implementation`, `final-verification`, `release-handoff`, `improvement-discovery` 실행 전 readiness 체크를 추가한다 |
630
630
  | `okstra setup --project-id <id>` | 현재 프로젝트의 `.okstra/project.json` 생성/갱신 |
631
631
  | `okstra check-project [--json]` | 현재 프로젝트가 등록되었는지 검증 |
@@ -124,14 +124,14 @@ Runtime/install asset changes follow this checklist:
124
124
  - `runtime/python/*` → `~/.okstra/lib/python/`
125
125
  - `runtime/bin/*` → `~/.okstra/bin/`
126
126
  - `runtime/templates/*` → `~/.okstra/templates/`
127
- - `runtime/skills/<name>` → `~/.claude/skills/<name>`
128
- - `runtime/agents/workers/*.md` → `~/.claude/agents/*-worker.md`
129
- - install manifests → `~/.okstra/installed-skills.json`, `~/.okstra/installed-agents.json`
127
+ - `runtime/skills/<name>` + `runtime/agents/SKILL.md` existing skill homes: `~/.claude/skills`, `~/.codex/skills`, or fallback `~/.omp/agent/skills`
128
+ - `runtime/agents/workers/*.md` → `~/.claude/agents/*-worker.md` when `~/.claude` exists
129
+ - install manifests → `~/.okstra/installed-skills.json` (target-aware), `~/.okstra/installed-agents.json`
130
130
  - version stamp → `~/.okstra/version`
131
131
 
132
132
  `--link <repo>` mode is for development and symlinks installed files back to repo sources.
133
133
 
134
- `src/runtime-resolver.mjs` is the single reference point for runtime auto-detection. `okstra install` defaults to `--runtime auto`, records the request and resolution in `installed-runtimes.json` schemaVersion 2, and still copies the shared runtime payload from the installed package `runtime/` tree. Dynamic capabilities such as `tmux` and `codex` CLI availability are checked by `doctor` and `run`, not frozen into the install manifest. Claude Code skills pass explicit `--runtime claude-code` / `--lead-runtime claude-code` so they never depend on host auto-detection.
134
+ `src/runtime-resolver.mjs` is the single reference point for runtime auto-detection. `okstra install` defaults to `--runtime auto`, records the request and resolution in `installed-runtimes.json` schemaVersion 2, and still copies the shared runtime payload from the installed package `runtime/` tree. Skill targets are discovered from existing agent homes: `~/.claude` and `~/.codex` are both populated when present; `~/.omp/agent` is the fallback only when neither exists. Dynamic capabilities such as `tmux` and `codex` CLI availability are checked by `doctor` and `run`, not frozen into the install manifest. Claude Code skills pass explicit `--runtime claude-code` / `--lead-runtime claude-code` so they never depend on host auto-detection.
135
135
 
136
136
  ---
137
137
 
@@ -392,7 +392,7 @@ Project-local `<PROJECT_ROOT>/.claude/settings.local.json` is provisioned as a s
392
392
 
393
393
  ### 7.1 First setup
394
394
 
395
- 1. `okstra install --runtime claude-code`
395
+ 1. `okstra install`
396
396
  2. `okstra doctor --runtime claude-code`
397
397
  3. inside target repo: `okstra setup --project-id <id>` or `/okstra-setup`
398
398
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "okstra",
3
- "version": "0.80.0",
3
+ "version": "0.81.0",
4
4
  "description": "Multi-agent cross-verification orchestrator runtime + Claude Code skills.",
5
5
  "license": "MIT",
6
6
  "author": "devonshin",
@@ -1,5 +1,5 @@
1
1
  {
2
- "package": "0.80.0",
3
- "builtAt": "2026-06-14T13:33:02.891Z",
2
+ "package": "0.81.0",
3
+ "builtAt": "2026-06-14T17:44:49.025Z",
4
4
  "repoRoot": "/home/runner/work/okstra/okstra"
5
5
  }
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  name: okstra
3
3
  description: Use when the user says "okstra", "cross verify", "cross-check with other AIs", "compare Claude, Codex, and Gemini", or asks for multi-agent comparison against a prepared task bundle.
4
+ hide: true
4
5
  ---
5
6
 
6
7
  # OKSTRA Skill
@@ -121,15 +121,9 @@ okstra check-project --json
121
121
  Parse the JSON from stdout:
122
122
 
123
123
  - `ok: true` → use `projectRoot` from the JSON output. Carry it as a literal string into the steps below.
124
- - `ok: false` → ask the user with a **plain text prompt** for an absolute project-root path, then rerun as a separate tool call with the literal absolute path (no `$(pwd)`, no shell variables):
124
+ - `ok: false` → tell the user: "this project has no okstra setup. Run `/okstra-setup` first." Then stop.
125
125
 
126
- ```bash
127
- okstra check-project --cwd /abs/path/from/user --json
128
- ```
129
-
130
- If still `ok: false`, tell the user to run `okstra-setup` and stop.
131
-
132
- > If the `okstra` binary is not on `PATH` at all, the command above will not run. In that case tell the user verbatim: "okstra not installed — run `npx okstra@latest install` once, then retry this skill." Do **not** try to invoke `npx -y okstra@latest ...` from this skill — `npx` is not on the literal-token allow-list this skill targets and will force a confirmation prompt on every subsequent call.
126
+ > If the `okstra` binary is not on `PATH` at all, the command above will not run. In that case tell the user verbatim: "okstra not installed — run `/okstra-setup` first." Do **not** try to invoke `npx -y okstra@latest ...` from this skill — `npx` is not on the literal-token allow-list this skill targets and will force a confirmation prompt on every subsequent call.
133
127
 
134
128
  ## Step 1: Choose input source
135
129
 
@@ -2,6 +2,7 @@
2
2
  name: okstra-coding-preflight
3
3
  description: Provides language-specific coding conventions, idioms, and test-writing guidance for Java, Kotlin, JavaScript, TypeScript, Node.js, Python, SQL, and Rust. okstra lead/workers MUST consult this skill before writing, editing, refactoring, or testing code in any of these languages — including any new file, function, or test — even when the task seems simple enough to handle directly. Detect the target language from the file extension, project config (package.json/Cargo.toml/pyproject.toml/pom.xml/build.gradle), or task brief, and apply the matching conventions.
4
4
  user-invocable: false
5
+ hide: true
5
6
  ---
6
7
 
7
8
  # okstra Code Preflight
@@ -2,6 +2,7 @@
2
2
  name: okstra-context-loader
3
3
  description: Use when okstra Phase 1 starts, or when the user asks for an okstra task bundle path, task-manifest location, or the current/latest okstra run artifacts.
4
4
  user-invocable: false
5
+ hide: true
5
6
  ---
6
7
 
7
8
  # OKSTRA Context Loader
@@ -2,6 +2,7 @@
2
2
  name: okstra-convergence
3
3
  description: Use when okstra Phase 5.5 needs iterative cross-verification between workers, or when worker findings must be classified by consensus level before final synthesis.
4
4
  user-invocable: false
5
+ hide: true
5
6
  ---
6
7
 
7
8
  # OKSTRA Convergence
@@ -22,7 +22,7 @@ Single read-side entry point for okstra runtime inspection plus the one write-si
22
22
  Before any sub-command, run each of the following commands as a **separate Bash tool call**. Each command starts with the literal token `okstra` so the `Bash(okstra:*)` permission match succeeds. Do **not** wrap any of them in `if`, `eval`, `export`, `$(...)`, `VAR=...`, `||`, or `&&`, and do **not** introduce a `$OKSTRA_CMD` variable or an `npx -y okstra@latest` fallback — those leading tokens defeat the permission match and force a confirmation prompt on every call. The LLM (you) inspects each command's output and decides what to do next in natural language — never in shell.
23
23
 
24
24
  1. `okstra ensure-installed --runtime claude-code`
25
- If this exits non-zero, tell the user: "okstra not installed — run `npx okstra@latest install` once, then retry this skill." Then stop. Do **not** try to invoke `npx -y okstra@latest ...` as a fallback.
25
+ If this exits non-zero, tell the user: "okstra not installed — run `/okstra-setup` first." Then stop. Do **not** try to invoke `npx -y okstra@latest ...` as a fallback.
26
26
 
27
27
  2. `okstra check-project --json`
28
28
  Reads the project from the current working directory. Parse the JSON from stdout. The shape is `{ok, projectRoot, projectJsonPath, projectId}`.
@@ -2,6 +2,7 @@
2
2
  name: okstra-report-writer
3
3
  description: Use when okstra is in Phase 6 or Phase 7 — writing the final synthesis report, persisting artifacts, or updating manifests.
4
4
  user-invocable: false
5
+ hide: true
5
6
  ---
6
7
 
7
8
  # OKSTRA Report Writer
@@ -56,19 +56,17 @@ Never invent additional questions. Never reorder. **Never drop, hide, or merge a
56
56
  Run each of the following commands as a **separate Bash tool call**. Each command starts with the literal token `okstra` so the `Bash(okstra:*)` permission match succeeds. Do **not** wrap any of them in `if`, `eval`, `export`, `$(...)`, `VAR=...`, `||`, or `&&` — those leading tokens defeat the permission match and force a confirmation prompt on every call. The LLM (you) inspects each command's JSON output and decides what to do next in natural language — never in shell.
57
57
 
58
58
  1. `okstra ensure-installed --runtime claude-code`
59
- If this exits non-zero, tell the user: "okstra runtime missing — run `npx okstra@latest install` once and retry." Then stop.
59
+ If this exits non-zero, tell the user: "okstra runtime missing — run `/okstra-setup` first." Then stop. Do **not** try to invoke `npx -y okstra@latest ...` as a fallback.
60
60
 
61
61
  2. `okstra paths --json`
62
62
  Read `pythonPath` and other path fields from the JSON output. **Do not** `export PYTHONPATH=...` from this skill — every subsequent `okstra <subcmd>` call already self-bootstraps its Python path. Keep the parsed values in mind only as diagnostic context.
63
63
 
64
64
  3. `okstra check-project --json`
65
- Reads the project from the current working directory. Parse the JSON from stdout. If `ok` is `false`, ask the user with a **plain text prompt** for an absolute project-root path, then rerun as a separate tool call (still literal-token-first):
66
- `okstra check-project --cwd /abs/path/from/user --json`
67
- Substitute the literal absolute path the user gave you (no `$(pwd)`, no shell variables). Re-prompt with plain text on continued failure.
65
+ Reads the project from the current working directory. Parse the JSON from stdout. If `ok` is `false`, tell the user: "this project has no okstra setup. Run `/okstra-setup` first." Then stop.
68
66
 
69
67
  Parse `projectRoot` and `projectId` from the successful `check-project --json` output and carry them as literal strings into Step 2.
70
68
 
71
- > If the `okstra` binary is not on `PATH` at all, none of the commands above will run. In that case tell the user verbatim: "okstra not installed — run `npx okstra@latest install` once, then retry this skill." Do **not** try to invoke `npx -y okstra@latest ...` from this skill — `npx` is not on the literal-token allow-list and will force a confirmation prompt on every wizard call afterward.
69
+ > If the `okstra` binary is not on `PATH` at all, none of the commands above will run. In that case tell the user verbatim: "okstra not installed — run `/okstra-setup` first." Do **not** try to invoke `npx -y okstra@latest ...` from this skill — `npx` is not on the literal-token allow-list and will force a confirmation prompt on every wizard call afterward.
72
70
 
73
71
  ## Step 2: Initialize the wizard
74
72
 
@@ -40,7 +40,7 @@ If `--title` is omitted, derive a default title from `task-group` (e.g. `uploadF
40
40
  Before anything else in this skill, run each of the following commands as a **separate Bash tool call**. Each command starts with the literal token `okstra` so the `Bash(okstra:*)` permission match succeeds. Do **not** wrap any of them in `if`, `eval`, `export`, `$(...)`, `VAR=...`, `||`, or `&&`, and do **not** introduce a `$OKSTRA_CMD` variable or an `npx -y okstra@latest` fallback — those leading tokens defeat the permission match and force a confirmation prompt on every call. The LLM (you) inspects each command's output and decides what to do next in natural language — never in shell.
41
41
 
42
42
  1. `okstra ensure-installed --runtime claude-code`
43
- If this exits non-zero, tell the user: "okstra not installed — run `npx okstra@latest install` once, then retry this skill." Then stop. Do **not** try to invoke `npx -y okstra@latest ...` as a fallback.
43
+ If this exits non-zero, tell the user: "okstra not installed — run `/okstra-setup` first." Then stop. Do **not** try to invoke `npx -y okstra@latest ...` as a fallback.
44
44
 
45
45
  2. `okstra check-project --json`
46
46
  Reads the project from the current working directory. Parse the JSON from stdout. The shape is `{ok, projectRoot, projectJsonPath, projectId}`.
@@ -2,6 +2,7 @@
2
2
  name: okstra-team-contract
3
3
  description: Use when okstra is in Phases 2–5 and needs team operating rules for the Claude lead + worker structure, or when verifying worker roster and model assignments.
4
4
  user-invocable: false
5
+ hide: true
5
6
  ---
6
7
 
7
8
  # OKSTRA Team Contract
@@ -6,7 +6,7 @@ export const COMMAND_REGISTRY = [
6
6
  module: "./install.mjs",
7
7
  export: "runInstall",
8
8
  category: "admin",
9
- summary: ["Install runtime; --runtime codex skips ~/.claude assets"],
9
+ summary: ["Install runtime; seed skills into existing agent homes"],
10
10
  },
11
11
  {
12
12
  name: "ensure-installed",
@@ -239,7 +239,7 @@ This CLI is the installer/admin tool. Day-to-day usage happens inside a
239
239
  Claude Code session via slash commands (/okstra-setup, /okstra-run, ...).
240
240
 
241
241
  Quick start (CLI):
242
- 1. npx -y okstra@latest install --runtime claude-code # one-time, this machine
242
+ 1. npx -y okstra@latest install # one-time, this machine
243
243
  2. cd <your project>
244
244
  3. npx -y okstra@latest setup --project-id <id> # one-time, this project
245
245
  4. open a Claude Code session and run /okstra-run # start a task
package/src/install.mjs CHANGED
@@ -10,8 +10,14 @@ import { normalizeRuntimeRequest, resolveRuntime } from "./runtime-resolver.mjs"
10
10
 
11
11
  const SKILLS_MANIFEST_REL = "installed-skills.json";
12
12
  const AGENTS_MANIFEST_REL = "installed-agents.json";
13
- const CLAUDE_SKILLS_DIR = join(homedir(), ".claude", "skills");
14
- const CLAUDE_AGENTS_DIR = join(homedir(), ".claude", "agents");
13
+ const USER_HOME = homedir();
14
+ const CLAUDE_HOME = join(USER_HOME, ".claude");
15
+ const CODEX_HOME = join(USER_HOME, ".codex");
16
+ const OMP_AGENT_HOME = join(USER_HOME, ".omp", "agent");
17
+ const CLAUDE_SKILLS_DIR = join(CLAUDE_HOME, "skills");
18
+ const CODEX_SKILLS_DIR = join(CODEX_HOME, "skills");
19
+ const OMP_SKILLS_DIR = join(OMP_AGENT_HOME, "skills");
20
+ const CLAUDE_AGENTS_DIR = join(CLAUDE_HOME, "agents");
15
21
 
16
22
  // Source template (relative to runtime root in copy mode, or repo root in link mode).
17
23
  const SETTINGS_TEMPLATE_SRC_REL = ["templates", "reports", "settings.template.json"];
@@ -52,10 +58,12 @@ Effect (copy mode):
52
58
  ${"$HOME"}/.okstra/bin <- runtime/bin
53
59
  ${"$HOME"}/.okstra/templates <- runtime/templates (report.css / report.js / *.template.md)
54
60
  ${"$HOME"}/.okstra/templates/settings.local.json <- runtime/templates/reports/settings.template.json
55
- ${"$HOME"}/.claude/skills/<name> <- runtime/skills/<name> (per skill)
56
- ${"$HOME"}/.claude/agents/<worker>.md <- runtime/agents/workers/<worker>.md
61
+ ${"$HOME"}/.claude/skills/<name> <- skills when ${"$HOME"}/.claude exists
62
+ ${"$HOME"}/.codex/skills/<name> <- skills when ${"$HOME"}/.codex exists
63
+ ${"$HOME"}/.omp/agent/skills/<name> <- skills fallback when Claude/Codex homes are absent
64
+ ${"$HOME"}/.claude/agents/<worker>.md <- worker agents when ${"$HOME"}/.claude exists
57
65
  ${"$HOME"}/.okstra/installed-runtimes.json <- manifest of installed runtime adapters
58
- ${"$HOME"}/.okstra/installed-skills.json <- manifest of installed skills
66
+ ${"$HOME"}/.okstra/installed-skills.json <- target-aware manifest of installed skills
59
67
  ${"$HOME"}/.okstra/installed-agents.json <- manifest of installed workers
60
68
  ${"$HOME"}/.okstra/version <- installed package version stamp
61
69
 
@@ -63,8 +71,10 @@ Effect (link mode):
63
71
  ${"$HOME"}/.okstra/lib/python/<pkg> -> <repo>/scripts/<pkg> (symlink)
64
72
  ${"$HOME"}/.okstra/bin/<name>.sh -> <repo>/scripts/<name>.sh
65
73
  ${"$HOME"}/.okstra/templates/settings.local.json -> <repo>/templates/reports/settings.template.json
66
- ${"$HOME"}/.claude/skills/<name> -> <repo>/skills/<name> (symlink dir)
67
- ${"$HOME"}/.claude/agents/<worker>.md -> <repo>/agents/workers/<worker>.md
74
+ ${"$HOME"}/.claude/skills/<name> -> <repo>/skills/<name> when ${"$HOME"}/.claude exists
75
+ ${"$HOME"}/.codex/skills/<name> -> <repo>/skills/<name> when ${"$HOME"}/.codex exists
76
+ ${"$HOME"}/.omp/agent/skills/<name> -> <repo>/skills/<name> fallback
77
+ ${"$HOME"}/.claude/agents/<worker>.md -> <repo>/agents/workers/<worker>.md when ${"$HOME"}/.claude exists
68
78
  ${"$HOME"}/.okstra/dev-link <- <repo> path stamp
69
79
  ${"$HOME"}/.okstra/version <- installed package version stamp
70
80
 
@@ -73,10 +83,9 @@ project-local <project>/.claude/settings.local.json that okstra-setup
73
83
  provisions, granting per-project Claude Code permissions for okstra
74
84
  worker wrapper scripts without modifying the user's global settings.
75
85
 
76
- Worker agent definitions are installed into ${"$HOME"}/.claude/agents/ so
77
- that Claude Code's subagent discovery picks them up; they cannot live
78
- inside the package alone because the harness only scans ~/.claude/agents/
79
- and <project>/.claude/agents/.
86
+ Worker agent definitions are installed into ${"$HOME"}/.claude/agents/ only
87
+ when ${"$HOME"}/.claude exists, so Claude Code's subagent discovery can pick
88
+ them up. Codex/OMP skill targets receive skill markdown only.
80
89
  `;
81
90
 
82
91
  const ENSURE_USAGE = `okstra ensure-installed — idempotent install check
@@ -305,19 +314,22 @@ async function installLinkMode(repoPath, paths, opts) {
305
314
  if (!quiet) process.stdout.write(` bin/${name}: ${action}\n`);
306
315
  }
307
316
 
308
- if (runtimeIncludesClaudeAssets(runtime, opts.resolution)) {
309
- const skillResult = await installSkillsLink(repoAbs, { dryRun, quiet });
310
- const leadSkillResult = await installLeadSkillLink(repoAbs, { dryRun, quiet });
311
- await writeSkillsManifest(paths.home, [...skillResult.installed, ...leadSkillResult], { dryRun });
317
+ const skillTargets = await detectSkillTargets();
318
+ const installedSkillTargets = await installSkillTargetsLink(repoAbs, skillTargets, { dryRun, quiet });
319
+ await writeSkillsManifest(paths.home, installedSkillTargets, { dryRun });
312
320
 
313
- const agentResult = await installAgentsLink(repoAbs, { dryRun, quiet });
314
- await writeAgentsManifest(paths.home, agentResult.installed, { dryRun });
315
- } else if (!quiet) {
316
- process.stdout.write(" claude assets: skipped for runtime=codex\n");
321
+ let agentResult = { installed: [] };
322
+ if (skillTargets.some((target) => target.provider === "claude")) {
323
+ agentResult = await installAgentsLink(repoAbs, { dryRun, quiet });
317
324
  }
325
+ await writeAgentsManifest(paths.home, agentResult.installed, { dryRun });
318
326
 
319
327
  await installNamedTemplate(repoAbs, paths, { mode: "link", dryRun, quiet }, SETTINGS_TEMPLATE_DESCRIPTOR);
320
- await writeRuntimeManifest(paths.home, runtime, { dryRun, resolution: opts.resolution });
328
+ await writeRuntimeManifest(paths.home, runtime, {
329
+ dryRun,
330
+ resolution: opts.resolution,
331
+ installedAssets: installedAssetOverrides(installedSkillTargets, agentResult.installed),
332
+ });
321
333
 
322
334
  if (!dryRun) {
323
335
  await writeFileAtomic(join(paths.home, "dev-link"), repoAbs + "\n", 0o644);
@@ -330,7 +342,7 @@ async function installLinkMode(repoPath, paths, opts) {
330
342
  process.stdout.write(
331
343
  "\nNext step: register the current project.\n" +
332
344
  " cd <your project> && npx -y okstra@latest setup --project-id <id>\n" +
333
- " (or run /okstra-setup inside a Claude Code session)\n" +
345
+ " (or run /okstra-setup inside a Harness Agent session (e.g. claude codex, codex, pi.. ))\n" +
334
346
  "\nTip: to use a bare 'okstra' command instead of npx, run:\n" +
335
347
  " npm i -g okstra\n",
336
348
  );
@@ -348,15 +360,58 @@ async function listSkillDirs(skillsRoot) {
348
360
  }
349
361
  }
350
362
 
351
- async function writeSkillsManifest(home, names, opts) {
363
+ async function detectSkillTargets() {
364
+ const targets = [];
365
+ const [hasClaude, hasCodex] = await Promise.all([dirExists(CLAUDE_HOME), dirExists(CODEX_HOME)]);
366
+ if (hasClaude) {
367
+ targets.push({ provider: "claude", home: CLAUDE_HOME, skillsDir: CLAUDE_SKILLS_DIR });
368
+ }
369
+ if (hasCodex) {
370
+ targets.push({ provider: "codex", home: CODEX_HOME, skillsDir: CODEX_SKILLS_DIR });
371
+ }
372
+ if (targets.length === 0 && (await dirExists(OMP_AGENT_HOME))) {
373
+ targets.push({ provider: "omp", home: OMP_AGENT_HOME, skillsDir: OMP_SKILLS_DIR });
374
+ }
375
+ return targets;
376
+ }
377
+
378
+ function skillTargetManifest(targets) {
379
+ return targets.map((target) => ({
380
+ provider: target.provider,
381
+ root: target.skillsDir,
382
+ skills: Array.from(new Set(target.installed)).sort(),
383
+ }));
384
+ }
385
+
386
+ function installedAssetOverrides(skillTargets, agentNames) {
387
+ const providers = new Set(skillTargets.map((target) => target.provider));
388
+ return {
389
+ claudeSkills: providers.has("claude"),
390
+ claudeAgents: agentNames.length > 0,
391
+ codexSkills: providers.has("codex"),
392
+ ompSkills: providers.has("omp"),
393
+ skillTargets: Array.from(providers).sort(),
394
+ };
395
+ }
396
+
397
+ function installedSkillNames(targets) {
398
+ return Array.from(new Set(targets.flatMap((target) => target.installed))).sort();
399
+ }
400
+
401
+
402
+ async function writeSkillsManifest(home, targets, opts) {
352
403
  const { dryRun = false } = opts ?? {};
404
+ const manifestTargets = skillTargetManifest(targets);
353
405
  const data = {
354
- version: 1,
406
+ version: 2,
355
407
  installedAt: new Date().toISOString(),
356
- skills: Array.from(new Set(names)).sort(),
408
+ skills: installedSkillNames(targets),
409
+ targets: manifestTargets,
357
410
  };
358
411
  if (dryRun) {
359
- process.stdout.write(`[dry-run] write skills manifest: ${data.skills.length} entries\n`);
412
+ process.stdout.write(
413
+ `[dry-run] write skills manifest: ${data.skills.length} skills across ${data.targets.length} targets\n`,
414
+ );
360
415
  return;
361
416
  }
362
417
  await writeFileAtomic(
@@ -385,8 +440,13 @@ async function writeAgentsManifest(home, names, opts) {
385
440
  }
386
441
 
387
442
  async function writeRuntimeManifest(home, runtime, opts) {
388
- const { dryRun = false, resolution = null } = opts ?? {};
389
- const data = buildRuntimeManifest(runtime, new Date().toISOString(), resolution);
443
+ const { dryRun = false, resolution = null, installedAssets = null } = opts ?? {};
444
+ const data = buildRuntimeManifest(
445
+ runtime,
446
+ new Date().toISOString(),
447
+ resolution,
448
+ installedAssets,
449
+ );
390
450
  if (dryRun) {
391
451
  process.stdout.write(`[dry-run] write runtime manifest: ${data.installedRuntimes.join(",")}\n`);
392
452
  return;
@@ -524,88 +584,100 @@ const SETTINGS_TEMPLATE_DESCRIPTOR = {
524
584
  label: "settings template",
525
585
  };
526
586
 
527
- async function installSkillsCopy(runtimeRoot, opts) {
587
+ const LEAD_SKILL_NAME = "okstra";
588
+
589
+ async function installSkillsCopy(runtimeRoot, target, opts) {
528
590
  const { refresh, dryRun, quiet } = opts;
529
591
  const srcRoot = join(runtimeRoot, "skills");
530
592
  const names = await listSkillDirs(srcRoot);
531
- if (names.length === 0) {
532
- if (!quiet) process.stdout.write(" skills: runtime/skills empty — skipped\n");
533
- return { installed: [] };
534
- }
535
593
  let copied = 0;
536
594
  let skipped = 0;
537
595
  for (const name of names) {
538
596
  const r = await copyTreeIfChanged(
539
597
  join(srcRoot, name),
540
- join(CLAUDE_SKILLS_DIR, name),
598
+ join(target.skillsDir, name),
541
599
  { refresh, dryRun, mode: 0o644 },
542
600
  );
543
601
  copied += r.copied;
544
602
  skipped += r.skipped;
545
603
  }
604
+ const lead = await installLeadSkillCopy(runtimeRoot, target, opts);
546
605
  if (!quiet) {
606
+ const totalCopied = copied + lead.copied;
607
+ const totalSkipped = skipped + lead.skipped;
608
+ const totalSkills = names.length + lead.installed.length;
547
609
  process.stdout.write(
548
- ` skills: copied=${copied} skipped=${skipped} -> ${CLAUDE_SKILLS_DIR}/ (${names.length} skills)\n`,
610
+ ` ${target.provider} skills: copied=${totalCopied} skipped=${totalSkipped} -> ${target.skillsDir}/ (${totalSkills} skills)\n`,
549
611
  );
550
612
  }
551
- return { installed: names };
613
+ return { ...target, installed: [...names, ...lead.installed] };
552
614
  }
553
615
 
554
- async function installSkillsLink(repoAbs, opts) {
616
+ async function installSkillsLink(repoAbs, target, opts) {
555
617
  const { dryRun, quiet } = opts;
556
618
  const srcRoot = join(repoAbs, "skills");
557
619
  const names = await listSkillDirs(srcRoot);
558
- if (names.length === 0) {
559
- if (!quiet) process.stdout.write(" skills: <repo>/skills missing — skipped\n");
560
- return { installed: [] };
561
- }
562
- if (!dryRun) await fs.mkdir(CLAUDE_SKILLS_DIR, { recursive: true });
620
+ if (!dryRun) await fs.mkdir(target.skillsDir, { recursive: true });
563
621
  for (const name of names) {
564
622
  const src = join(srcRoot, name);
565
- const dst = join(CLAUDE_SKILLS_DIR, name);
623
+ const dst = join(target.skillsDir, name);
566
624
  const action = await ensureSymlink(src, dst, { dryRun });
567
- if (!quiet) process.stdout.write(` skills/${name}: ${action}\n`);
625
+ if (!quiet) process.stdout.write(` ${target.provider} skills/${name}: ${action}\n`);
568
626
  }
569
- return { installed: names };
627
+ const lead = await installLeadSkillLink(repoAbs, target, opts);
628
+ return { ...target, installed: [...names, ...lead] };
570
629
  }
571
630
 
572
- // The lead `okstra` skill source is agents/SKILL.md (frontmatter `name: okstra`);
573
- // it doubles as the lead agent contract, so it lives under agents/ rather than
574
- // skills/. Claude Code only discovers skills at ~/.claude/skills/<name>/SKILL.md,
575
- // so it must be installed there as its own skill dir — otherwise install ships
576
- // every okstra-* helper skill but not the lead skill, and edits to agents/SKILL.md
577
- // never reach the running lead.
578
- const LEAD_SKILL_NAME = "okstra";
579
-
580
- async function installLeadSkillCopy(runtimeRoot, opts) {
631
+ async function installLeadSkillCopy(runtimeRoot, target, opts) {
581
632
  const { refresh, dryRun, quiet } = opts;
582
633
  const src = join(runtimeRoot, "agents", "SKILL.md");
583
- const dst = join(CLAUDE_SKILLS_DIR, LEAD_SKILL_NAME, "SKILL.md");
634
+ const dst = join(target.skillsDir, LEAD_SKILL_NAME, "SKILL.md");
584
635
  const r = await copyFileIfChanged(src, dst, { refresh, dryRun, mode: 0o644 });
585
636
  if (r.missingSource) {
586
- if (!quiet) process.stdout.write(" skills/okstra: runtime/agents/SKILL.md missing — skipped\n");
587
- return [];
588
- }
589
- if (!quiet) {
590
- process.stdout.write(` skills/okstra: ${r.copied ? "copied" : "unchanged"} -> ${dst}\n`);
637
+ if (!quiet) {
638
+ process.stdout.write(
639
+ ` ${target.provider} skills/okstra: runtime/agents/SKILL.md missing — skipped\n`,
640
+ );
641
+ }
642
+ return { installed: [], copied: 0, skipped: 0 };
591
643
  }
592
- return [LEAD_SKILL_NAME];
644
+ return { installed: [LEAD_SKILL_NAME], copied: r.copied, skipped: r.skipped };
593
645
  }
594
646
 
595
- async function installLeadSkillLink(repoAbs, opts) {
647
+ async function installLeadSkillLink(repoAbs, target, opts) {
596
648
  const { dryRun, quiet } = opts;
597
649
  const src = join(repoAbs, "agents", "SKILL.md");
598
650
  if (!(await fileExists(src))) {
599
- if (!quiet) process.stdout.write(" skills/okstra: <repo>/agents/SKILL.md missing — skipped\n");
651
+ if (!quiet) {
652
+ process.stdout.write(
653
+ ` ${target.provider} skills/okstra: <repo>/agents/SKILL.md missing — skipped\n`,
654
+ );
655
+ }
600
656
  return [];
601
657
  }
602
- const dst = join(CLAUDE_SKILLS_DIR, LEAD_SKILL_NAME, "SKILL.md");
603
- if (!dryRun) await fs.mkdir(join(CLAUDE_SKILLS_DIR, LEAD_SKILL_NAME), { recursive: true });
658
+ const dst = join(target.skillsDir, LEAD_SKILL_NAME, "SKILL.md");
659
+ if (!dryRun) await fs.mkdir(join(target.skillsDir, LEAD_SKILL_NAME), { recursive: true });
604
660
  const action = await ensureSymlink(src, dst, { dryRun });
605
- if (!quiet) process.stdout.write(` skills/okstra/SKILL.md: ${action}\n`);
661
+ if (!quiet) process.stdout.write(` ${target.provider} skills/okstra/SKILL.md: ${action}\n`);
606
662
  return [LEAD_SKILL_NAME];
607
663
  }
608
664
 
665
+ async function installSkillTargetsCopy(runtimeRoot, targets, opts) {
666
+ if (targets.length === 0) {
667
+ if (!opts.quiet) process.stdout.write(" skills: no Claude, Codex, or OMP agent home found — skipped\n");
668
+ return [];
669
+ }
670
+ return await Promise.all(targets.map((target) => installSkillsCopy(runtimeRoot, target, opts)));
671
+ }
672
+
673
+ async function installSkillTargetsLink(repoAbs, targets, opts) {
674
+ if (targets.length === 0) {
675
+ if (!opts.quiet) process.stdout.write(" skills: no Claude, Codex, or OMP agent home found — skipped\n");
676
+ return [];
677
+ }
678
+ return await Promise.all(targets.map((target) => installSkillsLink(repoAbs, target, opts)));
679
+ }
680
+
609
681
  export function parseInstallArgs(args) {
610
682
  const result = {
611
683
  dryRun: false,
@@ -764,23 +836,22 @@ export async function runInstall(args) {
764
836
  );
765
837
  }
766
838
 
767
- if (runtimeIncludesClaudeAssets(opts.runtime, runtimeResolution)) {
768
- const skillResult = await installSkillsCopy(runtimeRoot, opts);
769
- const leadSkillResult = await installLeadSkillCopy(runtimeRoot, opts);
770
- await writeSkillsManifest(
771
- paths.home,
772
- [...skillResult.installed, ...leadSkillResult],
773
- { dryRun: opts.dryRun },
774
- );
839
+ const skillTargets = await detectSkillTargets();
840
+ const installedSkillTargets = await installSkillTargetsCopy(runtimeRoot, skillTargets, opts);
841
+ await writeSkillsManifest(paths.home, installedSkillTargets, { dryRun: opts.dryRun });
775
842
 
776
- const agentResult = await installAgentsCopy(runtimeRoot, opts);
777
- await writeAgentsManifest(paths.home, agentResult.installed, { dryRun: opts.dryRun });
778
- } else if (!opts.quiet) {
779
- process.stdout.write(` claude assets: skipped for runtime=${resolvedRuntime}\n`);
843
+ let agentResult = { installed: [] };
844
+ if (skillTargets.some((target) => target.provider === "claude")) {
845
+ agentResult = await installAgentsCopy(runtimeRoot, opts);
780
846
  }
847
+ await writeAgentsManifest(paths.home, agentResult.installed, { dryRun: opts.dryRun });
781
848
 
782
849
  await installNamedTemplate(runtimeRoot, paths, { mode: "copy", ...opts }, SETTINGS_TEMPLATE_DESCRIPTOR);
783
- await writeRuntimeManifest(paths.home, opts.runtime, { dryRun: opts.dryRun, resolution: runtimeResolution });
850
+ await writeRuntimeManifest(paths.home, opts.runtime, {
851
+ dryRun: opts.dryRun,
852
+ resolution: runtimeResolution,
853
+ installedAssets: installedAssetOverrides(installedSkillTargets, agentResult.installed),
854
+ });
784
855
 
785
856
  if (!opts.dryRun) {
786
857
  await writeFileAtomic(join(paths.home, "version"), paths.package + "\n", 0o644);
@@ -791,7 +862,7 @@ export async function runInstall(args) {
791
862
  process.stdout.write(
792
863
  "\nNext step: register the current project.\n" +
793
864
  " cd <your project> && npx -y okstra@latest setup --project-id <id>\n" +
794
- " (or run /okstra-setup inside a Claude Code session)\n" +
865
+ " (or run /okstra-setup inside a Harness Agent session (e.g. claude codex, codex, pi.. ))\n" +
795
866
  "\nTip: to use a bare 'okstra' command instead of npx, run:\n" +
796
867
  " npm i -g okstra\n",
797
868
  );
@@ -799,6 +870,19 @@ export async function runInstall(args) {
799
870
  return 0;
800
871
  }
801
872
 
873
+ async function skillTargetDriftReasons(targets) {
874
+ const reasons = [];
875
+ for (const target of targets) {
876
+ for (const name of ["okstra-run", LEAD_SKILL_NAME]) {
877
+ const skillPath = join(target.skillsDir, name, "SKILL.md");
878
+ if (!(await fileExists(skillPath))) {
879
+ reasons.push(`missing ${skillPath}`);
880
+ }
881
+ }
882
+ }
883
+ return reasons;
884
+ }
885
+
802
886
  async function agentDriftReasons(paths) {
803
887
  const reasons = [];
804
888
  const workersDir = join(paths.agents, "workers");
@@ -886,10 +970,11 @@ export async function runEnsureInstalled(args) {
886
970
  if (!(await fileExists(join(paths.home, "schemas", "final-report-v1.0.schema.json")))) {
887
971
  reasons.push(`missing ${join(paths.home, "schemas", "final-report-v1.0.schema.json")}`);
888
972
  }
889
- if (runtimeIncludesClaudeAssets(opts.runtime, runtimeResolution)) {
890
- if (!(await fileExists(join(CLAUDE_SKILLS_DIR, "okstra-setup", "SKILL.md")))) {
891
- reasons.push(`missing ${CLAUDE_SKILLS_DIR}/okstra-setup/SKILL.md`);
892
- }
973
+ const skillTargets = await detectSkillTargets();
974
+ for (const reason of await skillTargetDriftReasons(skillTargets)) {
975
+ reasons.push(reason);
976
+ }
977
+ if (skillTargets.some((target) => target.provider === "claude")) {
893
978
  for (const reason of await agentDriftReasons(paths)) {
894
979
  reasons.push(reason);
895
980
  }
@@ -30,24 +30,30 @@ function defaultResolution(runtime) {
30
30
  };
31
31
  }
32
32
 
33
- export function buildRuntimeManifest(runtime, installedAt = new Date().toISOString(), resolution = null) {
33
+ export function buildRuntimeManifest(
34
+ runtime,
35
+ installedAt = new Date().toISOString(),
36
+ resolution = null,
37
+ installedAssets = null,
38
+ ) {
34
39
  const runtimeResolution = resolution ?? defaultResolution(runtime);
35
40
  const includesClaude = includesClaudeRuntime(runtime, runtimeResolution);
41
+ const defaultAssets = {
42
+ sharedRuntime: true,
43
+ claudeSkills: includesClaude,
44
+ claudeAgents: includesClaude,
45
+ binWrappers: true,
46
+ templates: true,
47
+ schemas: true,
48
+ validators: true,
49
+ };
36
50
  return {
37
51
  schemaVersion: RUNTIME_MANIFEST_SCHEMA_VERSION,
38
52
  installedAt,
39
53
  installRequest: runtime,
40
54
  runtimeResolution,
41
55
  installedRuntimes: runtimeListForSelection(runtime, runtimeResolution),
42
- installedAssets: {
43
- sharedRuntime: true,
44
- claudeSkills: includesClaude,
45
- claudeAgents: includesClaude,
46
- binWrappers: true,
47
- templates: true,
48
- schemas: true,
49
- validators: true,
50
- },
56
+ installedAssets: { ...defaultAssets, ...(installedAssets ?? {}) },
51
57
  };
52
58
  }
53
59
 
package/src/uninstall.mjs CHANGED
@@ -51,22 +51,36 @@ const FALLBACK_AGENT_NAMES = [
51
51
  "report-writer-worker.md",
52
52
  ];
53
53
 
54
- const CLAUDE_SKILLS_DIR = join(homedir(), ".claude", "skills");
55
- const CLAUDE_AGENTS_DIR = join(homedir(), ".claude", "agents");
54
+ const USER_HOME = homedir();
55
+ const CLAUDE_HOME = join(USER_HOME, ".claude");
56
+ const CODEX_HOME = join(USER_HOME, ".codex");
57
+ const OMP_AGENT_HOME = join(USER_HOME, ".omp", "agent");
58
+ const CLAUDE_SKILLS_DIR = join(CLAUDE_HOME, "skills");
59
+ const CODEX_SKILLS_DIR = join(CODEX_HOME, "skills");
60
+ const OMP_SKILLS_DIR = join(OMP_AGENT_HOME, "skills");
61
+ const CLAUDE_AGENTS_DIR = join(CLAUDE_HOME, "agents");
62
+ const FALLBACK_SKILL_ROOTS = [
63
+ CLAUDE_SKILLS_DIR,
64
+ CODEX_SKILLS_DIR,
65
+ OMP_SKILLS_DIR,
66
+ ];
56
67
  const SKILLS_MANIFEST_REL = "installed-skills.json";
57
68
  const AGENTS_MANIFEST_REL = "installed-agents.json";
58
69
 
59
- const USAGE = `okstra uninstall — remove installed runtime from ~/.okstra, ~/.claude/skills, ~/.claude/agents
70
+ const USAGE = `okstra uninstall — remove installed runtime and okstra skills
60
71
 
61
72
  Usage:
62
73
  okstra uninstall Remove ~/.okstra/{lib (python + validators),
63
74
  bin/<known>, schemas, templates, version,
64
75
  dev-link, installed-runtimes.json,
65
76
  installed-skills.json, installed-agents.json} AND
66
- ~/.claude/skills/<name> AND
67
- ~/.claude/agents/<worker>.md for every
68
- entry in the install manifests (fallback:
69
- hard-coded okstra-* / *-worker.md names).
77
+ every skill target recorded in
78
+ installed-skills.json
79
+ (~/.claude/skills, ~/.codex/skills, or
80
+ ~/.omp/agent/skills). Claude worker agents are
81
+ removed from ~/.claude/agents when installed.
82
+ Missing manifests fall back to hard-coded
83
+ Claude okstra-* / *-worker.md names.
70
84
  Preserves user data: recent.jsonl,
71
85
  active.jsonl, projects/, archive/,
72
86
  state.json, .locks/
@@ -74,7 +88,7 @@ Usage:
74
88
  (<PROJECT>/.claude/settings.local.json symlink)
75
89
  are NOT touched — remove them manually if needed.
76
90
  okstra uninstall --purge Remove the entire ~/.okstra directory AND
77
- ~/.claude/skills/<okstra-*> AND
91
+ okstra skills in Claude/Codex/OMP homes AND
78
92
  ~/.claude/agents/<*-worker.md> (DESTRUCTIVE).
79
93
  Requires -y or an interactive confirmation
80
94
  okstra uninstall --dry-run Print the plan without touching disk
@@ -138,7 +152,7 @@ export async function runUninstall(args) {
138
152
  if (opts.purge) {
139
153
  if (!opts.yes && !opts.dryRun) {
140
154
  const ok = await promptConfirm(
141
- `purge entire ${paths.home} AND ~/.claude/skills/{okstra-*,okstra-setup}? user data will be lost.`,
155
+ `purge entire ${paths.home} AND okstra skills in Claude/Codex/OMP homes? user data will be lost.`,
142
156
  );
143
157
  if (!ok) {
144
158
  process.stdout.write("aborted.\n");
@@ -148,8 +162,10 @@ export async function runUninstall(args) {
148
162
  if (!opts.quiet) process.stdout.write(`purging ${paths.home}\n`);
149
163
  await removePath(paths.home, opts);
150
164
  // Skills and worker agents live outside ~/.okstra — purge those too.
151
- for (const name of FALLBACK_SKILL_NAMES) {
152
- await removePath(join(CLAUDE_SKILLS_DIR, name), opts);
165
+ for (const root of FALLBACK_SKILL_ROOTS) {
166
+ for (const name of FALLBACK_SKILL_NAMES) {
167
+ await removePath(join(root, name), opts);
168
+ }
153
169
  }
154
170
  for (const name of FALLBACK_AGENT_NAMES) {
155
171
  await removePath(join(CLAUDE_AGENTS_DIR, name), opts);
@@ -190,22 +206,26 @@ export async function runUninstall(args) {
190
206
  // never removed it, leaving a stale final-report schema behind.
191
207
  await removePath(join(paths.home, "schemas"), opts);
192
208
 
193
- // Remove the skills we own. Manifest is authoritative; fall back to the
194
- // hard-coded okstra-* names if it is missing (e.g. an install from an old
195
- // version that did not write the manifest).
209
+ // Remove the skills we own. Manifest v2 records every target root; legacy
210
+ // manifests fall back to Claude-only names when the runtime manifest says
211
+ // Claude assets may have been installed.
196
212
  const runtimeManifest = await readRuntimeManifest(paths.home);
197
- const shouldRemoveClaudeAssets = shouldUseClaudeAssetFallback(runtimeManifest);
198
- const skillNames = shouldRemoveClaudeAssets ? await readSkillsManifest(paths.home) : [];
199
- if (!opts.quiet) {
200
- process.stdout.write(` skills: removing ${skillNames.length} entries from ${CLAUDE_SKILLS_DIR}\n`);
201
- }
202
- for (const name of skillNames) {
203
- await removePath(join(CLAUDE_SKILLS_DIR, name), opts);
213
+ const skillTargets = await readSkillsManifest(paths.home, runtimeManifest);
214
+ for (const target of skillTargets) {
215
+ if (!opts.quiet) {
216
+ process.stdout.write(` skills: removing ${target.skills.length} entries from ${target.root}\n`);
217
+ }
218
+ for (const name of target.skills) {
219
+ await removePath(join(target.root, name), opts);
220
+ }
204
221
  }
205
222
  await removePath(join(paths.home, SKILLS_MANIFEST_REL), opts);
206
223
 
207
- // Remove worker agents we own. Same authoritative-manifest pattern as skills.
208
- const agentNames = shouldRemoveClaudeAssets ? await readAgentsManifest(paths.home) : [];
224
+ // Remove worker agents we own. Agents only live in Claude Code's agent home.
225
+ const shouldRemoveClaudeAgents =
226
+ skillTargets.some((target) => target.provider === "claude") ||
227
+ shouldUseClaudeAssetFallback(runtimeManifest);
228
+ const agentNames = shouldRemoveClaudeAgents ? await readAgentsManifest(paths.home) : [];
209
229
  if (!opts.quiet) {
210
230
  process.stdout.write(` agents: removing ${agentNames.length} entries from ${CLAUDE_AGENTS_DIR}\n`);
211
231
  }
@@ -248,15 +268,38 @@ export function shouldUseClaudeAssetFallback(runtimeManifest) {
248
268
  return runtimeManifestIncludesClaudeAssets(runtimeManifest);
249
269
  }
250
270
 
251
- async function readSkillsManifest(home) {
271
+ function normalizeSkillTargets(data) {
272
+ if (Array.isArray(data?.targets)) {
273
+ return data.targets
274
+ .filter((target) => typeof target?.root === "string" && Array.isArray(target?.skills))
275
+ .map((target) => ({
276
+ provider: typeof target.provider === "string" ? target.provider : "unknown",
277
+ root: target.root,
278
+ skills: target.skills.filter((name) => typeof name === "string"),
279
+ }));
280
+ }
281
+ if (Array.isArray(data?.skills)) {
282
+ return [{
283
+ provider: "claude",
284
+ root: CLAUDE_SKILLS_DIR,
285
+ skills: data.skills.filter((name) => typeof name === "string"),
286
+ }];
287
+ }
288
+ return null;
289
+ }
290
+
291
+ async function readSkillsManifest(home, runtimeManifest) {
252
292
  try {
253
293
  const raw = await fs.readFile(join(home, SKILLS_MANIFEST_REL), "utf8");
254
294
  const data = JSON.parse(raw);
255
- if (Array.isArray(data?.skills)) return data.skills;
295
+ const targets = normalizeSkillTargets(data);
296
+ if (targets !== null) return targets;
256
297
  } catch {
257
298
  /* fall through */
258
299
  }
259
- return FALLBACK_SKILL_NAMES;
300
+ return shouldUseClaudeAssetFallback(runtimeManifest)
301
+ ? [{ provider: "claude", root: CLAUDE_SKILLS_DIR, skills: FALLBACK_SKILL_NAMES }]
302
+ : [];
260
303
  }
261
304
 
262
305
  async function readAgentsManifest(home) {