okstra 0.34.1 โ 0.36.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 +26 -16
- package/README.md +26 -16
- package/docs/kr/architecture.md +59 -45
- package/docs/kr/cli.md +61 -18
- package/docs/pr-template-usage.md +65 -0
- package/docs/project-structure-overview.md +358 -354
- package/docs/superpowers/plans/2026-05-12-ticket-id-in-reports.md +1 -1
- package/docs/superpowers/plans/2026-05-14-convergence-queue-pruning.md +1 -1
- package/docs/superpowers/plans/2026-05-17-dual-format-final-report.md +1 -1
- package/docs/superpowers/plans/2026-05-20-final-report-language.md +1501 -0
- package/docs/superpowers/plans/2026-05-20-implementation-planning-multi-stage.md +1267 -0
- package/docs/superpowers/plans/2026-05-20-okstra-run-prompt-sot-b1.md +1007 -0
- package/docs/superpowers/plans/2026-05-20-wizard-messages-json-sot.md +720 -0
- package/docs/superpowers/plans/2026-05-20-wizard-prompt-json-sot-a1.md +681 -0
- package/docs/superpowers/plans/2026-05-21-improvement-discovery-task-type.md +1691 -0
- package/docs/superpowers/specs/2026-05-20-final-report-language-design.md +383 -0
- package/docs/superpowers/specs/2026-05-20-implementation-planning-multi-stage-design.md +320 -0
- package/docs/superpowers/specs/2026-05-20-okstra-run-prompt-sot-design.md +299 -0
- package/docs/superpowers/specs/2026-05-21-improvement-discovery-task-type-design.md +335 -0
- package/docs/task-process/README.md +74 -0
- package/docs/task-process/common-flow.md +166 -0
- package/docs/task-process/error-analysis.md +101 -0
- package/docs/task-process/final-verification.md +167 -0
- package/docs/task-process/implementation-planning.md +128 -0
- package/docs/task-process/implementation.md +149 -0
- package/docs/task-process/release-handoff.md +206 -0
- package/docs/task-process/requirements-discovery.md +115 -0
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/SKILL.md +12 -2
- package/runtime/agents/workers/claude-worker.md +26 -0
- package/runtime/agents/workers/codex-worker.md +27 -1
- package/runtime/agents/workers/gemini-worker.md +27 -1
- package/runtime/agents/workers/report-writer-worker.md +8 -1
- package/runtime/bin/okstra-central.sh +6 -6
- package/runtime/bin/okstra-codex-exec.sh +49 -28
- package/runtime/bin/okstra-gemini-exec.sh +39 -21
- package/runtime/bin/okstra-render-final-report.py +13 -2
- package/runtime/bin/okstra-wrapper-status.py +155 -0
- package/runtime/bin/okstra.sh +2 -2
- package/runtime/prompts/profiles/_common-contract.md +11 -6
- package/runtime/prompts/profiles/error-analysis.md +3 -7
- package/runtime/prompts/profiles/implementation-planning.md +22 -21
- package/runtime/prompts/profiles/implementation.md +28 -11
- package/runtime/prompts/profiles/improvement-discovery.md +42 -0
- package/runtime/prompts/profiles/kr/_common-contract.md +92 -0
- package/runtime/prompts/profiles/kr/error-analysis.md +36 -0
- package/runtime/prompts/profiles/kr/final-verification.md +48 -0
- package/runtime/prompts/profiles/kr/implementation-planning.md +90 -0
- package/runtime/prompts/profiles/kr/implementation.md +144 -0
- package/runtime/prompts/profiles/kr/improvement-discovery.md +42 -0
- package/runtime/prompts/profiles/kr/release-handoff.md +104 -0
- package/runtime/prompts/profiles/kr/requirements-discovery.md +42 -0
- package/runtime/prompts/profiles/release-handoff.md +1 -1
- package/runtime/prompts/profiles/requirements-discovery.md +8 -12
- package/runtime/prompts/wizard/prompts.ko.json +230 -0
- package/runtime/python/lib/okstra/cli.sh +2 -49
- package/runtime/python/lib/okstra/globals.sh +21 -21
- package/runtime/python/lib/okstra/interactive.sh +7 -7
- package/runtime/python/okstra_ctl/clarification_items.py +3 -9
- package/runtime/python/okstra_ctl/consumers.py +53 -0
- package/runtime/python/okstra_ctl/final_report_schema.py +0 -7
- package/runtime/python/okstra_ctl/i18n.py +73 -0
- package/runtime/python/okstra_ctl/improvement_lenses.py +44 -0
- package/runtime/python/okstra_ctl/index.py +1 -1
- package/runtime/python/okstra_ctl/paths.py +23 -20
- package/runtime/python/okstra_ctl/render.py +147 -202
- package/runtime/python/okstra_ctl/render_final_report.py +53 -10
- package/runtime/python/okstra_ctl/run.py +292 -107
- package/runtime/python/okstra_ctl/run_context.py +22 -0
- package/runtime/python/okstra_ctl/seeding.py +186 -0
- package/runtime/python/okstra_ctl/wizard.py +348 -127
- package/runtime/python/okstra_ctl/workflow.py +21 -2
- package/runtime/python/okstra_ctl/worktree.py +54 -1
- package/runtime/python/okstra_project/resolver.py +4 -3
- package/runtime/python/okstra_token_usage/report.py +2 -2
- package/runtime/schemas/final-report-v1.0.schema.json +22 -16
- package/runtime/skills/okstra-brief/SKILL.md +124 -31
- package/runtime/skills/okstra-convergence/SKILL.md +2 -3
- package/runtime/skills/okstra-report-writer/SKILL.md +35 -15
- package/runtime/skills/okstra-run/SKILL.md +5 -4
- package/runtime/skills/okstra-schedule/SKILL.md +4 -4
- package/runtime/skills/okstra-setup/SKILL.md +27 -0
- package/runtime/skills/okstra-team-contract/SKILL.md +1 -1
- package/runtime/templates/okstra.CLAUDE.md +104 -0
- package/runtime/templates/reports/final-report.template.md +93 -98
- package/runtime/templates/reports/i18n/en.json +135 -0
- package/runtime/templates/reports/i18n/ko.json +135 -0
- package/runtime/templates/reports/implementation-planning-input.template.md +18 -0
- package/runtime/templates/reports/improvement-discovery-input.template.md +78 -0
- package/runtime/templates/reports/task-brief.template.md +2 -2
- package/runtime/validators/lib/fixtures.sh +30 -0
- package/runtime/validators/lib/runners.sh +1 -1
- package/runtime/validators/validate-implementation-plan-stages.py +211 -0
- package/runtime/validators/validate-run.py +121 -26
- package/runtime/validators/validate-workflow.sh +2 -2
- package/runtime/validators/validate_improvement_report.py +275 -0
- package/src/config.mjs +18 -0
- package/src/install.mjs +41 -14
- package/src/setup.mjs +133 -1
- package/src/uninstall.mjs +21 -1
|
@@ -50,6 +50,15 @@ def _task_lock_path(task_key: str) -> Path:
|
|
|
50
50
|
return locks / f"{safe}.lock"
|
|
51
51
|
|
|
52
52
|
|
|
53
|
+
def _consumers_lock_path(plan_task_key: str) -> Path:
|
|
54
|
+
"""plan-task-key ๋ณ consumers.jsonl append mutex."""
|
|
55
|
+
home = _okstra_home()
|
|
56
|
+
locks = home / ".locks"
|
|
57
|
+
locks.mkdir(parents=True, exist_ok=True)
|
|
58
|
+
safe = plan_task_key.replace("/", "_").replace(":", "_")
|
|
59
|
+
return locks / f"{safe}.consumers.lock"
|
|
60
|
+
|
|
61
|
+
|
|
53
62
|
@contextmanager
|
|
54
63
|
def task_mutex(task_key: str) -> Iterator[None]:
|
|
55
64
|
"""task-key per-process mutex. ๋์ ํธ์ถ์ ๋ฝ ์์์ ์ง๋ ฌํ๋๋ค."""
|
|
@@ -63,6 +72,19 @@ def task_mutex(task_key: str) -> Iterator[None]:
|
|
|
63
72
|
fcntl.flock(f.fileno(), fcntl.LOCK_UN)
|
|
64
73
|
|
|
65
74
|
|
|
75
|
+
@contextmanager
|
|
76
|
+
def consumers_mutex(plan_task_key: str) -> Iterator[None]:
|
|
77
|
+
"""plan-task-key ๋ณ consumers.jsonl append mutex."""
|
|
78
|
+
path = _consumers_lock_path(plan_task_key)
|
|
79
|
+
path.touch(exist_ok=True)
|
|
80
|
+
with path.open("r+") as f:
|
|
81
|
+
fcntl.flock(f.fileno(), fcntl.LOCK_EX)
|
|
82
|
+
try:
|
|
83
|
+
yield
|
|
84
|
+
finally:
|
|
85
|
+
fcntl.flock(f.fileno(), fcntl.LOCK_UN)
|
|
86
|
+
|
|
87
|
+
|
|
66
88
|
def _atomic_write_json(path: Path, payload: dict) -> None:
|
|
67
89
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
68
90
|
tmp = path.with_suffix(path.suffix + ".tmp")
|
|
@@ -23,6 +23,14 @@ class SettingsLinkError(Exception):
|
|
|
23
23
|
"""`<project>/.claude/settings.local.json` symlink provisioning ์คํจ."""
|
|
24
24
|
|
|
25
25
|
|
|
26
|
+
class ClaudeMdLinkError(Exception):
|
|
27
|
+
"""`<project>/.project-docs/okstra/CLAUDE.md` symlink or import-block provisioning ์คํจ."""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class AgentsMdLinkError(Exception):
|
|
31
|
+
"""`<project>/AGENTS.md` symlink provisioning ์คํจ."""
|
|
32
|
+
|
|
33
|
+
|
|
26
34
|
def installed_version() -> str:
|
|
27
35
|
"""Read the version stamp written by `okstra install` to `~/.okstra/version`.
|
|
28
36
|
|
|
@@ -195,3 +203,181 @@ def _backup_and_replace(target: Path, template: Path) -> None:
|
|
|
195
203
|
raise SettingsLinkError(
|
|
196
204
|
f"failed to create symlink {target} -> {template} after backup: {exc}"
|
|
197
205
|
) from exc
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def installed_claude_md_template_path() -> Path:
|
|
209
|
+
"""okstra install ์ด ๋ง๋ค์ด ๋ okstra.CLAUDE.md template ์ ์ ๋๊ฒฝ๋ก."""
|
|
210
|
+
return _okstra_home() / "templates" / "okstra.CLAUDE.md"
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
_CLAUDE_MD_SYMLINK_REL = Path(".project-docs") / "okstra" / "CLAUDE.md"
|
|
214
|
+
_CLAUDE_MD_IMPORT_LINE = "@.project-docs/okstra/CLAUDE.md"
|
|
215
|
+
_CLAUDE_MD_MARKER_BEGIN = (
|
|
216
|
+
"<!-- okstra:claude-md:begin (managed by okstra setup โ do not edit) -->"
|
|
217
|
+
)
|
|
218
|
+
_CLAUDE_MD_MARKER_END = "<!-- okstra:claude-md:end -->"
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def ensure_project_claude_md(*, project_root: Path) -> Optional[Path]:
|
|
222
|
+
"""`<project_root>/.project-docs/okstra/CLAUDE.md` ๋ฅผ `~/.okstra/templates/okstra.CLAUDE.md`
|
|
223
|
+
๋ก ๊ฐ๋ฆฌํค๋ symlink ๋ก provisioning ํ๊ณ , `<project_root>/CLAUDE.md` ์
|
|
224
|
+
`@.project-docs/okstra/CLAUDE.md` import block ์ ๋ฉฑ๋ฑํ๊ฒ ์ฃผ์
ํ๋ค.
|
|
225
|
+
|
|
226
|
+
Claude Code ๊ฐ ํด๋น ํ๋ก์ ํธ์์ host ์ธ์
์ผ๋ก ์คํ๋ ๋
|
|
227
|
+
`<project_root>/CLAUDE.md` ๊ฐ ์๋ ๋ก๋๋๋ฏ๋ก, okstra ๊ฐ ๊ด๋ฆฌํ๋ ๋ณธ๋ฌธ
|
|
228
|
+
(slash command catalog, workflow guidance, ...) ๋ ๊ฐ์ด surface ๋๋ค.
|
|
229
|
+
|
|
230
|
+
๋ฐํ๊ฐ:
|
|
231
|
+
- symlink Path: ์ ๊ท ์์ฑ๋๊ฑฐ๋ ์ด๋ฏธ ์ฌ๋ฐ๋ฅธ target ์ ๊ฐ๋ฆฌํค๊ณ ์์ ๋.
|
|
232
|
+
- None: install ์ด ์์ง CLAUDE.md template ์ ๊น์ง ์์์ ๋ (๊ตฌ๋ฒ์
|
|
233
|
+
okstra install). ์์์์ ๊ฒฝ๊ณ ๋ก ํ๋ ค๋ณด๋ธ๋ค.
|
|
234
|
+
|
|
235
|
+
์์ ํธ์ถ์๋ `ClaudeMdLinkError` ๋ง ์ฒ๋ฆฌํ๋ฉด ๋๋ค.
|
|
236
|
+
"""
|
|
237
|
+
project_root = Path(project_root)
|
|
238
|
+
template = installed_claude_md_template_path()
|
|
239
|
+
if not template.exists():
|
|
240
|
+
return None
|
|
241
|
+
|
|
242
|
+
target = project_root / _CLAUDE_MD_SYMLINK_REL
|
|
243
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
244
|
+
|
|
245
|
+
if target.is_symlink():
|
|
246
|
+
try:
|
|
247
|
+
current = os.readlink(target)
|
|
248
|
+
except OSError as exc:
|
|
249
|
+
raise ClaudeMdLinkError(
|
|
250
|
+
f"failed to read existing symlink {target}: {exc}"
|
|
251
|
+
) from exc
|
|
252
|
+
current_path = Path(current)
|
|
253
|
+
if current_path == template or (target.parent / current_path).resolve() == template.resolve():
|
|
254
|
+
_ensure_claude_md_import(project_root)
|
|
255
|
+
return target
|
|
256
|
+
_backup_and_replace_claude_md(target, template)
|
|
257
|
+
_ensure_claude_md_import(project_root)
|
|
258
|
+
return target
|
|
259
|
+
|
|
260
|
+
if target.exists():
|
|
261
|
+
_backup_and_replace_claude_md(target, template)
|
|
262
|
+
_ensure_claude_md_import(project_root)
|
|
263
|
+
return target
|
|
264
|
+
|
|
265
|
+
try:
|
|
266
|
+
target.symlink_to(template)
|
|
267
|
+
except OSError as exc:
|
|
268
|
+
raise ClaudeMdLinkError(
|
|
269
|
+
f"failed to create symlink {target} -> {template}: {exc}"
|
|
270
|
+
) from exc
|
|
271
|
+
_ensure_claude_md_import(project_root)
|
|
272
|
+
return target
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def _ensure_claude_md_import(project_root: Path) -> bool:
|
|
276
|
+
"""`<project_root>/CLAUDE.md` ์ import block ์ด ์์ผ๋ฉด append, ์์ผ๋ฉด no-op.
|
|
277
|
+
|
|
278
|
+
๋ฐํ: ์๋ก ์ฃผ์
ํ์ ๋ True, ์ด๋ฏธ ์์์ ๋ False.
|
|
279
|
+
"""
|
|
280
|
+
claude_md = project_root / "CLAUDE.md"
|
|
281
|
+
block = f"{_CLAUDE_MD_MARKER_BEGIN}\n{_CLAUDE_MD_IMPORT_LINE}\n{_CLAUDE_MD_MARKER_END}\n"
|
|
282
|
+
|
|
283
|
+
try:
|
|
284
|
+
existing = claude_md.read_text(encoding="utf-8")
|
|
285
|
+
except FileNotFoundError:
|
|
286
|
+
try:
|
|
287
|
+
claude_md.write_text(block, encoding="utf-8")
|
|
288
|
+
except OSError as exc:
|
|
289
|
+
raise ClaudeMdLinkError(
|
|
290
|
+
f"failed to create {claude_md}: {exc}"
|
|
291
|
+
) from exc
|
|
292
|
+
return True
|
|
293
|
+
except OSError as exc:
|
|
294
|
+
raise ClaudeMdLinkError(f"failed to read {claude_md}: {exc}") from exc
|
|
295
|
+
|
|
296
|
+
if _CLAUDE_MD_MARKER_BEGIN in existing and _CLAUDE_MD_MARKER_END in existing:
|
|
297
|
+
return False
|
|
298
|
+
|
|
299
|
+
separator = _block_separator_for(existing)
|
|
300
|
+
try:
|
|
301
|
+
claude_md.write_text(existing + separator + block, encoding="utf-8")
|
|
302
|
+
except OSError as exc:
|
|
303
|
+
raise ClaudeMdLinkError(f"failed to update {claude_md}: {exc}") from exc
|
|
304
|
+
return True
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def _block_separator_for(existing: str) -> str:
|
|
308
|
+
"""๊ธฐ์กด ํ์ผ ๋๊ณผ import block ์ฌ์ด์ ๊ณต๋ฐฑ ๊ฒฐ์ โ ๊ฐ๋
์ฑ ๋ณด์ฅ ๋ชฉ์ ."""
|
|
309
|
+
if not existing:
|
|
310
|
+
return ""
|
|
311
|
+
if existing.endswith("\n\n"):
|
|
312
|
+
return ""
|
|
313
|
+
if existing.endswith("\n"):
|
|
314
|
+
return "\n"
|
|
315
|
+
return "\n\n"
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def _backup_and_replace_claude_md(target: Path, template: Path) -> None:
|
|
319
|
+
"""๊ธฐ์กด ํ์ผ/์ฌ๋ณผ๋ฆญ๋งํฌ๋ฅผ timestamped backup ์ผ๋ก ์ฎ๊ธฐ๊ณ ์ symlink ์์ฑ."""
|
|
320
|
+
stamp = time.strftime("%Y%m%d-%H%M%S")
|
|
321
|
+
backup = target.with_name(f"{target.name}.bak.{stamp}")
|
|
322
|
+
try:
|
|
323
|
+
target.rename(backup)
|
|
324
|
+
except OSError as exc:
|
|
325
|
+
raise ClaudeMdLinkError(
|
|
326
|
+
f"failed to back up existing {target} to {backup}: {exc}"
|
|
327
|
+
) from exc
|
|
328
|
+
try:
|
|
329
|
+
target.symlink_to(template)
|
|
330
|
+
except OSError as exc:
|
|
331
|
+
raise ClaudeMdLinkError(
|
|
332
|
+
f"failed to create symlink {target} -> {template} after backup: {exc}"
|
|
333
|
+
) from exc
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def ensure_project_agents_md(*, project_root: Path) -> Optional[Path]:
|
|
337
|
+
"""`<project_root>/AGENTS.md` ๊ฐ ์์ ๋๋ง `~/.okstra/templates/okstra.CLAUDE.md`
|
|
338
|
+
๋ก์ ์ฌ๋งํฌ๋ก ์์ฑํ๋ค.
|
|
339
|
+
|
|
340
|
+
AGENTS.md ๋ codex / aider / ๊ธฐํ agent ๊ฐ ์ฝ๋ ํ์ผ์ด๊ณ @import ๊ฐ์
|
|
341
|
+
๋ถ๋ถ ํฌํจ ๋ฉ์ปค๋์ฆ์ด ์์ด, ํ์ผ ์ ์ฒด ๋ด์ฉ์ด ๊ณง agent ๊ฐ ๋ณด๋ ์ฝํ
์ธ ๊ฐ
|
|
342
|
+
๋๋ค. ๊ทธ๋์ CLAUDE.md (`@.project-docs/okstra/CLAUDE.md` ๋ง์ปค ๋ธ๋ก
|
|
343
|
+
์ฃผ์
) ์ ๋ฌ๋ฆฌ "AGENTS.md ๊ฐ ๋น์ด ์์ ๋๋ง ๋ง๋ค๊ณ , ์กด์ฌํ๋ฉด ์ ๋
|
|
344
|
+
๊ฑด๋๋ฆฌ์ง ์๋" ์ ์ฑ
์ ์ฌ์ฉํ๋ค โ ์ฌ์ฉ์๊ฐ ์ง์ ์์ฑํ AGENTS.md ๋ฅผ
|
|
345
|
+
๋ฎ์ด์ฐ์ง ์๋๋ค.
|
|
346
|
+
|
|
347
|
+
๋ฐํ๊ฐ:
|
|
348
|
+
- target Path: ์ ๊ท ์ฌ๋งํฌ ์์ฑ, ๋๋ ์ด๋ฏธ ์ฐ๋ฆฌ ํ
ํ๋ฆฟ์ ๊ฐ๋ฆฌํค๊ณ
|
|
349
|
+
์๋ ์ฌ๋งํฌ๊ฐ idempotent ํ๊ฒ ํ์ธ๋ ๊ฒฝ์ฐ.
|
|
350
|
+
- None: install ์ด ์์ง CLAUDE.md template ์ ๊น์ง ์์๊ฑฐ๋,
|
|
351
|
+
AGENTS.md ๊ฐ ์ด๋ฏธ ์ฌ์ฉ์ ์ฝํ
์ธ (regular file) ๊ฑฐ๋ ์ฐ๋ฆฌ๊ฐ
|
|
352
|
+
๋ง๋ค์ง ์์ ๋ค๋ฅธ ์ฌ๋งํฌ๋ก ์กด์ฌํ๋ ๊ฒฝ์ฐ. ํ์ ๋ ์ผ์ด์ค๋
|
|
353
|
+
์ฌ์ฉ์์ ์๋๋ก ๊ฐ์ฃผํ๊ณ ๊ฑด๋๋ฆฌ์ง ์๋๋ค.
|
|
354
|
+
|
|
355
|
+
์์ ํธ์ถ์๋ `AgentsMdLinkError` ๋ง ์ฒ๋ฆฌํ๋ฉด ๋๋ค.
|
|
356
|
+
"""
|
|
357
|
+
project_root = Path(project_root)
|
|
358
|
+
template = installed_claude_md_template_path()
|
|
359
|
+
if not template.exists():
|
|
360
|
+
return None
|
|
361
|
+
|
|
362
|
+
target = project_root / "AGENTS.md"
|
|
363
|
+
|
|
364
|
+
if target.is_symlink():
|
|
365
|
+
try:
|
|
366
|
+
current = os.readlink(target)
|
|
367
|
+
except OSError:
|
|
368
|
+
return None
|
|
369
|
+
current_path = Path(current)
|
|
370
|
+
if current_path == template or (target.parent / current_path).resolve() == template.resolve():
|
|
371
|
+
return target
|
|
372
|
+
return None # foreign symlink โ respect user
|
|
373
|
+
|
|
374
|
+
if target.exists():
|
|
375
|
+
return None # regular file โ respect user content
|
|
376
|
+
|
|
377
|
+
try:
|
|
378
|
+
target.symlink_to(template)
|
|
379
|
+
except OSError as exc:
|
|
380
|
+
raise AgentsMdLinkError(
|
|
381
|
+
f"failed to create symlink {target} -> {template}: {exc}"
|
|
382
|
+
) from exc
|
|
383
|
+
return target
|