okstra 0.159.0 → 0.160.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 (73) hide show
  1. package/README.md +1 -1
  2. package/docs/architecture/storage-model.md +2 -0
  3. package/docs/architecture.md +1 -1
  4. package/docs/cli.md +8 -3
  5. package/docs/for-ai/README.md +2 -2
  6. package/docs/for-ai/skills/okstra-inspect.md +3 -0
  7. package/docs/for-ai/skills/okstra-run.md +2 -1
  8. package/docs/for-ai/skills/okstra-user-response.md +5 -5
  9. package/docs/project-structure-overview.md +5 -1
  10. package/docs/task-process/implementation.md +28 -0
  11. package/package.json +1 -1
  12. package/runtime/BUILD.json +2 -2
  13. package/runtime/bin/okstra-claude-exec.sh +4 -1
  14. package/runtime/prompts/host-orchestration/README.md +18 -0
  15. package/runtime/prompts/host-orchestration/implementation.md +57 -0
  16. package/runtime/prompts/launch.template.md +10 -1
  17. package/runtime/prompts/lead/adapters/claude-code.md +1 -1
  18. package/runtime/prompts/lead/context-loader.md +5 -2
  19. package/runtime/prompts/lead/convergence.md +3 -1
  20. package/runtime/prompts/lead/plan-body-verification.md +21 -2
  21. package/runtime/prompts/lead/team-contract.md +2 -1
  22. package/runtime/prompts/profiles/_clarification-recommendation.md +11 -1
  23. package/runtime/prompts/profiles/_common-contract.md +3 -1
  24. package/runtime/prompts/profiles/implementation-planning.md +2 -0
  25. package/runtime/prompts/profiles/requirements-discovery.md +1 -1
  26. package/runtime/prompts/wizard/prompts.ko.json +3 -0
  27. package/runtime/python/okstra_ctl/clarification_items.py +9 -0
  28. package/runtime/python/okstra_ctl/codex_dispatch.py +6 -6
  29. package/runtime/python/okstra_ctl/convergence.py +168 -11
  30. package/runtime/python/okstra_ctl/dispatch_core.py +4 -2
  31. package/runtime/python/okstra_ctl/error_issue.py +640 -0
  32. package/runtime/python/okstra_ctl/error_report.py +56 -0
  33. package/runtime/python/okstra_ctl/error_zip.py +23 -10
  34. package/runtime/python/okstra_ctl/incremental_scope.py +159 -19
  35. package/runtime/python/okstra_ctl/initial_prompt_materialization.py +18 -5
  36. package/runtime/python/okstra_ctl/issue_signals.py +186 -0
  37. package/runtime/python/okstra_ctl/paths.py +38 -0
  38. package/runtime/python/okstra_ctl/plan_items_cli.py +167 -3
  39. package/runtime/python/okstra_ctl/profile_show.py +134 -0
  40. package/runtime/python/okstra_ctl/recap.py +63 -0
  41. package/runtime/python/okstra_ctl/render_final_report.py +7 -22
  42. package/runtime/python/okstra_ctl/report_translation.py +4 -0
  43. package/runtime/python/okstra_ctl/report_views.py +7 -3
  44. package/runtime/python/okstra_ctl/run.py +41 -2
  45. package/runtime/python/okstra_ctl/run_audit.py +477 -0
  46. package/runtime/python/okstra_ctl/user_response.py +25 -10
  47. package/runtime/python/okstra_ctl/verdict_blocks.py +183 -0
  48. package/runtime/python/okstra_ctl/wizard.py +64 -10
  49. package/runtime/python/okstra_ctl/worker_audit_check.py +44 -0
  50. package/runtime/python/okstra_ctl/worker_audit_ledger.py +207 -0
  51. package/runtime/python/okstra_ctl/worker_heartbeat.py +9 -3
  52. package/runtime/python/okstra_ctl/worker_liveness.py +81 -9
  53. package/runtime/schemas/final-report-v1.0.schema.json +14 -0
  54. package/runtime/schemas/final-report-v2.0.schema.json +51 -1
  55. package/runtime/skills/okstra-inspect/SKILL.md +3 -1
  56. package/runtime/skills/okstra-inspect/facets/error-issue.md +77 -0
  57. package/runtime/skills/okstra-inspect/facets/run-audit.md +34 -0
  58. package/runtime/skills/okstra-run/SKILL.md +28 -10
  59. package/runtime/skills/okstra-user-response/SKILL.md +18 -18
  60. package/runtime/templates/reports/final-report.template.md +4 -0
  61. package/runtime/templates/reports/html/i18n/en.json +5 -1
  62. package/runtime/templates/reports/html/i18n/ko.json +5 -1
  63. package/runtime/templates/reports/html/macros/forms.html +15 -0
  64. package/runtime/templates/reports/html/tasks/implementation-planning.template.html +1 -0
  65. package/runtime/templates/reports/i18n/en.json +2 -0
  66. package/runtime/validators/validate-run.py +267 -208
  67. package/runtime/validators/validate_session_conformance.py +102 -32
  68. package/src/cli-registry.mjs +34 -0
  69. package/src/commands/execute/incremental-scope.mjs +10 -0
  70. package/src/commands/execute/worker-audit-check.mjs +35 -0
  71. package/src/commands/inspect/error-issue.mjs +27 -0
  72. package/src/commands/inspect/profile-show.mjs +29 -0
  73. package/src/commands/inspect/run-audit.mjs +26 -0
@@ -6,7 +6,7 @@ Design: docs/superpowers/specs/2026-06-10-blocking-contract-posthoc-conformance-
6
6
  |-------|----------|----------|
7
7
  | 1. lead PROGRESS checkpoints | prompts/lead/okstra-lead-contract.md "Progress reporting (BLOCKING)" | selected adapter evidence source |
8
8
  | 2. worker-provider heartbeat | worker provider contract | audit sidecar `- PROGRESS: <stage> <ISO>` lines |
9
- | 3. implementation sidecar entry guard | prompts/lead/okstra-lead-contract.md "Entry guard (BLOCKING)" | selected adapter evidence source |
9
+ | 3. implementation entry guard | prompts/lead/okstra-lead-contract.md "Entry guard (BLOCKING)" + prompts/launch.template.md "Host Orchestration Rules" (only when the run staged the rules file) | selected adapter evidence source |
10
10
 
11
11
  Evidence rules prevent false passes:
12
12
  - `claude-jsonl` accepts only assistant text and Read tool-use records, excluding
@@ -25,7 +25,7 @@ import sys
25
25
  from dataclasses import dataclass, field
26
26
  from datetime import datetime
27
27
  from pathlib import Path
28
- from typing import Any, Mapping
28
+ from typing import Any, Mapping, NamedTuple
29
29
 
30
30
  # scripts/ (repo) and python/ (installed under ~/.okstra/lib) are not packages;
31
31
  # insert whichever exists so okstra_ctl is importable directly.
@@ -36,6 +36,7 @@ for _ssot_dir in (_VALIDATORS_DIR.parent / "scripts", _VALIDATORS_DIR.parent / "
36
36
 
37
37
  from okstra_ctl.worker_heartbeat import ( # noqa: E402
38
38
  HEARTBEAT_LINE_RE,
39
+ IN_STAGE_PREFIX,
39
40
  max_gap_seconds_after,
40
41
  )
41
42
  from okstra_ctl.lead_runtime import ARTIFACT_ONLY_LEAD_RUNTIMES # noqa: E402
@@ -67,18 +68,54 @@ _PROGRESS_LINE_RE = re.compile(
67
68
  # heartbeat 라인 shape 과 cadence 예산은 okstra_ctl.worker_heartbeat 정본을 쓴다 —
68
69
  # `okstra worker-liveness` 가 run 도중 같은 판정을 내리므로 정의가 갈리면 안 된다.
69
70
  _HEARTBEAT_LINE_RE = HEARTBEAT_LINE_RE
70
-
71
- # 장기 단계 중 append 되는 진행 라인 (claude-worker.md "Heartbeat").
72
- _IN_STAGE_PREFIX = "in-stage:"
73
-
74
- # Phase 5/6 진입 전 lead 가 Read 해야 하는 implementation 프로파일 sidecar.
75
- # 절대 경로는 레이어(repo / runtime / 설치본)마다 다르지만 basename 은 동일하다.
76
- _SIDECAR_BASENAMES = (
77
- "_implementation-executor.md",
78
- "_implementation-verifier.md",
79
- "_implementation-deliverable.md",
71
+ _IN_STAGE_PREFIX = IN_STAGE_PREFIX
72
+
73
+ _LEAD_CONTRACT_CITE = "prompts/lead/okstra-lead-contract.md 'Entry guard (BLOCKING)'"
74
+ _HOST_RULES_CITE = "prompts/launch.template.md 'Host Orchestration Rules'"
75
+
76
+
77
+ class _EntryGuardRead(NamedTuple):
78
+ """implementation entry guard 가 Read 를 요구하는 파일 한 건."""
79
+
80
+ basename: str
81
+ # (이 Read 가 앞서야 할 PROGRESS 체크포인트, 실패 메시지에 쓸 시점 표현).
82
+ # None 이면 순서 제약이 없고 run 윈도우 안의 존재만 검사한다.
83
+ anchor: tuple[str, str] | None
84
+ cite: str
85
+ # True 면 run 이 이 파일을 instruction-set 에 스테이징했을 때만 요구한다.
86
+ staged_per_run: bool = False
87
+
88
+
89
+ _ENTRY_GUARD_READS = (
90
+ _EntryGuardRead(
91
+ "_implementation-executor.md", ("phase-6-synthesis", "at Phase 5"), _LEAD_CONTRACT_CITE
92
+ ),
93
+ _EntryGuardRead(
94
+ "_implementation-verifier.md", ("phase-6-synthesis", "at Phase 5"), _LEAD_CONTRACT_CITE
95
+ ),
96
+ _EntryGuardRead(
97
+ "_implementation-deliverable.md", ("phase-7-persist", "at Phase 6"), _LEAD_CONTRACT_CITE
98
+ ),
99
+ # lead 프로파일 sidecar 가 아니라, run 이 instruction-set 에 스테이징한 host
100
+ # orchestration 규칙이다. 세 가지가 sidecar 와 다르다.
101
+ #
102
+ # anchor 없음: 이 파일은 `render-bundle` 이 만든다(run.py `_write_instruction_set_sources`).
103
+ # 그 앞에서 발화하는 gate(Step 5.1 waiver 제안)에는 아직 파일이 없고, 뒤에
104
+ # 오는 gate 는 run 의 PROGRESS 체크포인트와 시간 관계가 고정돼 있지 않다.
105
+ # 증거가 뒷받침하는 주장은 "run 윈도우 안에 Read 가 최소 한 번" 뿐이다.
106
+ # staged_per_run: 파일이 없는 번들 — 이 변경 전에 렌더된 run, 또는 payload 에
107
+ # prompts/host-orchestration/<task-type>.md 가 없는 설치본 — 은 읽을 수 없는
108
+ # 파일 때문에 막히면 안 된다. 규칙이 없는 task-type 에 conformance 가 규칙을
109
+ # 요구하지 않는다는 원칙과 같은 판단이다.
110
+ _EntryGuardRead(
111
+ "host-orchestration-rules.md", None, _HOST_RULES_CITE, staged_per_run=True
112
+ ),
80
113
  )
81
114
 
115
+ # Read 증거는 basename 으로 거른다 — 절대 경로는 레이어(repo / runtime / 설치본)
116
+ # 마다 다르지만 basename 은 동일하다. 목록이 갈리지 않도록 기대치에서 파생한다.
117
+ _ENTRY_GUARD_BASENAMES = tuple(row.basename for row in _ENTRY_GUARD_READS)
118
+
82
119
 
83
120
  @dataclass
84
121
  class SessionConformanceResult:
@@ -171,7 +208,7 @@ def _scan_one_jsonl(
171
208
  progress.append((ts, m.group("phase"), line))
172
209
  elif block.get("type") == "tool_use" and block.get("name") == "Read":
173
210
  base = Path(str((block.get("input") or {}).get("file_path") or "")).name
174
- if base in _SIDECAR_BASENAMES:
211
+ if base in _ENTRY_GUARD_BASENAMES:
175
212
  reads.setdefault(base, []).append(ts)
176
213
  return progress, reads, agent_name
177
214
 
@@ -297,7 +334,7 @@ def _sidecar_read_from_event(event) -> tuple[str, str] | None:
297
334
  if not isinstance(basename, str) or not basename:
298
335
  raw_path = details.get("path") or details.get("filePath")
299
336
  basename = Path(str(raw_path or "")).name
300
- if basename not in _SIDECAR_BASENAMES:
337
+ if basename not in _ENTRY_GUARD_BASENAMES:
301
338
  return None
302
339
  return (basename, event.timestamp)
303
340
 
@@ -696,36 +733,65 @@ def _check_worker_liveness(
696
733
  errors.append(f"registered wrapper status sidecar unreadable: {path}")
697
734
 
698
735
 
699
- def _check_implementation_sidecar_reads(evidence: _LeadEvidence, errors: list[str]) -> None:
736
+ def _instruction_set_dir(run_dir: Path, suffix: str | None, project_root: Path) -> Path | None:
737
+ """이 run 의 instruction-set 디렉터리. 해소 불가면 None (경로를 모르면 스테이징
738
+ 여부도 판정할 수 없고, 모르는 것을 근거로 run 을 떨어뜨리지 않는다)."""
739
+ if not suffix:
740
+ return None
741
+ manifest = run_dir / "manifests" / f"run-manifest-{suffix}.json"
742
+ try:
743
+ data = json.loads(manifest.read_text(encoding="utf-8"))
744
+ except (OSError, json.JSONDecodeError, UnicodeDecodeError):
745
+ return None
746
+ raw = data.get("instructionSetPath") if isinstance(data, dict) else None
747
+ if not isinstance(raw, str) or not raw:
748
+ return None
749
+ path = Path(raw)
750
+ if not path.is_absolute():
751
+ path = project_root / path
752
+ return path if path.is_dir() else None
753
+
754
+
755
+ def _check_implementation_entry_guard(
756
+ evidence: _LeadEvidence, errors: list[str], instruction_set: Path | None
757
+ ) -> None:
700
758
  """검사 3 — entry guard. fresh-read 규칙(이전 run 기억으로 갈음 불가)은 run
701
- 윈도우 스코핑이 보장한다: 이번 윈도우 안의 Read 만 증거로 인정된다."""
759
+ 윈도우 스코핑이 보장한다: 이번 윈도우 안의 Read 만 증거로 인정된다.
760
+
761
+ anchor 가 있는 행은 존재 + 순서를, anchor 가 `None` 인 행은 존재만 본다.
762
+ `staged_per_run` 행은 이 run 의 instruction-set 에 실제로 있을 때만 요구한다."""
702
763
  anchors: dict[str, str] = {}
703
764
  for ts, phase, _line in evidence.progress: # progress 는 ts 정렬 — 첫 항목이 최초 발생
704
765
  if phase in ("phase-6-synthesis", "phase-7-persist") and ts:
705
766
  anchors.setdefault(phase, ts)
706
- expectations = (
707
- ("_implementation-executor.md", "phase-6-synthesis", "Phase 5"),
708
- ("_implementation-verifier.md", "phase-6-synthesis", "Phase 5"),
709
- ("_implementation-deliverable.md", "phase-7-persist", "Phase 6"),
710
- )
711
- for basename, anchor_phase, read_at in expectations:
712
- ts_list = evidence.sidecar_reads.get(basename) or []
767
+ for row in _ENTRY_GUARD_READS:
768
+ if row.staged_per_run and not (
769
+ instruction_set and (instruction_set / row.basename).is_file()
770
+ ):
771
+ continue
772
+ ts_list = evidence.sidecar_reads.get(row.basename) or []
713
773
  if not ts_list:
774
+ demand = (
775
+ f"it MUST be read fresh {row.anchor[1]} of every implementation run"
776
+ if row.anchor
777
+ else "the staged copy MUST be read at least once inside that window"
778
+ )
714
779
  errors.append(
715
- f"implementation entry guard: no `Read` of `{basename}` found in "
716
- "the selected adapter evidence source within this run's window — "
717
- "the sidecar "
718
- f"MUST be read fresh at {read_at} every implementation run "
719
- "(prompts/lead/okstra-lead-contract.md 'Entry guard (BLOCKING)')."
780
+ f"implementation entry guard: no `Read` of `{row.basename}` found in "
781
+ f"the selected adapter evidence source within this run's window — "
782
+ f"{demand} ({row.cite})."
720
783
  )
721
784
  continue
785
+ if not row.anchor:
786
+ continue
787
+ anchor_phase, when = row.anchor
722
788
  anchor_ts = anchors.get(anchor_phase)
723
789
  if anchor_ts and min(ts_list) >= anchor_ts:
724
790
  errors.append(
725
- f"implementation entry guard: `{basename}` was first Read at "
791
+ f"implementation entry guard: `{row.basename}` was first Read at "
726
792
  f"{min(ts_list)}, not before the first `PROGRESS: {anchor_phase}` "
727
- f"line ({anchor_ts}) — it must be loaded at {read_at}, before "
728
- "that checkpoint (prompts/lead/okstra-lead-contract.md 'Entry guard (BLOCKING)')."
793
+ f"line ({anchor_ts}) — it must be loaded {when}, before "
794
+ f"that checkpoint ({row.cite})."
729
795
  )
730
796
 
731
797
 
@@ -772,5 +838,9 @@ def validate_session_conformance(
772
838
  return result
773
839
  _check_progress_checkpoints(evidence, team_state, run_dir, suffix, result.errors)
774
840
  if task_type == "implementation":
775
- _check_implementation_sidecar_reads(evidence, result.errors)
841
+ _check_implementation_entry_guard(
842
+ evidence,
843
+ result.errors,
844
+ _instruction_set_dir(run_dir, suffix, project_root),
845
+ )
776
846
  return result
@@ -87,6 +87,16 @@ export const COMMAND_REGISTRY = [
87
87
  category: "admin",
88
88
  summary: ["Score the §5.5.9 plan-body gate for one self-fix round"],
89
89
  },
90
+ {
91
+ name: "worker-audit-check",
92
+ module: "./commands/execute/worker-audit-check.mjs",
93
+ export: "run",
94
+ category: "admin",
95
+ summary: [
96
+ "Check a run's worker audit sidecars while the workers",
97
+ "are still alive (the Phase 7 rules, run early)",
98
+ ],
99
+ },
90
100
  {
91
101
  name: "git-reconcile",
92
102
  module: "./commands/execute/git-reconcile.mjs",
@@ -208,6 +218,20 @@ export const COMMAND_REGISTRY = [
208
218
  category: "introspection",
209
219
  summary: ["Bundle cross-project okstra error logs into a zip"],
210
220
  },
221
+ {
222
+ name: "error-issue",
223
+ module: "./commands/inspect/error-issue.mjs",
224
+ export: "run",
225
+ category: "introspection",
226
+ summary: ["Turn okstra run anomalies into GitHub issue candidates"],
227
+ },
228
+ {
229
+ name: "run-audit",
230
+ module: "./commands/inspect/run-audit.mjs",
231
+ export: "run",
232
+ category: "introspection",
233
+ summary: ["Check okstra run artifacts against progress invariants"],
234
+ },
211
235
  {
212
236
  name: "recap",
213
237
  module: "./commands/inspect/recap.mjs",
@@ -274,6 +298,16 @@ export const COMMAND_REGISTRY = [
274
298
  "python3 -m okstra_ctl.run --render-only)",
275
299
  ],
276
300
  },
301
+ {
302
+ name: "profile",
303
+ module: "./commands/inspect/profile-show.mjs",
304
+ export: "run",
305
+ category: "introspection",
306
+ summary: [
307
+ "Print a phase profile with {{INCLUDE:}} expanded and its",
308
+ "lazy-read sidecars appended (read-only)",
309
+ ],
310
+ },
277
311
  {
278
312
  name: "run",
279
313
  module: "./commands/execute/run.mjs",
@@ -10,8 +10,18 @@ instead of emitting a \`python3 "$HOME/..."\` invocation (which breaks
10
10
  Usage:
11
11
  okstra incremental-scope --prev-data <path> --cur-base-sha <sha> \\
12
12
  --prev-base-sha <sha> [--impacted <csv>] [--option <name>]
13
+ okstra incremental-scope --preview --prev-data <path> \\
14
+ --answered-clarifications <csv>
13
15
 
14
16
  Prints a JSON decision {mode, reverify_stages, carry_stages, reason} to stdout.
17
+
18
+ \`--preview\` prints {wouldForceFull, unlinkedIds, reason} instead and needs no
19
+ base SHA. It answers only the link half — whether the answered ids trace to a
20
+ stage in the prior report — so it can run before \`render-bundle\` fixes a
21
+ worktree base commit, while the decision to start is still reversible.
22
+ \`wouldForceFull: false\` is not a promise of \`incremental\`: the base-ref check
23
+ still runs at decision time.
24
+
15
25
  All arguments are forwarded verbatim to the python helper.
16
26
  `;
17
27
 
@@ -0,0 +1,35 @@
1
+ import { runPythonModule } from "../../lib/python-helper.mjs";
2
+
3
+ const USAGE = `okstra worker-audit-check — check one run's worker audit sidecars
4
+
5
+ Usage:
6
+ okstra worker-audit-check --run-dir <runs/<task-type>/> --task-type <type> \\
7
+ --seq <nnn> [--worker <id>]
8
+
9
+ --run-dir the run directory; worker-results/ and prompts/ hang off it
10
+ --worker check only this worker (default: every worker in the run)
11
+
12
+ Emits one JSON object — \`{ok, failures[]}\` — and exits 2 when failures[] is
13
+ non-empty, 0 otherwise.
14
+
15
+ Runs the Phase 7 audit-sidecar rules now, while the worker session is still
16
+ alive: that every result file carries no \`## 0. Reading Confirmation\` heading,
17
+ that its audit sidecar exists, and that every backticked \`path:line\` citation
18
+ has a matching Evidence read row in that sidecar.
19
+
20
+ Call it right after collecting a worker result. The same failure at Phase 7
21
+ leaves only a retroactive edit — which breaks the audit chain — or a failed run.
22
+ `;
23
+
24
+ export async function run(args) {
25
+ if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
26
+ process.stdout.write(USAGE);
27
+ return args.length === 0 ? 2 : 0;
28
+ }
29
+ const result = await runPythonModule({
30
+ module: "okstra_ctl.worker_audit_check",
31
+ args,
32
+ stdio: "inherit-stdout",
33
+ });
34
+ return result.code;
35
+ }
@@ -0,0 +1,27 @@
1
+ import { runPythonModule } from "../../lib/python-helper.mjs";
2
+
3
+ const USAGE = `okstra error-issue — turn okstra run anomalies into GitHub issue candidates
4
+
5
+ Usage:
6
+ okstra error-issue plan --out <path>
7
+ okstra error-issue submit --plan <path>
8
+
9
+ 'plan' walks the global run-index, gates clusters by occurrence and recency,
10
+ computes classification signals from the raw records, checks GitHub for an
11
+ existing issue carrying the same fingerprint, and writes plan.json.
12
+ 'submit' reads that file only, re-validates every candidate, and files or
13
+ comments via gh. A candidate that fails validation is never sent.
14
+ `;
15
+
16
+ export async function run(args) {
17
+ if (args.includes("--help") || args.includes("-h") || args.length === 0) {
18
+ process.stdout.write(USAGE);
19
+ return 0;
20
+ }
21
+ const result = await runPythonModule({
22
+ module: "okstra_ctl.error_issue",
23
+ args,
24
+ stdio: "inherit-stdout",
25
+ });
26
+ return result.code;
27
+ }
@@ -0,0 +1,29 @@
1
+ import { runPythonModule } from "../../lib/python-helper.mjs";
2
+
3
+ const USAGE = `okstra profile show — print a phase profile, fully resolved
4
+
5
+ Usage:
6
+ okstra profile show <task-type> [--resolved]
7
+
8
+ --resolved expand {{INCLUDE:}} targets and append the lazy-read sidecars
9
+
10
+ A profile is assembled from the top-level body, its {{INCLUDE:}} targets, and
11
+ the sidecars its own table names — so grepping the top-level file and finding
12
+ nothing does not mean the rule is absent. This prints all of it, so one grep
13
+ answers "does this task-type cover X".
14
+
15
+ Read-only: writes no manifest and registers no run. That is what separates it
16
+ from 'okstra render-bundle', which answers the same question with side effects.
17
+ `;
18
+
19
+ export async function run(args) {
20
+ if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
21
+ process.stdout.write(USAGE);
22
+ return args.length === 0 ? 2 : 0;
23
+ }
24
+ const { code } = await runPythonModule({
25
+ module: "okstra_ctl.profile_show",
26
+ args,
27
+ });
28
+ return code ?? 1;
29
+ }
@@ -0,0 +1,26 @@
1
+ import { runPythonModule } from "../../lib/python-helper.mjs";
2
+
3
+ const USAGE = `okstra run-audit — check okstra run artifacts against progress invariants
4
+
5
+ Usage:
6
+ okstra run-audit
7
+
8
+ Walks the global run-index and checks each run's artifacts (run-manifest,
9
+ final-report data, worker results) against invariants that no error log
10
+ would catch: verification rounds that never ran, reports that never
11
+ appeared, rosters that came up short, approvals waiting past the window.
12
+ Read-only. Prints a JSON summary to stdout.
13
+ `;
14
+
15
+ export async function run(args) {
16
+ if (args.includes("--help") || args.includes("-h")) {
17
+ process.stdout.write(USAGE);
18
+ return 0;
19
+ }
20
+ const result = await runPythonModule({
21
+ module: "okstra_ctl.run_audit",
22
+ args,
23
+ stdio: "inherit-stdout",
24
+ });
25
+ return result.code;
26
+ }