okstra 0.67.0 → 0.68.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 (32) hide show
  1. package/bin/okstra +7 -0
  2. package/docs/kr/architecture.md +17 -1
  3. package/docs/superpowers/plans/2026-06-10-concurrent-run-team-guard.md +456 -0
  4. package/docs/superpowers/plans/2026-06-10-git-reconcile-stale-sha-recovery.md +1408 -0
  5. package/docs/superpowers/plans/2026-06-10-stage-group-handoff.md +1572 -0
  6. package/docs/superpowers/specs/2026-06-06-stage-worktree-isolation-design.md +1 -1
  7. package/docs/superpowers/specs/2026-06-10-concurrent-run-team-guard-design.md +107 -0
  8. package/docs/superpowers/specs/2026-06-10-git-reconcile-stale-sha-recovery-design.md +105 -0
  9. package/docs/superpowers/specs/2026-06-10-stage-group-handoff-design.md +156 -0
  10. package/package.json +1 -1
  11. package/runtime/BUILD.json +2 -2
  12. package/runtime/agents/SKILL.md +5 -4
  13. package/runtime/prompts/profiles/_common-contract.md +6 -6
  14. package/runtime/prompts/profiles/final-verification.md +3 -2
  15. package/runtime/prompts/profiles/release-handoff.md +12 -5
  16. package/runtime/prompts/wizard/prompts.ko.json +1 -1
  17. package/runtime/python/okstra_ctl/consumers.py +72 -5
  18. package/runtime/python/okstra_ctl/git_reconcile.py +322 -0
  19. package/runtime/python/okstra_ctl/handoff.py +348 -0
  20. package/runtime/python/okstra_ctl/render.py +44 -2
  21. package/runtime/python/okstra_ctl/run.py +88 -27
  22. package/runtime/python/okstra_ctl/wizard.py +25 -4
  23. package/runtime/python/okstra_ctl/worktree.py +10 -0
  24. package/runtime/python/okstra_ctl/worktree_registry.py +40 -9
  25. package/runtime/skills/okstra-context-loader/SKILL.md +1 -1
  26. package/runtime/skills/okstra-convergence/SKILL.md +1 -1
  27. package/runtime/skills/okstra-report-writer/SKILL.md +2 -2
  28. package/runtime/skills/okstra-run/SKILL.md +43 -3
  29. package/runtime/skills/okstra-team-contract/SKILL.md +2 -2
  30. package/runtime/validators/validate-run.py +49 -9
  31. package/src/git-reconcile.mjs +31 -0
  32. package/src/handoff.mjs +30 -0
@@ -748,6 +748,36 @@ def _parse_diff_summary_files(content: str) -> list[str]:
748
748
  return _DIFF_ROW_PATH_RE.findall(section.group(0))
749
749
 
750
750
 
751
+ _STAGE_RUN_DIR_RE = re.compile(r"^stage-\d+$")
752
+
753
+
754
+ def _implementation_stage_name(run_dir: Path) -> str | None:
755
+ """implementation stage 격리 run(`runs/implementation/stage-<N>`)이면
756
+ `stage-<N>` 을 반환. 그 외(final-verification 등 task-type 레벨 run)는
757
+ None — whole-task 스코프."""
758
+ if run_dir.parent.name == "implementation" and _STAGE_RUN_DIR_RE.match(run_dir.name):
759
+ return run_dir.name
760
+ return None
761
+
762
+
763
+ def _scope_manifest_entries(manifest: dict, stage_name: str | None) -> dict:
764
+ """게이트 평가 대상 entry 를 run 스코프로 좁힌 manifest 를 반환.
765
+
766
+ implementation 은 한 run = 한 stage 이므로 자기 stageKey
767
+ (`<task-id>-stage-<N>`) entry 만 게이트한다 — 다른 stage 는 각자의
768
+ implementation run / final-verification(whole-task) 이 검증한다.
769
+ suffix 매칭인 이유: stageKey 의 `<task-id>` 는 planning 이 쓴 원문이라
770
+ task 디렉터리 segment 와 표기가 다를 수 있다.
771
+ """
772
+ if stage_name is None:
773
+ return manifest
774
+ entries = [
775
+ e for e in manifest.get("entries", [])
776
+ if isinstance(e, dict) and str(e.get("stageKey", "")).endswith(f"-{stage_name}")
777
+ ]
778
+ return {"entries": entries}
779
+
780
+
751
781
  def _task_root_from_run_dir(run_dir: Path) -> Path:
752
782
  """run_dir 에서 `runs` 디렉터리를 앵커로 task_root 를 복원한다.
753
783
 
@@ -770,6 +800,12 @@ def _validate_conformance(report_path: Path, failures: list[str],
770
800
  가 없다는 뜻 — 선언을 강제하는 것은 planning 계약(Phase 4)의 몫). 매니페스트가
771
801
  있으면 결과 사이드카와 함께 evaluate_conformance 로 판정하고 BLOCKING verdict
772
802
  를 run 검증 실패로 승격한다. WAIVED(conditional)/EXEMPT 는 통과시킨다.
803
+
804
+ 게이트 스코프: implementation stage 격리 run 은 자기 stage entry 만(결과
805
+ 게이트·diff-surface 교차검증 모두 — 미래 stage 의 미실행이 현재 run 을
806
+ 막으면 안 된다), final-verification 등 task-type 레벨 run 은 전 entry
807
+ (whole-task). prompts/profiles/_implementation-verifier.md §Tier 3 /
808
+ final-verification.md 의 스코프 계약과 동형.
773
809
  """
774
810
  # conformance 산출물은 task-level(<task_root>/qa)에 있어 planning/
775
811
  # implementation/final-verification 가 공유한다. report_path 는
@@ -792,8 +828,9 @@ def _validate_conformance(report_path: Path, failures: list[str],
792
828
  if schema_errors:
793
829
  failures.extend(f"conformance manifest: {e}" for e in schema_errors)
794
830
  return
795
- results = _load_conformance_results(qa_dir, manifest)
796
- for verdict in evaluate_conformance(manifest, results):
831
+ scoped = _scope_manifest_entries(manifest, _implementation_stage_name(run_dir))
832
+ results = _load_conformance_results(qa_dir, scoped)
833
+ for verdict in evaluate_conformance(scoped, results):
797
834
  if not verdict.ok:
798
835
  failures.append(
799
836
  f"conformance gate BLOCKING for stage {verdict.stage_key}: "
@@ -803,13 +840,14 @@ def _validate_conformance(report_path: Path, failures: list[str],
803
840
  )
804
841
  changed_files = _parse_diff_summary_files(report_path.read_text(encoding="utf-8"))
805
842
  if changed_files:
806
- uncovered = detect_surfaces(changed_files, surface_patterns) - manifest_required_surfaces(manifest)
843
+ uncovered = detect_surfaces(changed_files, surface_patterns) - manifest_required_surfaces(scoped)
807
844
  if uncovered:
808
845
  failures.append(
809
846
  "conformance gate BLOCKING: implementation diff touches undeclared "
810
- f"surface(s) {sorted(uncovered)} — no stage declares `requires` for "
811
- "them. Declare a conformance entry (requires=[...]) for the touching "
812
- "stage, or an explicit exemption. (silent mock-green 방지 — DEV-9184)"
847
+ f"surface(s) {sorted(uncovered)} — no in-scope stage declares "
848
+ "`requires` for them. Declare a conformance entry (requires=[...]) "
849
+ "for the touching stage, or an explicit exemption. "
850
+ "(silent mock-green 방지 — DEV-9184)"
813
851
  )
814
852
 
815
853
 
@@ -1399,11 +1437,13 @@ def _validate_final_verification_consistency(data: dict, failures: list[str]) ->
1399
1437
  f"final-verification: verificationScope must be `whole-task` or "
1400
1438
  f"`single-stage`, got {scope!r}."
1401
1439
  )
1402
- if scope == "single-stage" and "release-handoff" in routing:
1440
+ if (scope == "single-stage" and "release-handoff" in routing
1441
+ and "release-handoff(stage-group)" not in routing):
1403
1442
  failures.append(
1404
1443
  "final-verification: verificationScope `single-stage` cannot recommend "
1405
- "release-handoff routing — single-stage is a partial verification and "
1406
- "release-handoff requires whole-task verification."
1444
+ "plain release-handoff routing — a single-stage accepted verdict may "
1445
+ "only route to `release-handoff(stage-group)` (partial-PR mode); "
1446
+ "whole-task release-handoff requires whole-task verification."
1407
1447
  )
1408
1448
 
1409
1449
 
@@ -0,0 +1,31 @@
1
+ import { runPythonModule } from "./_python-helper.mjs";
2
+
3
+ const USAGE = `okstra git-reconcile — okstra 밖 git 히스토리 변경 후 기록 화해
4
+
5
+ A thin shim over \`python3 -m okstra_ctl.git_reconcile\`. 기본은 검사:
6
+ stale 항목을 JSON 으로 출력하고, confirm 항목이 남으면 exit 2.
7
+ patch-id 로 내용 동일성이 증명되는 항목(auto)은 --apply 로 일괄 보정되고,
8
+ 내용이 바뀐 항목(confirm)은 --stage/--use-ref 로만 보정된다.
9
+
10
+ Usage:
11
+ okstra git-reconcile --plan-run-root <dir> --project-id <id> \\
12
+ --task-group <g> --task-id <t> --work-category <c> \\
13
+ [--apply] [--stage <N> --use-ref <ref>] [--reset-anchor <ref>] [--json]
14
+
15
+ Exit codes:
16
+ 0 stale 없음 또는 보정 완료
17
+ 2 confirm 항목 잔존 (check: 확인 필요 / apply: 일부 미보정)
18
+ 1 error (resolve 실패 등)
19
+ `;
20
+
21
+ export async function run(args) {
22
+ if (args.includes("--help") || args.includes("-h")) {
23
+ process.stdout.write(USAGE);
24
+ return 0;
25
+ }
26
+ const { code } = await runPythonModule({
27
+ module: "okstra_ctl.git_reconcile",
28
+ args,
29
+ });
30
+ return code ?? 1;
31
+ }
@@ -0,0 +1,30 @@
1
+ import { runPythonModule } from "./_python-helper.mjs";
2
+
3
+ const USAGE = `okstra handoff — release-handoff stage-group 보조 (자격/수집/기록)
4
+
5
+ A thin shim over \`python3 -m okstra_ctl.handoff\`. JSON 출력.
6
+
7
+ Usage:
8
+ okstra handoff eligible --plan-run-root <dir> --approved-plan <md>
9
+ okstra handoff assemble --plan-run-root <dir> --approved-plan <md> \\
10
+ --project-root <dir> --project-id <id> --task-group <g> --task-id <t> \\
11
+ --work-category <c> --stages 2,3 --base <branch>
12
+ okstra handoff record-verified --plan-run-root <dir> --stage <N> \\
13
+ --report-path <md> --data-json <json>
14
+ okstra handoff record-pr --plan-run-root <dir> --stages 2,3 \\
15
+ --branch <b> --url <u>
16
+
17
+ Exit codes: 0 ok / 1 자격·전제 위반 / 2 stage 간 merge 충돌(conflicts 동봉)
18
+ `;
19
+
20
+ export async function run(args) {
21
+ if (args.includes("--help") || args.includes("-h")) {
22
+ process.stdout.write(USAGE);
23
+ return 0;
24
+ }
25
+ const { code } = await runPythonModule({
26
+ module: "okstra_ctl.handoff",
27
+ args,
28
+ });
29
+ return code ?? 1;
30
+ }