okstra 0.207.1 → 0.209.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 (133) hide show
  1. package/README.md +3 -2
  2. package/dist/cli-registry.mjs +6 -0
  3. package/dist/cli-registry.mjs.map +1 -1
  4. package/dist/commands/execute/render-bundle.mjs +1 -1
  5. package/dist/commands/lifecycle/doctor.mjs +1 -1
  6. package/dist/lib/skill-catalog.mjs +1 -0
  7. package/dist/lib/skill-catalog.mjs.map +1 -1
  8. package/docs/architecture/storage-model.md +14 -0
  9. package/docs/architecture.md +30 -9
  10. package/docs/cli.md +26 -22
  11. package/docs/contributor-change-matrix.md +1 -1
  12. package/docs/project-structure-overview.md +15 -8
  13. package/package.json +1 -1
  14. package/runtime/BUILD.json +2 -2
  15. package/runtime/agents/operations/explain-flow.json +6 -0
  16. package/runtime/bin/lib/okstra/cli.sh +1 -5
  17. package/runtime/bin/lib/okstra/globals.sh +0 -2
  18. package/runtime/bin/lib/okstra/usage.sh +5 -3
  19. package/runtime/bin/okstra.sh +0 -2
  20. package/runtime/prompts/duties/business-flow-investigator.json +14 -0
  21. package/runtime/prompts/lead/context-loader.md +1 -1
  22. package/runtime/prompts/lead/convergence.md +22 -7
  23. package/runtime/prompts/lead/okstra-lead-contract.md +10 -6
  24. package/runtime/prompts/lead/report-writer.md +1 -1
  25. package/runtime/prompts/lead/team-contract.md +12 -17
  26. package/runtime/prompts/profiles/_common-contract.md +3 -3
  27. package/runtime/prompts/wizard/prompts.ko.json +0 -91
  28. package/runtime/python/okstra_ctl/adapters/providers/claude/adapter.py +6 -0
  29. package/runtime/python/okstra_ctl/agent/invocation.py +1 -1
  30. package/runtime/python/okstra_ctl/agent/prompt_cli/batch.py +1 -0
  31. package/runtime/python/okstra_ctl/agent/prompt_cli/cli.py +1 -0
  32. package/runtime/python/okstra_ctl/agent/prompt_cli/materialize.py +11 -125
  33. package/runtime/python/okstra_ctl/agent/standalone.py +183 -0
  34. package/runtime/python/okstra_ctl/analysis_packet.py +39 -8
  35. package/runtime/python/okstra_ctl/assignment_resolver.py +7 -1
  36. package/runtime/python/okstra_ctl/brief_frontmatter.py +10 -0
  37. package/runtime/python/okstra_ctl/business_flow/__init__.py +4 -0
  38. package/runtime/python/okstra_ctl/business_flow/cli.py +134 -0
  39. package/runtime/python/okstra_ctl/business_flow/contracts.py +268 -0
  40. package/runtime/python/okstra_ctl/business_flow/engine.py +518 -0
  41. package/runtime/python/okstra_ctl/business_flow/hooks.py +221 -0
  42. package/runtime/python/okstra_ctl/business_flow/invocation.py +170 -0
  43. package/runtime/python/okstra_ctl/business_flow/report.py +49 -0
  44. package/runtime/python/okstra_ctl/business_flow/source.py +206 -0
  45. package/runtime/python/okstra_ctl/business_flow/store.py +388 -0
  46. package/runtime/python/okstra_ctl/cmux.py +21 -16
  47. package/runtime/python/okstra_ctl/convergence.py +173 -2
  48. package/runtime/python/okstra_ctl/convergence_critic_verify_prompt.py +18 -0
  49. package/runtime/python/okstra_ctl/convergence_provenance.py +8 -0
  50. package/runtime/python/okstra_ctl/coverage_census.py +603 -0
  51. package/runtime/python/okstra_ctl/design_surfaces.py +4 -0
  52. package/runtime/python/okstra_ctl/direct_work.py +1 -1
  53. package/runtime/python/okstra_ctl/dispatch_core.py +7 -3
  54. package/runtime/python/okstra_ctl/doctor.py +12 -6
  55. package/runtime/python/okstra_ctl/domain/role.py +1 -0
  56. package/runtime/python/okstra_ctl/group_context.py +5 -4
  57. package/runtime/python/okstra_ctl/legacy_model_selection.py +7 -51
  58. package/runtime/python/okstra_ctl/manager_split.py +4 -1
  59. package/runtime/python/okstra_ctl/model_io/lines.py +1 -24
  60. package/runtime/python/okstra_ctl/model_io/renderers.py +54 -41
  61. package/runtime/python/okstra_ctl/phases/change_impact_analysis/profile.json +1 -1
  62. package/runtime/python/okstra_ctl/phases/change_impact_analysis/profile.md +0 -8
  63. package/runtime/python/okstra_ctl/phases/error_analysis/profile.json +1 -1
  64. package/runtime/python/okstra_ctl/phases/error_analysis/profile.md +6 -8
  65. package/runtime/python/okstra_ctl/phases/feature_analysis/profile.json +1 -1
  66. package/runtime/python/okstra_ctl/phases/feature_analysis/profile.md +0 -8
  67. package/runtime/python/okstra_ctl/phases/final_verification/profile.json +1 -1
  68. package/runtime/python/okstra_ctl/phases/final_verification/profile.md +2 -8
  69. package/runtime/python/okstra_ctl/phases/implementation/boundary.json +1 -1
  70. package/runtime/python/okstra_ctl/phases/implementation/profile.json +1 -1
  71. package/runtime/python/okstra_ctl/phases/implementation/profile.md +0 -6
  72. package/runtime/python/okstra_ctl/phases/implementation/report_assets/implementation-input.template.md +1 -1
  73. package/runtime/python/okstra_ctl/phases/implementation_option_selection/authoring.py +4 -3
  74. package/runtime/python/okstra_ctl/phases/implementation_option_selection/entry.py +1 -14
  75. package/runtime/python/okstra_ctl/phases/implementation_option_selection/profile.json +1 -1
  76. package/runtime/python/okstra_ctl/phases/implementation_option_selection/profile.md +5 -8
  77. package/runtime/python/okstra_ctl/phases/implementation_option_selection/spec.md +3 -3
  78. package/runtime/python/okstra_ctl/phases/implementation_option_selection/validation.py +15 -5
  79. package/runtime/python/okstra_ctl/phases/implementation_planning/authoring.py +9 -1
  80. package/runtime/python/okstra_ctl/phases/implementation_planning/boundary.json +1 -1
  81. package/runtime/python/okstra_ctl/phases/implementation_planning/instructions/plan-body-verification.md +4 -2
  82. package/runtime/python/okstra_ctl/phases/implementation_planning/plan_body.py +25 -9
  83. package/runtime/python/okstra_ctl/phases/implementation_planning/profile.json +1 -1
  84. package/runtime/python/okstra_ctl/phases/implementation_planning/profile.md +7 -10
  85. package/runtime/python/okstra_ctl/phases/improvement_discovery/profile.json +1 -1
  86. package/runtime/python/okstra_ctl/phases/improvement_discovery/profile.md +4 -11
  87. package/runtime/python/okstra_ctl/phases/project_analysis/profile.json +1 -1
  88. package/runtime/python/okstra_ctl/phases/project_analysis/profile.md +0 -8
  89. package/runtime/python/okstra_ctl/phases/release_handoff/profile.md +1 -1
  90. package/runtime/python/okstra_ctl/phases/release_handoff/spec.md +1 -1
  91. package/runtime/python/okstra_ctl/phases/requirements_discovery/profile.json +1 -1
  92. package/runtime/python/okstra_ctl/phases/requirements_discovery/profile.md +14 -8
  93. package/runtime/python/okstra_ctl/phases/requirements_discovery/spec.md +3 -3
  94. package/runtime/python/okstra_ctl/phases/technical_verification/profile.json +1 -1
  95. package/runtime/python/okstra_ctl/phases/technical_verification/profile.md +0 -4
  96. package/runtime/python/okstra_ctl/plan_items.py +1 -1
  97. package/runtime/python/okstra_ctl/render.py +10 -43
  98. package/runtime/python/okstra_ctl/render_final_report.py +3 -0
  99. package/runtime/python/okstra_ctl/report_assembly.py +15 -1
  100. package/runtime/python/okstra_ctl/report_finalize.py +50 -0
  101. package/runtime/python/okstra_ctl/report_html/render.py +3 -0
  102. package/runtime/python/okstra_ctl/report_synthesis_packet.py +1 -2
  103. package/runtime/python/okstra_ctl/run.py +78 -409
  104. package/runtime/python/okstra_ctl/wizard/__init__.py +2 -24
  105. package/runtime/python/okstra_ctl/wizard/cli.py +3 -6
  106. package/runtime/python/okstra_ctl/wizard/confirmation.py +3 -35
  107. package/runtime/python/okstra_ctl/wizard/engine.py +2 -4
  108. package/runtime/python/okstra_ctl/wizard/ids.py +1 -88
  109. package/runtime/python/okstra_ctl/wizard/registry.py +36 -228
  110. package/runtime/python/okstra_ctl/wizard/render.py +2 -2
  111. package/runtime/python/okstra_ctl/wizard/roles.py +1 -3
  112. package/runtime/python/okstra_ctl/wizard/sources.py +9 -40
  113. package/runtime/python/okstra_ctl/wizard/state.py +36 -145
  114. package/runtime/python/okstra_ctl/wizard/statefile.py +27 -128
  115. package/runtime/python/okstra_ctl/wizard/steps_identity.py +22 -10
  116. package/runtime/python/okstra_ctl/wizard/steps_options.py +5 -4
  117. package/runtime/python/okstra_ctl/wizard/steps_roles.py +15 -565
  118. package/runtime/python/okstra_ctl/worker_prompt_policy.py +9 -2
  119. package/runtime/schemas/business-flow-v1.schema.json +847 -0
  120. package/runtime/schemas/convergence-groups-v2.0.schema.json +7 -0
  121. package/runtime/skills/okstra-explain-flow/SKILL.md +42 -0
  122. package/runtime/skills/okstra-inspect/facets/history.md +5 -5
  123. package/runtime/skills/okstra-run/SKILL.md +2 -2
  124. package/runtime/templates/reports/business-flow.template.md +106 -0
  125. package/runtime/templates/reports/html/base.template.html +14 -1
  126. package/runtime/templates/reports/html/business-flow.template.html +31 -0
  127. package/runtime/templates/reports/html/i18n/en.json +1 -0
  128. package/runtime/templates/reports/html/i18n/ko.json +1 -0
  129. package/runtime/templates/worker-prompt-preamble.md +11 -2
  130. package/runtime/validators/checks/validate-prompt-metadata-01.py +10 -10
  131. package/runtime/validators/validate-run.py +70 -21
  132. package/runtime/validators/validate_analysis_report.py +21 -21
  133. package/runtime/python/okstra_ctl/workers.py +0 -133
@@ -174,18 +174,18 @@ def _validate_all_current_code_evidence(
174
174
 
175
175
 
176
176
 
177
- def _required_worker_roles(
177
+ def _selected_worker_roles(
178
178
  run_manifest: dict, errors: list[str]
179
179
  ) -> tuple[str, ...]:
180
180
  contract = run_manifest.get("teamContract")
181
181
  entries = (
182
- contract.get("requiredWorkerRoles")
182
+ contract.get("workerRoles")
183
183
  if isinstance(contract, dict)
184
184
  else None
185
185
  )
186
186
  if not isinstance(entries, list) or not entries:
187
187
  errors.append(
188
- "run-manifest.teamContract.requiredWorkerRoles must be a non-empty "
188
+ "run-manifest.teamContract.workerRoles must be a non-empty "
189
189
  "array of worker contract objects"
190
190
  )
191
191
  return ()
@@ -198,13 +198,13 @@ def _required_worker_roles(
198
198
  or role != role.strip()
199
199
  ):
200
200
  errors.append(
201
- "run-manifest.teamContract.requiredWorkerRoles"
201
+ "run-manifest.teamContract.workerRoles"
202
202
  f"[{index}].role must be an exact non-empty string"
203
203
  )
204
204
  return ()
205
205
  if role in roles:
206
206
  errors.append(
207
- "run-manifest.teamContract.requiredWorkerRoles must not contain "
207
+ "run-manifest.teamContract.workerRoles must not contain "
208
208
  f"duplicate role `{role}`"
209
209
  )
210
210
  return ()
@@ -212,17 +212,17 @@ def _required_worker_roles(
212
212
  return tuple(roles)
213
213
 
214
214
 
215
- def _required_worker_rows(data: dict, required_roles: tuple[str, ...]) -> list[dict]:
216
- required = set(required_roles)
215
+ def _selected_worker_status_rows(data: dict, selected_roles: tuple[str, ...]) -> list[dict]:
216
+ selected = set(selected_roles)
217
217
  return [
218
218
  row
219
219
  for row in data.get("executionStatus") or []
220
- if isinstance(row, dict) and row.get("role") in required
220
+ if isinstance(row, dict) and row.get("role") in selected
221
221
  ]
222
222
 
223
223
 
224
224
  def _validate_execution_status_roles(
225
- data: dict, required_roles: tuple[str, ...], errors: list[str]
225
+ data: dict, selected_roles: tuple[str, ...], errors: list[str]
226
226
  ) -> bool:
227
227
  """Validate exact labels and report whether verdict matching is unambiguous."""
228
228
  actual_roles = {
@@ -230,7 +230,7 @@ def _validate_execution_status_roles(
230
230
  for row in data.get("executionStatus") or []
231
231
  if isinstance(row, dict)
232
232
  }
233
- expected_by_fold = {role.casefold(): role for role in required_roles}
233
+ expected_by_fold = {role.casefold(): role for role in selected_roles}
234
234
  verdict_roles_unambiguous = True
235
235
  for actual_role in sorted(actual_roles):
236
236
  expected_role = expected_by_fold.get(actual_role.casefold())
@@ -241,10 +241,10 @@ def _validate_execution_status_roles(
241
241
  f"expected exact role `{expected_role}`"
242
242
  )
243
243
  verdict_roles_unambiguous = False
244
- for required_role in required_roles:
245
- if required_role in actual_roles:
244
+ for selected_role in selected_roles:
245
+ if selected_role in actual_roles:
246
246
  continue
247
- errors.append(f"missing required analysis role `{required_role}`")
247
+ errors.append(f"missing selected analysis role `{selected_role}`")
248
248
  return verdict_roles_unambiguous
249
249
 
250
250
 
@@ -291,13 +291,13 @@ def _scope_confirmation_status(
291
291
 
292
292
  def _validate_analysis_verdict(
293
293
  data: dict,
294
- required_roles: tuple[str, ...],
294
+ selected_roles: tuple[str, ...],
295
295
  reporter_confirmation: str | None,
296
296
  errors: list[str],
297
297
  ) -> None:
298
298
  common = data.get("analysisCommon") or {}
299
299
  scope = common.get("scope") or {}
300
- worker_rows = _required_worker_rows(data, required_roles)
300
+ worker_rows = _selected_worker_status_rows(data, selected_roles)
301
301
  worker_blocked = not any(row.get("status") == "completed" for row in worker_rows)
302
302
  scope_blocked = reporter_confirmation != "complete"
303
303
  unresolved_review = any(
@@ -316,7 +316,7 @@ def _validate_analysis_verdict(
316
316
  return
317
317
  reasons = []
318
318
  if worker_blocked:
319
- reasons.append("required analysis workers produced zero completed results")
319
+ reasons.append("selected analysis workers produced zero completed results")
320
320
  if scope_blocked:
321
321
  reasons.append("brief frontmatter reporter-confirmations is not complete")
322
322
  if partial:
@@ -330,7 +330,7 @@ def _validate_analysis_verdict(
330
330
 
331
331
  def _validate_scope_before_dispatch(
332
332
  data: dict,
333
- required_roles: tuple[str, ...],
333
+ selected_roles: tuple[str, ...],
334
334
  reporter_confirmation: str | None,
335
335
  errors: list[str],
336
336
  ) -> None:
@@ -338,11 +338,11 @@ def _validate_scope_before_dispatch(
338
338
  return
339
339
  dispatched = any(
340
340
  row.get("status") != "not-run"
341
- for row in _required_worker_rows(data, required_roles)
341
+ for row in _selected_worker_status_rows(data, selected_roles)
342
342
  )
343
343
  if dispatched:
344
344
  errors.append(
345
- "required analysis workers were dispatched before "
345
+ "analysis workers were dispatched before "
346
346
  "reporter-confirmations: complete was recorded"
347
347
  )
348
348
 
@@ -354,9 +354,9 @@ def validate_analysis_semantics(
354
354
  reporter_confirmation: str | None,
355
355
  errors: list[str],
356
356
  ) -> None:
357
- required_roles = _required_worker_roles(run_manifest, errors)
357
+ selected_roles = _selected_worker_roles(run_manifest, errors)
358
358
  analysis_worker_roles = tuple(
359
- role for role in required_roles if not _is_report_writer_role(role)
359
+ role for role in selected_roles if not _is_report_writer_role(role)
360
360
  )
361
361
  verdict_roles_unambiguous = _validate_execution_status_roles(
362
362
  data, analysis_worker_roles, errors
@@ -1,133 +0,0 @@
1
- """V1 provider-worker roster compatibility parsing.
2
-
3
- Profile 파일에서 권장 worker 목록을 뽑고(`resolve_profile_workers`), 사용자
4
- 오버라이드와 합쳐 정규화한다(`normalize_workers`). bash workers.sh 의
5
- 동등한 python 구현.
6
- """
7
- from __future__ import annotations
8
-
9
- from pathlib import Path
10
-
11
- from .models import provider_ids
12
-
13
- ALLOWED_WORKERS = provider_ids("analyser") + ["report-writer"]
14
- DEFAULT_WORKERS = ["claude", "codex", "report-writer"]
15
- PROFILE_BULLET_HEADERS = {
16
- "- Workers:",
17
- "- Required workers:",
18
- "- Reviewers:",
19
- "- Analysers:",
20
- }
21
- PROFILE_BULLET_HEADERS_OPTIONAL = {
22
- "- Optional workers:",
23
- "- Optional workers (opt-in via `--workers`):",
24
- }
25
-
26
-
27
- class WorkersError(Exception):
28
- """invalid worker selection — surface to user."""
29
-
30
-
31
- def _resolve_v1_workers_under(profile_path: Path, headers: set[str]) -> list[str]:
32
- """Read legacy provider-worker bullets without interpreting role YAML.
33
-
34
- Collect ` - <id> …` sub-bullets under any of `headers`
35
- until the next top-level bullet. Returns first token (before any
36
- ` — ` / ` -- ` / whitespace) of each captured line.
37
- """
38
- if not Path(profile_path).is_file():
39
- return []
40
- capturing = False
41
- out: list[str] = []
42
- for line in Path(profile_path).read_text(encoding="utf-8").splitlines():
43
- stripped = line.strip()
44
- if stripped in headers:
45
- capturing = True
46
- continue
47
- if not capturing:
48
- continue
49
- if line.startswith("- "):
50
- break
51
- if line.startswith(" - "):
52
- body = line[4:].strip()
53
- token = body.split(" — ", 1)[0].split(" -- ", 1)[0].split()[0]
54
- out.append(token)
55
- continue
56
- if stripped:
57
- break
58
- return out
59
-
60
-
61
- def resolve_profile_workers(profile_path: Path) -> list[str]:
62
- """`prompts/profiles/<task-type>.md` 본문의 `- Required workers:` (또는
63
- `- Workers:` / `- Reviewers:` / `- Analysers:`) 섹션 아래 sub-bullet 들을
64
- worker id 리스트로 돌려준다. profile 파일이 없거나 섹션이 없으면 빈 리스트.
65
- """
66
- return _resolve_v1_workers_under(profile_path, PROFILE_BULLET_HEADERS)
67
-
68
-
69
- def resolve_optional_workers(profile_path: Path) -> list[str]:
70
- r"""`- Optional workers (opt-in via \`--workers\`):` 섹션 아래 sub-bullet
71
- 들에서 worker id 만 추출한다. (` - antigravity — when added …` → `antigravity`)
72
- Required 와 중복되는 항목은 제거. ALLOWED_WORKERS 밖 토큰도 제거.
73
- """
74
- raw = _resolve_v1_workers_under(profile_path, PROFILE_BULLET_HEADERS_OPTIONAL)
75
- required = set(resolve_profile_workers(profile_path))
76
- seen: set[str] = set()
77
- out: list[str] = []
78
- for w in raw:
79
- if w in required or w in seen or w not in ALLOWED_WORKERS:
80
- continue
81
- seen.add(w)
82
- out.append(w)
83
- return out
84
-
85
-
86
- def normalize_workers(value: str) -> list[str]:
87
- """CSV 입력을 정규화한다.
88
-
89
- - 공백 strip, 소문자화, 중복 제거(첫 출현 우선).
90
- - 빈 입력이면 `DEFAULT_WORKERS` 를 default 로 사용 (antigravity 제외).
91
- - 허용 외 worker 가 포함되면 `WorkersError`.
92
- """
93
- items = [v.strip().lower() for v in (value or "").split(",") if v.strip()]
94
- source = items or DEFAULT_WORKERS
95
- unknown = [v for v in source if v not in ALLOWED_WORKERS]
96
- if unknown:
97
- raise WorkersError(f"unknown workers: {','.join(unknown)}")
98
- seen: set[str] = set()
99
- out: list[str] = []
100
- for v in source:
101
- if v in seen:
102
- continue
103
- seen.add(v)
104
- out.append(v)
105
- return out
106
-
107
-
108
- def validate_workers_against_profile(
109
- workers: list[str],
110
- profile_workers: list[str],
111
- optional_workers: list[str] | None = None,
112
- ) -> None:
113
- """프로파일이 `Required workers:` 로 로스터를 선언했다면, 사용자
114
- override 가 (required ∪ optional) 의 부분집합인지 검증한다.
115
-
116
- `Required workers:` 는 이 시스템에서 **allowlist + 기본 로스터**다 —
117
- 전원 필수 집합이 아니다. 사용자는 그 안에서 좁혀 고를 수 있고(최소 1),
118
- wizard 는 분석 워커만 고르게 한 뒤 report-writer 를 뒤에서 강제
119
- 추가하므로 누락 검증을 여기서 하면 그 흐름이 깨진다.
120
-
121
- `profile_workers` 가 비어 있으면(프로파일이 로스터를 선언하지 않은
122
- 구버전) 검증을 건너뛴다.
123
- """
124
- if not profile_workers:
125
- return
126
- allowed = set(profile_workers) | set(optional_workers or [])
127
- extras = [w for w in workers if w not in allowed]
128
- if extras:
129
- allow_str = ",".join(profile_workers + list(optional_workers or []))
130
- raise WorkersError(
131
- "workers not allowed by profile roster: "
132
- f"{','.join(extras)} (profile allows: {allow_str})"
133
- )