okstra 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,4 +1,175 @@
1
- # OKSTRA Usage Manual
1
+ # okstra
2
+
3
+ > npm: [`okstra`](https://www.npmjs.com/package/okstra) · 설치: `npx -y okstra@latest install`
4
+
5
+ ## 1. 용도
6
+
7
+ `okstra` 는 **Claude Code 안에서 task 한 건을 lead + 워커(Codex, Gemini, Claude) 조합으로 cross-verify 하기 위한 정형화된 진행 도구**입니다. 사용자는 자연어로 task brief 만 적어두면, okstra 가 task 정체성 · profile · prompt · run history · 프로젝트 레벨 metadata 를 한 묶음(task bundle)으로 만들어 Claude lead 가 안정적으로 phase 진행과 워커 dispatch 를 수행하도록 돕습니다.
8
+
9
+ 핵심 가치는 다음 세 가지입니다.
10
+
11
+ - **단일 진입점(`prepare_task_bundle`)**: 어떤 호출자(스킬, bash CLI)가 들어와도 task 디렉터리 구조 · 매니페스트 · 검증 경로가 동일하게 산출됩니다.
12
+ - **task 정체성의 영구화**: `<project-id>/<task-group>/<task-id>` stable task key 가 phase 간 / 세션 간 / 모델 간 컨텍스트를 잇습니다.
13
+ - **lead/worker 계약 강제**: 모든 task 는 Claude lead + 4종 worker(claude, codex, gemini, report-writer) 의 표준 구성을 갖고 진행되며, 산출 형식과 검증 절차가 templates/ + validators/ 로 묶여 있습니다.
14
+
15
+ cross-verify 가 필요 없는 단발 코드 리뷰는 okstra 의 범위가 아닙니다. okstra 는 **여러 단계로 나뉘는, 여러 에이전트가 의견을 내는, 그리고 그 결과가 다음 phase 의 입력이 되어야 하는** 작업을 위해 만들어졌습니다.
16
+
17
+ ## 2. 구조
18
+
19
+ ### 2.1 repo 레이아웃
20
+
21
+ ```
22
+ okstra/ npm 패키지 = repo 루트 (0.3.0 부터)
23
+ ├── package.json name: "okstra", publish 단위
24
+ ├── bin/okstra 노드 CLI 진입점
25
+ ├── src/ 설치자/리졸버 (install, paths, doctor, check-project, uninstall)
26
+ ├── tools/build.mjs runtime/ 동기화 스크립트 (prepack 에서 호출)
27
+ ├── runtime/ (gitignored, 빌드 산출물; npm tarball 에는 포함)
28
+ │ ├── python/ ← scripts/{okstra_project, okstra_ctl, okstra_token_usage, lib}
29
+ │ ├── bin/ ← scripts/*.{sh,py}
30
+ │ ├── agents/ ← agents/ (lead SKILL.md + workers/)
31
+ │ ├── skills/ ← skills/ (사용자 노출 스킬 11종)
32
+ │ ├── prompts/, templates/, validators/ ← 동명 디렉터리
33
+ │ └── BUILD.json 빌드 stamp
34
+ ├── scripts/ python+bash 런타임 원본
35
+ │ ├── okstra_project/, okstra_ctl/, okstra_token_usage/, lib/
36
+ │ ├── okstra.sh, okstra-codex-exec.sh, okstra-gemini-exec.sh, okstra-central.sh
37
+ │ └── okstra-token-usage.py, okstra-error-log.py
38
+ ├── skills/ Claude Code 스킬 마크다운 원본
39
+ │ ├── okstra-setup/SKILL.md 부트스트랩 (프로젝트별 1회)
40
+ │ ├── okstra-run/SKILL.md task 시작
41
+ │ ├── okstra-status/SKILL.md 진행 현황
42
+ │ ├── okstra-history/, okstra-schedule/, okstra-time-summary/, okstra-report-finder/
43
+ │ └── okstra-context-loader/, okstra-team-contract/, okstra-convergence/, okstra-report-writer/
44
+ ├── agents/ lead + workers
45
+ ├── prompts/, templates/, validators/
46
+ ├── tests/, tests-e2e/
47
+ ├── .claude-plugin/plugin.json skills 채널(보조) 매니페스트
48
+ ├── .github/workflows/{release-please.yml, release.yml}
49
+ ├── release-please-config.json, .release-please-manifest.json
50
+ ├── RELEASING.md, CHANGELOG.md, README.md
51
+ └── CHANGES.md
52
+ ```
53
+
54
+ ### 2.2 설치 후 사용자 머신 레이아웃
55
+
56
+ ```
57
+ ~/.okstra/ okstra install 이 채우는 런타임 home
58
+ ├── version package 버전 stamp
59
+ ├── lib/python/ okstra_project/, okstra_ctl/, ...
60
+ ├── bin/ okstra.sh, codex-exec, gemini-exec, ...
61
+ ├── installed-skills.json 설치된 스킬 manifest (uninstall 용)
62
+ ├── recent.jsonl, active.jsonl 실행 인덱스
63
+ ├── projects/ 프로젝트별 메타 미러
64
+ ├── archive/ 완료된 run 보관
65
+ └── .locks/ task 단위 mutex
66
+
67
+ ~/.claude/skills/ Claude Code 가 자동 발견
68
+ ├── okstra-setup/SKILL.md
69
+ ├── okstra-run/SKILL.md
70
+ └── ... (11종)
71
+
72
+ <프로젝트 루트>/.project-docs/okstra/ 대상 프로젝트의 okstra metadata
73
+ ├── project.json {projectId, projectRoot, ...} (okstra-setup 이 작성)
74
+ ├── discovery/{task-catalog,latest-task}.json
75
+ └── tasks/<task-group>/<task-id>/ task bundle (run history, manifest, reports)
76
+ ```
77
+
78
+ ### 2.3 단일 권위 요약
79
+
80
+ | 자원 | 위치 | 책임자 |
81
+ |---|---|---|
82
+ | 런타임 코드(python+bash) | `~/.okstra/{lib/python, bin}` | `okstra install` |
83
+ | agents/prompts/templates/validators | npm 패키지의 `runtime/<...>/` | `okstra` 패키지 자체 (복사 없음, `okstra paths` 로 조회) |
84
+ | 스킬 마크다운 | `~/.claude/skills/<name>/SKILL.md` | `okstra install` (+ `installed-skills.json` 매니페스트) |
85
+ | 프로젝트 메타 | `<프로젝트>/.project-docs/okstra/` | `okstra-setup` 스킬 + 프로젝트 자체 |
86
+ | 실행 인덱스 | `~/.okstra/{recent,active}.jsonl` | `prepare_task_bundle` |
87
+ | task 단위 락 | `~/.okstra/.locks/<task-key>.lock` | `prepare_task_bundle` |
88
+
89
+ ## 3. 사용 매뉴얼
90
+
91
+ ### 3.1 최초 셋업 (머신당 1회)
92
+
93
+ ```bash
94
+ npx -y okstra@latest install
95
+ ```
96
+
97
+ 위 한 명령으로 다음이 한꺼번에 깔립니다.
98
+
99
+ - `~/.okstra/{lib/python, bin, version}` — python + bash 런타임
100
+ - `~/.claude/skills/<name>/SKILL.md` × 11 — 스킬 마크다운
101
+ - `~/.okstra/installed-skills.json` — 매니페스트
102
+
103
+ 이미 깔린 상태에서 다시 실행해도 됩니다(per-file hash 비교로 변경분만 갱신, idempotent).
104
+
105
+ 검증:
106
+
107
+ ```bash
108
+ npx -y okstra@latest doctor
109
+ ```
110
+
111
+ `result: OK` 가 나오면 정상. FAIL 항목 있으면 그 안내대로(보통 install 재실행).
112
+
113
+ ### 3.2 프로젝트 등록 (프로젝트당 1회)
114
+
115
+ 대상 프로젝트 디렉터리에서 Claude Code 세션 열고:
116
+
117
+ ```
118
+ /okstra-setup
119
+ ```
120
+
121
+ 스킬이 인터랙티브하게:
122
+
123
+ 1. okstra 런타임 상태 확인 (위 3.1 미실행 시 안내)
124
+ 2. `projectId` 묻기 (자유 식별자 — `INV-1234`, `fontsninja`, `okstra` 등)
125
+ 3. `<프로젝트>/.project-docs/okstra/project.json` 생성
126
+
127
+ 이후의 모든 진입점 스킬은 이 파일이 없으면 "먼저 `/okstra-setup` 을 실행하세요" 라고 거부합니다.
128
+
129
+ ### 3.3 일상 명령
130
+
131
+ Claude Code 세션 안에서 / 슬래시 명령 또는 자연어 트리거:
132
+
133
+ | 명령 | 용도 |
134
+ |---|---|
135
+ | `/okstra-run` | 새 task 시작 (또는 기존 task 다음 phase 이어가기) |
136
+ | `/okstra-status` | 프로젝트 전체 또는 특정 task 의 phase/상태 조회, workStatus 변경 |
137
+ | `/okstra-history` | 과거 task 목록, 재개 후보 확인 |
138
+ | `/okstra-schedule` | task-group 단위 작업 계획표 생성 |
139
+ | `/okstra-time-summary` | task 의 소요 시간 분석 (lead, 워커별) |
140
+ | `/okstra-report-finder` | task-key 로 최종 보고서 위치 조회 / 보고서 읽기 |
141
+
142
+ 내부적으로 lead 가 phase 진행 중 호출하는 helper 스킬 4종(`okstra-context-loader`, `okstra-team-contract`, `okstra-convergence`, `okstra-report-writer`) 은 사용자가 직접 호출할 일이 거의 없습니다.
143
+
144
+ ### 3.4 CLI 모드 (선택)
145
+
146
+ Claude Code 가 아닌 별도 터미널에서 task 를 띄울 때:
147
+
148
+ ```bash
149
+ ~/.okstra/bin/okstra.sh \
150
+ --project-id <id> \
151
+ --task-group <group> \
152
+ --task-id <id> \
153
+ --task-type <error-analysis|implementation-planning|...> \
154
+ --task-brief ./brief.md
155
+ ```
156
+
157
+ 새 `claude` 프로세스를 띄워 lead 로 동작시킵니다. 인자 자세히는 [Required arguments](#required-arguments) 섹션 참조.
158
+
159
+ ### 3.5 운영 명령
160
+
161
+ | 명령 | 용도 |
162
+ |---|---|
163
+ | `npx -y okstra@latest paths` | 런타임 경로 조회 (JSON 또는 `--field <name>` / `--shell`) |
164
+ | `npx -y okstra@latest doctor` | 진단 (런타임 + 스킬 + python import) |
165
+ | `npx -y okstra@latest ensure-installed` | 매 호출 시 idempotent 검증 + 필요 시 자동 install (스킬 내부에서 호출) |
166
+ | `npx -y okstra@latest check-project` | 현재 디렉터리에 okstra-setup 이 완료됐는지 검사 |
167
+ | `npx -y okstra@latest uninstall` | 런타임 + 스킬 제거. user data(recent.jsonl, projects/ 등) 보존 |
168
+ | `npx -y okstra@latest uninstall --purge -y` | 전부 제거 (user data 포함) |
169
+
170
+ 릴리스 절차는 [`RELEASING.md`](RELEASING.md) 참조.
171
+
172
+ ---
2
173
 
3
174
  ## At a glance
4
175
 
@@ -155,7 +326,7 @@ okstra 의 prepare 책임은 단일 python 진입점 [`okstra_ctl.run.prepare_ta
155
326
  ### Skills (`skills/`, `agents/`)
156
327
 
157
328
  - [`agents/SKILL.md`](agents/SKILL.md) — main okstra skill (cross-verify 트리거).
158
- - [`skills/setup-okstra/SKILL.md`](skills/setup-okstra/SKILL.md) — **첫 실행 부트스트랩**. `okstra install` + `project.json` 생성.
329
+ - [`skills/okstra-setup/SKILL.md`](skills/okstra-setup/SKILL.md) — **첫 실행 부트스트랩**. `okstra install` + `project.json` 생성.
159
330
  - [`skills/okstra-run/SKILL.md`](skills/okstra-run/SKILL.md) — **현재 claude 세션 안에서 okstra task 를 시작**하는 in-session 진입점. `prepare_task_bundle` 직접 호출.
160
331
  - `skills/okstra-{status,history,convergence,schedule,context-loader,team-contract,report-finder,report-writer,time-summary}/SKILL.md` — phase 진행·status·history 보조 skill.
161
332
  - 플러그인 매니페스트: [`.claude-plugin/plugin.json`](.claude-plugin/plugin.json) — `npx skills@latest add Devonshin/okstra` 보조 채널이 참조. 0.3.0 부터는 `npx okstra install` 한 명령이 동일 결과를 보장하므로 일반 셋업에는 이 채널이 필요 없다.
package/bin/okstra CHANGED
@@ -7,22 +7,24 @@ const COMMANDS = new Map([
7
7
  ["ensure-installed", () => import("../src/install.mjs").then((m) => m.runEnsureInstalled)],
8
8
  ["uninstall", () => import("../src/uninstall.mjs").then((m) => m.runUninstall)],
9
9
  ["doctor", () => import("../src/doctor.mjs").then((m) => m.run)],
10
+ ["check-project", () => import("../src/check-project.mjs").then((m) => m.run)],
10
11
  ]);
11
12
 
12
- const USAGE = `okstra-cli — runtime installer and path resolver for okstra skills
13
+ const USAGE = `okstra — runtime installer + path/setup resolver for okstra skills
13
14
 
14
15
  Usage:
15
16
  okstra <command> [options]
16
17
 
17
18
  Commands:
18
- install Install okstra runtime into ~/.okstra
19
+ install Install okstra runtime + skills into ~/.okstra + ~/.claude/skills
19
20
  ensure-installed Verify install is fresh; reinstall if stale (idempotent)
20
- uninstall Remove installed runtime (user data preserved by default)
21
- paths Print runtime paths (agents/pythonpath/bin/home/version)
21
+ uninstall Remove installed runtime + skills (user data preserved by default)
22
+ paths Print runtime paths (workspace/agents/pythonpath/bin/home/version)
22
23
  doctor Diagnostic check of the installed runtime
24
+ check-project Verify current project has .project-docs/okstra/project.json
23
25
 
24
26
  Global options:
25
- --version Print okstra-cli version and exit
27
+ --version Print okstra version and exit
26
28
  --help Print this help
27
29
 
28
30
  Run 'okstra <command> --help' for command-specific options.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "okstra",
3
- "version": "0.3.0",
3
+ "version": "0.4.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.3.0",
3
- "builtAt": "2026-05-11T15:55:55.785Z",
2
+ "package": "0.4.0",
3
+ "builtAt": "2026-05-12T03:09:54.795Z",
4
4
  "repoRoot": "/home/runner/work/okstra/okstra"
5
5
  }
@@ -11,6 +11,25 @@ description: Use when the user asks to list past okstra runs, check execution hi
11
11
  - When re-running or resuming a previous execution
12
12
  - When checking the execution status of each task
13
13
 
14
+ ## Step 0: Verify okstra runtime + project setup
15
+
16
+ ```bash
17
+ npx -y okstra@latest ensure-installed >/dev/null 2>&1 || {
18
+ echo "FAIL: okstra not installed; tell the user to run: npx okstra@latest install" >&2
19
+ exit 1
20
+ }
21
+ eval "$(npx -y okstra@latest paths --shell)"
22
+ export PYTHONPATH="$OKSTRA_PYTHONPATH"
23
+ OKSTRA_PROJECT_INFO="$(npx -y okstra@latest check-project --json)" || {
24
+ echo "FAIL: this project has no okstra setup. Tell the user to run /okstra-setup first." >&2
25
+ echo "$OKSTRA_PROJECT_INFO" >&2
26
+ exit 1
27
+ }
28
+ ```
29
+
30
+ `$OKSTRA_PROJECT_INFO` is JSON `{ok, projectRoot, projectJsonPath, projectId}` —
31
+ use `projectRoot` to locate `.project-docs/okstra/discovery/task-catalog.json`.
32
+
14
33
  ## Step 1: Read the Task Catalog
15
34
 
16
35
  1. Read `.project-docs/okstra/discovery/task-catalog.json`.
@@ -11,6 +11,25 @@ description: Use when the user provides a task key and needs to find the final r
11
11
  - 이전 okstra 보고서를 읽고 후속 작업을 진행할 때
12
12
  - 보고서 내용을 기반으로 구현, 수정, 추가 검증을 시작할 때
13
13
 
14
+ ## Step 0: Verify okstra runtime + project setup
15
+
16
+ ```bash
17
+ npx -y okstra@latest ensure-installed >/dev/null 2>&1 || {
18
+ echo "FAIL: okstra not installed; tell the user to run: npx okstra@latest install" >&2
19
+ exit 1
20
+ }
21
+ eval "$(npx -y okstra@latest paths --shell)"
22
+ export PYTHONPATH="$OKSTRA_PYTHONPATH"
23
+ OKSTRA_PROJECT_INFO="$(npx -y okstra@latest check-project --json)" || {
24
+ echo "FAIL: this project has no okstra setup. Tell the user to run /okstra-setup first." >&2
25
+ echo "$OKSTRA_PROJECT_INFO" >&2
26
+ exit 1
27
+ }
28
+ ```
29
+
30
+ `$OKSTRA_PROJECT_INFO` (JSON `{ok, projectRoot, projectJsonPath, projectId}`) —
31
+ `projectRoot` 로 catalog/manifest 위치를 잡는다.
32
+
14
33
  ## Step 1: Task Key로 Report 경로 찾기
15
34
 
16
35
  task-key 형식: `<project-id>:<task-group>:<task-id>`
@@ -30,21 +30,28 @@ Every step reads disk afresh. The `OKSTRA_*` env vars below identify the
30
30
  - `<PROJECT_ROOT>/.project-docs/okstra/discovery/{task-catalog,latest-task}.json`
31
31
  - `<task-root>/task-manifest.json`
32
32
 
33
- ## Step 0: Resolve okstra runtime paths
33
+ ## Step 0: Verify okstra runtime + project setup
34
34
 
35
35
  Do NOT hard-code or guess any okstra path. Every run loads them fresh from
36
36
  the single authority — `okstra`:
37
37
 
38
38
  ```bash
39
- # 1) Ensure okstra runtime is fresh (idempotent, cached when up-to-date)
39
+ # 1) Ensure runtime is fresh (idempotent, cached when up-to-date)
40
40
  npx -y okstra@latest ensure-installed >/dev/null 2>&1 || {
41
- echo "FAIL: okstra not installed; run: npx okstra@latest install" >&2
41
+ echo "FAIL: okstra not installed; tell the user to run: npx okstra@latest install" >&2
42
42
  exit 1
43
43
  }
44
44
 
45
45
  # 2) Load all runtime paths into the shell as OKSTRA_* exports
46
46
  eval "$(npx -y okstra@latest paths --shell)"
47
47
  export PYTHONPATH="$OKSTRA_PYTHONPATH"
48
+
49
+ # 3) Verify the current project has okstra metadata (project.json + projectId)
50
+ OKSTRA_PROJECT_INFO="$(npx -y okstra@latest check-project --json)" || {
51
+ echo "FAIL: this project has no okstra setup. Tell the user to run /okstra-setup first." >&2
52
+ echo "$OKSTRA_PROJECT_INFO" >&2
53
+ exit 1
54
+ }
48
55
  ```
49
56
 
50
57
  After Step 0 the following are guaranteed:
@@ -56,6 +63,7 @@ After Step 0 the following are guaranteed:
56
63
  | `$OKSTRA_PYTHONPATH` | already exported as `PYTHONPATH` |
57
64
  | `$OKSTRA_BIN` | bash entrypoints (`okstra.sh`, codex/gemini exec wrappers) |
58
65
  | `$OKSTRA_HOME` | `~/.okstra` (recent.jsonl, locks, projects/, archive/) |
66
+ | `$OKSTRA_PROJECT_INFO` | JSON: `{ok, projectRoot, projectJsonPath, projectId}` — parse and reuse instead of re-resolving in Step 1 |
59
67
 
60
68
  ## Step 1: Resolve PROJECT_ROOT and projectId
61
69
 
@@ -35,6 +35,28 @@ The default mode is lightweight (single Claude lead synthesis). The `--cross-ver
35
35
 
36
36
  If `--title` is omitted, derive a default title from `task-group` (e.g. `uploadFont` → `uploadFont — Work Schedule`).
37
37
 
38
+ ## Step 0: Verify okstra runtime + project setup
39
+
40
+ Run before anything else in this skill:
41
+
42
+ ```bash
43
+ npx -y okstra@latest ensure-installed >/dev/null 2>&1 || {
44
+ echo "FAIL: okstra not installed; tell the user to run: npx okstra@latest install" >&2
45
+ exit 1
46
+ }
47
+ eval "$(npx -y okstra@latest paths --shell)"
48
+ export PYTHONPATH="$OKSTRA_PYTHONPATH"
49
+ OKSTRA_PROJECT_INFO="$(npx -y okstra@latest check-project --json)" || {
50
+ echo "FAIL: this project has no okstra setup. Tell the user to run /okstra-setup first." >&2
51
+ echo "$OKSTRA_PROJECT_INFO" >&2
52
+ exit 1
53
+ }
54
+ ```
55
+
56
+ `$OKSTRA_PROJECT_INFO` is JSON `{ok, projectRoot, projectJsonPath, projectId}` —
57
+ use `projectRoot` to locate `.project-docs/okstra/discovery/task-catalog.json`
58
+ and the task-group directory.
59
+
38
60
  ## Process Procedure
39
61
 
40
62
  ### Step 0: Verify model (HARD GATE)
@@ -1,9 +1,9 @@
1
1
  ---
2
- name: setup-okstra
3
- description: One-time bootstrap for okstra in a new project or on a new machine — installs the okstra runtime via npx and creates the project's .project-docs/okstra/project.json. Trigger words include "setup okstra", "initialize okstra", "okstra init", "first time okstra setup", "configure okstra here".
2
+ name: okstra-setup
3
+ description: One-time bootstrap for okstra in a new project or on a new machine — installs the okstra runtime via npx and creates the project's .project-docs/okstra/project.json. Trigger words include "okstra setup", "setup okstra", "initialize okstra", "okstra init", "first time okstra setup", "configure okstra here".
4
4
  ---
5
5
 
6
- # setup-okstra
6
+ # okstra-setup
7
7
 
8
8
  One-time bootstrap. Run when okstra is being used for the first time on this
9
9
  machine, or when adopting okstra in a new project.
@@ -11,6 +11,28 @@ description: Use when the user asks for overall okstra task status, current life
11
11
  - When user want to check the current phase, next phase, blockers, and resume points for a specific `task-key`
12
12
  - When user want to check which tasks are pending approval or which tasks can be resumed
13
13
 
14
+ ## Step 0: Verify okstra runtime + project setup
15
+
16
+ Before any other step, ensure both the okstra runtime and the current
17
+ project's okstra metadata are in place:
18
+
19
+ ```bash
20
+ npx -y okstra@latest ensure-installed >/dev/null 2>&1 || {
21
+ echo "FAIL: okstra not installed; tell the user to run: npx okstra@latest install" >&2
22
+ exit 1
23
+ }
24
+ eval "$(npx -y okstra@latest paths --shell)"
25
+ export PYTHONPATH="$OKSTRA_PYTHONPATH"
26
+ OKSTRA_PROJECT_INFO="$(npx -y okstra@latest check-project --json)" || {
27
+ echo "FAIL: this project has no okstra setup. Tell the user to run /okstra-setup first." >&2
28
+ echo "$OKSTRA_PROJECT_INFO" >&2
29
+ exit 1
30
+ }
31
+ ```
32
+
33
+ `$OKSTRA_PROJECT_INFO` is JSON `{ok, projectRoot, projectJsonPath, projectId}` —
34
+ parse and reuse it instead of re-resolving in the steps below.
35
+
14
36
  ## Step 1: Overall Project Status
15
37
 
16
38
  To view the overall project status, first read `.project-docs/okstra/discovery/task-catalog.json`.
@@ -26,6 +26,25 @@ Two sources, both already collected by `okstra`:
26
26
 
27
27
  If a run never reached Phase 7, its `team-state` will not have `durationMs` filled in. Mark such runs as `unavailable` rather than guessing.
28
28
 
29
+ ## Step 0: Verify okstra runtime + project setup
30
+
31
+ ```bash
32
+ npx -y okstra@latest ensure-installed >/dev/null 2>&1 || {
33
+ echo "FAIL: okstra not installed; tell the user to run: npx okstra@latest install" >&2
34
+ exit 1
35
+ }
36
+ eval "$(npx -y okstra@latest paths --shell)"
37
+ export PYTHONPATH="$OKSTRA_PYTHONPATH"
38
+ OKSTRA_PROJECT_INFO="$(npx -y okstra@latest check-project --json)" || {
39
+ echo "FAIL: this project has no okstra setup. Tell the user to run /okstra-setup first." >&2
40
+ echo "$OKSTRA_PROJECT_INFO" >&2
41
+ exit 1
42
+ }
43
+ ```
44
+
45
+ `$OKSTRA_PROJECT_INFO` (JSON `{ok, projectRoot, projectJsonPath, projectId}`)
46
+ gives `projectRoot` for locating `.project-docs/okstra/discovery/task-catalog.json`.
47
+
29
48
  ## Step 1: Resolve task-id → timeline path
30
49
 
31
50
  1. If the user gave a full `task-key` (`<project-id>:<task-group>:<task-id>`), use it directly.
@@ -0,0 +1,188 @@
1
+ import { promises as fs } from "node:fs";
2
+ import { spawn } from "node:child_process";
3
+ import { join } from "node:path";
4
+ import { resolvePaths } from "./paths.mjs";
5
+
6
+ const USAGE = `okstra check-project — verify that the current project has okstra setup
7
+
8
+ Usage:
9
+ okstra check-project Resolve PROJECT_ROOT from cwd, look for
10
+ .project-docs/okstra/project.json,
11
+ print JSON status to stdout.
12
+ okstra check-project --cwd <dir> Use <dir> as the search starting point
13
+ instead of process cwd.
14
+ okstra check-project --json Same as default (kept for symmetry with
15
+ 'paths --json').
16
+ okstra check-project --quiet Suppress stdout on success; exit code
17
+ alone reports state.
18
+
19
+ Exit codes:
20
+ 0 project.json found, projectId present
21
+ 1 project.json missing or unreadable (project setup not done)
22
+ 2 PROJECT_ROOT could not be resolved from cwd (no project marker
23
+ ancestor; user should pass --cwd or run from within a project)
24
+
25
+ User-facing skills should call this after 'ensure-installed' and refuse
26
+ to proceed if the exit code is non-zero, directing the user to
27
+ '/okstra-setup' first.
28
+ `;
29
+
30
+ function runProcess(cmd, args, env) {
31
+ return new Promise((resolve) => {
32
+ const child = spawn(cmd, args, {
33
+ stdio: ["ignore", "pipe", "pipe"],
34
+ env: { ...process.env, ...env },
35
+ });
36
+ let stdout = "";
37
+ let stderr = "";
38
+ child.stdout.on("data", (b) => (stdout += b.toString()));
39
+ child.stderr.on("data", (b) => (stderr += b.toString()));
40
+ child.on("error", (err) => resolve({ code: -1, stdout, stderr: err.message }));
41
+ child.on("close", (code) => resolve({ code, stdout, stderr }));
42
+ });
43
+ }
44
+
45
+ async function fileExists(p) {
46
+ try {
47
+ await fs.access(p);
48
+ return true;
49
+ } catch {
50
+ return false;
51
+ }
52
+ }
53
+
54
+ function parseArgs(args) {
55
+ const opts = { cwd: process.cwd(), quiet: false, json: true };
56
+ for (let i = 0; i < args.length; i++) {
57
+ const a = args[i];
58
+ if (a === "--quiet" || a === "-q") opts.quiet = true;
59
+ else if (a === "--json") opts.json = true;
60
+ else if (a === "--cwd") {
61
+ const next = args[i + 1];
62
+ if (!next || next.startsWith("--")) throw new Error("--cwd requires a path");
63
+ opts.cwd = next;
64
+ i++;
65
+ } else {
66
+ throw new Error(`unknown argument '${a}'`);
67
+ }
68
+ }
69
+ return opts;
70
+ }
71
+
72
+ function emit(opts, payload) {
73
+ if (opts.quiet) return;
74
+ process.stdout.write(JSON.stringify(payload, null, 2) + "\n");
75
+ }
76
+
77
+ export async function run(args) {
78
+ if (args.includes("--help") || args.includes("-h")) {
79
+ process.stdout.write(USAGE);
80
+ return 0;
81
+ }
82
+
83
+ const opts = parseArgs(args);
84
+ const paths = await resolvePaths();
85
+
86
+ const probe = await runProcess(
87
+ "python3",
88
+ [
89
+ "-c",
90
+ [
91
+ "import json, sys",
92
+ "from okstra_project import resolve_project_root, project_json_path, ResolverError",
93
+ "try:",
94
+ " pr = resolve_project_root(explicit_root='', cwd=sys.argv[1])",
95
+ " print('PROJECT_ROOT', pr)",
96
+ " print('PROJECT_JSON', project_json_path(pr))",
97
+ "except ResolverError as e:",
98
+ " print('RESOLVER_ERROR', e)",
99
+ ].join("\n"),
100
+ opts.cwd,
101
+ ],
102
+ { PYTHONPATH: paths.pythonpath },
103
+ );
104
+
105
+ if (probe.code !== 0) {
106
+ emit(opts, {
107
+ ok: false,
108
+ stage: "python",
109
+ reason: `python invocation failed: ${probe.stderr.trim() || probe.stdout.trim()}`,
110
+ });
111
+ return 1;
112
+ }
113
+
114
+ const lines = probe.stdout.trim().split("\n");
115
+ const tagOf = (key) =>
116
+ lines
117
+ .find((l) => l.startsWith(key + " "))
118
+ ?.slice(key.length + 1)
119
+ .trim() ?? null;
120
+
121
+ const resolverError = tagOf("RESOLVER_ERROR");
122
+ if (resolverError) {
123
+ emit(opts, {
124
+ ok: false,
125
+ stage: "resolve",
126
+ reason: resolverError,
127
+ cwd: opts.cwd,
128
+ });
129
+ return 2;
130
+ }
131
+
132
+ const projectRoot = tagOf("PROJECT_ROOT");
133
+ const projectJsonPath = tagOf("PROJECT_JSON");
134
+ if (!projectRoot || !projectJsonPath) {
135
+ emit(opts, {
136
+ ok: false,
137
+ stage: "parse",
138
+ reason: "could not parse python output",
139
+ raw: probe.stdout,
140
+ });
141
+ return 1;
142
+ }
143
+
144
+ if (!(await fileExists(projectJsonPath))) {
145
+ emit(opts, {
146
+ ok: false,
147
+ stage: "project_json_missing",
148
+ reason: `${projectJsonPath} not found — run /okstra-setup in this project first`,
149
+ projectRoot,
150
+ projectJsonPath,
151
+ });
152
+ return 1;
153
+ }
154
+
155
+ let projectId = null;
156
+ try {
157
+ const data = JSON.parse(await fs.readFile(projectJsonPath, "utf8"));
158
+ projectId = typeof data?.projectId === "string" ? data.projectId : null;
159
+ } catch (err) {
160
+ emit(opts, {
161
+ ok: false,
162
+ stage: "project_json_invalid",
163
+ reason: `failed to parse ${projectJsonPath}: ${err.message}`,
164
+ projectRoot,
165
+ projectJsonPath,
166
+ });
167
+ return 1;
168
+ }
169
+
170
+ if (!projectId) {
171
+ emit(opts, {
172
+ ok: false,
173
+ stage: "project_json_invalid",
174
+ reason: `${projectJsonPath} missing projectId field`,
175
+ projectRoot,
176
+ projectJsonPath,
177
+ });
178
+ return 1;
179
+ }
180
+
181
+ emit(opts, {
182
+ ok: true,
183
+ projectRoot,
184
+ projectJsonPath,
185
+ projectId,
186
+ });
187
+ return 0;
188
+ }
package/src/doctor.mjs CHANGED
@@ -5,7 +5,7 @@ import { join } from "node:path";
5
5
  import { resolvePaths } from "./paths.mjs";
6
6
 
7
7
  const CLAUDE_SKILLS_DIR = join(homedir(), ".claude", "skills");
8
- const REQUIRED_SKILLS = ["setup-okstra", "okstra-run"];
8
+ const REQUIRED_SKILLS = ["okstra-setup", "okstra-run"];
9
9
 
10
10
  const USAGE = `okstra doctor — diagnose the installed runtime
11
11
 
package/src/install.mjs CHANGED
@@ -430,8 +430,8 @@ export async function runEnsureInstalled(args) {
430
430
  }
431
431
  if (!(await dirExists(paths.pythonpath))) reasons.push(`missing ${paths.pythonpath}`);
432
432
  if (!(await dirExists(paths.agents))) reasons.push(`missing agents dir ${paths.agents}`);
433
- if (!(await fileExists(join(CLAUDE_SKILLS_DIR, "setup-okstra", "SKILL.md")))) {
434
- reasons.push(`missing ${CLAUDE_SKILLS_DIR}/setup-okstra/SKILL.md`);
433
+ if (!(await fileExists(join(CLAUDE_SKILLS_DIR, "okstra-setup", "SKILL.md")))) {
434
+ reasons.push(`missing ${CLAUDE_SKILLS_DIR}/okstra-setup/SKILL.md`);
435
435
  }
436
436
 
437
437
  if (reasons.length === 0) {
package/src/uninstall.mjs CHANGED
@@ -13,7 +13,7 @@ const BIN_ENTRYPOINTS = [
13
13
  ];
14
14
 
15
15
  const FALLBACK_SKILL_NAMES = [
16
- "setup-okstra",
16
+ "okstra-setup",
17
17
  "okstra-run",
18
18
  "okstra-status",
19
19
  "okstra-history",
@@ -103,7 +103,7 @@ export async function runUninstall(args) {
103
103
  if (opts.purge) {
104
104
  if (!opts.yes && !opts.dryRun) {
105
105
  const ok = await promptConfirm(
106
- `purge entire ${paths.home} AND ~/.claude/skills/{okstra-*,setup-okstra}? user data will be lost.`,
106
+ `purge entire ${paths.home} AND ~/.claude/skills/{okstra-*,okstra-setup}? user data will be lost.`,
107
107
  );
108
108
  if (!ok) {
109
109
  process.stdout.write("aborted.\n");