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.
Files changed (101) hide show
  1. package/README.kr.md +26 -16
  2. package/README.md +26 -16
  3. package/docs/kr/architecture.md +59 -45
  4. package/docs/kr/cli.md +61 -18
  5. package/docs/pr-template-usage.md +65 -0
  6. package/docs/project-structure-overview.md +358 -354
  7. package/docs/superpowers/plans/2026-05-12-ticket-id-in-reports.md +1 -1
  8. package/docs/superpowers/plans/2026-05-14-convergence-queue-pruning.md +1 -1
  9. package/docs/superpowers/plans/2026-05-17-dual-format-final-report.md +1 -1
  10. package/docs/superpowers/plans/2026-05-20-final-report-language.md +1501 -0
  11. package/docs/superpowers/plans/2026-05-20-implementation-planning-multi-stage.md +1267 -0
  12. package/docs/superpowers/plans/2026-05-20-okstra-run-prompt-sot-b1.md +1007 -0
  13. package/docs/superpowers/plans/2026-05-20-wizard-messages-json-sot.md +720 -0
  14. package/docs/superpowers/plans/2026-05-20-wizard-prompt-json-sot-a1.md +681 -0
  15. package/docs/superpowers/plans/2026-05-21-improvement-discovery-task-type.md +1691 -0
  16. package/docs/superpowers/specs/2026-05-20-final-report-language-design.md +383 -0
  17. package/docs/superpowers/specs/2026-05-20-implementation-planning-multi-stage-design.md +320 -0
  18. package/docs/superpowers/specs/2026-05-20-okstra-run-prompt-sot-design.md +299 -0
  19. package/docs/superpowers/specs/2026-05-21-improvement-discovery-task-type-design.md +335 -0
  20. package/docs/task-process/README.md +74 -0
  21. package/docs/task-process/common-flow.md +166 -0
  22. package/docs/task-process/error-analysis.md +101 -0
  23. package/docs/task-process/final-verification.md +167 -0
  24. package/docs/task-process/implementation-planning.md +128 -0
  25. package/docs/task-process/implementation.md +149 -0
  26. package/docs/task-process/release-handoff.md +206 -0
  27. package/docs/task-process/requirements-discovery.md +115 -0
  28. package/package.json +1 -1
  29. package/runtime/BUILD.json +2 -2
  30. package/runtime/agents/SKILL.md +12 -2
  31. package/runtime/agents/workers/claude-worker.md +26 -0
  32. package/runtime/agents/workers/codex-worker.md +27 -1
  33. package/runtime/agents/workers/gemini-worker.md +27 -1
  34. package/runtime/agents/workers/report-writer-worker.md +8 -1
  35. package/runtime/bin/okstra-central.sh +6 -6
  36. package/runtime/bin/okstra-codex-exec.sh +49 -28
  37. package/runtime/bin/okstra-gemini-exec.sh +39 -21
  38. package/runtime/bin/okstra-render-final-report.py +13 -2
  39. package/runtime/bin/okstra-wrapper-status.py +155 -0
  40. package/runtime/bin/okstra.sh +2 -2
  41. package/runtime/prompts/profiles/_common-contract.md +11 -6
  42. package/runtime/prompts/profiles/error-analysis.md +3 -7
  43. package/runtime/prompts/profiles/implementation-planning.md +22 -21
  44. package/runtime/prompts/profiles/implementation.md +28 -11
  45. package/runtime/prompts/profiles/improvement-discovery.md +42 -0
  46. package/runtime/prompts/profiles/kr/_common-contract.md +92 -0
  47. package/runtime/prompts/profiles/kr/error-analysis.md +36 -0
  48. package/runtime/prompts/profiles/kr/final-verification.md +48 -0
  49. package/runtime/prompts/profiles/kr/implementation-planning.md +90 -0
  50. package/runtime/prompts/profiles/kr/implementation.md +144 -0
  51. package/runtime/prompts/profiles/kr/improvement-discovery.md +42 -0
  52. package/runtime/prompts/profiles/kr/release-handoff.md +104 -0
  53. package/runtime/prompts/profiles/kr/requirements-discovery.md +42 -0
  54. package/runtime/prompts/profiles/release-handoff.md +1 -1
  55. package/runtime/prompts/profiles/requirements-discovery.md +8 -12
  56. package/runtime/prompts/wizard/prompts.ko.json +230 -0
  57. package/runtime/python/lib/okstra/cli.sh +2 -49
  58. package/runtime/python/lib/okstra/globals.sh +21 -21
  59. package/runtime/python/lib/okstra/interactive.sh +7 -7
  60. package/runtime/python/okstra_ctl/clarification_items.py +3 -9
  61. package/runtime/python/okstra_ctl/consumers.py +53 -0
  62. package/runtime/python/okstra_ctl/final_report_schema.py +0 -7
  63. package/runtime/python/okstra_ctl/i18n.py +73 -0
  64. package/runtime/python/okstra_ctl/improvement_lenses.py +44 -0
  65. package/runtime/python/okstra_ctl/index.py +1 -1
  66. package/runtime/python/okstra_ctl/paths.py +23 -20
  67. package/runtime/python/okstra_ctl/render.py +147 -202
  68. package/runtime/python/okstra_ctl/render_final_report.py +53 -10
  69. package/runtime/python/okstra_ctl/run.py +292 -107
  70. package/runtime/python/okstra_ctl/run_context.py +22 -0
  71. package/runtime/python/okstra_ctl/seeding.py +186 -0
  72. package/runtime/python/okstra_ctl/wizard.py +348 -127
  73. package/runtime/python/okstra_ctl/workflow.py +21 -2
  74. package/runtime/python/okstra_ctl/worktree.py +54 -1
  75. package/runtime/python/okstra_project/resolver.py +4 -3
  76. package/runtime/python/okstra_token_usage/report.py +2 -2
  77. package/runtime/schemas/final-report-v1.0.schema.json +22 -16
  78. package/runtime/skills/okstra-brief/SKILL.md +124 -31
  79. package/runtime/skills/okstra-convergence/SKILL.md +2 -3
  80. package/runtime/skills/okstra-report-writer/SKILL.md +35 -15
  81. package/runtime/skills/okstra-run/SKILL.md +5 -4
  82. package/runtime/skills/okstra-schedule/SKILL.md +4 -4
  83. package/runtime/skills/okstra-setup/SKILL.md +27 -0
  84. package/runtime/skills/okstra-team-contract/SKILL.md +1 -1
  85. package/runtime/templates/okstra.CLAUDE.md +104 -0
  86. package/runtime/templates/reports/final-report.template.md +93 -98
  87. package/runtime/templates/reports/i18n/en.json +135 -0
  88. package/runtime/templates/reports/i18n/ko.json +135 -0
  89. package/runtime/templates/reports/implementation-planning-input.template.md +18 -0
  90. package/runtime/templates/reports/improvement-discovery-input.template.md +78 -0
  91. package/runtime/templates/reports/task-brief.template.md +2 -2
  92. package/runtime/validators/lib/fixtures.sh +30 -0
  93. package/runtime/validators/lib/runners.sh +1 -1
  94. package/runtime/validators/validate-implementation-plan-stages.py +211 -0
  95. package/runtime/validators/validate-run.py +121 -26
  96. package/runtime/validators/validate-workflow.sh +2 -2
  97. package/runtime/validators/validate_improvement_report.py +275 -0
  98. package/src/config.mjs +18 -0
  99. package/src/install.mjs +41 -14
  100. package/src/setup.mjs +133 -1
  101. 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