okstra 0.36.2 → 0.38.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 +6 -6
- package/README.md +6 -6
- package/bin/okstra +4 -2
- package/docs/kr/architecture.md +31 -31
- package/docs/kr/cli.md +10 -9
- package/docs/pr-template-usage.md +2 -2
- package/docs/project-structure-overview.md +4 -4
- package/docs/superpowers/plans/2026-05-25-okstra-project-root-rename.md +159 -0
- package/docs/superpowers/plans/2026-05-26-wizard-3-option-picker.md +860 -0
- package/docs/task-process/common-flow.md +2 -2
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/SKILL.md +16 -14
- package/runtime/agents/workers/claude-worker.md +1 -1
- package/runtime/prompts/profiles/_common-contract.md +7 -7
- package/runtime/prompts/profiles/_implementation-executor.md +2 -2
- package/runtime/prompts/profiles/_implementation-verifier.md +2 -2
- package/runtime/prompts/profiles/final-verification.md +1 -1
- package/runtime/prompts/profiles/implementation-planning.md +5 -5
- package/runtime/prompts/profiles/release-handoff.md +1 -1
- package/runtime/prompts/profiles/requirements-discovery.md +3 -3
- package/runtime/prompts/wizard/prompts.ko.json +80 -6
- package/runtime/python/lib/okstra/globals.sh +2 -2
- package/runtime/python/lib/okstra/interactive.sh +2 -2
- package/runtime/python/lib/okstra/project-resolver.sh +1 -1
- package/runtime/python/lib/okstra/usage.sh +9 -9
- package/runtime/python/lib/okstra-ctl/cmd-rerun.sh +1 -1
- package/runtime/python/okstra_ctl/backfill.py +5 -3
- package/runtime/python/okstra_ctl/migrate.py +408 -0
- package/runtime/python/okstra_ctl/paths.py +12 -3
- package/runtime/python/okstra_ctl/pr_template.py +4 -2
- package/runtime/python/okstra_ctl/render.py +8 -6
- package/runtime/python/okstra_ctl/run.py +7 -7
- package/runtime/python/okstra_ctl/seeding.py +12 -6
- package/runtime/python/okstra_ctl/sequence.py +3 -1
- package/runtime/python/okstra_ctl/wizard.py +412 -77
- package/runtime/python/okstra_ctl/worktree.py +8 -6
- package/runtime/python/okstra_project/__init__.py +35 -5
- package/runtime/python/okstra_project/dirs.py +67 -0
- package/runtime/python/okstra_project/resolver.py +8 -8
- package/runtime/python/okstra_project/state.py +11 -9
- package/runtime/python/okstra_token_usage/collect.py +3 -1
- package/runtime/skills/okstra-brief/SKILL.md +30 -30
- package/runtime/skills/okstra-context-loader/SKILL.md +7 -7
- package/runtime/skills/okstra-inspect/SKILL.md +25 -25
- package/runtime/skills/okstra-run/templates/pr-body.template.md +1 -1
- package/runtime/skills/okstra-schedule/SKILL.md +7 -7
- package/runtime/skills/okstra-setup/SKILL.md +8 -8
- package/runtime/skills/okstra-team-contract/SKILL.md +4 -4
- package/runtime/templates/okstra.CLAUDE.md +4 -4
- package/runtime/templates/reports/brief.template.md +5 -5
- package/runtime/templates/reports/task-brief.template.md +1 -1
- package/runtime/validators/lib/fixtures.sh +2 -2
- package/runtime/validators/lib/paths.sh +9 -3
- package/runtime/validators/validate-brief.py +2 -2
- package/runtime/validators/validate-brief.sh +1 -1
- package/runtime/validators/validate-run.py +3 -1
- package/runtime/validators/validate-workflow.sh +2 -2
- package/src/check-project.mjs +3 -3
- package/src/config.mjs +6 -5
- package/src/install.mjs +5 -5
- package/src/migrate.mjs +163 -0
- package/src/okstra-dirs.mjs +37 -0
- package/src/paths.mjs +17 -0
- package/src/setup.mjs +8 -4
- package/src/uninstall.mjs +3 -3
|
@@ -41,6 +41,8 @@ from dataclasses import dataclass
|
|
|
41
41
|
from pathlib import Path
|
|
42
42
|
from typing import Optional
|
|
43
43
|
|
|
44
|
+
from okstra_project.dirs import project_json_path
|
|
45
|
+
|
|
44
46
|
from .ids import _safe_fs_segment
|
|
45
47
|
from . import worktree_registry
|
|
46
48
|
from .seeding import (
|
|
@@ -60,13 +62,13 @@ from .seeding import (
|
|
|
60
62
|
# live shared state and disk/CPU cost stays near zero; the trade-off is
|
|
61
63
|
# that any write through the link reaches the main worktree, which is
|
|
62
64
|
# acceptable because okstra only writes inside its own task-scoped
|
|
63
|
-
# subdirectory (e.g. `.
|
|
65
|
+
# subdirectory (e.g. `.okstra/tasks/<task-id>/runs/...`).
|
|
64
66
|
#
|
|
65
67
|
# Override precedence (most-specific first):
|
|
66
68
|
# 1. `OKSTRA_WORKTREE_SYNC_DIRS` env var — colon-separated list, REPLACES
|
|
67
69
|
# defaults. Empty string disables the feature entirely. One-off
|
|
68
70
|
# operator override.
|
|
69
|
-
# 2. `worktreeSyncDirs` array in `.
|
|
71
|
+
# 2. `worktreeSyncDirs` array in `.okstra/project.json` —
|
|
70
72
|
# project-level config, persists across runs. Same semantics: array
|
|
71
73
|
# REPLACES defaults, empty array disables.
|
|
72
74
|
# 3. The built-in `DEFAULT_WORKTREE_SYNC_DIRS` below.
|
|
@@ -88,7 +90,7 @@ DEFAULT_WORKTREE_SYNC_DIRS: tuple[str, ...] = (
|
|
|
88
90
|
#
|
|
89
91
|
# Override precedence mirrors `DEFAULT_WORKTREE_SYNC_DIRS`:
|
|
90
92
|
# 1. `OKSTRA_WORKTREE_SYNC_FILES` env var (colon-separated, REPLACES).
|
|
91
|
-
# 2. `worktreeSyncFiles` array in `.
|
|
93
|
+
# 2. `worktreeSyncFiles` array in `.okstra/project.json`.
|
|
92
94
|
# 3. The built-in `DEFAULT_WORKTREE_SYNC_FILES` below.
|
|
93
95
|
DEFAULT_WORKTREE_SYNC_FILES: tuple[str, ...] = (
|
|
94
96
|
".env",
|
|
@@ -227,7 +229,7 @@ def _main_worktree_path(project_root: Path) -> Path:
|
|
|
227
229
|
|
|
228
230
|
|
|
229
231
|
def _read_project_json_field(project_root: Path, field: str) -> Optional[tuple[str, ...]]:
|
|
230
|
-
"""Read a string-array field from
|
|
232
|
+
"""Read a string-array field from the project's okstra project.json.
|
|
231
233
|
|
|
232
234
|
Returns None if the field is absent or the file cannot be parsed (so
|
|
233
235
|
the caller falls back to defaults). Returns an empty tuple if the
|
|
@@ -235,7 +237,7 @@ def _read_project_json_field(project_root: Path, field: str) -> Optional[tuple[s
|
|
|
235
237
|
A non-list value is treated as missing — we do not raise here because
|
|
236
238
|
field resolution must never block worktree provisioning.
|
|
237
239
|
"""
|
|
238
|
-
target = project_root
|
|
240
|
+
target = project_json_path(project_root)
|
|
239
241
|
if not target.is_file():
|
|
240
242
|
return None
|
|
241
243
|
try:
|
|
@@ -398,7 +400,7 @@ def _seed_worktree_settings_symlink(worktree_path: Path) -> None:
|
|
|
398
400
|
|
|
399
401
|
|
|
400
402
|
def _seed_worktree_claude_md(worktree_path: Path) -> None:
|
|
401
|
-
"""Seed `.
|
|
403
|
+
"""Seed `.okstra/CLAUDE.md` symlink + `<worktree>/CLAUDE.md`
|
|
402
404
|
import block in the worker worktree so dispatched Claude sessions auto-load
|
|
403
405
|
okstra's runtime guidance. Mirrors `_seed_worktree_settings_symlink` —
|
|
404
406
|
needed because the worktree's `CLAUDE.md` comes from the git checkout (no
|
|
@@ -1,14 +1,31 @@
|
|
|
1
1
|
"""Project root self-registration model.
|
|
2
2
|
|
|
3
|
-
`<PROJECT_ROOT
|
|
3
|
+
`<PROJECT_ROOT>/<OKSTRA_DIR_NAME>/project.json` 을 권위 소스로 삼아
|
|
4
4
|
PROJECT_ROOT 를 해석하고, 실행 시점에 upsert 한다. 과거 모델
|
|
5
5
|
(`examples/projects/<id>.conf.sh`) 는 폐기되었다.
|
|
6
|
+
|
|
7
|
+
`OKSTRA_DIR_NAME` 및 자주 쓰는 path 헬퍼는 `.dirs` 모듈이 SSOT.
|
|
6
8
|
"""
|
|
7
9
|
from __future__ import annotations
|
|
8
10
|
|
|
11
|
+
from .dirs import (
|
|
12
|
+
CLAUDE_MD_IMPORT_LINE,
|
|
13
|
+
CLAUDE_MD_SYMLINK_RELATIVE,
|
|
14
|
+
DISCOVERY_RELATIVE,
|
|
15
|
+
LATEST_TASK_RELATIVE,
|
|
16
|
+
OKSTRA_DIR_NAME,
|
|
17
|
+
OKSTRA_RELATIVE,
|
|
18
|
+
PROJECT_JSON_RELATIVE,
|
|
19
|
+
TASK_CATALOG_RELATIVE,
|
|
20
|
+
TASKS_RELATIVE,
|
|
21
|
+
claude_md_symlink_path,
|
|
22
|
+
discovery_dir,
|
|
23
|
+
okstra_root,
|
|
24
|
+
project_json_path,
|
|
25
|
+
tasks_root,
|
|
26
|
+
)
|
|
9
27
|
from .resolver import (
|
|
10
28
|
ResolverError,
|
|
11
|
-
project_json_path,
|
|
12
29
|
resolve_project_root,
|
|
13
30
|
upsert_project_json,
|
|
14
31
|
)
|
|
@@ -25,17 +42,30 @@ from .state import (
|
|
|
25
42
|
)
|
|
26
43
|
|
|
27
44
|
__all__ = [
|
|
45
|
+
"CLAUDE_MD_IMPORT_LINE",
|
|
46
|
+
"CLAUDE_MD_SYMLINK_RELATIVE",
|
|
47
|
+
"DISCOVERY_RELATIVE",
|
|
48
|
+
"LATEST_TASK_RELATIVE",
|
|
49
|
+
"OKSTRA_DIR_NAME",
|
|
50
|
+
"OKSTRA_RELATIVE",
|
|
51
|
+
"PROJECT_JSON_RELATIVE",
|
|
28
52
|
"ResolverError",
|
|
29
53
|
"StateError",
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
54
|
+
"TASKS_RELATIVE",
|
|
55
|
+
"TASK_CATALOG_RELATIVE",
|
|
56
|
+
"claude_md_symlink_path",
|
|
57
|
+
"discovery_dir",
|
|
33
58
|
"find_task_root",
|
|
34
59
|
"list_project_tasks",
|
|
60
|
+
"okstra_root",
|
|
35
61
|
"parse_task_key",
|
|
62
|
+
"project_json_path",
|
|
36
63
|
"read_latest_task",
|
|
37
64
|
"read_task_catalog",
|
|
38
65
|
"read_task_manifest",
|
|
66
|
+
"resolve_project_root",
|
|
39
67
|
"resolve_task_identity",
|
|
40
68
|
"slugify",
|
|
69
|
+
"tasks_root",
|
|
70
|
+
"upsert_project_json",
|
|
41
71
|
]
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""Single source of truth for the okstra-relative directory name.
|
|
2
|
+
|
|
3
|
+
okstra 가 한 프로젝트 안에 만드는 모든 산출물은 `<PROJECT_ROOT>/.okstra/`
|
|
4
|
+
한 디렉토리 아래 모인다. 이 모듈은 그 디렉토리 이름과 자주 쓰이는 하위 path 조합을
|
|
5
|
+
한 곳에서 export 한다. 호출자는 절대 path 문자열을 직접 만들지 말고 여기의 상수 /
|
|
6
|
+
헬퍼를 사용해야 한다.
|
|
7
|
+
|
|
8
|
+
DRY 위반의 비용: 이전에는 동일 path 문자열이 50+ Python·Shell·markdown 파일에
|
|
9
|
+
중복으로 박혀 있었고, 디렉토리 이름을 바꾸려면 60+ 파일을 동시에 수정해야 했다.
|
|
10
|
+
이 모듈은 그 비용을 한 줄 수정으로 줄인다.
|
|
11
|
+
|
|
12
|
+
의존성 0 (Path only). `paths.py` 와 `state.py` 양쪽에서 import 되므로 순환 위험을
|
|
13
|
+
피하기 위해 다른 okstra 모듈을 import 하지 않는다.
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
OKSTRA_DIR_NAME = ".okstra"
|
|
20
|
+
"""Project-relative directory name where okstra stores all per-project state.
|
|
21
|
+
|
|
22
|
+
CLI / docs / message 문자열도 모두 이 값과 일치해야 한다. 값 자체를 import 해 쓰는
|
|
23
|
+
대신 path 합성에는 `OKSTRA_RELATIVE` / `okstra_root()` 등을 권장.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
LEGACY_OKSTRA_DIR_NAME = ".project-docs/okstra"
|
|
27
|
+
"""Pre-v0.37 layout. ``okstra migrate`` reads this to find unmigrated projects.
|
|
28
|
+
|
|
29
|
+
코드 path 빌드에는 절대 사용 금지 — `OKSTRA_DIR_NAME` / `OKSTRA_RELATIVE` 만.
|
|
30
|
+
오로지 migration 탐지/안내 메시지 용도.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
OKSTRA_RELATIVE = Path(OKSTRA_DIR_NAME)
|
|
34
|
+
LEGACY_OKSTRA_RELATIVE = Path(LEGACY_OKSTRA_DIR_NAME)
|
|
35
|
+
PROJECT_JSON_RELATIVE = OKSTRA_RELATIVE / "project.json"
|
|
36
|
+
TASKS_RELATIVE = OKSTRA_RELATIVE / "tasks"
|
|
37
|
+
DISCOVERY_RELATIVE = OKSTRA_RELATIVE / "discovery"
|
|
38
|
+
TASK_CATALOG_RELATIVE = DISCOVERY_RELATIVE / "task-catalog.json"
|
|
39
|
+
LATEST_TASK_RELATIVE = DISCOVERY_RELATIVE / "latest-task.json"
|
|
40
|
+
CLAUDE_MD_SYMLINK_RELATIVE = OKSTRA_RELATIVE / "CLAUDE.md"
|
|
41
|
+
CLAUDE_MD_IMPORT_LINE = f"@{OKSTRA_DIR_NAME}/CLAUDE.md"
|
|
42
|
+
LEGACY_CLAUDE_MD_IMPORT_LINE = f"@{LEGACY_OKSTRA_DIR_NAME}/CLAUDE.md"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def okstra_root(project_root: Path) -> Path:
|
|
46
|
+
"""`<project_root>/.okstra` 절대 path."""
|
|
47
|
+
return Path(project_root) / OKSTRA_RELATIVE
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def project_json_path(project_root: Path) -> Path:
|
|
51
|
+
"""`<project_root>/.okstra/project.json` 절대 path."""
|
|
52
|
+
return Path(project_root) / PROJECT_JSON_RELATIVE
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def tasks_root(project_root: Path) -> Path:
|
|
56
|
+
"""`<project_root>/.okstra/tasks` 절대 path."""
|
|
57
|
+
return Path(project_root) / TASKS_RELATIVE
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def discovery_dir(project_root: Path) -> Path:
|
|
61
|
+
"""`<project_root>/.okstra/discovery` 절대 path."""
|
|
62
|
+
return Path(project_root) / DISCOVERY_RELATIVE
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def claude_md_symlink_path(project_root: Path) -> Path:
|
|
66
|
+
"""`<project_root>/.okstra/CLAUDE.md` 절대 path."""
|
|
67
|
+
return Path(project_root) / CLAUDE_MD_SYMLINK_RELATIVE
|
|
@@ -8,23 +8,23 @@ from datetime import datetime, timezone
|
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
from typing import Optional
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
from .dirs import (
|
|
12
|
+
OKSTRA_DIR_NAME,
|
|
13
|
+
PROJECT_JSON_RELATIVE,
|
|
14
|
+
project_json_path,
|
|
15
|
+
)
|
|
12
16
|
|
|
13
17
|
|
|
14
18
|
class ResolverError(Exception):
|
|
15
19
|
"""PROJECT_ROOT 해석 또는 project.json 충돌 실패."""
|
|
16
20
|
|
|
17
21
|
|
|
18
|
-
def project_json_path(project_root: Path) -> Path:
|
|
19
|
-
return Path(project_root) / PROJECT_JSON_RELATIVE
|
|
20
|
-
|
|
21
|
-
|
|
22
22
|
def _now_iso() -> str:
|
|
23
23
|
return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
def _ancestor_with_project_json(start: Path) -> Optional[Path]:
|
|
27
|
-
"""start 부터 위로 올라가며
|
|
27
|
+
"""start 부터 위로 올라가며 okstra `project.json` 보유 디렉터리를 찾는다."""
|
|
28
28
|
cur = Path(start).resolve()
|
|
29
29
|
while True:
|
|
30
30
|
if (cur / PROJECT_JSON_RELATIVE).is_file():
|
|
@@ -57,7 +57,7 @@ def resolve_project_root(*, explicit_root: str = "",
|
|
|
57
57
|
|
|
58
58
|
우선순위:
|
|
59
59
|
1. explicit_root (CLI `--project-root`) — 비어있지 않으면 그대로 절대화.
|
|
60
|
-
2. cwd 또는 그 조상 중
|
|
60
|
+
2. cwd 또는 그 조상 중 `<dir>/project.json` 보유 디렉터리 (dir = OKSTRA_DIR_NAME).
|
|
61
61
|
3. cwd 의 `git rev-parse --show-toplevel`.
|
|
62
62
|
셋 다 실패하면 ResolverError.
|
|
63
63
|
"""
|
|
@@ -77,7 +77,7 @@ def resolve_project_root(*, explicit_root: str = "",
|
|
|
77
77
|
raise ResolverError(
|
|
78
78
|
"could not resolve PROJECT_ROOT from cwd. "
|
|
79
79
|
"Pass --project-root <abs-path>, "
|
|
80
|
-
"run from inside a project (a directory with
|
|
80
|
+
f"run from inside a project (a directory with {OKSTRA_DIR_NAME}/project.json at or above cwd), "
|
|
81
81
|
"or run from inside a git working tree. "
|
|
82
82
|
"(PROJECT_ROOT 를 해석할 수 없습니다 — --project-root 를 명시하거나, "
|
|
83
83
|
"프로젝트 루트 또는 그 하위에서 실행하거나, git 작업 트리 안에서 실행해 주십시오.)")
|
|
@@ -6,12 +6,12 @@ being passed via process environment. This keeps concurrent claude-code skill
|
|
|
6
6
|
invocations isolated — each call reads the authoritative state at the moment
|
|
7
7
|
it runs and never sees stale snapshots inherited from a parent process.
|
|
8
8
|
|
|
9
|
-
권위 파일
|
|
10
|
-
- <
|
|
9
|
+
권위 파일 매핑 (okstra root = <PROJECT_ROOT>/<OKSTRA_DIR_NAME>):
|
|
10
|
+
- <okstra root>/project.json
|
|
11
11
|
-> {projectId, projectRoot, ...}
|
|
12
|
-
- <
|
|
12
|
+
- <okstra root>/discovery/task-catalog.json
|
|
13
13
|
-> tasks[]: 각 task 의 stable identity 와 phase pointer
|
|
14
|
-
- <
|
|
14
|
+
- <okstra root>/discovery/latest-task.json
|
|
15
15
|
-> 가장 최근에 prepare/run 된 task 의 포인터
|
|
16
16
|
- <task-root>/task-manifest.json
|
|
17
17
|
-> 한 task 의 manifest (workflow.* phase 정보 포함)
|
|
@@ -23,10 +23,12 @@ import re
|
|
|
23
23
|
from pathlib import Path
|
|
24
24
|
from typing import Optional
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
LATEST_TASK_RELATIVE
|
|
29
|
-
|
|
26
|
+
from .dirs import (
|
|
27
|
+
DISCOVERY_RELATIVE,
|
|
28
|
+
LATEST_TASK_RELATIVE,
|
|
29
|
+
TASK_CATALOG_RELATIVE,
|
|
30
|
+
TASKS_RELATIVE,
|
|
31
|
+
)
|
|
30
32
|
|
|
31
33
|
|
|
32
34
|
class StateError(Exception):
|
|
@@ -93,7 +95,7 @@ def find_task_root(project_root: Path, task_key: str) -> Optional[Path]:
|
|
|
93
95
|
|
|
94
96
|
해석 우선순위:
|
|
95
97
|
1. task-catalog.json 의 같은 taskKey 항목의 taskRootPath
|
|
96
|
-
2. <PROJECT_ROOT
|
|
98
|
+
2. <PROJECT_ROOT>/<OKSTRA_DIR_NAME>/tasks/<slug-group>/<slug-id>/
|
|
97
99
|
|
|
98
100
|
어느 쪽도 디렉터리로 존재하지 않으면 None.
|
|
99
101
|
"""
|
|
@@ -4,6 +4,8 @@ from __future__ import annotations
|
|
|
4
4
|
import json
|
|
5
5
|
from datetime import datetime
|
|
6
6
|
from pathlib import Path
|
|
7
|
+
from okstra_project.dirs import OKSTRA_RELATIVE
|
|
8
|
+
|
|
7
9
|
from .blocks import na_block, usage_block
|
|
8
10
|
from .claude import claude_session_totals, find_claude_team_sessions
|
|
9
11
|
from .codex import codex_session_total, find_codex_session
|
|
@@ -255,7 +257,7 @@ def _infer_project_root(team_state_path: Path, state: dict) -> Path:
|
|
|
255
257
|
while p != p.parent:
|
|
256
258
|
if rel and (p / rel).is_dir():
|
|
257
259
|
return p
|
|
258
|
-
if (p /
|
|
260
|
+
if (p / OKSTRA_RELATIVE).is_dir():
|
|
259
261
|
return p
|
|
260
262
|
p = p.parent
|
|
261
263
|
raise SystemExit(f"could not infer project root from {team_state_path}")
|
|
@@ -6,7 +6,7 @@ description: Use when the user wants to generate a task brief file for okstra fr
|
|
|
6
6
|
# okstra-brief
|
|
7
7
|
|
|
8
8
|
Generate a single `task brief` markdown file under the okstra project domain
|
|
9
|
-
(`.
|
|
9
|
+
(`.okstra/briefs/<task-group>/<task-id>.md`) so it can be fed to
|
|
10
10
|
[`okstra-run`](../okstra-run/SKILL.md) as `--task-brief`.
|
|
11
11
|
|
|
12
12
|
**Purpose — pre-discovery artifact.** A brief is the **pre-discovery** stage
|
|
@@ -75,40 +75,40 @@ okstra-brief (codebase-scan variant)
|
|
|
75
75
|
|
|
76
76
|
## Artifact-home rule (okstra-wide)
|
|
77
77
|
|
|
78
|
-
**All okstra-generated artifacts live under `<PROJECT_ROOT>/.
|
|
78
|
+
**All okstra-generated artifacts live under `<PROJECT_ROOT>/.okstra/`.**
|
|
79
79
|
This includes briefs, run manifests, reports, worker results, status,
|
|
80
80
|
sessions, and any other run output.
|
|
81
81
|
|
|
82
|
-
All writes by this skill stay inside `.
|
|
82
|
+
All writes by this skill stay inside `.okstra/`. When domain
|
|
83
83
|
alignment (Step 3b / Step 4.5) yields a glossary change that the user
|
|
84
84
|
approves inline, the change is written to:
|
|
85
85
|
|
|
86
|
-
- `<PROJECT_ROOT>/.
|
|
86
|
+
- `<PROJECT_ROOT>/.okstra/glossary.md` — okstra's internal
|
|
87
87
|
glossary. Created if absent; appended to a `## Glossary` section
|
|
88
88
|
otherwise.
|
|
89
89
|
|
|
90
|
-
Paths outside `<PROJECT_ROOT>/.
|
|
90
|
+
Paths outside `<PROJECT_ROOT>/.okstra/**` are not okstra
|
|
91
91
|
memory. This skill reads them only when the reporter explicitly cited them
|
|
92
92
|
as source material, and never writes them. Decision files (when raised by
|
|
93
93
|
`implementation-planning`) also live inside okstra's subtree at
|
|
94
|
-
`<PROJECT_ROOT>/.
|
|
94
|
+
`<PROJECT_ROOT>/.okstra/decisions/<NNNN>-<slug>.md`.
|
|
95
95
|
|
|
96
96
|
## Authority files
|
|
97
97
|
|
|
98
|
-
- `<PROJECT_ROOT>/.
|
|
98
|
+
- `<PROJECT_ROOT>/.okstra/project.json` — must exist; if not,
|
|
99
99
|
ask the user to run `okstra-setup` first.
|
|
100
|
-
- `<PROJECT_ROOT>/.
|
|
100
|
+
- `<PROJECT_ROOT>/.okstra/briefs/<task-group>/<ticket-id>-<file-title>.md`
|
|
101
101
|
(single / parent) or
|
|
102
|
-
`<PROJECT_ROOT>/.
|
|
102
|
+
`<PROJECT_ROOT>/.okstra/briefs/<task-group>/sub/<ticket-id>-<file-title>.md`
|
|
103
103
|
(tracker child ticket; at depth N, `sub/` is nested N times) — output
|
|
104
104
|
location for this skill. `<ticket-id>` is the raw issue-id when the source
|
|
105
105
|
is a tracker, otherwise free-text supplied by the user. `<file-title>` is
|
|
106
106
|
auto-slugified from the tracker title, otherwise from user input.
|
|
107
107
|
- okstra-internal glossary (write target of Step 4.5):
|
|
108
|
-
`<PROJECT_ROOT>/.
|
|
108
|
+
`<PROJECT_ROOT>/.okstra/glossary.md`. Created if absent.
|
|
109
109
|
- okstra-internal decisions (write target of `implementation-planning`
|
|
110
110
|
via `implementation`):
|
|
111
|
-
`<PROJECT_ROOT>/.
|
|
111
|
+
`<PROJECT_ROOT>/.okstra/decisions/<NNNN>-<slug>.md`.
|
|
112
112
|
|
|
113
113
|
## Step 0: Resolve project root
|
|
114
114
|
|
|
@@ -255,7 +255,7 @@ Order of operations:
|
|
|
255
255
|
visited set is per-run and disappears between invocations. When this
|
|
256
256
|
skill runs again over a tree that already has briefs on disk, it MUST
|
|
257
257
|
reseed the visited set by scanning
|
|
258
|
-
`<PROJECT_ROOT>/.
|
|
258
|
+
`<PROJECT_ROOT>/.okstra/briefs/<task-group>/` recursively
|
|
259
259
|
and reading each brief's frontmatter `ticket-id` + `source-type`.
|
|
260
260
|
Reseed precedence:
|
|
261
261
|
1. On-disk frontmatter (`ticket-id` ≠ "") populates visited entries
|
|
@@ -356,10 +356,10 @@ preflight), reuse that value silently — do NOT ask again.
|
|
|
356
356
|
Final brief path rule (fixed; one extra `sub/` per depth):
|
|
357
357
|
|
|
358
358
|
```
|
|
359
|
-
depth 0 (parent / single) : <PROJECT_ROOT>/.
|
|
360
|
-
depth 1 (child) : <PROJECT_ROOT>/.
|
|
361
|
-
depth 2 (grandchild) : <PROJECT_ROOT>/.
|
|
362
|
-
depth N : <PROJECT_ROOT>/.
|
|
359
|
+
depth 0 (parent / single) : <PROJECT_ROOT>/.okstra/briefs/<task-group>/<ticket-id>-<file-title>.md
|
|
360
|
+
depth 1 (child) : <PROJECT_ROOT>/.okstra/briefs/<task-group>/sub/<ticket-id>-<file-title>.md
|
|
361
|
+
depth 2 (grandchild) : <PROJECT_ROOT>/.okstra/briefs/<task-group>/sub/sub/<ticket-id>-<file-title>.md
|
|
362
|
+
depth N : <PROJECT_ROOT>/.okstra/briefs/<task-group>/<sub/ nested N times>/<ticket-id>-<file-title>.md
|
|
363
363
|
```
|
|
364
364
|
|
|
365
365
|
`<sub/ nested N times>` is a meta-notation; the actual path concatenates
|
|
@@ -429,8 +429,8 @@ Read in this order — absent files are the normal state; skip silently and
|
|
|
429
429
|
never error:
|
|
430
430
|
|
|
431
431
|
1. **okstra-internal (authoritative)** — always check first:
|
|
432
|
-
- `<PROJECT_ROOT>/.
|
|
433
|
-
- `<PROJECT_ROOT>/.
|
|
432
|
+
- `<PROJECT_ROOT>/.okstra/glossary.md` if present
|
|
433
|
+
- `<PROJECT_ROOT>/.okstra/decisions/` titles if present
|
|
434
434
|
2. **Explicit source material only** — if the reporter cited a path outside
|
|
435
435
|
okstra's subtree, read it as source evidence only; do not treat it as
|
|
436
436
|
okstra memory.
|
|
@@ -477,7 +477,7 @@ on a project-internal concrete object whenever possible.
|
|
|
477
477
|
knows to query the reporter directly rather than infer.
|
|
478
478
|
- See Step 4.5 for the approval-gated path to actually write the
|
|
479
479
|
okstra-internal glossary at
|
|
480
|
-
`<PROJECT_ROOT>/.
|
|
480
|
+
`<PROJECT_ROOT>/.okstra/glossary.md`.
|
|
481
481
|
|
|
482
482
|
Skip 3b/3c for purely external request material with no overlap to the
|
|
483
483
|
project's domain.
|
|
@@ -560,7 +560,7 @@ labels. Unlabelled augmentation is rejected as half-formed translation.
|
|
|
560
560
|
|---|---|---|
|
|
561
561
|
| `evidence-link` | Cross-reference / file path / symbol resolved from the source against the actual codebase. Pure fact, verified by `Read` / `Grep`. | Trust without verification. |
|
|
562
562
|
| `format-conversion` | Format-only transform (Jira ADF → Markdown, HTML → Markdown, etc.). Semantics unchanged. | Trust without verification. |
|
|
563
|
-
| `terminology-mapping` | Reporter's word mapped to an okstra-canonical term from Step 3b. | Verify against `.
|
|
563
|
+
| `terminology-mapping` | Reporter's word mapped to an okstra-canonical term from Step 3b. | Verify against `.okstra/glossary.md`; if mismatch, treat as a clarification candidate. |
|
|
564
564
|
| `intent-inference` | Reporter did NOT literally state this — the skill inferred meaning from context (e.g. classifying "가끔 안 됨" as "intermittent failure on a specific path"). Qualitative only — **never** invent quantitative thresholds (numbers, latencies, percentages, counts). | **Verify with the reporter before acting.** Auto-mirrored into `Open Questions`. |
|
|
565
565
|
|
|
566
566
|
**Inline form** — `> augmented: intent-inference — <one line>`.
|
|
@@ -617,7 +617,7 @@ Those belong to later okstra phases.
|
|
|
617
617
|
For each `conflict` / `fuzzy` term collected in Step 3b that *was not*
|
|
618
618
|
resolved by a budgeted Step 4 question, draft a glossary proposal and
|
|
619
619
|
offer it to the user. This step writes to the **okstra-internal
|
|
620
|
-
glossary** at `<PROJECT_ROOT>/.
|
|
620
|
+
glossary** at `<PROJECT_ROOT>/.okstra/glossary.md` only —
|
|
621
621
|
the skill does not write outside okstra's subtree.
|
|
622
622
|
|
|
623
623
|
> **Decision scope**: this skill does NOT evaluate decision candidates
|
|
@@ -627,12 +627,12 @@ the skill does not write outside okstra's subtree.
|
|
|
627
627
|
> goes to `Open Questions` with the `adr-candidate:` prefix and is
|
|
628
628
|
> evaluated by `implementation-planning` against the three-criteria
|
|
629
629
|
> gate. Approved decision files land at
|
|
630
|
-
> `<PROJECT_ROOT>/.
|
|
630
|
+
> `<PROJECT_ROOT>/.okstra/decisions/<NNNN>-<slug>.md`.
|
|
631
631
|
|
|
632
632
|
### 4.5a. Draft glossary proposals
|
|
633
633
|
|
|
634
634
|
- **Glossary entry**: `proposed glossary entry: <term> = <one-line definition>`
|
|
635
|
-
— destined for `<PROJECT_ROOT>/.
|
|
635
|
+
— destined for `<PROJECT_ROOT>/.okstra/glossary.md`.
|
|
636
636
|
|
|
637
637
|
### 4.5b. Approval flow (per entry)
|
|
638
638
|
|
|
@@ -652,7 +652,7 @@ the user sees the exact file that will be written), then `AskUserQuestion`:
|
|
|
652
652
|
Ask once per entry. Never batch. If the user picks `Apply`:
|
|
653
653
|
|
|
654
654
|
- Append the entry to
|
|
655
|
-
`<PROJECT_ROOT>/.
|
|
655
|
+
`<PROJECT_ROOT>/.okstra/glossary.md` under an existing
|
|
656
656
|
relevant heading, or create a `## Glossary` section if none exists. If
|
|
657
657
|
the file does not exist, create it with a `## Glossary` heading and
|
|
658
658
|
the entry. Preserve all existing content byte-for-byte.
|
|
@@ -662,12 +662,12 @@ Ask once per entry. Never batch. If the user picks `Apply`:
|
|
|
662
662
|
Regardless of Apply / Skip, record the outcome under `Augmentation >
|
|
663
663
|
Domain alignment`:
|
|
664
664
|
|
|
665
|
-
- `terminology-mapping: applied glossary: <term> → <PROJECT_ROOT>/.
|
|
665
|
+
- `terminology-mapping: applied glossary: <term> → <PROJECT_ROOT>/.okstra/glossary.md` or
|
|
666
666
|
- `terminology-mapping: skipped glossary: <term> = <definition>`
|
|
667
667
|
|
|
668
668
|
Decision candidates are recorded under `Open Questions` as
|
|
669
669
|
`adr-candidate: <topic>` (targeting
|
|
670
|
-
`<PROJECT_ROOT>/.
|
|
670
|
+
`<PROJECT_ROOT>/.okstra/decisions/`) — not here. This keeps
|
|
671
671
|
the brief itself self-documenting about what side effects it caused.
|
|
672
672
|
|
|
673
673
|
## Step 5: Write the brief(s)
|
|
@@ -865,10 +865,10 @@ started.
|
|
|
865
865
|
## Output Rules
|
|
866
866
|
|
|
867
867
|
- All okstra-generated artifacts live under
|
|
868
|
-
`<PROJECT_ROOT>/.
|
|
869
|
-
under `.
|
|
870
|
-
`.
|
|
871
|
-
- Paths outside `<PROJECT_ROOT>/.
|
|
868
|
+
`<PROJECT_ROOT>/.okstra/`. This skill's brief files go
|
|
869
|
+
under `.okstra/briefs/`; its glossary writes go to
|
|
870
|
+
`.okstra/glossary.md` (Step 4.5 approval flow).
|
|
871
|
+
- Paths outside `<PROJECT_ROOT>/.okstra/**` are read-only
|
|
872
872
|
source material only when explicitly cited by the reporter. This skill
|
|
873
873
|
never writes outside okstra's subtree.
|
|
874
874
|
- **Verbatim source**: never paraphrase, summarize, restructure, or reorder
|
|
@@ -19,19 +19,19 @@ user-invocable: false
|
|
|
19
19
|
### Default Location Rules
|
|
20
20
|
|
|
21
21
|
- AI documentation root: `.project-docs/`
|
|
22
|
-
- Project-level latest-task pointer: `.
|
|
23
|
-
- Project-level task catalog: `.
|
|
24
|
-
- okstra task root: `.
|
|
25
|
-
- Task path pattern: `.
|
|
22
|
+
- Project-level latest-task pointer: `.okstra/discovery/latest-task.json`
|
|
23
|
+
- Project-level task catalog: `.okstra/discovery/task-catalog.json`
|
|
24
|
+
- okstra task root: `.okstra/tasks/`
|
|
25
|
+
- Task path pattern: `.okstra/tasks/<task-group>/<task-id>/`
|
|
26
26
|
|
|
27
27
|
### Task Identification
|
|
28
28
|
1. If the user specifies the `task-manifest.json` path or the task root path, that path is used.
|
|
29
29
|
2. If the user specifies only the task key, the expected task root is calculated by converting the `task-group` and `task-id` to lowercase and applying the slug rule (`[^a-z0-9]+` → `-`), and the corresponding `task-manifest.json` is opened.
|
|
30
|
-
3. If the user attempts to find a task based on `task-group` + `task-id` or `task-id`, `.
|
|
30
|
+
3. If the user attempts to find a task based on `task-group` + `task-id` or `task-id`, `.okstra/discovery/task-catalog.json` is read to find candidates.
|
|
31
31
|
4. If multiple candidates are found based on `task-id` alone, the situation is ambiguous, so `task-group` or the full `taskKey` is required.
|
|
32
|
-
5. If the user has not provided an explicit task key/path, first read `.
|
|
32
|
+
5. If the user has not provided an explicit task key/path, first read `.okstra/discovery/latest-task.json` using the current-task convenience pointer.
|
|
33
33
|
6. If the latest-task pointer is missing or corrupted but the task catalog exists, list candidates from the catalog. Do not use the legacy `CLAUDE.md`, project guide, or task scan fallback.
|
|
34
|
-
7. If **neither** `latest-task.json` **nor** `task-catalog.json` exists, ABORT Phase 1 with `OKSTRA_CONTEXT_NOT_INITIALIZED`. Suggest the user run `/okstra-setup` and `/okstra-brief` to bootstrap the project. Do NOT crawl `.
|
|
34
|
+
7. If **neither** `latest-task.json` **nor** `task-catalog.json` exists, ABORT Phase 1 with `OKSTRA_CONTEXT_NOT_INITIALIZED`. Suggest the user run `/okstra-setup` and `/okstra-brief` to bootstrap the project. Do NOT crawl `.okstra/tasks/` directly — discovery pointers are the only supported entry path.
|
|
35
35
|
|
|
36
36
|
## Step 2: Open and Parse task-manifest.json
|
|
37
37
|
|