okstra 0.102.0 → 0.102.2
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 +5 -6
- package/README.md +5 -6
- package/docs/for-ai/README.md +2 -3
- package/docs/for-ai/skills/{okstra-container.md → okstra-container-build.md} +3 -4
- package/docs/for-ai/skills/okstra-inspect.md +1 -2
- package/docs/kr/architecture/storage-model.md +1 -2
- package/docs/kr/architecture.md +7 -7
- package/docs/kr/cli.md +5 -5
- package/docs/kr/container.md +1 -1
- package/docs/project-structure-overview.md +5 -5
- package/docs/superpowers/plans/2026-06-15-internal-skill-resource-migration.md +3 -3
- package/docs/superpowers/plans/2026-06-24-manual-user-test-section.md +5 -5
- package/docs/superpowers/plans/2026-06-24-okstra-container-build-rename.md +525 -0
- package/docs/superpowers/specs/2026-06-13-neutral-tmux-lead-adapter-design.md +3 -3
- package/docs/superpowers/specs/2026-06-24-manual-user-test-section-design.md +6 -6
- package/docs/superpowers/specs/2026-06-24-okstra-container-build-rename-design.md +110 -0
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/prompts/lead/team-contract.md +2 -2
- package/runtime/prompts/profiles/_implementation-deliverable.md +1 -1
- package/runtime/prompts/profiles/final-verification.md +1 -1
- package/runtime/python/okstra_token_usage/paths.py +1 -2
- package/runtime/skills/{okstra-container → okstra-container-build}/SKILL.md +2 -2
- package/src/cli-registry.mjs +1 -1
- package/src/commands/lifecycle/install.mjs +17 -28
- package/src/commands/lifecycle/uninstall.mjs +9 -10
- package/src/lib/skill-catalog.mjs +10 -9
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# okstra-container-build 스킬 rename 설계
|
|
2
|
+
|
|
3
|
+
- 작성일: 2026-06-24
|
|
4
|
+
- 상태: 설계 합의 완료, 구현 계획 대기
|
|
5
|
+
- 범위: 공개 agent skill 이름을 `okstra-container` 에서 `okstra-container-build` 로 변경
|
|
6
|
+
|
|
7
|
+
## 1. 배경
|
|
8
|
+
|
|
9
|
+
현재 컨테이너 기능은 세 표면이 분리되어 있다.
|
|
10
|
+
|
|
11
|
+
- 공개 스킬 이름과 설치 대상은 `src/lib/skill-catalog.mjs` 의 `USER_SKILL_NAMES` 가 정본이다([skill-catalog.mjs:7](../../../src/lib/skill-catalog.mjs)).
|
|
12
|
+
- 플러그인 보조 채널은 `.claude-plugin/plugin.json` 의 `skills` 배열을 사용한다([plugin.json:4](../../../.claude-plugin/plugin.json)).
|
|
13
|
+
- 실제 CLI 명령은 `okstra container ...` 이며, Node shim 이 Python `okstra_ctl.container` 로 위임한다([container.mjs:3](../../../src/commands/inspect/container.mjs), [container.py:927](../../../scripts/okstra_ctl/container.py)).
|
|
14
|
+
|
|
15
|
+
사용자 요청은 스킬 이름을 `okstra-container-build` 로 바꾸는 것이다. 따라서 slash-command / skill discovery 표면은 새 이름으로 바꾸되, 안정된 CLI 명령과 런타임 리소스 이름은 유지한다.
|
|
16
|
+
|
|
17
|
+
## 2. 목표 / 비목표
|
|
18
|
+
|
|
19
|
+
### 목표
|
|
20
|
+
|
|
21
|
+
- 설치되는 공개 스킬과 slash command 를 `/okstra-container-build` 로 바꾼다.
|
|
22
|
+
- 신규 설치와 업그레이드 모두에서 옛 `/okstra-container` 가 남지 않게 한다.
|
|
23
|
+
- README, architecture, AI manual, phase prompt 의 사용자-facing 안내를 새 스킬명으로 맞춘다.
|
|
24
|
+
- `okstra container` CLI 와 Python entrypoint 는 유지해 기존 자동화와 테스트 표면을 흔들지 않는다.
|
|
25
|
+
|
|
26
|
+
### 비목표
|
|
27
|
+
|
|
28
|
+
- `okstra container` CLI command rename.
|
|
29
|
+
- `okstra-container-<slug>` tmux session prefix rename.
|
|
30
|
+
- docker compose 생성 기능 추가. `build` 는 스킬 이름의 suffix 일 뿐, 기존처럼 프로젝트의 `docker-compose.yml` 을 재사용한다.
|
|
31
|
+
- 과거 changelog / 과거 구현 계획 문서의 역사적 문구 전면 수정.
|
|
32
|
+
|
|
33
|
+
## 3. 검토한 접근
|
|
34
|
+
|
|
35
|
+
| 접근 | 판단 | 이유 |
|
|
36
|
+
|---|---|---|
|
|
37
|
+
| A. 공개 스킬명만 rename, CLI/runtime 이름 유지 | 선택 | 사용자 요청을 만족하면서 `okstra container` 자동화, Python argparse, tmux session 추적을 보존한다. |
|
|
38
|
+
| B. CLI 도 `okstra container-build` 로 rename | 제외 | `src/commands/inspect/container.mjs` 와 `scripts/okstra_ctl/container.py` 의 명령 계약까지 깨져 범위가 커진다. |
|
|
39
|
+
| C. 새 스킬을 추가하고 옛 `okstra-container` alias 를 유지 | 제외 | 설치 후 slash command 가 두 개 보인다. 사용자 요청이 rename 이므로 옛 이름은 upgrade 시 prune 해야 한다. |
|
|
40
|
+
|
|
41
|
+
## 4. 설계 결정
|
|
42
|
+
|
|
43
|
+
| 결정 | 선택 | 근거 |
|
|
44
|
+
|---|---|---|
|
|
45
|
+
| 스킬 정본 | `USER_SKILL_NAMES` 의 `okstra-container` 를 `okstra-container-build` 로 교체 | installer 와 plugin sync 테스트가 이 목록을 기준으로 맞물린다([install.mjs:631](../../../src/commands/lifecycle/install.mjs), [skill-catalog.test.mjs:14](../../../tests-js/skill-catalog.test.mjs)). |
|
|
46
|
+
| 원천 디렉터리 | `skills/okstra-container/` 를 `skills/okstra-container-build/` 로 이동 | install copy/link mode 모두 `<name>/SKILL.md` 디렉터리명을 사용한다([install.mjs:631](../../../src/commands/lifecycle/install.mjs), [install.mjs:653](../../../src/commands/lifecycle/install.mjs)). |
|
|
47
|
+
| frontmatter | `name: okstra-container-build` | agent skill discovery 는 frontmatter 이름을 사용자 표면으로 노출한다. |
|
|
48
|
+
| CLI | `okstra container <up|status|logs|stop-watcher|down>` 유지 | CLI shim 과 Python parser 는 컨테이너 런타임 API 이름이며 스킬명과 독립적이다([container.mjs:3](../../../src/commands/inspect/container.mjs), [container.py:927](../../../scripts/okstra_ctl/container.py)). |
|
|
49
|
+
| runtime session prefix | `okstra-container-<slug>` 유지 | tmux/docker trace 리소스 이름이며 테스트가 고정한다([ids.py:93](../../../scripts/okstra_ctl/ids.py), [test_container_session_name.py:14](../../../tests/container/test_container_session_name.py)). |
|
|
50
|
+
| upgrade cleanup | 옛 `okstra-container` 를 exact-name prune 대상에 추가 | 설치는 새 스킬을 복사하지만 기존 skill home 의 옛 디렉터리를 자동 삭제하지 않는다([install.mjs:669](../../../src/commands/lifecycle/install.mjs)). |
|
|
51
|
+
|
|
52
|
+
## 5. 영향 파일
|
|
53
|
+
|
|
54
|
+
### 소스 / 설치 계약
|
|
55
|
+
|
|
56
|
+
| 파일 | 변경 |
|
|
57
|
+
|---|---|
|
|
58
|
+
| `skills/okstra-container/SKILL.md` | 디렉터리명을 `skills/okstra-container-build/` 로 변경하고 frontmatter `name` 갱신. 본문에서 slash-command 명칭만 새 이름으로 맞추고 `okstra container ...` CLI 예시는 유지. |
|
|
59
|
+
| `src/lib/skill-catalog.mjs` | `USER_SKILL_NAMES` 에서 `okstra-container-build` 로 교체. `OBSOLETE_INTERNAL_SKILL_NAMES` 는 `OBSOLETE_SKILL_NAMES` 로 재명명하고, 옛 공개 스킬 `okstra-container` 를 같은 exact-name prune 목록에 추가한다. |
|
|
60
|
+
| `.claude-plugin/plugin.json` | `./skills/okstra-container-build` 로 교체. |
|
|
61
|
+
| `src/commands/lifecycle/uninstall.mjs` | fallback skill names 에 `okstra-container-build` 를 포함하고, 옛 `okstra-container` 도 fallback cleanup 대상에 유지한다. |
|
|
62
|
+
| `tools/build.mjs` | 코드 변경 없음. `skills/` sync 로 새 디렉터리가 `runtime/skills/okstra-container-build` 로 반영되는지 build 로 검증. |
|
|
63
|
+
|
|
64
|
+
### 문서 / 프롬프트
|
|
65
|
+
|
|
66
|
+
| 파일군 | 변경 |
|
|
67
|
+
|---|---|
|
|
68
|
+
| `README.md`, `README.kr.md` | slash command 표를 `/okstra-container-build` 로 갱신. |
|
|
69
|
+
| `docs/kr/architecture.md`, `docs/project-structure-overview.md` | 공개 스킬 7종 목록과 skill role 표를 새 이름으로 갱신. CLI `container` 설명은 유지하되 "for the okstra-container-build skill" 로 조정. |
|
|
70
|
+
| `docs/for-ai/README.md`, `docs/for-ai/skills/okstra-container.md` | AI manual 파일명을 `okstra-container-build.md` 로 바꾸고 라우팅 표를 새 이름으로 갱신. |
|
|
71
|
+
| `docs/for-ai/skills/okstra-inspect.md` | "container 는 별도 스킬" 링크를 새 manual 로 갱신. |
|
|
72
|
+
| `prompts/profiles/_implementation-deliverable.md`, `prompts/profiles/final-verification.md` | 사람이 실행할 스킬명은 `/okstra-container-build`, 실제 CLI 명령 예시는 `okstra container up ...` 로 표현해 혼동을 줄인다. |
|
|
73
|
+
| `docs/kr/container.md` | CLI 레퍼런스는 제목과 명령을 유지하고, 단일 진입점 bullet 의 스킬명만 `/okstra-container-build` 로 갱신. |
|
|
74
|
+
|
|
75
|
+
### 그대로 둘 파일군
|
|
76
|
+
|
|
77
|
+
- `CHANGES.md`, `CHANGELOG.md`: 과거 릴리스 기록이므로 기존 `okstra-container` 항목을 역사로 보존한다. 이번 변경은 새 항목만 추가한다.
|
|
78
|
+
- `docs/superpowers/plans/2026-06-21-okstra-container-local-user-test.md`, `docs/superpowers/specs/2026-06-21-okstra-container-local-user-test-design.md`: 과거 기능 도입 당시 설계/계획 문서라 전체 rewrite 하지 않는다. 현재 문서로 rename 결정을 보완한다.
|
|
79
|
+
- `tests/container/*` 의 `okstra-container-<slug>` 기대값: 런타임 session prefix 검증이므로 유지한다.
|
|
80
|
+
|
|
81
|
+
## 6. 업그레이드 동작
|
|
82
|
+
|
|
83
|
+
문제: 현재 install 은 `USER_SKILL_NAMES` 를 설치한 뒤 manifest 를 새 목록으로 쓴다. 하지만 이미 `~/.claude/skills/okstra-container` 또는 `~/.agent/skills/okstra-container` 가 있으면 새 install 만으로 옛 디렉터리가 삭제되지 않는다.
|
|
84
|
+
|
|
85
|
+
설계:
|
|
86
|
+
|
|
87
|
+
1. old public skill 이름 `okstra-container` 를 exact-name prune 대상에 넣는다.
|
|
88
|
+
2. `installSkillsCopy()` / `installSkillsLink()` 의 prune 단계에서 old dir 을 제거한다.
|
|
89
|
+
3. wildcard `okstra-*` 삭제는 금지한다. 기존 obsolete cleanup 도 exact-name 방식이다([install.mjs:666](../../../src/commands/lifecycle/install.mjs)).
|
|
90
|
+
4. uninstall fallback 은 manifest 가 없는 오래된 설치도 정리할 수 있게 old/new 이름을 모두 포함한다.
|
|
91
|
+
|
|
92
|
+
## 7. 테스트 계획
|
|
93
|
+
|
|
94
|
+
- `node --test tests-js/skill-catalog.test.mjs`: plugin manifest 와 `USER_SKILL_NAMES` 순서 동기화.
|
|
95
|
+
- `node --test tests-js/install-runtime.test.mjs`: 새 스킬이 Claude/Codex skill home 에 설치되고 manifest 에 기록되는지 확인. 추가 회귀 테스트로 old `okstra-container` 디렉터리를 seed 한 뒤 install 이 prune 하는지 검증.
|
|
96
|
+
- `node --test tests-js/doctor-runtime.test.mjs`: 비필수 container skill 이 `REQUIRED_SKILLS` 에 들어가지 않는다는 테스트명을 새 이름으로 갱신.
|
|
97
|
+
- `python3 -m pytest tests/contract/test_docs_runtime_contract.py`: plugin manifest 계약과 docs runtime 계약 갱신.
|
|
98
|
+
- `npm run build`: `runtime/skills/okstra-container-build/SKILL.md` 생성과 `runtime/skills/okstra-container/` 부재 확인.
|
|
99
|
+
- 최종 게이트: `npm run check`.
|
|
100
|
+
|
|
101
|
+
## 8. 사용자 영향
|
|
102
|
+
|
|
103
|
+
- 새 설치와 재설치 후 스킬 호출명은 `/okstra-container-build` 가 된다.
|
|
104
|
+
- 기존 `okstra container up/status/logs/stop-watcher/down` CLI 는 그대로 동작한다.
|
|
105
|
+
- 기존 tmux watcher/session 이름과 docker trace 라벨은 유지된다.
|
|
106
|
+
- 이전 `/okstra-container` 는 upgrade install 과정에서 제거된다.
|
|
107
|
+
|
|
108
|
+
## 9. 미해결 사항
|
|
109
|
+
|
|
110
|
+
없음.
|
package/package.json
CHANGED
package/runtime/BUILD.json
CHANGED
|
@@ -398,7 +398,7 @@ okstra token-usage /abs/path/to/run/state/team-state-<task-type>-<seq>.json --wr
|
|
|
398
398
|
|
|
399
399
|
The script reads:
|
|
400
400
|
- `~/.claude/projects/<encoded-cwd>/<sessionId>.jsonl` for the lead and every Claude-side worker (Claude worker, Report writer worker, plus the Claude wrappers around Codex/Antigravity workers). Sessions are discovered by the recorded team-state `teamName`, lead is identified by `lead.sessionId`, and other workers are identified by `agentName` (e.g. `claude-worker`, `codex-worker`, `antigravity-worker`, `report-writer`). **For this `agentName` match to work, Lead MUST set the Agent `name` arg to `<workerId>-worker` on every dispatch** (see [agents okstra-lead-contract.md Phase 4 — "Agent `name` on dispatch"](./okstra-lead-contract.md)); a worker dispatched without `name` carries no `agentName`, so the collector cannot attribute its session and records it `unavailable` (now surfaced as a `usageSummary.unattributedTeamSessions` entry rather than dropped silently).
|
|
401
|
-
- `~/.
|
|
401
|
+
- `~/.agent/sessions/Y/M/D/rollout-*.jsonl` for the underlying Codex CLI session (matched by `cwd` and timestamp window of the wrapper subagent). Last `event_msg.token_count.total_token_usage.total_tokens` is the session total.
|
|
402
402
|
- `~/.antigravity/tmp/<project>/chats/session-*.json` for the underlying Antigravity CLI session. Sum of per-message `tokens.total`.
|
|
403
403
|
|
|
404
404
|
### Resulting team-state shape
|
|
@@ -424,7 +424,7 @@ The script reads:
|
|
|
424
424
|
"totalTokens": 2274011,
|
|
425
425
|
"source": "claude-jsonl",
|
|
426
426
|
"sessionId": "<wrapper-session-id>",
|
|
427
|
-
"cliSessionPaths": ["/Users/.../.
|
|
427
|
+
"cliSessionPaths": ["/Users/.../.agent/sessions/.../rollout-*.jsonl"],
|
|
428
428
|
"cliTotalTokens": 5261833,
|
|
429
429
|
"cliModel": "gpt-5.5"
|
|
430
430
|
}
|
|
@@ -30,7 +30,7 @@ are collected and convergence finished. Phase 1-5 do not need it.
|
|
|
30
30
|
- **Pure code changes** (no persisted state, no infra mutation): a reachable revert SHA is sufficient. Record the exact `git revert <SHA>` command that would undo the change, and confirm `git rev-parse <SHA>` resolves.
|
|
31
31
|
- **Feature-flag-gated changes**: confirm the off-switch path was exercised in this run's validation evidence (i.e. one of the validation commands ran with the flag off and succeeded). A plan that ships a flag without exercising the off-path does NOT satisfy this requirement.
|
|
32
32
|
- **Schema migrations, config-format changes, or any change with persisted state**: a **dry-run of the rollback step is mandatory**, not preferred. Record the exact rollback command and its captured exit code / stdout. If the migration tool offers no dry-run mode (`--dry-run`, `--plan`, equivalent), the executor MUST refuse to claim rollback verification and instead end the run with a routing recommendation back to `implementation-planning` for a safer rollback strategy. Skipping this step on a stateful change is treated as a `contract-violated` outcome by `final-verification`.
|
|
33
|
-
- **Manual user test draft**: when this run produces a user-observable change (UI / API / CLI / artifact), write `target / environment / steps / expected result` per change into §5.7.9 (data field `implementation.manualUserTest`, `applicable=true` with `items`). Environment line: if the project has a `docker-compose.yml`, use
|
|
33
|
+
- **Manual user test draft**: when this run produces a user-observable change (UI / API / CLI / artifact), write `target / environment / steps / expected result` per change into §5.7.9 (data field `implementation.manualUserTest`, `applicable=true` with `items`). Environment line: if the project has a `docker-compose.yml`, use `/okstra-container-build` (which runs `okstra container up <task-id>`) then connect to the published port; otherwise the project's run command (e.g. `npm start`). When there is no user-observable change, set `applicable=false` and give a one-line `exemptionReason` instead of items. Base the steps on the approved plan's `Acceptance:` and this run's actual diff — these are the steps a human (or `final-verification`) re-runs by hand, NOT the automated validation commands in `Validation evidence`.
|
|
34
34
|
- **Routing recommendation for `final-verification`**: brief note on whether the changes are ready for final-verification phase or need a new error-analysis / planning loop first.
|
|
35
35
|
- **Follow-up tasks (Section 4 of the final report)**: every item discovered during this run that was *not* delivered MUST appear in the final report's `## 4. Follow-up Tasks (후속 작업)` table with a concrete `Origin`, `New Task ID`, `Suggested task-type`, `Scope`, and `Reason / Why deferred`. Sources include: out-of-scope discoveries that the executor consciously chose not to fold into this run, verifier concerns the executor declined to fix in-place, scope-boundary items from the approved plan that turned out to need their own ticket, and any unresolved `## 1. Clarification Items` row carried over from the approved plan (`Status` ∈ `{open, answered}` at approval time). An empty section is acceptable but only when expressed as the single line `- 후속 작업 없음.` — silence is treated as a contract violation. Rows with `Auto-spawn? = yes` will be materialised by `scripts/okstra-spawn-followups.py` in Phase 7; rows with `Auto-spawn? = no` MUST also appear in `Section 3. Recommended Next Steps` so the user knows to act manually.
|
|
36
36
|
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
- **Read-only command log**: any pre-existing test/validation command executed during this run MUST be listed with its exact command line and exit code. No mutating commands may appear here.
|
|
39
39
|
- **Two-tier command lookup (shared with `implementation`):** when this phase performs its own independent re-validation, the command source is exactly the same two tiers `implementation` verifiers use — Tier 1 is the originating task brief / approved plan's `validation` set, Tier 2 is `<PROJECT_ROOT>/.okstra/project.json` under `qaCommands`. Auto-detecting tools from manifest files is forbidden; missing tiers are recorded as `qa-command not configured: <category>` rather than guessed. The `cmd` deny-list (`--fix`, `--write`, ` -w`, ` -u`, `--snapshot-update`, `INSTA_UPDATE=<not-no>`, `cargo update`, `npm install` without `ci`, etc.) is enforced identically. NOTE: runtime fail-fast validation (`okstra_ctl.qa_commands.validate_qa_commands`) only fires at `--task-type implementation` run-prep, so this phase MUST self-check each `qaCommands` entry against the deny-list before executing it — if a denied token is present, skip the command and record it as a `Read-only command log` line `qa-command rejected (denied token: <token>): <label>`.
|
|
40
40
|
- **Tier 3 — stage conformance scripts (whole-task union):** because this phase verifies the **integrated, merged** state, it re-runs conformance against that state rather than per-stage. Read the task-level manifest `<task_root>/qa/conformance-manifest.json` (the directory is the `TASK_QA_PATH` token) and, in **whole-task scope**, run the `runCommand` of **every** `entries[]` item against the merged worktree, refreshing each `<task_root>/qa/result-<stageKey>.json` (`{ "stageKey", "overall": "PASS"|"FAIL"|"MISSING", "ranAt", "requirements" }`). In **single-stage scope**, run only the entry whose `stageKey` matches the verified stage. An entry carrying an `exemption` or user `waiver` is NOT executed — record the skip and reason; a `waiver` becomes a `conditional-accept` condition surfaced in the section 7 Verdict (conformance left unverified by user acknowledgement). Each `runCommand` runs in the worktree cwd with `qaEnv` env (replica DB DSN / app base URL / env file) — **replica / test environment only**, never shared / staging / prod, and the same source/lockfile mutation deny-list applies (a conformance script MAY mutate only its `qaEnv` replica datastore). Interpret each result from the exit code + stdout `QA-RESULT: PASS|FAIL` (last wins) and `REQ <id>: PASS|FAIL: <reason>` lines; no `QA-RESULT` marker → `MISSING`. Any entry whose result is not `PASS` (including `MISSING` or a never-run/missing sidecar) is an **Acceptance Blocker** (`major`+), so the verdict becomes `conditional-accept` / `blocked`. This is the same gate the `validate-run.py` Tier 3 check enforces on the result sidecars.
|
|
41
|
-
- **Manual user test results**: take each item from the source implementation report's §5.7.9 Manual User Test (Draft), execute the ones reproducible in this environment (e.g.
|
|
41
|
+
- **Manual user test results**: take each item from the source implementation report's §5.7.9 Manual User Test (Draft), execute the ones reproducible in this environment (e.g. `/okstra-container-build`, which runs `okstra container up`, then the documented steps), and record `result` (`pass` / `fail` / `blocked`) + observed value in §5.8.7 (data field `finalVerification.manualUserTest`). Steps that need human-only interaction this run cannot perform are recorded as `blocked` with the reason (handed to the user), never silently skipped. A failed manual test is an Acceptance Blocker. If the draft was an exemption (`applicable=false`), reaffirm the reason in one line (`applicable=false` + `exemptionReaffirm`).
|
|
42
42
|
- **Routing recommendation**: the next safe phase — one of `release-handoff`, `done`, `error-analysis`, `implementation-planning` — tied to the verdict and blocker list. `release-handoff` is allowed ONLY when the Verdict Token is `accepted`. `release-handoff` is additionally allowed ONLY when the verification scope (the `Verification scope:` line of the injected `VERIFICATION_TARGET` block, recorded as the report's `verificationScope` field) is `whole-task`; a `single-stage` accepted run routes to `release-handoff(stage-group)` (or `implementation` / `done`); plain `release-handoff` remains whole-task-only. Enforcement: `validators/validate-run.py` rejects a `single-stage` report whose routing cites plain `release-handoff`.
|
|
43
43
|
- **Verified-row recording** (single-stage scope only): when the Verdict Token is `accepted`, the lead MUST run `okstra handoff record-verified --plan-run-root <plan-run-root> --stage <N> --report-path <final-report.md path> --data-json <final-report data.json path>` and quote the command + exit code in the report. The helper re-validates taskType/scope/verdict from data.json, so a non-accepted or whole-task report is rejected at the tool layer.
|
|
44
44
|
- Clarification request policy (phase-specific addendum — shared policy is in `_common-contract.md`):
|
|
@@ -7,7 +7,7 @@ from pathlib import Path
|
|
|
7
7
|
|
|
8
8
|
HOME = Path.home()
|
|
9
9
|
CLAUDE_PROJECTS = HOME / ".claude" / "projects"
|
|
10
|
-
CODEX_SESSIONS = HOME / ".
|
|
10
|
+
CODEX_SESSIONS = HOME / ".agent" / "sessions"
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
def utc_now() -> str:
|
|
@@ -45,4 +45,3 @@ def claude_project_dir(cwd: Path, projects_root: Path | None = None) -> Path:
|
|
|
45
45
|
# `projects_root` 는 테스트/진단용 주입 시드 — 기본은 실제 ~/.claude/projects.
|
|
46
46
|
encoded = "-" + str(cwd).strip("/").replace("/", "-")
|
|
47
47
|
return (projects_root or CLAUDE_PROJECTS) / encoded
|
|
48
|
-
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: okstra-container
|
|
2
|
+
name: okstra-container-build
|
|
3
3
|
description: |
|
|
4
4
|
Use to bring up, inspect, or tear down the okstra user-test container runtime for an implementation task — the docker-compose group okstra deploys from a task's worktree so a human can poke at the running build. Trigger words include "okstra container", "okstra container up", "okstra container down", "컨테이너 띄워", "컨테이너 올려", "컨테이너 내려", "컨테이너 상태", "컨테이너 로그", "유저 테스트 환경", "user test environment", "docker compose 띄워줘", "이 task 컨테이너로 띄워", "watcher 멈춰", "stop watcher", "감시 종료".
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
# OKSTRA Container
|
|
7
|
+
# OKSTRA Container Build
|
|
8
8
|
|
|
9
9
|
Single entry point for the okstra user-test container runtime. okstra provisions a docker-compose group from an `implementation` task's worktree (the `docker-compose.yml` at the worktree root), labels it with the task's run-trace so later sub-commands can find the group, and tails it through a tmux watcher pane. This skill drives that lifecycle. Sub-commands:
|
|
10
10
|
|
package/src/cli-registry.mjs
CHANGED
|
@@ -6,7 +6,7 @@ export const COMMAND_REGISTRY = [
|
|
|
6
6
|
module: "./commands/lifecycle/install.mjs",
|
|
7
7
|
export: "runInstall",
|
|
8
8
|
category: "admin",
|
|
9
|
-
summary: ["Install runtime; seed skills into
|
|
9
|
+
summary: ["Install runtime; seed skills into Agent and Claude homes"],
|
|
10
10
|
},
|
|
11
11
|
{
|
|
12
12
|
name: "ensure-installed",
|
|
@@ -7,17 +7,15 @@ import { resolvePaths } from "../../lib/paths.mjs";
|
|
|
7
7
|
import { fileExists } from "../../lib/proc.mjs";
|
|
8
8
|
import { buildRuntimeManifest, RUNTIMES_MANIFEST_REL } from "../../lib/runtime-manifest.mjs";
|
|
9
9
|
import { normalizeRuntimeRequest, resolveRuntime } from "../../lib/runtime-resolver.mjs";
|
|
10
|
-
import {
|
|
10
|
+
import { OBSOLETE_SKILL_NAMES, USER_SKILL_NAMES } from "../../lib/skill-catalog.mjs";
|
|
11
11
|
|
|
12
12
|
const SKILLS_MANIFEST_REL = "installed-skills.json";
|
|
13
13
|
const AGENTS_MANIFEST_REL = "installed-agents.json";
|
|
14
14
|
const USER_HOME = homedir();
|
|
15
15
|
const CLAUDE_HOME = join(USER_HOME, ".claude");
|
|
16
|
-
const
|
|
17
|
-
const OMP_AGENT_HOME = join(USER_HOME, ".omp", "agent");
|
|
16
|
+
const AGENT_HOME = join(USER_HOME, ".agent");
|
|
18
17
|
const CLAUDE_SKILLS_DIR = join(CLAUDE_HOME, "skills");
|
|
19
|
-
const
|
|
20
|
-
const OMP_SKILLS_DIR = join(OMP_AGENT_HOME, "skills");
|
|
18
|
+
const AGENT_SKILLS_DIR = join(AGENT_HOME, "skills");
|
|
21
19
|
const CLAUDE_AGENTS_DIR = join(CLAUDE_HOME, "agents");
|
|
22
20
|
|
|
23
21
|
// Source template (relative to runtime root in copy mode, or repo root in link mode).
|
|
@@ -82,8 +80,7 @@ Effect (copy mode):
|
|
|
82
80
|
${"$HOME"}/.okstra/prompts <- runtime/prompts (lead contracts + coding-preflight pack)
|
|
83
81
|
${"$HOME"}/.okstra/templates/settings.local.json <- runtime/templates/reports/settings.template.json
|
|
84
82
|
${"$HOME"}/.claude/skills/<name> <- skills when ${"$HOME"}/.claude exists
|
|
85
|
-
${"$HOME"}/.
|
|
86
|
-
${"$HOME"}/.omp/agent/skills/<name> <- skills fallback when Claude/Codex homes are absent
|
|
83
|
+
${"$HOME"}/.agent/skills/<name> <- skills (created when needed)
|
|
87
84
|
${"$HOME"}/.claude/agents/<worker>.md <- worker agents when ${"$HOME"}/.claude exists
|
|
88
85
|
${"$HOME"}/.okstra/installed-runtimes.json <- install request/resolution manifest
|
|
89
86
|
${"$HOME"}/.okstra/installed-skills.json <- target-aware manifest of installed skills
|
|
@@ -96,8 +93,7 @@ Effect (link mode):
|
|
|
96
93
|
${"$HOME"}/.okstra/prompts -> <repo>/prompts
|
|
97
94
|
${"$HOME"}/.okstra/templates/settings.local.json -> <repo>/templates/reports/settings.template.json
|
|
98
95
|
${"$HOME"}/.claude/skills/<name> -> <repo>/skills/<name> when ${"$HOME"}/.claude exists
|
|
99
|
-
${"$HOME"}/.
|
|
100
|
-
${"$HOME"}/.omp/agent/skills/<name> -> <repo>/skills/<name> fallback
|
|
96
|
+
${"$HOME"}/.agent/skills/<name> -> <repo>/skills/<name> (created when needed)
|
|
101
97
|
${"$HOME"}/.claude/agents/<worker>.md -> <repo>/agents/workers/<worker>.md when ${"$HOME"}/.claude exists
|
|
102
98
|
${"$HOME"}/.okstra/dev-link <- <repo> path stamp
|
|
103
99
|
${"$HOME"}/.okstra/version <- installed package version stamp
|
|
@@ -109,10 +105,10 @@ worker wrapper scripts without modifying the user's global settings.
|
|
|
109
105
|
|
|
110
106
|
Worker agent definitions are installed into ${"$HOME"}/.claude/agents/ only
|
|
111
107
|
when ${"$HOME"}/.claude exists, so Claude Code's subagent discovery can pick
|
|
112
|
-
them up.
|
|
108
|
+
them up. Agent skill targets receive skill markdown only.
|
|
113
109
|
|
|
114
110
|
Default auto runtime installation does not require host detection; it still
|
|
115
|
-
installs shared runtime files and any
|
|
111
|
+
installs shared runtime files, the Agent skill target, and any Claude home it finds.
|
|
116
112
|
`;
|
|
117
113
|
|
|
118
114
|
const ENSURE_USAGE = `okstra ensure-installed — idempotent install check
|
|
@@ -401,16 +397,11 @@ async function installLinkMode(repoPath, paths, opts) {
|
|
|
401
397
|
|
|
402
398
|
async function detectSkillTargets() {
|
|
403
399
|
const targets = [];
|
|
404
|
-
const
|
|
400
|
+
const hasClaude = await dirExists(CLAUDE_HOME);
|
|
405
401
|
if (hasClaude) {
|
|
406
402
|
targets.push({ provider: "claude", home: CLAUDE_HOME, skillsDir: CLAUDE_SKILLS_DIR });
|
|
407
403
|
}
|
|
408
|
-
|
|
409
|
-
targets.push({ provider: "codex", home: CODEX_HOME, skillsDir: CODEX_SKILLS_DIR });
|
|
410
|
-
}
|
|
411
|
-
if (targets.length === 0 && (await dirExists(OMP_AGENT_HOME))) {
|
|
412
|
-
targets.push({ provider: "omp", home: OMP_AGENT_HOME, skillsDir: OMP_SKILLS_DIR });
|
|
413
|
-
}
|
|
404
|
+
targets.push({ provider: "agent", home: AGENT_HOME, skillsDir: AGENT_SKILLS_DIR });
|
|
414
405
|
return targets;
|
|
415
406
|
}
|
|
416
407
|
|
|
@@ -428,7 +419,6 @@ function installedAssetOverrides(skillTargets, agentNames) {
|
|
|
428
419
|
claudeSkills: providers.has("claude"),
|
|
429
420
|
claudeAgents: agentNames.length > 0,
|
|
430
421
|
codexSkills: providers.has("codex"),
|
|
431
|
-
ompSkills: providers.has("omp"),
|
|
432
422
|
skillTargets: Array.from(providers).sort(),
|
|
433
423
|
};
|
|
434
424
|
}
|
|
@@ -637,7 +627,7 @@ async function installSkillsCopy(runtimeRoot, target, opts) {
|
|
|
637
627
|
copied += r.copied;
|
|
638
628
|
skipped += r.skipped;
|
|
639
629
|
}
|
|
640
|
-
const pruned = await
|
|
630
|
+
const pruned = await pruneObsoleteSkills(target, opts);
|
|
641
631
|
if (!quiet) {
|
|
642
632
|
process.stdout.write(
|
|
643
633
|
` ${target.provider} skills: copied=${copied} skipped=${skipped} pruned=${pruned} -> ${target.skillsDir}/ (${USER_SKILL_NAMES.length} skills)\n`,
|
|
@@ -656,19 +646,18 @@ async function installSkillsLink(repoAbs, target, opts) {
|
|
|
656
646
|
const action = await ensureSymlink(src, dst, { dryRun });
|
|
657
647
|
if (!quiet) process.stdout.write(` ${target.provider} skills/${name}: ${action}\n`);
|
|
658
648
|
}
|
|
659
|
-
const pruned = await
|
|
649
|
+
const pruned = await pruneObsoleteSkills(target, opts);
|
|
660
650
|
if (!quiet && pruned > 0) {
|
|
661
651
|
process.stdout.write(` ${target.provider} obsolete internal skills: pruned=${pruned}\n`);
|
|
662
652
|
}
|
|
663
653
|
return { ...target, installed: [...USER_SKILL_NAMES] };
|
|
664
654
|
}
|
|
665
655
|
|
|
666
|
-
// Remove former
|
|
667
|
-
//
|
|
668
|
-
|
|
669
|
-
async function pruneObsoleteInternalSkills(target, opts) {
|
|
656
|
+
// Remove former skill directories left by older installs. Exact-name only
|
|
657
|
+
// (OBSOLETE_SKILL_NAMES) — never wildcard-delete okstra-*.
|
|
658
|
+
async function pruneObsoleteSkills(target, opts) {
|
|
670
659
|
let pruned = 0;
|
|
671
|
-
for (const name of
|
|
660
|
+
for (const name of OBSOLETE_SKILL_NAMES) {
|
|
672
661
|
const path = join(target.skillsDir, name);
|
|
673
662
|
if (!(await pathEntryExists(path))) continue;
|
|
674
663
|
pruned += 1;
|
|
@@ -683,7 +672,7 @@ async function pruneObsoleteInternalSkills(target, opts) {
|
|
|
683
672
|
|
|
684
673
|
async function installSkillTargetsCopy(runtimeRoot, targets, opts) {
|
|
685
674
|
if (targets.length === 0) {
|
|
686
|
-
if (!opts.quiet) process.stdout.write(" skills: no
|
|
675
|
+
if (!opts.quiet) process.stdout.write(" skills: no skill target found — skipped\n");
|
|
687
676
|
return [];
|
|
688
677
|
}
|
|
689
678
|
return await Promise.all(targets.map((target) => installSkillsCopy(runtimeRoot, target, opts)));
|
|
@@ -691,7 +680,7 @@ async function installSkillTargetsCopy(runtimeRoot, targets, opts) {
|
|
|
691
680
|
|
|
692
681
|
async function installSkillTargetsLink(repoAbs, targets, opts) {
|
|
693
682
|
if (targets.length === 0) {
|
|
694
|
-
if (!opts.quiet) process.stdout.write(" skills: no
|
|
683
|
+
if (!opts.quiet) process.stdout.write(" skills: no skill target found — skipped\n");
|
|
695
684
|
return [];
|
|
696
685
|
}
|
|
697
686
|
return await Promise.all(targets.map((target) => installSkillsLink(repoAbs, target, opts)));
|
|
@@ -15,6 +15,8 @@ const FALLBACK_SKILL_NAMES = [
|
|
|
15
15
|
"okstra-memory",
|
|
16
16
|
"okstra-inspect",
|
|
17
17
|
"okstra-schedule",
|
|
18
|
+
"okstra-container-build",
|
|
19
|
+
"okstra-container",
|
|
18
20
|
"okstra-context-loader",
|
|
19
21
|
"okstra-team-contract",
|
|
20
22
|
"okstra-convergence",
|
|
@@ -38,16 +40,13 @@ const FALLBACK_AGENT_NAMES = [
|
|
|
38
40
|
|
|
39
41
|
const USER_HOME = homedir();
|
|
40
42
|
const CLAUDE_HOME = join(USER_HOME, ".claude");
|
|
41
|
-
const
|
|
42
|
-
const OMP_AGENT_HOME = join(USER_HOME, ".omp", "agent");
|
|
43
|
+
const AGENT_HOME = join(USER_HOME, ".agent");
|
|
43
44
|
const CLAUDE_SKILLS_DIR = join(CLAUDE_HOME, "skills");
|
|
44
|
-
const
|
|
45
|
-
const OMP_SKILLS_DIR = join(OMP_AGENT_HOME, "skills");
|
|
45
|
+
const AGENT_SKILLS_DIR = join(AGENT_HOME, "skills");
|
|
46
46
|
const CLAUDE_AGENTS_DIR = join(CLAUDE_HOME, "agents");
|
|
47
47
|
const FALLBACK_SKILL_ROOTS = [
|
|
48
48
|
CLAUDE_SKILLS_DIR,
|
|
49
|
-
|
|
50
|
-
OMP_SKILLS_DIR,
|
|
49
|
+
AGENT_SKILLS_DIR,
|
|
51
50
|
];
|
|
52
51
|
const SKILLS_MANIFEST_REL = "installed-skills.json";
|
|
53
52
|
const AGENTS_MANIFEST_REL = "installed-agents.json";
|
|
@@ -61,8 +60,8 @@ Usage:
|
|
|
61
60
|
installed-skills.json, installed-agents.json} AND
|
|
62
61
|
every skill target recorded in
|
|
63
62
|
installed-skills.json
|
|
64
|
-
(~/.claude/skills
|
|
65
|
-
|
|
63
|
+
(~/.claude/skills or ~/.agent/skills).
|
|
64
|
+
Claude worker agents are
|
|
66
65
|
removed from ~/.claude/agents when installed.
|
|
67
66
|
Missing manifests fall back to hard-coded
|
|
68
67
|
Claude okstra-* / *-worker.md names.
|
|
@@ -73,7 +72,7 @@ Usage:
|
|
|
73
72
|
(<PROJECT>/.claude/settings.local.json symlink)
|
|
74
73
|
are NOT touched — remove them manually if needed.
|
|
75
74
|
okstra uninstall --purge Remove the entire ~/.okstra directory AND
|
|
76
|
-
okstra skills in Claude/
|
|
75
|
+
okstra skills in Claude/Agent homes AND
|
|
77
76
|
~/.claude/agents/<*-worker.md> (DESTRUCTIVE).
|
|
78
77
|
Requires -y or an interactive confirmation
|
|
79
78
|
okstra uninstall --dry-run Print the plan without touching disk
|
|
@@ -137,7 +136,7 @@ export async function runUninstall(args) {
|
|
|
137
136
|
if (opts.purge) {
|
|
138
137
|
if (!opts.yes && !opts.dryRun) {
|
|
139
138
|
const ok = await promptConfirm(
|
|
140
|
-
`purge entire ${paths.home} AND okstra skills in Claude/
|
|
139
|
+
`purge entire ${paths.home} AND okstra skills in Claude/Agent homes? user data will be lost.`,
|
|
141
140
|
);
|
|
142
141
|
if (!ok) {
|
|
143
142
|
process.stdout.write("aborted.\n");
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Single source of truth for which okstra skills are user-facing and which
|
|
2
|
-
// former
|
|
2
|
+
// former or renamed skill directories must be pruned from agent skill homes on
|
|
3
3
|
// (re)install. The Claude plugin manifest (.claude-plugin/plugin.json) and the
|
|
4
4
|
// installer both derive their skill list from here, so the public surface
|
|
5
5
|
// cannot drift between discovery and install.
|
|
@@ -11,26 +11,27 @@ export const USER_SKILL_NAMES = Object.freeze([
|
|
|
11
11
|
"okstra-memory",
|
|
12
12
|
"okstra-inspect",
|
|
13
13
|
"okstra-schedule",
|
|
14
|
-
"okstra-container",
|
|
14
|
+
"okstra-container-build",
|
|
15
15
|
]);
|
|
16
16
|
|
|
17
|
-
// Names okstra used to install as skills before
|
|
18
|
-
// into the runtime resource tree
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
export const
|
|
17
|
+
// Names okstra used to install as skills before they were renamed or moved
|
|
18
|
+
// into the runtime resource tree. Install prunes these exact directories from
|
|
19
|
+
// each target skill home so upgrades do not leave stale slash commands behind.
|
|
20
|
+
// Exact-name only — never wildcard.
|
|
21
|
+
export const OBSOLETE_SKILL_NAMES = Object.freeze([
|
|
22
22
|
"okstra",
|
|
23
23
|
"okstra-context-loader",
|
|
24
24
|
"okstra-team-contract",
|
|
25
25
|
"okstra-convergence",
|
|
26
26
|
"okstra-report-writer",
|
|
27
27
|
"okstra-coding-preflight",
|
|
28
|
+
"okstra-container",
|
|
28
29
|
]);
|
|
29
30
|
|
|
30
31
|
export function userSkillNames() {
|
|
31
32
|
return [...USER_SKILL_NAMES];
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
export function
|
|
35
|
-
return [...
|
|
35
|
+
export function obsoleteSkillNames() {
|
|
36
|
+
return [...OBSOLETE_SKILL_NAMES];
|
|
36
37
|
}
|