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 +13 -11
- package/README.md +13 -11
- package/docs/kr/architecture.md +7 -7
- package/docs/kr/cli.md +6 -6
- package/docs/project-structure-overview.md +5 -5
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/SKILL.md +1 -0
- package/runtime/skills/okstra-brief/SKILL.md +2 -8
- package/runtime/skills/okstra-coding-preflight/SKILL.md +1 -0
- package/runtime/skills/okstra-context-loader/SKILL.md +1 -0
- package/runtime/skills/okstra-convergence/SKILL.md +1 -0
- package/runtime/skills/okstra-inspect/SKILL.md +1 -1
- package/runtime/skills/okstra-report-writer/SKILL.md +1 -0
- package/runtime/skills/okstra-run/SKILL.md +3 -5
- package/runtime/skills/okstra-schedule/SKILL.md +1 -1
- package/runtime/skills/okstra-team-contract/SKILL.md +1 -0
- package/src/cli-registry.mjs +2 -2
- package/src/install.mjs +168 -83
- package/src/runtime-manifest.mjs +16 -10
- package/src/uninstall.mjs +69 -26
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
|
|
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
|
-
|
|
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 가 자동 인식 (
|
|
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
|
|
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
|
|
110
|
+
npx -y okstra@latest install
|
|
109
111
|
```
|
|
110
112
|
|
|
111
|
-
`~/.okstra/{lib/python, bin, templates, version}
|
|
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
|
|
130
|
+
okstra install # 'npx -y okstra@latest install' 과 동일
|
|
129
131
|
```
|
|
130
132
|
|
|
131
|
-
글로벌 설치는 Node CLI 를 PATH 에 등록할 뿐입니다. 런타임(`~/.okstra/`) 과
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
109
|
+
npx -y okstra@latest install
|
|
108
110
|
```
|
|
109
111
|
|
|
110
|
-
Provisions `~/.okstra/{lib/python, bin, templates, version}
|
|
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
|
|
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
|
|
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
|
|
package/docs/kr/architecture.md
CHANGED
|
@@ -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
|
|
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
|
|
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하는
|
|
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
|
|
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`
|
|
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 설치/갱신.
|
|
627
|
-
| `okstra ensure-installed [--runtime claude-code\|codex\|external\|all] [-q]` | 설치 상태 확인, stale이면 같은 runtime 으로 재설치.
|
|
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
|
|
128
|
-
- `runtime/agents/workers/*.md` → `~/.claude/agents/*-worker.md`
|
|
129
|
-
- install manifests → `~/.okstra/installed-skills.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
|
|
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
package/runtime/BUILD.json
CHANGED
package/runtime/agents/SKILL.md
CHANGED
|
@@ -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` →
|
|
124
|
+
- `ok: false` → tell the user: "this project has no okstra setup. Run `/okstra-setup` first." Then stop.
|
|
125
125
|
|
|
126
|
-
|
|
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-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
|
|
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}`.
|
|
@@ -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
|
|
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`,
|
|
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
|
|
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
|
|
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
|
package/src/cli-registry.mjs
CHANGED
|
@@ -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;
|
|
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
|
|
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
|
|
14
|
-
const
|
|
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> <-
|
|
56
|
-
${"$HOME"}/.
|
|
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>
|
|
67
|
-
${"$HOME"}/.
|
|
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/
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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
|
-
|
|
314
|
-
|
|
315
|
-
|
|
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, {
|
|
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
|
|
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
|
|
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:
|
|
406
|
+
version: 2,
|
|
355
407
|
installedAt: new Date().toISOString(),
|
|
356
|
-
skills:
|
|
408
|
+
skills: installedSkillNames(targets),
|
|
409
|
+
targets: manifestTargets,
|
|
357
410
|
};
|
|
358
411
|
if (dryRun) {
|
|
359
|
-
process.stdout.write(
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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=${
|
|
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 (
|
|
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(
|
|
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
|
-
|
|
627
|
+
const lead = await installLeadSkillLink(repoAbs, target, opts);
|
|
628
|
+
return { ...target, installed: [...names, ...lead] };
|
|
570
629
|
}
|
|
571
630
|
|
|
572
|
-
|
|
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(
|
|
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)
|
|
587
|
-
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
|
|
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)
|
|
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(
|
|
603
|
-
if (!dryRun) await fs.mkdir(join(
|
|
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
|
-
|
|
768
|
-
|
|
769
|
-
|
|
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
|
-
|
|
777
|
-
|
|
778
|
-
|
|
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, {
|
|
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
|
|
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
|
-
|
|
890
|
-
|
|
891
|
-
|
|
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
|
}
|
package/src/runtime-manifest.mjs
CHANGED
|
@@ -30,24 +30,30 @@ function defaultResolution(runtime) {
|
|
|
30
30
|
};
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
export function buildRuntimeManifest(
|
|
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
|
|
55
|
-
const
|
|
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
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
152
|
-
|
|
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
|
|
194
|
-
//
|
|
195
|
-
//
|
|
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
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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.
|
|
208
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
295
|
+
const targets = normalizeSkillTargets(data);
|
|
296
|
+
if (targets !== null) return targets;
|
|
256
297
|
} catch {
|
|
257
298
|
/* fall through */
|
|
258
299
|
}
|
|
259
|
-
return
|
|
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) {
|