okstra 0.196.0 → 0.197.1

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 (39) hide show
  1. package/dist/cli-registry.mjs +6 -0
  2. package/dist/cli-registry.mjs.map +1 -1
  3. package/docs/cli.md +14 -1
  4. package/docs/project-structure-overview.md +1 -0
  5. package/package.json +1 -1
  6. package/runtime/BUILD.json +2 -2
  7. package/runtime/prompts/host-orchestration/implementation-planning.md +56 -0
  8. package/runtime/prompts/lead/plan-body-verification.md +21 -3
  9. package/runtime/prompts/profiles/implementation-planning.md +3 -2
  10. package/runtime/prompts/wizard/prompts.ko.json +2 -2
  11. package/runtime/python/okstra_ctl/adapters/hosts/claude-code/relay.md +15 -0
  12. package/runtime/python/okstra_ctl/adapters/hosts/codex/relay.md +15 -0
  13. package/runtime/python/okstra_ctl/adapters/hosts/grok/relay.md +15 -0
  14. package/runtime/python/okstra_ctl/agent/prompt_cli/dynamic_verifier.py +12 -10
  15. package/runtime/python/okstra_ctl/blocking_checks.py +19 -0
  16. package/runtime/python/okstra_ctl/conformance.py +36 -5
  17. package/runtime/python/okstra_ctl/dispatch_core.py +11 -6
  18. package/runtime/python/okstra_ctl/dispatch_state.py +11 -7
  19. package/runtime/python/okstra_ctl/domain/worker_stream.py +4 -5
  20. package/runtime/python/okstra_ctl/final_report_schema.py +62 -1
  21. package/runtime/python/okstra_ctl/group_context.py +96 -3
  22. package/runtime/python/okstra_ctl/plan_items.py +14 -0
  23. package/runtime/python/okstra_ctl/plan_items_cli.py +45 -3
  24. package/runtime/python/okstra_ctl/run.py +77 -0
  25. package/runtime/python/okstra_ctl/session_transcript.py +4 -4
  26. package/runtime/python/okstra_ctl/set_work_status.py +30 -1
  27. package/runtime/python/okstra_ctl/stage_close.py +244 -0
  28. package/runtime/python/okstra_ctl/tdd_bypass.py +131 -0
  29. package/runtime/python/okstra_ctl/wizard/engine.py +27 -24
  30. package/runtime/python/okstra_ctl/wizard/picker_navigation.py +37 -14
  31. package/runtime/python/okstra_ctl/wizard/roles.py +16 -11
  32. package/runtime/python/okstra_ctl/worker_prompt_contract.py +15 -1
  33. package/runtime/python/okstra_ctl/worker_prompt_policy.py +45 -2
  34. package/runtime/skills/okstra-brief-gen/SKILL.md +24 -7
  35. package/runtime/skills/okstra-inspect/facets/recap.md +17 -1
  36. package/runtime/skills/okstra-run/SKILL.md +63 -4
  37. package/runtime/templates/reports/group-context.template.md +1 -1
  38. package/runtime/validators/validate-implementation-plan-stages.py +109 -23
  39. package/runtime/validators/validate-run.py +63 -8
@@ -41,6 +41,7 @@ from okstra_project.dirs import tasks_root as _okstra_tasks_root # noqa: E402
41
41
  from okstra_project.resolver import resolve_architecture # noqa: E402
42
42
 
43
43
  from okstra_ctl.conformance import ( # noqa: E402
44
+ conformance_result_file,
44
45
  detect_surfaces,
45
46
  exempt_stage_surface_conflicts,
46
47
  evaluate_conformance,
@@ -56,6 +57,10 @@ from okstra_ctl.dispatch_state import ( # noqa: E402
56
57
  v2_worker_state_key,
57
58
  )
58
59
  from okstra_ctl.paths import RunRef, okstra_home, project_rel # noqa: E402
60
+ from okstra_ctl.tdd_bypass import ( # noqa: E402
61
+ bypass_file as tdd_bypass_file,
62
+ granted_stages,
63
+ )
59
64
  from okstra_ctl.reconcile import settle_run_row # noqa: E402
60
65
  from okstra_ctl.report_contract import CURRENT_REPORT_SCHEMA_VERSION # noqa: E402
61
66
  from okstra_ctl.release_gate import ( # noqa: E402
@@ -1700,7 +1705,7 @@ def _load_conformance_results(qa_dir: Path, manifest: dict) -> dict:
1700
1705
  key = entry.get("stageKey") if isinstance(entry, dict) else None
1701
1706
  if not isinstance(key, str) or not key:
1702
1707
  continue
1703
- sidecar = qa_dir / f"result-{key}.json"
1708
+ sidecar = conformance_result_file(qa_dir, key)
1704
1709
  if not sidecar.is_file():
1705
1710
  continue
1706
1711
  try:
@@ -1997,10 +2002,32 @@ def _approved_plan_conformance_manifest(
1997
2002
  }
1998
2003
 
1999
2004
 
2005
+ def _conformance_script_matches(actual: str, declared: str) -> bool:
2006
+ """두 script 표기가 같은 파일을 가리키는지 판정한다.
2007
+
2008
+ 1순위는 task-root 상대형끼리의 일치다. task_root 아래로 접히지 않는
2009
+ 절대경로(예: 워크트리 경로로 적힌 선언)가 남으면, 남은 쪽이 다른 쪽을
2010
+ 경로 경계에서 후행 일치하는지까지 본다 — `/…/wt/qa/scripts/s.ts` 와
2011
+ `qa/scripts/s.ts` 는 같은 파일이다. 후행 일치는 경계(`/`)를 요구하므로
2012
+ `renamed-stage-1.ts` 같은 다른 파일은 걸리지 않는다.
2013
+ """
2014
+ if actual == declared:
2015
+ return True
2016
+ if not actual or not declared:
2017
+ return False
2018
+ if actual.startswith("/") != declared.startswith("/"):
2019
+ longer, shorter = (
2020
+ (actual, declared) if actual.startswith("/") else (declared, actual)
2021
+ )
2022
+ return longer.endswith("/" + shorter)
2023
+ return False
2024
+
2025
+
2000
2026
  def _declared_conformance_errors(
2001
2027
  declared_manifest: dict,
2002
2028
  actual_manifest: dict,
2003
2029
  stage_name: str | None,
2030
+ task_root: Path,
2004
2031
  ) -> list[str]:
2005
2032
  """Compare scoped plan declarations with their one actual manifest entry."""
2006
2033
  declared = _scope_manifest_entries(declared_manifest, stage_name).get("entries", [])
@@ -2025,8 +2052,15 @@ def _declared_conformance_errors(
2025
2052
  errors.append(f"stage {stage_number} has multiple matching entries")
2026
2053
  continue
2027
2054
  actual_entry = matches[0]
2028
- actual_script = _normalize_conformance_script(str(actual_entry.get("script") or ""))
2029
- if actual_script != declaration.get("script"):
2055
+ # 양쪽을 같은 task-root 상대형으로 접은 뒤 대조한다 — 계획이 절대경로를,
2056
+ # 실행자가 상대형을 쓰면 같은 파일이 문자열로는 영영 안 맞는다.
2057
+ actual_script = _normalize_conformance_script(
2058
+ str(actual_entry.get("script") or ""), task_root
2059
+ )
2060
+ declared_script = _normalize_conformance_script(
2061
+ str(declaration.get("script") or ""), task_root
2062
+ )
2063
+ if not _conformance_script_matches(actual_script, declared_script):
2030
2064
  errors.append(f"stage {stage_number} script mismatch")
2031
2065
  actual_requires = actual_entry.get("requires")
2032
2066
  actual_capabilities = (
@@ -2226,6 +2260,7 @@ def _validate_conformance(
2226
2260
  declared_manifest,
2227
2261
  empty_scoped_manifest,
2228
2262
  stage_name,
2263
+ task_root,
2229
2264
  ):
2230
2265
  failures.append(
2231
2266
  f"conformance gate BLOCKING: approved plan {error}; "
@@ -2252,6 +2287,7 @@ def _validate_conformance(
2252
2287
  declared_manifest,
2253
2288
  manifest,
2254
2289
  stage_name,
2290
+ task_root,
2255
2291
  ):
2256
2292
  failures.append(
2257
2293
  f"conformance gate BLOCKING: approved plan {error} "
@@ -6352,9 +6388,14 @@ def _plan_verify_dispatched_results(
6352
6388
  # critic 동수 라운드는 `kind: "critic"` 으로 나간다 — 결과 파일명은
6353
6389
  # 같은 `-plan-verify-r<N>-` 꼴이다. reverify 계열만 세면 동수가 있던
6354
6390
  # run 마다 critic 표가 "디스패치 기록 없음" 으로 오탐된다(2026-09-09,
6355
- # fontsninja-v3-site dev-10627 planning 002).
6391
+ # fontsninja-v3-site dev-10627 planning 002). 계획 본문 라운드 자신의
6392
+ # kind 인 `plan-verify-r<N>` 도 같은 이유로 센다.
6356
6393
  kind = str(row.get("kind") or "")
6357
- if not (kind.startswith("reverify-r") or kind == "critic"):
6394
+ if not (
6395
+ kind.startswith("reverify-r")
6396
+ or kind.startswith("plan-verify-r")
6397
+ or kind == "critic"
6398
+ ):
6358
6399
  continue
6359
6400
  name = Path(str(row.get("workerResultPath") or "")).name
6360
6401
  if "-plan-verify-r" not in name:
@@ -8003,13 +8044,19 @@ def _load_stage_validator():
8003
8044
  return mod
8004
8045
 
8005
8046
 
8006
- def _append_stage_data_failures(data: Mapping[str, Any], failures: list[str]) -> None:
8047
+ def _append_stage_data_failures(
8048
+ data: Mapping[str, Any], failures: list[str], task_root: Path | None = None,
8049
+ ) -> None:
8007
8050
  """Run the stage relationship checks that schema v2 cannot express.
8008
8051
 
8009
8052
  The depends-on DAG, parallel-stage file safety, RED→GREEN ordering, and
8010
8053
  the TDD-exemption vocabulary are relationships between stages, which a
8011
8054
  JSON Schema cannot state. They are enforced here, against the data.json,
8012
8055
  by the same validator that owns the rule vocabulary.
8056
+
8057
+ `tddExemption: user-bypass` is the one reason the plan cannot assert by
8058
+ itself, so the user's grants are read from the task's own bypass ledger
8059
+ and passed in; without the task root no stage counts as granted.
8013
8060
  """
8014
8061
  if (data or {}).get("schemaVersion") != CURRENT_REPORT_SCHEMA_VERSION:
8015
8062
  return # Schema validation already rejected an unknown version.
@@ -8020,7 +8067,11 @@ def _append_stage_data_failures(data: Mapping[str, Any], failures: list[str]) ->
8020
8067
  if mod is None: # pragma: no cover — repo/runtime always ship the file
8021
8068
  failures.append(f"cannot load Stage Map validator at {_STAGE_VALIDATOR_PATH}")
8022
8069
  return
8023
- for e in mod.collect_data_validation_errors(dict(planning)):
8070
+ granted = (
8071
+ frozenset(granted_stages(tdd_bypass_file(task_root)))
8072
+ if task_root is not None else frozenset()
8073
+ )
8074
+ for e in mod.collect_data_validation_errors(dict(planning), granted):
8024
8075
  failures.append(
8025
8076
  f"implementation-planning stage contract invalid "
8026
8077
  f"[{e.code} stage={e.stage}]: {e.message}"
@@ -9485,7 +9536,11 @@ def main() -> int:
9485
9536
  )
9486
9537
  _validate_stage_has_requirement(validation_data, failures)
9487
9538
  if task_type == "implementation-planning":
9488
- _append_stage_data_failures(validation_data, failures)
9539
+ _append_stage_data_failures(
9540
+ validation_data,
9541
+ failures,
9542
+ _task_root_from_run_dir(report_path.parent.parent),
9543
+ )
9489
9544
  if task_type == "improvement-discovery":
9490
9545
  run_dir = report_path.parent.parent
9491
9546
  _validate_improvement_discovery(report_path, run_dir, brief_path, failures)