okstra 0.184.0 → 0.185.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 (36) hide show
  1. package/README.md +1 -1
  2. package/dist/cli-registry.mjs +9 -0
  3. package/dist/cli-registry.mjs.map +1 -1
  4. package/dist/commands/chat/chat.d.mts +1 -0
  5. package/dist/commands/chat/chat.mjs +385 -0
  6. package/dist/commands/chat/chat.mjs.map +1 -0
  7. package/dist/lib/skill-catalog.mjs +1 -0
  8. package/dist/lib/skill-catalog.mjs.map +1 -1
  9. package/docs/architecture.md +8 -6
  10. package/docs/cli.md +2 -1
  11. package/docs/for-ai/README.md +4 -2
  12. package/docs/for-ai/skills/okstra-chat.md +28 -0
  13. package/docs/for-ai/skills/okstra-inspect.md +1 -1
  14. package/docs/for-ai/skills/okstra-run.md +2 -2
  15. package/docs/for-ai/skills/okstra-user-response.md +10 -8
  16. package/docs/project-structure-overview.md +5 -4
  17. package/docs/task-process/README.md +1 -1
  18. package/docs/task-process/implementation-planning.md +1 -1
  19. package/package.json +1 -1
  20. package/runtime/BUILD.json +2 -2
  21. package/runtime/prompts/lead/okstra-lead-contract.md +6 -5
  22. package/runtime/prompts/lead/report-writer.md +1 -1
  23. package/runtime/prompts/profiles/_clarification-recommendation.md +2 -2
  24. package/runtime/prompts/profiles/implementation-planning.md +2 -1
  25. package/runtime/prompts/wizard/prompts.ko.json +2 -0
  26. package/runtime/python/okstra_ctl/adapters/hosts/claude-code/relay.md +2 -2
  27. package/runtime/python/okstra_ctl/adapters/hosts/codex/relay.md +1 -1
  28. package/runtime/python/okstra_ctl/adapters/hosts/grok/relay.md +1 -1
  29. package/runtime/python/okstra_ctl/next_phase.py +67 -4
  30. package/runtime/python/okstra_ctl/user_response.py +147 -37
  31. package/runtime/python/okstra_ctl/wizard.py +13 -0
  32. package/runtime/skills/okstra-chat/SKILL.md +104 -0
  33. package/runtime/skills/okstra-inspect/facets/status.md +6 -5
  34. package/runtime/skills/okstra-run/SKILL.md +2 -2
  35. package/runtime/skills/okstra-user-response/SKILL.md +50 -16
  36. package/runtime/validators/validate-run.py +90 -15
@@ -642,6 +642,35 @@ def write_json(path: Path, payload: dict) -> None:
642
642
  path.write_text(json.dumps(payload, indent=2, ensure_ascii=False) + "\n")
643
643
 
644
644
 
645
+ def _report_already_approved(report_data: Mapping[str, Any] | None) -> bool:
646
+ if not isinstance(report_data, Mapping):
647
+ return False
648
+ frontmatter = report_data.get("frontmatter")
649
+ return isinstance(frontmatter, Mapping) and frontmatter.get("approved") is True
650
+
651
+
652
+ def _derive_awaiting_approval(
653
+ *,
654
+ existing: bool,
655
+ validation_status: str,
656
+ current_phase: str,
657
+ pointer: Mapping[str, str],
658
+ report_data: Mapping[str, Any] | None,
659
+ ) -> bool:
660
+ """planning 이 승인 가능한 plan-ready 를 남기면 올리고, implementation
661
+ 이 그 승인을 소비하면 내린다. 차단 게이트나 열린 Blocks=approval 은
662
+ 포인터가 blocked 라 올리지 않는다."""
663
+ if validation_status == "passed" and current_phase == "implementation":
664
+ return False
665
+ if validation_status == "passed" and current_phase == "implementation-planning":
666
+ return (
667
+ pointer.get("phase") == "implementation"
668
+ and pointer.get("status") == next_phase.STATUS_READY
669
+ and not _report_already_approved(report_data)
670
+ )
671
+ return existing
672
+
673
+
645
674
  def update_workflow_metadata(
646
675
  run_manifest: dict,
647
676
  task_manifest: dict,
@@ -693,7 +722,8 @@ def update_workflow_metadata(
693
722
  next_recommended_phase = next_phase.make(
694
723
  phase=projected["phase"],
695
724
  status=projected["status"],
696
- rationale=(
725
+ rationale=projected["rationale"]
726
+ or (
697
727
  "리포트 라우팅에서 투영됨. 리드가 쓴 값과 근거는 "
698
728
  "nextRecommendedPhaseCorrection.authored 에 있다."
699
729
  ),
@@ -713,16 +743,16 @@ def update_workflow_metadata(
713
743
  status=next_phase.STATUS_BLOCKED, rationale=authored["rationale"]
714
744
  )
715
745
 
716
- awaiting_approval = workflow.get("awaitingApproval")
717
- if not isinstance(awaiting_approval, bool):
718
- awaiting_approval = False
719
- # 승인 게이트(`frontmatter approved`)는 implementation 진입 직전에 한 번만 의미를 가진다.
720
- # implementation run 이 검증을 통과했다는 것은 `_validate_approved_plan` 이 이미 사용자
721
- # 승인 플래그(frontmatter `approved: true`)를 소비했다는 뜻이므로, 이 시점에
722
- # awaitingApproval 플래그를 명시적으로 내려 다음 phase 의 status 뷰에서 stale 상태로
723
- # 남지 않게 한다.
724
- if validation_status == "passed" and current_phase == "implementation":
725
- awaiting_approval = False
746
+ awaiting_existing = workflow.get("awaitingApproval")
747
+ if not isinstance(awaiting_existing, bool):
748
+ awaiting_existing = False
749
+ awaiting_approval = _derive_awaiting_approval(
750
+ existing=awaiting_existing,
751
+ validation_status=validation_status,
752
+ current_phase=current_phase,
753
+ pointer=next_recommended_phase,
754
+ report_data=report_data,
755
+ )
726
756
 
727
757
  last_safe_checkpoint = workflow.get("lastSafeCheckpoint", {})
728
758
  if not isinstance(last_safe_checkpoint, dict):
@@ -3562,6 +3592,7 @@ def validate_final_report_data(
3562
3592
  _validate_clarification_evidence_note(data, failures)
3563
3593
  _validate_approval_clarification_backtrace(data, failures)
3564
3594
  _validate_rerun_guidance(data, failures)
3595
+ _validate_approval_guidance(data, failures)
3565
3596
  _validate_variation_point_analysis(
3566
3597
  (data.get("implementationPlanning") or {}).get("variationPointAnalysis"),
3567
3598
  resolve_architecture(_project_root_from_report(report_path)),
@@ -6140,6 +6171,7 @@ def _validate_approval_clarification_backtrace(
6140
6171
 
6141
6172
 
6142
6173
  _RERUN_FLAG = "--answered-clarifications"
6174
+ _APPROVE_HINT = re.compile(r"--approve|\bapprov", re.IGNORECASE)
6143
6175
 
6144
6176
 
6145
6177
  def _next_step_texts(steps: object) -> list[str]:
@@ -6156,6 +6188,24 @@ def _next_step_texts(steps: object) -> list[str]:
6156
6188
  return texts
6157
6189
 
6158
6190
 
6191
+ def _has_blocks_approval_row(data: dict) -> bool:
6192
+ return any(
6193
+ isinstance(row, dict) and row.get("blocks") == "approval"
6194
+ for row in data.get("clarificationItems") or []
6195
+ )
6196
+
6197
+
6198
+ def _planning_gate_blocks_approval(data: dict) -> bool:
6199
+ planning = data.get("implementationPlanning")
6200
+ if not isinstance(planning, dict):
6201
+ return False
6202
+ verification = planning.get("planBodyVerification")
6203
+ if not isinstance(verification, dict):
6204
+ return False
6205
+ gate = str(verification.get("gateResult") or "").strip().lower()
6206
+ return gate in {"blocked-by-disagreement", "aborted-non-result"}
6207
+
6208
+
6159
6209
  def _validate_rerun_guidance(data: dict, failures: list[str]) -> None:
6160
6210
  """A report that withholds approval must say how to come back from it.
6161
6211
 
@@ -6168,10 +6218,7 @@ def _validate_rerun_guidance(data: dict, failures: list[str]) -> None:
6168
6218
  """
6169
6219
  if (data.get("header") or {}).get("taskType") != "implementation-planning":
6170
6220
  return
6171
- has_blocker = any(
6172
- isinstance(row, dict) and row.get("blocks") == "approval"
6173
- for row in data.get("clarificationItems") or []
6174
- )
6221
+ has_blocker = _has_blocks_approval_row(data)
6175
6222
  if not has_blocker:
6176
6223
  return
6177
6224
  if any(_RERUN_FLAG in text for text in _next_step_texts(
@@ -6187,6 +6234,34 @@ def _validate_rerun_guidance(data: dict, failures: list[str]) -> None:
6187
6234
  )
6188
6235
 
6189
6236
 
6237
+ def _validate_approval_guidance(data: dict, failures: list[str]) -> None:
6238
+ """승인 가능한 plan-ready 는 사용자에게 승인하라고 말해야 한다.
6239
+
6240
+ 포인터가 implementation/ready 여도 승인은 사용자만 뒤집는다. 다음 단계
6241
+ 안내가 계획 재실행이면 승인 칸을 건너뛰고 같은 단계를 다시 돈다.
6242
+ """
6243
+ if (data.get("header") or {}).get("taskType") != "implementation-planning":
6244
+ return
6245
+ planning = data.get("implementationPlanning")
6246
+ if not isinstance(planning, dict) or planning.get("outcome") != "plan-ready":
6247
+ return
6248
+ if _has_blocks_approval_row(data) or _planning_gate_blocks_approval(data):
6249
+ return
6250
+ if _report_already_approved(data):
6251
+ return
6252
+ if any(_APPROVE_HINT.search(text) for text in _next_step_texts(
6253
+ data.get("recommendedNextSteps")
6254
+ )):
6255
+ return
6256
+ failures.append(
6257
+ "final-report data.json: this plan is ready for the user to approve, "
6258
+ "but no `recommendedNextSteps` entry tells the reader to approve — "
6259
+ "name `--approve` or the in-session wizard in a step's `text` or "
6260
+ "one of its `commands`. Do not recommend another "
6261
+ "implementation-planning run."
6262
+ )
6263
+
6264
+
6190
6265
  def _validate_self_fix_grouping(data: dict, failures: list[str]) -> None:
6191
6266
  """A self-fix round must be instructed by cause, not as a flat item list.
6192
6267