okstra 0.36.1 → 0.37.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.
Files changed (66) hide show
  1. package/README.kr.md +6 -6
  2. package/README.md +6 -6
  3. package/bin/okstra +4 -2
  4. package/docs/kr/architecture.md +29 -29
  5. package/docs/kr/cli.md +7 -6
  6. package/docs/pr-template-usage.md +2 -2
  7. package/docs/project-structure-overview.md +4 -4
  8. package/docs/superpowers/plans/2026-05-25-okstra-project-root-rename.md +159 -0
  9. package/docs/superpowers/plans/2026-05-26-wizard-3-option-picker.md +860 -0
  10. package/docs/task-process/common-flow.md +2 -2
  11. package/package.json +1 -1
  12. package/runtime/BUILD.json +2 -2
  13. package/runtime/agents/SKILL.md +2 -2
  14. package/runtime/agents/workers/claude-worker.md +1 -1
  15. package/runtime/prompts/profiles/_common-contract.md +6 -6
  16. package/runtime/prompts/profiles/_implementation-executor.md +2 -2
  17. package/runtime/prompts/profiles/_implementation-verifier.md +1 -1
  18. package/runtime/prompts/profiles/final-verification.md +1 -1
  19. package/runtime/prompts/profiles/implementation-planning.md +5 -5
  20. package/runtime/prompts/profiles/release-handoff.md +1 -1
  21. package/runtime/prompts/profiles/requirements-discovery.md +3 -3
  22. package/runtime/prompts/wizard/prompts.ko.json +80 -6
  23. package/runtime/python/lib/okstra/interactive.sh +2 -2
  24. package/runtime/python/lib/okstra/project-resolver.sh +1 -1
  25. package/runtime/python/lib/okstra/usage.sh +5 -5
  26. package/runtime/python/lib/okstra-ctl/cmd-rerun.sh +1 -1
  27. package/runtime/python/okstra_ctl/backfill.py +5 -3
  28. package/runtime/python/okstra_ctl/migrate.py +408 -0
  29. package/runtime/python/okstra_ctl/paths.py +12 -3
  30. package/runtime/python/okstra_ctl/pr_template.py +4 -2
  31. package/runtime/python/okstra_ctl/render.py +8 -6
  32. package/runtime/python/okstra_ctl/render_final_report.py +4 -1
  33. package/runtime/python/okstra_ctl/run.py +5 -5
  34. package/runtime/python/okstra_ctl/seeding.py +12 -6
  35. package/runtime/python/okstra_ctl/sequence.py +3 -1
  36. package/runtime/python/okstra_ctl/wizard.py +412 -77
  37. package/runtime/python/okstra_ctl/worktree.py +8 -6
  38. package/runtime/python/okstra_project/__init__.py +35 -5
  39. package/runtime/python/okstra_project/dirs.py +67 -0
  40. package/runtime/python/okstra_project/resolver.py +8 -8
  41. package/runtime/python/okstra_project/state.py +11 -9
  42. package/runtime/python/okstra_token_usage/collect.py +3 -1
  43. package/runtime/python/okstra_token_usage/report.py +6 -2
  44. package/runtime/skills/okstra-brief/SKILL.md +30 -30
  45. package/runtime/skills/okstra-context-loader/SKILL.md +7 -7
  46. package/runtime/skills/okstra-inspect/SKILL.md +25 -25
  47. package/runtime/skills/okstra-run/templates/pr-body.template.md +1 -1
  48. package/runtime/skills/okstra-schedule/SKILL.md +7 -7
  49. package/runtime/skills/okstra-setup/SKILL.md +8 -8
  50. package/runtime/templates/okstra.CLAUDE.md +4 -4
  51. package/runtime/templates/reports/brief.template.md +5 -5
  52. package/runtime/templates/reports/task-brief.template.md +1 -1
  53. package/runtime/validators/lib/fixtures.sh +2 -2
  54. package/runtime/validators/lib/paths.sh +9 -3
  55. package/runtime/validators/validate-brief.py +2 -2
  56. package/runtime/validators/validate-brief.sh +1 -1
  57. package/runtime/validators/validate-run.py +3 -1
  58. package/runtime/validators/validate-workflow.sh +2 -2
  59. package/src/check-project.mjs +3 -3
  60. package/src/config.mjs +6 -5
  61. package/src/install.mjs +5 -5
  62. package/src/migrate.mjs +163 -0
  63. package/src/okstra-dirs.mjs +37 -0
  64. package/src/paths.mjs +17 -0
  65. package/src/setup.mjs +8 -4
  66. 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. `.project-docs/okstra/tasks/<task-id>/runs/...`).
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 `.project-docs/okstra/project.json` —
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 `.project-docs/okstra/project.json`.
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 `.project-docs/okstra/project.json`.
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 / ".project-docs" / "okstra" / "project.json"
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 `.project-docs/okstra/CLAUDE.md` symlink + `<worktree>/CLAUDE.md`
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>/.project-docs/okstra/project.json` 을 권위 소스로 삼아
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
- "project_json_path",
31
- "resolve_project_root",
32
- "upsert_project_json",
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
- PROJECT_JSON_RELATIVE = Path(".project-docs/okstra/project.json")
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 부터 위로 올라가며 `.project-docs/okstra/project.json` 보유 디렉터리를 찾는다."""
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 또는 그 조상 중 `.project-docs/okstra/project.json` 보유 디렉터리.
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 .project-docs/okstra/project.json at or above cwd), "
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
- - <PROJECT_ROOT>/.project-docs/okstra/project.json
9
+ 권위 파일 매핑 (okstra root = <PROJECT_ROOT>/<OKSTRA_DIR_NAME>):
10
+ - <okstra root>/project.json
11
11
  -> {projectId, projectRoot, ...}
12
- - <PROJECT_ROOT>/.project-docs/okstra/discovery/task-catalog.json
12
+ - <okstra root>/discovery/task-catalog.json
13
13
  -> tasks[]: 각 task 의 stable identity 와 phase pointer
14
- - <PROJECT_ROOT>/.project-docs/okstra/discovery/latest-task.json
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
- DISCOVERY_RELATIVE = Path(".project-docs/okstra/discovery")
27
- TASK_CATALOG_RELATIVE = DISCOVERY_RELATIVE / "task-catalog.json"
28
- LATEST_TASK_RELATIVE = DISCOVERY_RELATIVE / "latest-task.json"
29
- TASKS_RELATIVE = Path(".project-docs/okstra/tasks")
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>/.project-docs/okstra/tasks/<slug-group>/<slug-id>/
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 / ".project-docs").is_dir():
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}")
@@ -125,13 +125,17 @@ def populate_data_token_cells(data_path: Path, team_state: dict) -> int:
125
125
  changes += 4
126
126
 
127
127
  # Execution Status by Agent — per-row token / cost / duration.
128
- lead_entry = team_state.get("lead") or {}
128
+ # `collect.py` writes lead usage to `team_state["leadUsage"]` (flat
129
+ # usage_block), while worker usage lives at `worker["usage"]`. Wrap
130
+ # the lead block so `_populate_execution_row` can read both shapes
131
+ # via the same `source["usage"]` path.
132
+ lead_source = {"usage": team_state.get("leadUsage") or {}}
129
133
  workers = team_state.get("workers") or []
130
134
  rows = data.get("executionStatus") or []
131
135
  for row in rows:
132
136
  role = row.get("role", "")
133
137
  if "lead" in role.lower():
134
- _populate_execution_row(row, lead_entry)
138
+ _populate_execution_row(row, lead_source)
135
139
  changes += 1
136
140
  continue
137
141
  for worker in workers:
@@ -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
- (`.project-docs/okstra/briefs/<task-group>/<task-id>.md`) so it can be fed to
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>/.project-docs/okstra/`.**
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 `.project-docs/okstra/`. When domain
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>/.project-docs/okstra/glossary.md` — okstra's internal
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>/.project-docs/okstra/**` are not okstra
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>/.project-docs/okstra/decisions/<NNNN>-<slug>.md`.
94
+ `<PROJECT_ROOT>/.okstra/decisions/<NNNN>-<slug>.md`.
95
95
 
96
96
  ## Authority files
97
97
 
98
- - `<PROJECT_ROOT>/.project-docs/okstra/project.json` — must exist; if not,
98
+ - `<PROJECT_ROOT>/.okstra/project.json` — must exist; if not,
99
99
  ask the user to run `okstra-setup` first.
100
- - `<PROJECT_ROOT>/.project-docs/okstra/briefs/<task-group>/<ticket-id>-<file-title>.md`
100
+ - `<PROJECT_ROOT>/.okstra/briefs/<task-group>/<ticket-id>-<file-title>.md`
101
101
  (single / parent) or
102
- `<PROJECT_ROOT>/.project-docs/okstra/briefs/<task-group>/sub/<ticket-id>-<file-title>.md`
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>/.project-docs/okstra/glossary.md`. Created if absent.
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>/.project-docs/okstra/decisions/<NNNN>-<slug>.md`.
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>/.project-docs/okstra/briefs/<task-group>/` recursively
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>/.project-docs/okstra/briefs/<task-group>/<ticket-id>-<file-title>.md
360
- depth 1 (child) : <PROJECT_ROOT>/.project-docs/okstra/briefs/<task-group>/sub/<ticket-id>-<file-title>.md
361
- depth 2 (grandchild) : <PROJECT_ROOT>/.project-docs/okstra/briefs/<task-group>/sub/sub/<ticket-id>-<file-title>.md
362
- depth N : <PROJECT_ROOT>/.project-docs/okstra/briefs/<task-group>/<sub/ nested N times>/<ticket-id>-<file-title>.md
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>/.project-docs/okstra/glossary.md` if present
433
- - `<PROJECT_ROOT>/.project-docs/okstra/decisions/` titles if present
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>/.project-docs/okstra/glossary.md`.
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 `.project-docs/okstra/glossary.md`; if mismatch, treat as a clarification candidate. |
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>/.project-docs/okstra/glossary.md` only —
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>/.project-docs/okstra/decisions/<NNNN>-<slug>.md`.
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>/.project-docs/okstra/glossary.md`.
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>/.project-docs/okstra/glossary.md` under an existing
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>/.project-docs/okstra/glossary.md` or
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>/.project-docs/okstra/decisions/`) — not here. This keeps
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>/.project-docs/okstra/`. This skill's brief files go
869
- under `.project-docs/okstra/briefs/`; its glossary writes go to
870
- `.project-docs/okstra/glossary.md` (Step 4.5 approval flow).
871
- - Paths outside `<PROJECT_ROOT>/.project-docs/okstra/**` are read-only
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: `.project-docs/okstra/discovery/latest-task.json`
23
- - Project-level task catalog: `.project-docs/okstra/discovery/task-catalog.json`
24
- - okstra task root: `.project-docs/okstra/tasks/`
25
- - Task path pattern: `.project-docs/okstra/tasks/<task-group>/<task-id>/`
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`, `.project-docs/okstra/discovery/task-catalog.json` is read to find candidates.
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 `.project-docs/okstra/discovery/latest-task.json` using the current-task convenience pointer.
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 `.project-docs/okstra/tasks/` directly — discovery pointers are the only supported entry path.
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