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 +173 -2
- package/bin/okstra +7 -5
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/skills/okstra-history/SKILL.md +19 -0
- package/runtime/skills/okstra-report-finder/SKILL.md +19 -0
- package/runtime/skills/okstra-run/SKILL.md +11 -3
- package/runtime/skills/okstra-schedule/SKILL.md +22 -0
- package/runtime/skills/{setup-okstra → okstra-setup}/SKILL.md +3 -3
- package/runtime/skills/okstra-status/SKILL.md +22 -0
- package/runtime/skills/okstra-time-summary/SKILL.md +19 -0
- package/src/check-project.mjs +188 -0
- package/src/doctor.mjs +1 -1
- package/src/install.mjs +2 -2
- package/src/uninstall.mjs +2 -2
package/README.md
CHANGED
|
@@ -1,4 +1,175 @@
|
|
|
1
|
-
#
|
|
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
|
|
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
|
|
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
|
|
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
package/runtime/BUILD.json
CHANGED
|
@@ -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:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
434
|
-
reasons.push(`missing ${CLAUDE_SKILLS_DIR}/setup
|
|
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
|
|
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
|
|
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");
|