okstra 0.218.0 → 0.219.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 (92) hide show
  1. package/README.md +5 -5
  2. package/dist/cli-registry.mjs +0 -10
  3. package/dist/cli-registry.mjs.map +1 -1
  4. package/dist/commands/lifecycle/config.mjs +2 -3
  5. package/dist/commands/lifecycle/config.mjs.map +1 -1
  6. package/dist/commands/pr/pr.d.mts +6 -0
  7. package/dist/commands/pr/pr.mjs +58 -0
  8. package/dist/commands/pr/pr.mjs.map +1 -1
  9. package/dist/lib/runtime-payload.mjs +1 -2
  10. package/dist/lib/runtime-payload.mjs.map +1 -1
  11. package/docs/architecture/storage-model.md +1 -1
  12. package/docs/architecture.md +9 -7
  13. package/docs/cli.md +3 -4
  14. package/docs/project-structure-overview.md +4 -5
  15. package/package.json +1 -1
  16. package/runtime/BUILD.json +2 -2
  17. package/runtime/prompts/launch.template.md +1 -1
  18. package/runtime/prompts/lead/okstra-lead-contract.md +1 -3
  19. package/runtime/prompts/lead/report-writer.md +10 -17
  20. package/runtime/prompts/lead/team-contract.md +1 -2
  21. package/runtime/prompts/wizard/prompts.ko.json +0 -1
  22. package/runtime/python/okstra_ctl/adapters/hosts/codex/relay.md +1 -1
  23. package/runtime/python/okstra_ctl/adapters/providers/antigravity/adapter.py +1 -1
  24. package/runtime/python/okstra_ctl/adapters/providers/claude/adapter.py +0 -1
  25. package/runtime/python/okstra_ctl/adapters/providers/codex/adapter.py +1 -1
  26. package/runtime/python/okstra_ctl/adapters/providers/grok/adapter.py +0 -1
  27. package/runtime/python/okstra_ctl/adapters/providers/kimi/adapter.py +0 -1
  28. package/runtime/python/okstra_ctl/agent/invocation.py +1 -2
  29. package/runtime/python/okstra_ctl/agent/prompt_cli/cli.py +2 -2
  30. package/runtime/python/okstra_ctl/agent/prompt_cli/jobs.py +0 -2
  31. package/runtime/python/okstra_ctl/agent/prompt_cli/materialize.py +2 -27
  32. package/runtime/python/okstra_ctl/agent/prompt_cli/run_identity.py +0 -4
  33. package/runtime/python/okstra_ctl/dispatch_core.py +3 -349
  34. package/runtime/python/okstra_ctl/dispatch_state.py +0 -13
  35. package/runtime/python/okstra_ctl/domain/provider.py +1 -1
  36. package/runtime/python/okstra_ctl/domain/role.py +0 -6
  37. package/runtime/python/okstra_ctl/final_report_paths.py +0 -16
  38. package/runtime/python/okstra_ctl/i18n.py +2 -2
  39. package/runtime/python/okstra_ctl/path_hints.py +0 -4
  40. package/runtime/python/okstra_ctl/paths.py +0 -3
  41. package/runtime/python/okstra_ctl/phases/final_verification/profile.md +2 -0
  42. package/runtime/python/okstra_ctl/phases/final_verification/spec.md +2 -0
  43. package/runtime/python/okstra_ctl/phases/final_verification/target.py +17 -1
  44. package/runtime/python/okstra_ctl/phases/implementation/entry.py +12 -0
  45. package/runtime/python/okstra_ctl/phases/implementation/instructions/_implementation-deliverable.md +1 -0
  46. package/runtime/python/okstra_ctl/phases/implementation/instructions/_implementation-executor.md +1 -0
  47. package/runtime/python/okstra_ctl/phases/implementation/spec.md +4 -0
  48. package/runtime/python/okstra_ctl/phases/release_handoff/boundary.json +1 -1
  49. package/runtime/python/okstra_ctl/phases/release_handoff/operations.py +24 -3
  50. package/runtime/python/okstra_ctl/phases/release_handoff/profile.md +11 -11
  51. package/runtime/python/okstra_ctl/phases/release_handoff/spec.md +4 -2
  52. package/runtime/python/okstra_ctl/render.py +0 -10
  53. package/runtime/python/okstra_ctl/render_final_report.py +4 -4
  54. package/runtime/python/okstra_ctl/report_finalize.py +37 -76
  55. package/runtime/python/okstra_ctl/report_html/render.py +4 -44
  56. package/runtime/python/okstra_ctl/report_language.py +3 -3
  57. package/runtime/python/okstra_ctl/report_prose_keys.py +153 -0
  58. package/runtime/python/okstra_ctl/report_synthesis_packet.py +1 -1
  59. package/runtime/python/okstra_ctl/run.py +1 -21
  60. package/runtime/python/okstra_ctl/stage_integrate.py +37 -2
  61. package/runtime/python/okstra_ctl/stage_rebase.py +189 -0
  62. package/runtime/python/okstra_ctl/stage_targets.py +29 -62
  63. package/runtime/python/okstra_ctl/team.py +1 -6
  64. package/runtime/python/okstra_ctl/wizard/confirmation.py +0 -8
  65. package/runtime/python/okstra_ctl/wizard/registry.py +2 -3
  66. package/runtime/python/okstra_ctl/wizard/sources.py +10 -0
  67. package/runtime/python/okstra_ctl/worker_prompt_policy.py +3 -12
  68. package/runtime/python/okstra_ctl/worktree/__init__.py +1 -1
  69. package/runtime/python/okstra_ctl/worktree/decisions.py +3 -4
  70. package/runtime/python/okstra_ctl/worktree/provision.py +27 -0
  71. package/runtime/python/okstra_ctl/worktree/rebase.py +108 -0
  72. package/runtime/python/okstra_ctl/worktree_registry.py +14 -0
  73. package/runtime/skills/_fragments/wizard-render-bundle.md +1 -1
  74. package/runtime/skills/okstra-code-review/SKILL.md +1 -1
  75. package/runtime/skills/okstra-discussion/SKILL.md +1 -1
  76. package/runtime/skills/okstra-pr-gen/SKILL.md +9 -0
  77. package/runtime/skills/okstra-run/SKILL.md +1 -1
  78. package/runtime/skills/okstra-setup/SKILL.md +2 -2
  79. package/runtime/skills/okstra-setup/references/project-config.md +2 -2
  80. package/runtime/templates/pr/drafting-rules.md +4 -0
  81. package/runtime/templates/report-writer-prompt-preamble.md +1 -1
  82. package/runtime/templates/reports/html/i18n/en.json +0 -1
  83. package/runtime/templates/reports/html/i18n/ko.json +0 -1
  84. package/runtime/templates/worker-prompt-preamble.md +1 -1
  85. package/runtime/validators/validate-run.py +1 -28
  86. package/runtime/agents/operations/report-translation.json +0 -6
  87. package/runtime/agents/roles/translator.json +0 -19
  88. package/runtime/bin/okstra-report-translate.py +0 -404
  89. package/runtime/prompts/duties/translator.json +0 -32
  90. package/runtime/python/okstra_ctl/report_translation.py +0 -509
  91. package/runtime/python/okstra_ctl/report_translation_dispatch.py +0 -356
  92. package/runtime/templates/translator-prompt-preamble.md +0 -36
@@ -13,7 +13,6 @@ PromptAudience = Literal[
13
13
  "implementation-executor",
14
14
  "implementation-verifier",
15
15
  "report-writer",
16
- "translator",
17
16
  "reverify",
18
17
  "lead-only",
19
18
  ]
@@ -84,7 +83,6 @@ WORKER_PREAMBLE_FILENAME_BY_AUDIENCE = {
84
83
  "implementation-executor": "implementation-worker-preamble.md",
85
84
  "implementation-verifier": "implementation-worker-preamble.md",
86
85
  "report-writer": "report-writer-prompt-preamble.md",
87
- "translator": "translator-prompt-preamble.md",
88
86
  }
89
87
  WORKER_ERROR_CONTRACT_FILENAME = "worker-error-contract.md"
90
88
  # coverage-critic gap 검증 라운드의 dispatch kind. 번호 라운드(`reverify-r<N>`)와
@@ -199,7 +197,7 @@ def resolve_prompt_plan(
199
197
  """Resolve prompt policy from the worker's role and the dispatch kind.
200
198
 
201
199
  Neither the worker id nor the provider or model identity takes part. The id
202
- used to: `report-writer`, `translator`, and "the executor's worker id" were
200
+ used to: `report-writer` and "the executor's worker id" were
203
201
  compared by spelling, so a verifier that happened to share the executor's
204
202
  id received the executor's prompt.
205
203
  """
@@ -210,12 +208,6 @@ def resolve_prompt_plan(
210
208
  # 걸리지 않아 분석 계획으로 떨어졌고, 리드 프롬프트가 분석 워커 직무를
211
209
  # 달고 나갔다.
212
210
  return _plan("lead-only")
213
- if dispatch_kind == "translator":
214
- if role not in (None, "translator"):
215
- raise ValueError(f"translator dispatch cannot run role {role}")
216
- return _plan("translator", duty_audience="translator")
217
- if role == "translator":
218
- raise ValueError("translator role requires the translator dispatch kind")
219
211
  if is_verification_dispatch_kind(dispatch_kind):
220
212
  return _plan("reverify")
221
213
  # A critic pass keeps the full analysis contract — worker anchor headers, the
@@ -307,8 +299,8 @@ def resolve_prompt_plan_for_manifest(
307
299
  """Resolve policy from the manifest's task type and the worker's roster role.
308
300
 
309
301
  *worker_id* is the lookup key into `workerAssignments`; its spelling decides
310
- nothing. A worker with no roster row — the translator, or a verification
311
- pass whose kind alone decides — resolves with no role.
302
+ nothing. A worker with no roster row — a verification pass whose kind
303
+ alone decides — resolves with no role.
312
304
  """
313
305
  task_type = _required_string(manifest, "taskType")
314
306
  if not worker_id.strip():
@@ -383,7 +375,6 @@ def _duty_audience(audience: PromptAudience) -> AgentAudience:
383
375
  "implementation-executor": "implementation-executor",
384
376
  "implementation-verifier": "implementation-verifier",
385
377
  "report-writer": "report-writer",
386
- "translator": "translator",
387
378
  "reverify": "reverification-worker",
388
379
  "lead-only": "lead",
389
380
  }[audience]
@@ -34,7 +34,7 @@ Side effects:
34
34
  `sync_config`(무엇을 옮길지), `git_ops`(git 사실) — 은 서로를 모른다. 그 위에
35
35
  `cleanliness`(무엇이 더러운가) · `linking`(실제로 링크 걸기) ·
36
36
  `decisions`(무엇이 일어날지 미리 답하기)가 있고, 부수 효과를 내는 것은
37
- `provision` 하나뿐이다. 이 파일은 밖으로 나가는 이름만 모아 둔다.
37
+ `provision` 과 단계 브랜치를 재배치하는 `rebase` 뿐이다. 이 파일은 밖으로 나가는 이름만 모아 둔다.
38
38
  """
39
39
  from __future__ import annotations
40
40
 
@@ -118,10 +118,9 @@ def _stage_entry_is_reusable(entry: worktree_registry.WorktreeEntry) -> bool:
118
118
  the fix-run case: a stage whose verifier returned FAIL records a `failed`
119
119
  consumers row, which frees the occupancy but deliberately keeps the worktree
120
120
  and its branch as the reviewable stack. Re-entry MUST reuse that tree —
121
- provisioning anew refuses on the existing path and branch. After whole-task
122
- final-verification removes the directory the entry stops being reusable, so
123
- the stage provisions from scratch.
121
+ provisioning anew refuses on the existing branch. A removed directory is
122
+ restored from that branch on the next implementation or verification entry.
124
123
  """
125
124
  if entry.status == "active":
126
125
  return True
127
- return entry.status == "released" and Path(entry.worktree_path).is_dir()
126
+ return entry.status == "released"
@@ -13,6 +13,7 @@ from pathlib import Path
13
13
 
14
14
  from .. import worktree_registry
15
15
  from .decisions import preview_worktree_decision, resolve_stage_worktree_decision
16
+ from .rebase import rebase_stage_branch
16
17
  from .git_ops import (
17
18
  _branch_exists,
18
19
  _branch_exists_message,
@@ -259,6 +260,21 @@ def provision_task_worktree(
259
260
  )
260
261
 
261
262
 
263
+ def restore_stage_worktree(project_root: Path, path: str, branch: str) -> None:
264
+ target = Path(path)
265
+ if target.exists():
266
+ return
267
+ root = main_worktree_path(project_root)
268
+ target.parent.mkdir(parents=True, exist_ok=True)
269
+ result = _git(root, "worktree", "add", str(target), branch)
270
+ if result.returncode:
271
+ raise RuntimeError(f"cannot restore stage worktree {target}: {result.stderr.strip()}")
272
+ _link_sync_dirs(root, target)
273
+ _link_sync_files(root, target)
274
+ _copy_snapshot_files(root, target)
275
+ _seed_worktree_settings_symlink(target)
276
+
277
+
262
278
  def provision_stage_worktree(
263
279
  *,
264
280
  project_root: Path,
@@ -298,6 +314,17 @@ def provision_stage_worktree(
298
314
  stage_number=stage_number,
299
315
  )
300
316
  if decision.status == "reused":
317
+ restore_stage_worktree(project_root, decision.path, decision.branch)
318
+ resolved_base = _resolve_commit_sha(project_root, base_commit)
319
+ if not resolved_base:
320
+ raise RuntimeError(f"could not resolve base_commit `{base_commit}`")
321
+ if decision.base_ref != resolved_base:
322
+ rebase_stage_branch(project_root, decision.branch, decision.base_ref, resolved_base)
323
+ worktree_registry.update_stage_base(
324
+ project_id, task_group_segment, task_id_segment, stage_number,
325
+ expected_base=decision.base_ref, base_ref=resolved_base,
326
+ )
327
+ decision.base_ref = resolved_base
301
328
  drift = _sync_key_drift_note(
302
329
  main_worktree_path(project_root),
303
330
  worktree_registry.lookup(
@@ -0,0 +1,108 @@
1
+ """단계 브랜치 하나를 새 기준 위로 재배치하는 git 작업.
2
+
3
+ `provision` 의 재진입 경로와 `stage_rebase` 의 의존 순서 재배치가 함께 쓴다.
4
+ """
5
+ from __future__ import annotations
6
+
7
+ import tempfile
8
+ from collections.abc import Iterator
9
+ from contextlib import contextmanager
10
+ from pathlib import Path
11
+
12
+ from .cleanliness import is_dirty_excluding_okstra
13
+ from .git_ops import _git, is_ancestor
14
+
15
+
16
+ def _checked_git(root: Path, *args: str) -> str:
17
+ result = _git(root, *args)
18
+ if result.returncode:
19
+ raise RuntimeError(f"git {' '.join(args)} failed: {result.stderr.strip()}")
20
+ return result.stdout.strip()
21
+
22
+
23
+ @contextmanager
24
+ def _stage_worktree(project_root: Path, branch: str) -> Iterator[Path]:
25
+ worktrees = _checked_git(project_root, "worktree", "list", "--porcelain")
26
+ holder = None
27
+ current = None
28
+ for line in worktrees.splitlines():
29
+ if line.startswith("worktree "):
30
+ current = Path(line.removeprefix("worktree "))
31
+ elif line == f"branch refs/heads/{branch}":
32
+ holder = current
33
+ if holder is not None and is_dirty_excluding_okstra(holder):
34
+ raise RuntimeError(
35
+ f"cannot rebase {branch}: worktree {holder} has uncommitted changes"
36
+ )
37
+ temporary = holder is None
38
+ if temporary:
39
+ holder = Path(tempfile.mkdtemp(prefix="okstra-rebase-"))
40
+ holder.rmdir()
41
+ _checked_git(project_root, "worktree", "add", str(holder), branch)
42
+ try:
43
+ yield holder
44
+ finally:
45
+ if temporary:
46
+ # 복구에 실패한 작업 트리는 삭제하지 않는다.
47
+ states = ["rebase-merge", "rebase-apply"]
48
+ if not any(
49
+ Path(_checked_git(holder, "rev-parse", "--git-path", state)).exists()
50
+ for state in states
51
+ ):
52
+ _checked_git(project_root, "worktree", "remove", str(holder))
53
+
54
+
55
+ def rebase_stage_branch(
56
+ project_root: Path,
57
+ branch: str,
58
+ old_base: str,
59
+ new_base: str,
60
+ ) -> str:
61
+ old = _checked_git(project_root, "rev-parse", f"{old_base}^{{commit}}")
62
+ new = _checked_git(project_root, "rev-parse", f"{new_base}^{{commit}}")
63
+ head = _checked_git(project_root, "rev-parse", f"refs/heads/{branch}")
64
+ if old == new:
65
+ return head
66
+ if not is_ancestor(project_root, old, head):
67
+ raise RuntimeError(
68
+ f"cannot rebase {branch}: recorded base {old} is not an ancestor"
69
+ )
70
+ with _stage_worktree(project_root, branch) as holder:
71
+ for state in ["rebase-merge", "rebase-apply", "MERGE_HEAD", "CHERRY_PICK_HEAD"]:
72
+ path = Path(_checked_git(holder, "rev-parse", "--git-path", state))
73
+ if path.exists():
74
+ raise RuntimeError(
75
+ f"cannot rebase {branch}: unfinished git operation at {path}"
76
+ )
77
+ _checked_git(
78
+ project_root,
79
+ "update-ref",
80
+ f"refs/okstra/rebase-backups/{branch}/{head}",
81
+ head,
82
+ )
83
+ return _apply_rebase(holder, branch, old, new)
84
+
85
+
86
+ def _apply_rebase(holder: Path, branch: str, old: str, new: str) -> str:
87
+ result = _git(
88
+ holder,
89
+ "rebase",
90
+ "--no-autostash",
91
+ "--no-autosquash",
92
+ "--no-update-refs",
93
+ "--no-rebase-merges",
94
+ "--onto",
95
+ new,
96
+ old,
97
+ )
98
+ if result.returncode:
99
+ abort = _git(holder, "rebase", "--abort")
100
+ detail = (result.stderr or result.stdout).strip()
101
+ if abort.returncode:
102
+ raise RuntimeError(
103
+ f"rebase failed for {branch}; recovery worktree: {holder}; {detail}; abort: {abort.stderr}"
104
+ )
105
+ raise RuntimeError(
106
+ f"rebase failed for {branch}; original branch restored: {detail}"
107
+ )
108
+ return _checked_git(holder, "rev-parse", "HEAD")
@@ -310,6 +310,20 @@ def get_stage_row(
310
310
  return data["tasks"].get(key)
311
311
 
312
312
 
313
+ def update_stage_base(
314
+ project_id: str, task_group: str, task_id: str, stage: int, *,
315
+ expected_base: str, base_ref: str,
316
+ ) -> None:
317
+ key = task_key(project_id, task_group, task_id, stage)
318
+ with _registry_lock():
319
+ data = _load()
320
+ row = data["tasks"].get(key)
321
+ if row is None or row.get("base_ref") != expected_base:
322
+ raise RuntimeError(f"stage base changed concurrently: {key}")
323
+ row["base_ref"] = base_ref
324
+ _save(data)
325
+
326
+
313
327
  def list_active_stage_numbers(
314
328
  project_id: str, task_group: str, task_id: str,
315
329
  ) -> set:
@@ -10,7 +10,7 @@ Output: `{ok: true, outcome: {renderArgv: ["--lead-runtime", "...", ...], render
10
10
 
11
11
  `renderArgv` is the canonical ordered `okstra render-bundle` argument list. Pass its tokens verbatim and in order. Repeated `--role-count` and `--role-model` flags are intentional. `renderArgs` is a compatibility and inspection view only; do not reconstruct an invocation from it. `orchestration` holds signals this skill acts on itself — never pass its entries to `render-bundle`.
12
12
 
13
- `userAuthorization`, when present, preserves the confirmation prompt actually emitted by this wizard and the relayed `proceed` response. Its `--user-authorization-json` token carries that record into the run manifest; preserve it unchanged. Do not synthesize a receipt for older confirmed states. Include the translator's disclosed provider/model and report material when citing this record in host execution requests, including `report-finalize`. The record documents task scope; it does not grant host execution privileges.
13
+ `userAuthorization`, when present, preserves the confirmation prompt actually emitted by this wizard and the relayed `proceed` response. Its `--user-authorization-json` token carries that record into the run manifest; preserve it unchanged. Do not synthesize a receipt for older confirmed states. The record documents task scope; it does not grant host execution privileges.
14
14
 
15
15
  Before retrying a worker in an existing run, inspect its last invocation attempt: await running work; append an attempt only after `failed-no-mutation` within the retry budget. Preserve every other terminal attempt. `okstra team dispatch` routes the legacy declaration-only implementer failure to a separate same-model evidence-recovery invocation; collect its result before independent verification. Other terminal failures need `okstra agent-prompt materialize` with a new `--invocation-id`, distinct `--prompt`/`--result` paths, and unchanged assignment/model for a bounded correction. Do not restart completed implementation or request renewed approval for unchanged scope. Enforcement: `dispatch_core._next_attempt` and `execution_manifest._validate_next_attempt`.
16
16
 
@@ -167,7 +167,7 @@ Axis scope — each reviewer works all four axes; the `Reads` column names each
167
167
  | `structural` | non-test source file × `structural` | core principles 1, 2, 3, 6; `clean-code.md` DRY / KISS / SOLID / YAGNI; the completion sweep's domain-literal and documented-fork items; plus the architecture pack when one was routed |
168
168
  | `semantic` | function × `semantic` | core principles 4, 5; `clean-code.md` meaningful naming, functions-do-one-thing, the plain-English summary test, the 50-line cap, nesting depth, magic numbers, comments-explain-why |
169
169
  | `state-and-tests` | non-test source file × `state-and-tests`, test file × `state-and-tests` | `clean-code.md` "Mutation and state boundaries" and "Testing discipline"; plus the routed language pack's self-mock signals |
170
- | `general` | non-test source file × `general` | correctness bugs, swallowed errors, security, clear performance problems, core principle 7 (fix at the cause — nothing previously green edited to silence a failure); `clean-code.md` "Wrapping a third-party call" — a recovery branch the library already performs, a comment naming a condition the call site never establishes, a rethrow that drops the original error; plus the routed language pack's language-specific traps |
170
+ | `general` | non-test source file × `general` | correctness bugs, swallowed errors, `clean-code.md` "Untrusted input and security" (work that grows faster than the input before a size bound rejects it, input building SQL / shell / paths / fetch targets / templates / regexes, a new entry point without auth or ownership checks, secrets or personal data in code, logs or responses, evaluating input), clear performance problems, core principle 7 (fix at the cause — nothing previously green edited to silence a failure); `clean-code.md` "Wrapping a third-party call" — a recovery branch the library already performs, a comment naming a condition the call site never establishes, a rethrow that drops the original error; plus the routed language pack's language-specific traps |
171
171
 
172
172
  Say it plainly in every brief: **the census is law** — work the cells exactly as given, return a verdict for every one, never re-derive the list. There is no length budget; dropping a finding to stay brief is the failure this skill exists to prevent.
173
173
 
@@ -223,7 +223,7 @@ Output: `{ok: true, outcome: {renderArgv: ["--lead-runtime", "...", ...], render
223
223
 
224
224
  `renderArgv` is the canonical ordered `okstra render-bundle` argument list. Pass its tokens verbatim and in order. Repeated `--role-count` and `--role-model` flags are intentional. `renderArgs` is a compatibility and inspection view only; do not reconstruct an invocation from it. `orchestration` holds signals this skill acts on itself — never pass its entries to `render-bundle`.
225
225
 
226
- `userAuthorization`, when present, preserves the confirmation prompt actually emitted by this wizard and the relayed `proceed` response. Its `--user-authorization-json` token carries that record into the run manifest; preserve it unchanged. Do not synthesize a receipt for older confirmed states. Include the translator's disclosed provider/model and report material when citing this record in host execution requests, including `report-finalize`. The record documents task scope; it does not grant host execution privileges.
226
+ `userAuthorization`, when present, preserves the confirmation prompt actually emitted by this wizard and the relayed `proceed` response. Its `--user-authorization-json` token carries that record into the run manifest; preserve it unchanged. Do not synthesize a receipt for older confirmed states. The record documents task scope; it does not grant host execution privileges.
227
227
 
228
228
  Before retrying a worker in an existing run, inspect its last invocation attempt: await running work; append an attempt only after `failed-no-mutation` within the retry budget. Preserve every other terminal attempt. `okstra team dispatch` routes the legacy declaration-only implementer failure to a separate same-model evidence-recovery invocation; collect its result before independent verification. Other terminal failures need `okstra agent-prompt materialize` with a new `--invocation-id`, distinct `--prompt`/`--result` paths, and unchanged assignment/model for a bounded correction. Do not restart completed implementation or request renewed approval for unchanged scope. Enforcement: `dispatch_core._next_attempt` and `execution_manifest._validate_next_attempt`.
229
229
 
@@ -95,6 +95,15 @@ carry. release-handoff drafts its PR bodies from the same rules.
95
95
 
96
96
  ### A4. Output and offer to create the PR
97
97
 
98
+ Write the title and body to a temp file and check them:
99
+
100
+ ```bash
101
+ okstra pr check --body-file <path-to-body>
102
+ ```
103
+
104
+ Each printed `L<line>: <pattern>: <match>` row is an okstra identifier a reviewer cannot open:
105
+ restate that line in the reviewer's terms and check again. `clean` means none is left.
106
+
98
107
  Print the filled PR body to chat as a single fenced markdown block. Then ask the
99
108
  user whether to open a real PR. Only on an explicit yes:
100
109
 
@@ -256,7 +256,7 @@ Output: `{ok: true, outcome: {renderArgv: ["--lead-runtime", "...", ...], render
256
256
 
257
257
  `renderArgv` is the canonical ordered `okstra render-bundle` argument list. Pass its tokens verbatim and in order. Repeated `--role-count` and `--role-model` flags are intentional. `renderArgs` is a compatibility and inspection view only; do not reconstruct an invocation from it. `orchestration` holds signals this skill acts on itself — never pass its entries to `render-bundle`.
258
258
 
259
- `userAuthorization`, when present, preserves the confirmation prompt actually emitted by this wizard and the relayed `proceed` response. Its `--user-authorization-json` token carries that record into the run manifest; preserve it unchanged. Do not synthesize a receipt for older confirmed states. Include the translator's disclosed provider/model and report material when citing this record in host execution requests, including `report-finalize`. The record documents task scope; it does not grant host execution privileges.
259
+ `userAuthorization`, when present, preserves the confirmation prompt actually emitted by this wizard and the relayed `proceed` response. Its `--user-authorization-json` token carries that record into the run manifest; preserve it unchanged. Do not synthesize a receipt for older confirmed states. The record documents task scope; it does not grant host execution privileges.
260
260
 
261
261
  Before retrying a worker in an existing run, inspect its last invocation attempt: await running work; append an attempt only after `failed-no-mutation` within the retry budget. Preserve every other terminal attempt. `okstra team dispatch` routes the legacy declaration-only implementer failure to a separate same-model evidence-recovery invocation; collect its result before independent verification. Other terminal failures need `okstra agent-prompt materialize` with a new `--invocation-id`, distinct `--prompt`/`--result` paths, and unchanged assignment/model for a bounded correction. Do not restart completed implementation or request renewed approval for unchanged scope. Enforcement: `dispatch_core._next_attempt` and `execution_manifest._validate_next_attempt`.
262
262
 
@@ -169,8 +169,8 @@ matching section:
169
169
  - **D. PR body template** (release-handoff) — opt-in pre-registration; also
170
170
  offered on the first release-handoff run.
171
171
  - **E. final report language** (`reportLanguage`) — a language tag (`en`,
172
- `ko`, `fr`, …); default `en`. The Phase 7 translator translates the human
173
- HTML into it.
172
+ `ko`, `fr`, …); default `en`. The report writer writes the report prose
173
+ directly in it.
174
174
  - **F. declared architecture style** (`architecture.style`) — `hexagonal` /
175
175
  `layered` / `none`; hand-added, default `none`. Declaring one makes that
176
176
  architecture's placement rules binding in planning + verification.
@@ -196,8 +196,8 @@ per-run override prompt during the next release-handoff run.
196
196
 
197
197
  ## E. Final report language (`reportLanguage`)
198
198
 
199
- The value is a language tag (`en`, `ko`, `fr`, `pt-BR`, …). The data.json
200
- stays English; the Phase 7 translator translates the human HTML into the tag.
199
+ The value is a language tag (`en`, `ko`, `fr`, `pt-BR`, …). The report writer
200
+ writes the report prose directly in that language; no translation step runs.
201
201
  If you skip, no `reportLanguage` field is written and the runtime uses `en`.
202
202
 
203
203
  AskUserQuestion (fixed options):
@@ -30,3 +30,7 @@
30
30
 
31
31
  When the only source for a claim is such an artifact, restate its substance in the reviewer's
32
32
  terms — what changed in the code, and why — instead of citing the id.
33
+
34
+ Check the drafted title and body with `okstra pr check --body-file <file>` from the repository:
35
+ it prints each line that still carries one of these identifiers, skipping terms the
36
+ repository's own tracked files use.
@@ -30,7 +30,7 @@ The narrative is not free-form Markdown. A narrative file is the line `# OKSTRA
30
30
 
31
31
  Blank lines are ignored. **Every other line is rejected**, including Markdown headings (`#`, `##`, `###`), a pipe table written at column 0, code fences, bare paragraphs, JSON, YAML, and JSON Pointer. A heading or table that belongs in the reader's report is written as the text of a `> ` value, never as a narrative line of its own.
32
32
 
33
- Follow the task-specific schema order and write the prose in English.
33
+ Follow the task-specific schema order. Write every `> ` prose value directly in the language named by `**Report Language:**`; do not draft in English first, because no translation step follows. Field names, `Verdict Token` and other enum or status values, finding and requirement ids, paths, symbols, commands, and model names stay exactly as the schema and sources spell them.
34
34
 
35
35
  ### Top-level fields you may author
36
36
 
@@ -184,7 +184,6 @@
184
184
  "implementer": "Implementer",
185
185
  "planner": "Planner",
186
186
  "report-writer": "Report writer",
187
- "translator": "Translator",
188
187
  "verifier": "Verifier"
189
188
  },
190
189
  "requirementStatus": {
@@ -184,7 +184,6 @@
184
184
  "implementer": "구현",
185
185
  "planner": "계획",
186
186
  "report-writer": "리포트 작성",
187
- "translator": "번역",
188
187
  "verifier": "검증"
189
188
  },
190
189
  "requirementStatus": {
@@ -2,7 +2,7 @@
2
2
 
3
3
  This file is the audience-specific contract for initial `analysis` workers. Read it end-to-end from the path selected by the dispatch prompt. Before doing work, also read the shared file named by `**Worker Error Contract Path:**`; that file is the only source for error-sidecar rules.
4
4
 
5
- Your identity is the stored role execution (`executionLabel`). Canonical roles are `leader`, `analyser`, `critic`, `designer`, `planner`, `implementer`, `verifier`, `report-writer`, and `translator`. `lead` and `executor` are compatibility aliases only.
5
+ Your identity is the stored role execution (`executionLabel`). Canonical roles are `leader`, `analyser`, `critic`, `designer`, `planner`, `implementer`, `verifier`, and `report-writer`. `lead` and `executor` are compatibility aliases only.
6
6
 
7
7
  ## Required reading
8
8
 
@@ -230,7 +230,6 @@ from okstra_ctl.workflow import ( # noqa: E402
230
230
  )
231
231
  from okstra_ctl.final_report_paths import ( # noqa: E402
232
232
  final_report_data_path as _data_path_for,
233
- translation_sidecar_path,
234
233
  )
235
234
  from okstra_token_usage.report import _match_worker_index # noqa: E402
236
235
  from okstra_ctl.phases.requirements_discovery.validation import (
@@ -3328,7 +3327,7 @@ _CITATION_LIST_KEYS = ("evidenceRefs", "supportingEvidence", "falsifyingEvidence
3328
3327
 
3329
3328
  def _prose_pointers(node: object, key: str = "", pointer: str = "") -> list[tuple[str, str]]:
3330
3329
  """(JSON pointer, value) for every string under a reader-facing prose key."""
3331
- from okstra_ctl.report_translation import PROSE_KEYS
3330
+ from okstra_ctl.report_prose_keys import PROSE_KEYS
3332
3331
 
3333
3332
  out: list[tuple[str, str]] = []
3334
3333
  if isinstance(node, dict):
@@ -4685,29 +4684,6 @@ def _validate_improvement_discovery(
4685
4684
  failures.append(f"improvement-discovery: {err}")
4686
4685
 
4687
4686
 
4688
- def _validate_translation_sidecar(
4689
- data: Mapping[str, Any], report_path: Path, failures: list[str]
4690
- ) -> None:
4691
- """영어가 아닌 사람 HTML 은 번역 사이드카가 있어야 한다.
4692
-
4693
- 사이드카가 없으면 렌더러가 크롬만 번역하고 본문은 영어로 남긴다.
4694
- `render-views` 가 그 조합을 거부하고, 여기가 같은 규칙의 두 번째 게이트다.
4695
- """
4696
- if not data:
4697
- return
4698
- lang = str((data.get("meta") or {}).get("reportLanguage") or "en")
4699
- if lang == "en":
4700
- return
4701
- sidecar = translation_sidecar_path(report_path, lang)
4702
- if not sidecar.is_file():
4703
- failures.append(
4704
- f"final-report has reportLanguage {lang!r} but no translation "
4705
- f"sidecar at {sidecar.name}. The human HTML rendered from the "
4706
- "English source instead; re-run `report-finalize --only translate "
4707
- "--only render-views` to dispatch the translator and overlay it."
4708
- )
4709
-
4710
-
4711
4687
  def _validate_session_conformance(
4712
4688
  team_state: dict,
4713
4689
  team_state_path: Path,
@@ -6003,9 +5979,6 @@ def main() -> int:
6003
5979
  ),
6004
5980
  )
6005
5981
  validation_data = report_data if isinstance(report_data, Mapping) else {}
6006
- _validate_translation_sidecar(
6007
- validation_data, _data_path_for(report_path), failures
6008
- )
6009
5982
  validate_report(
6010
5983
  report_path,
6011
5984
  failures,
@@ -1,6 +0,0 @@
1
- {
2
- "schemaVersion": "1.0",
3
- "id": "report-translation",
4
- "dutyId": "translator",
5
- "count": 1
6
- }
@@ -1,19 +0,0 @@
1
- {
2
- "schemaVersion": "1.0",
3
- "id": "translator",
4
- "legacyAliases": [],
5
- "identity": "Renders the report's reader-facing text in another language without editing the underlying decision.",
6
- "responsibilities": [
7
- "Carry the source's precision, uncertainty, and operational force into the target language.",
8
- "Preserve identifiers, paths, commands, and status tokens exactly as the source writes them."
9
- ],
10
- "requiredCapabilities": [
11
- "extended-artifact-authoring",
12
- "source-readonly",
13
- "worker-artifact-io"
14
- ],
15
- "prohibitions": [
16
- "Do not edit the source, translate an unassigned file, or add analysis of your own.",
17
- "Do not strengthen or weaken a normative statement in translation."
18
- ]
19
- }