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.
- package/README.md +5 -5
- package/dist/cli-registry.mjs +0 -10
- package/dist/cli-registry.mjs.map +1 -1
- package/dist/commands/lifecycle/config.mjs +2 -3
- package/dist/commands/lifecycle/config.mjs.map +1 -1
- package/dist/commands/pr/pr.d.mts +6 -0
- package/dist/commands/pr/pr.mjs +58 -0
- package/dist/commands/pr/pr.mjs.map +1 -1
- package/dist/lib/runtime-payload.mjs +1 -2
- package/dist/lib/runtime-payload.mjs.map +1 -1
- package/docs/architecture/storage-model.md +1 -1
- package/docs/architecture.md +9 -7
- package/docs/cli.md +3 -4
- package/docs/project-structure-overview.md +4 -5
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/prompts/launch.template.md +1 -1
- package/runtime/prompts/lead/okstra-lead-contract.md +1 -3
- package/runtime/prompts/lead/report-writer.md +10 -17
- package/runtime/prompts/lead/team-contract.md +1 -2
- package/runtime/prompts/wizard/prompts.ko.json +0 -1
- package/runtime/python/okstra_ctl/adapters/hosts/codex/relay.md +1 -1
- package/runtime/python/okstra_ctl/adapters/providers/antigravity/adapter.py +1 -1
- package/runtime/python/okstra_ctl/adapters/providers/claude/adapter.py +0 -1
- package/runtime/python/okstra_ctl/adapters/providers/codex/adapter.py +1 -1
- package/runtime/python/okstra_ctl/adapters/providers/grok/adapter.py +0 -1
- package/runtime/python/okstra_ctl/adapters/providers/kimi/adapter.py +0 -1
- package/runtime/python/okstra_ctl/agent/invocation.py +1 -2
- package/runtime/python/okstra_ctl/agent/prompt_cli/cli.py +2 -2
- package/runtime/python/okstra_ctl/agent/prompt_cli/jobs.py +0 -2
- package/runtime/python/okstra_ctl/agent/prompt_cli/materialize.py +2 -27
- package/runtime/python/okstra_ctl/agent/prompt_cli/run_identity.py +0 -4
- package/runtime/python/okstra_ctl/dispatch_core.py +3 -349
- package/runtime/python/okstra_ctl/dispatch_state.py +0 -13
- package/runtime/python/okstra_ctl/domain/provider.py +1 -1
- package/runtime/python/okstra_ctl/domain/role.py +0 -6
- package/runtime/python/okstra_ctl/final_report_paths.py +0 -16
- package/runtime/python/okstra_ctl/i18n.py +2 -2
- package/runtime/python/okstra_ctl/path_hints.py +0 -4
- package/runtime/python/okstra_ctl/paths.py +0 -3
- package/runtime/python/okstra_ctl/phases/final_verification/profile.md +2 -0
- package/runtime/python/okstra_ctl/phases/final_verification/spec.md +2 -0
- package/runtime/python/okstra_ctl/phases/final_verification/target.py +17 -1
- package/runtime/python/okstra_ctl/phases/implementation/entry.py +12 -0
- package/runtime/python/okstra_ctl/phases/implementation/instructions/_implementation-deliverable.md +1 -0
- package/runtime/python/okstra_ctl/phases/implementation/instructions/_implementation-executor.md +1 -0
- package/runtime/python/okstra_ctl/phases/implementation/spec.md +4 -0
- package/runtime/python/okstra_ctl/phases/release_handoff/boundary.json +1 -1
- package/runtime/python/okstra_ctl/phases/release_handoff/operations.py +24 -3
- package/runtime/python/okstra_ctl/phases/release_handoff/profile.md +11 -11
- package/runtime/python/okstra_ctl/phases/release_handoff/spec.md +4 -2
- package/runtime/python/okstra_ctl/render.py +0 -10
- package/runtime/python/okstra_ctl/render_final_report.py +4 -4
- package/runtime/python/okstra_ctl/report_finalize.py +37 -76
- package/runtime/python/okstra_ctl/report_html/render.py +4 -44
- package/runtime/python/okstra_ctl/report_language.py +3 -3
- package/runtime/python/okstra_ctl/report_prose_keys.py +153 -0
- package/runtime/python/okstra_ctl/report_synthesis_packet.py +1 -1
- package/runtime/python/okstra_ctl/run.py +1 -21
- package/runtime/python/okstra_ctl/stage_integrate.py +37 -2
- package/runtime/python/okstra_ctl/stage_rebase.py +189 -0
- package/runtime/python/okstra_ctl/stage_targets.py +29 -62
- package/runtime/python/okstra_ctl/team.py +1 -6
- package/runtime/python/okstra_ctl/wizard/confirmation.py +0 -8
- package/runtime/python/okstra_ctl/wizard/registry.py +2 -3
- package/runtime/python/okstra_ctl/wizard/sources.py +10 -0
- package/runtime/python/okstra_ctl/worker_prompt_policy.py +3 -12
- package/runtime/python/okstra_ctl/worktree/__init__.py +1 -1
- package/runtime/python/okstra_ctl/worktree/decisions.py +3 -4
- package/runtime/python/okstra_ctl/worktree/provision.py +27 -0
- package/runtime/python/okstra_ctl/worktree/rebase.py +108 -0
- package/runtime/python/okstra_ctl/worktree_registry.py +14 -0
- package/runtime/skills/_fragments/wizard-render-bundle.md +1 -1
- package/runtime/skills/okstra-code-review/SKILL.md +1 -1
- package/runtime/skills/okstra-discussion/SKILL.md +1 -1
- package/runtime/skills/okstra-pr-gen/SKILL.md +9 -0
- package/runtime/skills/okstra-run/SKILL.md +1 -1
- package/runtime/skills/okstra-setup/SKILL.md +2 -2
- package/runtime/skills/okstra-setup/references/project-config.md +2 -2
- package/runtime/templates/pr/drafting-rules.md +4 -0
- package/runtime/templates/report-writer-prompt-preamble.md +1 -1
- package/runtime/templates/reports/html/i18n/en.json +0 -1
- package/runtime/templates/reports/html/i18n/ko.json +0 -1
- package/runtime/templates/worker-prompt-preamble.md +1 -1
- package/runtime/validators/validate-run.py +1 -28
- package/runtime/agents/operations/report-translation.json +0 -6
- package/runtime/agents/roles/translator.json +0 -19
- package/runtime/bin/okstra-report-translate.py +0 -404
- package/runtime/prompts/duties/translator.json +0 -32
- package/runtime/python/okstra_ctl/report_translation.py +0 -509
- package/runtime/python/okstra_ctl/report_translation_dispatch.py +0 -356
- package/runtime/templates/translator-prompt-preamble.md +0 -36
|
@@ -172,6 +172,10 @@ Stage selection is `auto` or a number. If `--stage` comes from another task-type
|
|
|
172
172
|
|
|
173
173
|
The stage worktree base is decided by dependency shape. An independent stage uses the task-key worktree HEAD fixed at first implementation entry as its anchor, and a single-dependency stage branches from the predecessor stage's done `head_commit`. A multi-dependency stage branches from a commit holding exactly its predecessors: the predecessor commit that already contains the others, or else an integration branch merging them (`<work-category-namespace>/<task-id-segment>-g<n1>-<n2>`). okstra creates that branch itself, so a predecessor that has not been merged into the task branch no longer blocks the stage; release-handoff later reuses the same branch as that stage's PR base.
|
|
174
174
|
|
|
175
|
+
When a predecessor advances, implementation entry synchronizes existing completed stage branches in dependency order through `okstra_ctl.stage_rebase.synchronize_stage_stack`. Each downstream stage is rebased with `git rebase --onto <new-base> <recorded-old-base>`; merging a moving predecessor into that stage is not an update strategy. Re-entering an unfinished stage also rebases its existing worktree when the computed base changes. Old heads are preserved under `refs/okstra/rebase-backups/`, registry bases and completion records are updated, and previous verification no longer qualifies. Conflicts abort the current rebase and stop the chain; active runs and dirty worktrees are not rewritten. Tests: `tests/worktree/test_stage_rebase.py`.
|
|
176
|
+
|
|
177
|
+
Successful implementation report finalization automatically removes that stage's clean worktree after its `done` record is persisted. The report's last commit, the completion ledger, and the current branch tip must agree for cleanup (`stage_integrate.reclaim_completed_stage`). Failed, active, dirty, nested, or moved stages are preserved with a reason in the finalization step result. Cleanup retains the branch and registry base; implementation re-entry and final-verification restore the worktree from that branch. The task worktree and other stages are outside this scoped cleanup. Tests: `tests/worktree/test_stage_rebase.py` and `tests/report/test_report_finalize.py`.
|
|
178
|
+
|
|
175
179
|
## 6. Deliverables
|
|
176
180
|
|
|
177
181
|
```mermaid
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"allowed": " - entering this phase only when the cited final-verification report's verdict is release-ready — `Verdict Token` exactly `accepted`, or exactly `conditional-accept` with every Conditional Acceptance Condition row declaring `blocksReleaseHandoff: false` (`okstra_ctl.release_gate.release_handoff_allowed`)\n - asking the user (via `AskUserQuestion` / interactive prompt) which delivery action to take: `local checkout`, `push + PR`, or `skip` (end the run)\n - asking the user to pick a PR base branch from `staging` | `preprod` | `prod` | `main` | `dev` | a user-supplied branch name\n - drafting PR title and PR body **inline as the Okstra lead** (no drafter worker and no delegated dispatch); the lead reviews its own draft with the user before any mutating git / gh command runs\n - read-only git inspection: `git status`, `git diff`, `git log`, `git rev-parse`\n - pushing the current feature branch to its origin remote via `git push -u origin <current-branch>` (the feature branch only — NEVER the base branch)\n - creating a pull request via `gh pr create --base <chosen-base> --head <current-branch>`; if a PR with the same head already exists, surface its URL and skip creation\n - the lead writes the final report directly (no `Report writer worker` dispatch); the report still conforms to the standard final-report template",
|
|
3
3
|
"forbidden": [
|
|
4
4
|
"entering this phase when the cited final-verification `Verdict Token` is `conditional-accept` or `blocked`, or when no final-verification report is cited",
|
|
5
|
-
"local commit commands of any kind (`git add`, `git commit`, `git restore --staged`, `git stash`), and any direct `git merge` / `git rebase` / `git rebase --onto` / `git cherry-pick` / `git commit --amend` run by the lead.
|
|
5
|
+
"local commit commands of any kind (`git add`, `git commit`, `git restore --staged`, `git stash`), and any direct `git merge` / `git rebase` / `git rebase --onto` / `git cherry-pick` / `git commit --amend` run by the lead. Runtime exceptions are dependency-ordered rebases by `okstra_ctl.stage_rebase` (which invalidate verification and stop PR delivery), and merge commits on a dedicated multi-predecessor `merge-base` branch. Never merge a moving predecessor or origin branch into a downstream stage to update its base.",
|
|
6
6
|
"any git push variant that rewrites remote history, regardless of intent or whether the user said \"force it\": `git push --force`, `git push --force-with-lease`, `git push -f`, `git push +<refspec>`, or any other history-rewriting invocation",
|
|
7
7
|
"pushing directly to a release base branch — i.e. `git push origin <branch>` where `<branch>` is `main`, `master`, `prod`, `preprod`, `staging`, `dev`, or the branch the user chose as the release base in this run. The only permitted push targets are the branches `okstra handoff pr-plan` listed for this run (each stage `head_branch`, and any `merge-base` branch).",
|
|
8
8
|
"bypassing repo safeguards: `--no-verify` / `-n` on `git push`, bypassing GPG signing, disabling safeguards via equivalent flags, or any hook bypass.",
|
|
@@ -5,9 +5,14 @@ import subprocess
|
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
from typing import Any, Dict, List, Optional
|
|
7
7
|
|
|
8
|
+
from okstra_project.dirs import okstra_home
|
|
9
|
+
|
|
8
10
|
from okstra_ctl import consumers, stage_targets, worktree_registry
|
|
9
11
|
from okstra_ctl.handoff_verification import verification_row_is_current
|
|
10
12
|
from okstra_ctl.handoff_error import HandoffError
|
|
13
|
+
from okstra_ctl.locks import worktree_provision_mutex
|
|
14
|
+
from okstra_ctl.stage_rebase import synchronize_stage_stack
|
|
15
|
+
from okstra_ctl.stage_targets import StageWorktreeIdentity
|
|
11
16
|
from okstra_ctl.stage_integrate import (
|
|
12
17
|
IntegrateError, MergeBranchConflict, ensure_stage_merge_branch,
|
|
13
18
|
)
|
|
@@ -306,15 +311,31 @@ def _reconcile_with_origin(
|
|
|
306
311
|
def pr_plan(*, project_root, plan_run_root, stage_map, stages, base_branch,
|
|
307
312
|
work_category, project_id, task_group, task_id) -> Dict[str, Any]:
|
|
308
313
|
"""선택한 stage 각각의 PR head/base 를 정하고, 필요한 선행 머지 브랜치를
|
|
309
|
-
만든다.
|
|
310
|
-
|
|
311
|
-
|
|
314
|
+
만든다. 이동한 의존 기준은 먼저 재배치하고 재검증 전에는 전달하지 않는다."""
|
|
315
|
+
try:
|
|
316
|
+
with worktree_provision_mutex(okstra_home(), project_id, task_group, task_id):
|
|
317
|
+
rebased = synchronize_stage_stack(
|
|
318
|
+
project_root=Path(project_root), plan_run_root=Path(plan_run_root),
|
|
319
|
+
stage_map=stage_map,
|
|
320
|
+
identity=StageWorktreeIdentity(project_id, task_group, task_id, work_category),
|
|
321
|
+
)
|
|
322
|
+
except RuntimeError as exc:
|
|
323
|
+
raise HandoffError(str(exc)) from exc
|
|
324
|
+
if rebased:
|
|
325
|
+
raise HandoffError(
|
|
326
|
+
f"stages {rebased} were rebased onto updated dependencies; "
|
|
327
|
+
"re-run final-verification before release-handoff"
|
|
328
|
+
)
|
|
312
329
|
rows = consumers.read_consumers(Path(plan_run_root))
|
|
313
330
|
_require_eligible(stage_map, rows, stages)
|
|
314
331
|
_run_git(["fetch", "origin", base_branch], project_root)
|
|
315
332
|
done = consumers.latest_done_by_stage(rows)
|
|
316
333
|
pr_covered = consumers.pr_covered_stages(rows)
|
|
317
334
|
by_n = {s["stage_number"]: s for s in stage_map}
|
|
335
|
+
stages = stage_targets.order_stage_closure(
|
|
336
|
+
[(n, s.get("depends_on") or []) for n, s in by_n.items()],
|
|
337
|
+
set(stages), set(by_n) - set(stages),
|
|
338
|
+
)
|
|
318
339
|
anchor_base = worktree_registry.get_implementation_base(
|
|
319
340
|
project_id, task_group, task_id) or ""
|
|
320
341
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
- Record the handoff shape in `releaseHandoff.handoffScope`: the `stages` this run delivered and
|
|
4
4
|
the `releaseBase` the user picked. The report is the only place a reader learns which stages
|
|
5
5
|
shipped and in what order their PRs must be merged — the branch names do not say.
|
|
6
|
-
- Purpose: take a release-ready single-stage final-verification verdict for each already-committed implementation stage and turn it into a delivered push and/or pull request, with explicit user selection at every
|
|
6
|
+
- Purpose: take a release-ready single-stage final-verification verdict for each already-committed implementation stage and turn it into a delivered push and/or pull request, with explicit user selection at every delivery step. **One stage is one PR.** The PR head is that stage's stack branch (`<prefix>-<task-id>-s<N>`); nothing is squashed or collected into a bundle branch. Runtime dependency rebases happen before delivery and invalidate the prior verification.
|
|
7
7
|
- **Execution model: single-lead, no worker dispatch.** This phase is a thin orchestrator over `git` / `gh`; it does NOT dispatch teammates, does NOT dispatch analysis or drafter sub-agents, and does NOT run convergence. The host-native Okstra lead performs every step inline (drafting PR text, asking the user, running git / gh, writing the final report) — see "Lead-only contract" below.
|
|
8
8
|
- Worker roster: none — `profile.json` declares no roles; the run is executed entirely by the Okstra lead.
|
|
9
9
|
- Lead-only contract (replaces the shared team contract for this phase):
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
- the lead MUST capture `git status --short` and confirm the working tree is clean. Dirty state aborts the run; release-handoff packages the commits produced by `implementation`, it does not stage or commit changes.
|
|
21
21
|
- the lead MUST capture `git rev-parse --abbrev-ref HEAD` and record it as the run's current branch. It is evidence, not a PR head: every PR head comes from `okstra handoff pr-plan`. If the current branch is `main`, `master`, `prod`, `preprod`, `staging`, or `dev`, record it and do not push it — release-handoff never pushes a base branch.
|
|
22
22
|
- before pushing, run `okstra handoff pr-plan` again if anything mutated since entry, and confirm each selected stage is still present in its output. `pr-plan` re-checks eligibility and each stage's recorded verified commit. Record the pr-plan output in Executed Commands. These pre-push comparisons are lead instructions; the automated entry checks are enforced by `okstra_ctl.handoff_verification` and the handoff tests.
|
|
23
|
-
- **
|
|
23
|
+
- **Dependency updates use rebase.** When a predecessor advances, the runtime replays each dependent stage's own commits onto the new base in dependency order with `git rebase --onto`; never merge a moving predecessor into a downstream stage. `okstra_ctl.stage_rebase.synchronize_stage_stack` enforces this at implementation entry, verification target acquisition, and PR planning. It preserves the old head under `refs/okstra/rebase-backups/`, updates the registry base and completion ledger, and invalidates prior verification. Conflicts abort the current rebase and stop the remaining stages. The lead does not rebase or merge by hand. A rebase during `pr-plan` stops delivery and requires fresh final-verification. Remote divergence stops delivery for explicit user instructions; never repair it by merging origin back into the rebased stage. Merge PRs in dependency order, using merge commits or rebase-merge rather than squash. State that order and rule in the final report and closeout, not in individual PR bodies.
|
|
24
24
|
- User interaction protocol (Okstra lead — performed in order, using the selected runtime adapter's interactive prompt):
|
|
25
25
|
1. **Action selection** — present three choices and capture exactly one:
|
|
26
26
|
- `local checkout` — bring one stage's branch into the MAIN worktree for local testing (no push, no PR). See step 1c.
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
`okstra handoff pr-plan --plan-run-root <...> --approved-plan <...> --project-root <project root> --project-id <id> --task-group <g> --task-id <t> --work-category <c> --stages <HANDOFF_STAGES> --base <chosen-base>`.
|
|
40
40
|
It returns one row per stage with `head_branch`, `head_commit`, `base_kind` (`release-base` | `stage` | `merge-base`), `base_branch` and `base_commit`. Exit 2 means the predecessors of a multi-dependency stage conflict with each other: show the `conflicts` paths and stop (route: resolve manually, then retry). Exit 1 means an eligibility or dependency violation — most often a stage whose predecessor is neither in this run nor already PR'd nor merged into the release base: show the error verbatim and re-ask the action selection. On success these rows are authoritative for every subsequent step; the lead does not recompute a base by hand.
|
|
41
41
|
A `merge-base` row means `pr-plan` created a branch merging that stage's predecessors. That branch is a PR base only — it is never a PR head — and it MUST be pushed before the PR that targets it.
|
|
42
|
-
The output also carries `push_plan` and `warnings`. `push_plan` is every branch the PRs sit on, already in push order (each `merge-base` first, then the stage heads in
|
|
43
|
-
2b. **Pre-merge conflict probe** (only when the user picked `push + PR`) — before pushing, for each planned stage in
|
|
42
|
+
The output also carries `push_plan` and `warnings`. `push_plan` is every branch the PRs sit on, already in push order (each `merge-base` first, then the stage heads in dependency order), with the `commit` it must reach and an `action` of `create` (origin does not have it), `fast-forward` (origin is behind) or `up-to-date` (origin already has that commit — do not push it). GitHub resolves a PR's head and base by name on origin, so this list, not the local branch set, is what makes the stack real. A `warnings` row means `pr-plan` fast-forwarded a local branch to origin because origin was ahead: the commits origin adds are outside what this run verified. Quote every `warnings` row verbatim in Executed Commands and say so in the branch/PR explanation — the user is deciding whether to release commits this run did not verify. `pr-plan` refuses outright when a branch moved outside okstra or diverged from origin, since only a force push could deliver it.
|
|
43
|
+
2b. **Pre-merge conflict probe** (only when the user picked `push + PR`) — before pushing, for each planned stage in dependency order:
|
|
44
44
|
- run `git fetch origin <chosen-base>` once (read-only on the local working tree).
|
|
45
45
|
- run `git merge-tree --write-tree <head_branch> <base_branch>` for that stage, using the `base_branch` from the pr-plan row (`origin/<chosen-base>` for a `release-base` row). Exit 0 means clean. Exit 1 with a result-tree object ID on the first stdout line means a merge conflict. A missing result tree, or any other exit code, is an execution error: stop and report it, without offering permission to proceed as if it were a content conflict. An invalid ref can also return exit 1, so the exit code alone is insufficient.
|
|
46
46
|
- If no stage conflicts, proceed silently to Q3 (do NOT add a confirmation prompt — keep the happy path frictionless).
|
|
@@ -59,12 +59,12 @@
|
|
|
59
59
|
- produce **two artifacts per stage** before showing them to the user:
|
|
60
60
|
1. **PR title** — by default the subject of that stage's most recent implementation commit, or a concise Conventional Commits-style summary of the stage's committed range.
|
|
61
61
|
2. **PR body** — markdown filled from `PR_TEMPLATE_PATH`, and nothing else. Do NOT append a stack plan, a stage checklist, a sentence about where this PR stops in the stack, or a merge-policy line: the stack's shape and merge rule belong to the final report and the closeout, not to a reviewer's PR. The user-confirmation step's diff (Q3 `edit then proceed`) is computed against the filled template, not against the raw template file.
|
|
62
|
-
- **Filled-body self-check (runs on each drafted body, before Q3 shows it).** "
|
|
62
|
+
- **Filled-body self-check (runs on each drafted body, before Q3 shows it).** First write the drafted title and body to a file in this run's artifact directory and run `okstra pr check --body-file <that file>` from the stage worktree or the MAIN worktree; each `L<line>: <pattern>: <match>` row it prints is an okstra identifier a reviewer cannot open, reported as a hit with its line (exit 1 means hits, not a failure of the run). Then, because "fill in the placeholders" is only observable if someone checks that they were filled, scan the drafted body for these and carry the result into Q3: a residual HTML comment or template marker (`<!-- … -->`, `_Describe your changes…_`, a bare `TODO` / `FIXME` the diff did not introduce), an unchecked `- [ ]` box with no inline N/A justification, a section heading whose body is empty, and a whole body short enough to carry no substance. Each hit is reported to the user with its line — never silently left in, and never auto-filled with invented content. The user remains free to accept it; the point is that they see it before choosing `use as-is`.
|
|
63
63
|
- Allowed actions during the run (Okstra lead only):
|
|
64
|
-
- read-only inspection: `git status`, `git status --short`, `git diff`, `git log`, `git rev-parse`, `git ls-remote --heads origin <name>`, `gh pr list --head <branch>`, `gh pr view <url>`, and the drafting bundle `okstra pr gen --base <base_commit> --head <head_commit> --template-file <PR_TEMPLATE_PATH>`.
|
|
64
|
+
- read-only inspection: `git status`, `git status --short`, `git diff`, `git log`, `git rev-parse`, `git ls-remote --heads origin <name>`, `gh pr list --head <branch>`, `gh pr view <url>`, and the drafting bundle `okstra pr gen --base <base_commit> --head <head_commit> --template-file <PR_TEMPLATE_PATH>`, and the body check `okstra pr check --body-file <draft file>`.
|
|
65
65
|
- merge-conflict probe (only when the user picked `push + PR`): `git fetch origin <chosen-base>` and the exact `git merge-tree` commands from step 2b. Both preserve the working tree and branch tips.
|
|
66
|
-
- branch push (only when the user picked `push + PR`): `git push -u origin <branch>` for each `push_plan` row whose `action` is `create` or `fast-forward`, in the order `push_plan` lists them (each `merge-base` branch before the PR that targets it, then the stage heads in
|
|
67
|
-
- PR creation, one per stage (only when the user picked `push + PR` AND no PR with the same head already exists on origin): `gh pr create --base <row base_branch> --head <row head_branch> --title "<title>" --body "<body>"`. The title and body are the user-confirmed PR draft for that stage. Open them in
|
|
66
|
+
- branch push (only when the user picked `push + PR`): `git push -u origin <branch>` for each `push_plan` row whose `action` is `create` or `fast-forward`, in the order `push_plan` lists them (each `merge-base` branch before the PR that targets it, then the stage heads in dependency order). Skip an `up-to-date` row — origin already holds that commit. The pushed ref MUST come from a `push_plan` row — never the release base branch, and never a ref that list does not name.
|
|
67
|
+
- PR creation, one per stage (only when the user picked `push + PR` AND no PR with the same head already exists on origin): `gh pr create --base <row base_branch> --head <row head_branch> --title "<title>" --body "<body>"`. The title and body are the user-confirmed PR draft for that stage. Open them in dependency order so each base branch already exists on origin.
|
|
68
68
|
- PR reuse: if `gh pr list --head <branch> --state open --json url --jq '.[0].url'` returns a URL, treat that PR as already existing — record the URL in the final report and SKIP `gh pr create` for that stage.
|
|
69
69
|
- after each `gh pr create` succeeds (or an existing PR is reused), the lead MUST run `okstra handoff record-pr --plan-run-root <...> --stage <N> --branch <head branch> --base <base branch> --url <pr url>` and quote the command + exit code in the final report. One record-pr call per stage — the consumers ledger is what stops the same stage being PR'd twice.
|
|
70
70
|
- stage helpers: `okstra handoff eligible`, `okstra handoff pr-plan`, `okstra handoff record-pr`, `okstra handoff local-checkout`. `pr-plan` is the ONLY path that may create commits in this phase (merge commits on a `merge-base` branch, and only for a multi-dependency stage).
|
|
@@ -73,8 +73,8 @@
|
|
|
73
73
|
- Required deliverable shape (final report, in addition to the standard sections):
|
|
74
74
|
- **Source Verification Reports**: one row per selected stage — relative path of that stage's originating `final-verification` final-report plus the literal quoted `Verdict Token` row, and — when that token is `conditional-accept` — every Conditional Acceptance Condition row quoted with its `blocksReleaseHandoff` value.
|
|
75
75
|
- **Feature Branch & Working-Tree State**: the branch from `git rev-parse --abbrev-ref HEAD` at run start and the output of `git status --short` at run start.
|
|
76
|
-
- **Stage PR Plan**: the pr-plan rows — stage, head branch, head commit, base kind, base branch, base commit — plus, for every `merge-base` row, which predecessor stages it merges and the merge commits it created. This is the table a reader uses to merge the stack in the right order, so it carries the merge rule with it: merge in
|
|
77
|
-
- **User narrative `nextAction`**: when this run opened or reused any PR, it names the merge order (
|
|
76
|
+
- **Stage PR Plan**: the pr-plan rows — stage, head branch, head commit, base kind, base branch, base commit — plus, for every `merge-base` row, which predecessor stages it merges and the merge commits it created. This is the table a reader uses to merge the stack in the right order, so it carries the merge rule with it: merge in dependency order, with a merge commit or rebase-merge, never a squash.
|
|
77
|
+
- **User narrative `nextAction`**: when this run opened or reused any PR, it names the merge order (dependency order) and the merge rule (merge commit or rebase-merge, never squash), and says why — squashing one PR in the stack invalidates the next PR's base. This is where the operator reads the rule; the PR bodies do not carry it.
|
|
78
78
|
- **User Selections**: a block recording each prompt and the user's verbatim answer.
|
|
79
79
|
- Q1 action: `local checkout` | `push + PR` | `skip`.
|
|
80
80
|
- Q1c checkout stage (only when Q1 is `local checkout`): the `--stage <N>` value this answer produced. Record it in the `User Selections` row `H1c` (`userSelections.h1c`); omit the row entirely when Q1 was not `local checkout`.
|
|
@@ -108,7 +108,7 @@
|
|
|
108
108
|
7. **Merge-conflict probe audit** — for any `push + PR` run, confirm the report's `Merge Conflict Probe` section is present and either records `Clean` or records `Conflicts detected` with the user's verbatim choice. A missing or unparseable probe entry on a `push + PR` run is a contract violation.
|
|
109
109
|
- Non-goals:
|
|
110
110
|
- re-litigating a final-verification verdict — release-handoff trusts the cited release-ready verdicts and does not reopen acceptance checks. Restating a conditional-accept's conditions in the PR body is reporting, not verification: the lead does not check whether a condition has been satisfied.
|
|
111
|
-
-
|
|
111
|
+
- manual commit creation, amendment, squash, or rebase by the lead. Runtime dependency rebases are owned by `okstra_ctl.stage_rebase`; `pr-plan` stops delivery after any such change until final-verification is repeated. Combining several independent predecessors on a dedicated `merge-base` branch remains separate from updating a downstream stage; never merge that base into the downstream stage.
|
|
112
112
|
- bundling several stages into one PR, or building a branch that collects every stage. One stage is one PR.
|
|
113
113
|
- opening releases or deployments beyond the per-stage PRs the user chose to create.
|
|
114
114
|
- merging the PRs. Merging is a separate, manual step performed by the user (or by repo automation) after release-handoff ends; the lead MUST NOT call `gh pr merge`.
|
|
@@ -27,9 +27,11 @@ Report shapes remain in `schemas/final-report-v2.0.schema.json` and `schemas/fin
|
|
|
27
27
|
|
|
28
28
|
## 1. Purpose
|
|
29
29
|
|
|
30
|
-
`release-handoff` is the terminal phase that pushes already-committed implementation stages with a release-ready verdict, or hands them off as pull requests. **One stage is one PR.** The PR head is that stage's stack branch; the base comes from its `depends-on` — the release base for a stage with no live dependency, the predecessor's branch for a stage with one (a stacked PR), and a branch merging the predecessors for a stage with several.
|
|
30
|
+
`release-handoff` is the terminal phase that pushes already-committed implementation stages with a release-ready verdict, or hands them off as pull requests. **One stage is one PR.** The PR head is that stage's stack branch; the base comes from its `depends-on` — the release base for a stage with no live dependency, the predecessor's branch for a stage with one (a stacked PR), and a branch merging the predecessors for a stage with several.
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
When a predecessor advances, `okstra_ctl.stage_rebase.synchronize_stage_stack` rebases downstream stages in dependency order rather than merging the predecessor into them. It records the new bases and completion commits, preserving prior heads under `refs/okstra/rebase-backups/`. Implementation entry and verification target acquisition use the same operation. PR planning stops after a stack change and requires fresh final-verification; the new completion rows invalidate earlier verification through `consumers.verified_accepted_stages`. Dirty or active stages, rewritten unproven predecessors, and rebase conflicts stop synchronization. The tests in `tests/worktree/test_stage_rebase.py` cover ordering, preservation, and verification invalidation. Origin divergence remains a delivery error, never a reason to merge old origin history into the rebased branch.
|
|
33
|
+
|
|
34
|
+
Because the PRs are a stack, they must be merged in dependency order with a merge commit or a rebase-merge. A squash replaces the commits the next PR's base points at, so the stack breaks — every PR body states this, and the lead never merges.
|
|
33
35
|
|
|
34
36
|
This phase has no worker dispatch. It does not use a provider or report-writer roster; the host-native Okstra lead performs git/gh inspection, user questions, the PR draft, and the final report inline.
|
|
35
37
|
|
|
@@ -697,8 +697,6 @@ def _active_runtime_resources(ctx: dict) -> dict:
|
|
|
697
697
|
or str(runtime_home / "templates" / "implementation-worker-preamble.md"),
|
|
698
698
|
"report-writer": ctx.get("REPORT_WRITER_PREAMBLE_PATH")
|
|
699
699
|
or str(runtime_home / "templates" / "report-writer-prompt-preamble.md"),
|
|
700
|
-
"translator": ctx.get("TRANSLATOR_PREAMBLE_PATH")
|
|
701
|
-
or str(runtime_home / "templates" / "translator-prompt-preamble.md"),
|
|
702
700
|
},
|
|
703
701
|
"workerErrorContractPath": ctx.get("WORKER_ERROR_CONTRACT_PATH")
|
|
704
702
|
or str(runtime_home / "templates" / "worker-error-contract.md"),
|
|
@@ -2638,9 +2636,6 @@ def inject_lead_prompt_computed_tokens(ctx: dict) -> None:
|
|
|
2638
2636
|
report_writer_preamble = ctx.get("REPORT_WRITER_PREAMBLE_PATH") or str(
|
|
2639
2637
|
runtime_templates / "report-writer-prompt-preamble.md"
|
|
2640
2638
|
)
|
|
2641
|
-
translator_preamble = ctx.get("TRANSLATOR_PREAMBLE_PATH") or str(
|
|
2642
|
-
runtime_templates / "translator-prompt-preamble.md"
|
|
2643
|
-
)
|
|
2644
2639
|
worker_error_contract = ctx.get("WORKER_ERROR_CONTRACT_PATH") or str(
|
|
2645
2640
|
runtime_templates / "worker-error-contract.md"
|
|
2646
2641
|
)
|
|
@@ -2663,7 +2658,6 @@ def inject_lead_prompt_computed_tokens(ctx: dict) -> None:
|
|
|
2663
2658
|
f"- Analysis worker preamble: `{analysis_preamble}`\n"
|
|
2664
2659
|
f"- Implementation worker preamble: `{implementation_preamble}`\n"
|
|
2665
2660
|
f"- Report writer preamble: `{report_writer_preamble}`\n"
|
|
2666
|
-
f"- Translator preamble: `{translator_preamble}`\n"
|
|
2667
2661
|
|
|
2668
2662
|
f"- Shared worker error contract: `{worker_error_contract}`"
|
|
2669
2663
|
)
|
|
@@ -2886,10 +2880,6 @@ def apply_lead_prompt_defaults(ctx: dict) -> None:
|
|
|
2886
2880
|
"REPORT_WRITER_PREAMBLE_PATH",
|
|
2887
2881
|
str(runtime_home / "templates" / "report-writer-prompt-preamble.md"),
|
|
2888
2882
|
)
|
|
2889
|
-
ctx.setdefault(
|
|
2890
|
-
"TRANSLATOR_PREAMBLE_PATH",
|
|
2891
|
-
str(runtime_home / "templates" / "translator-prompt-preamble.md"),
|
|
2892
|
-
)
|
|
2893
2883
|
ctx.setdefault(
|
|
2894
2884
|
"WORKER_ERROR_CONTRACT_PATH",
|
|
2895
2885
|
str(runtime_home / "templates" / "worker-error-contract.md"),
|
|
@@ -588,10 +588,10 @@ def _markdown_task_template(task_type: str) -> str:
|
|
|
588
588
|
return ""
|
|
589
589
|
|
|
590
590
|
|
|
591
|
-
# The Markdown is the AI handoff sibling of the data.json
|
|
592
|
-
#
|
|
593
|
-
#
|
|
594
|
-
#
|
|
591
|
+
# The Markdown is the AI handoff sibling of the data.json. Its prose is the
|
|
592
|
+
# record's own, already in the report language; only its fixed labels come from
|
|
593
|
+
# the English dictionary. A malformed tag is still rejected here, before the
|
|
594
|
+
# HTML render meets it at the very end of the run.
|
|
595
595
|
MARKDOWN_LANG = "en"
|
|
596
596
|
|
|
597
597
|
|
|
@@ -25,14 +25,6 @@ placeholders, which a v2 report never carries because its numeric cells are
|
|
|
25
25
|
`null` until this step fills them. Substituting the tokens on a later retry
|
|
26
26
|
then leaves the already-rendered html stale for `validators/validate-report-views.py`.
|
|
27
27
|
|
|
28
|
-
The translation sidecar is the `translate` step, directly before `render-views`,
|
|
29
|
-
which overlays it. It used to be a manual lead sequence outside this command (materialize the translator
|
|
30
|
-
prompt, dispatch it, resume finalize at `render-views`), written only in a doc
|
|
31
|
-
the lead lazy-reads; a lead that ran the whole sequence at once skipped it and
|
|
32
|
-
the run kept an English view under a `ko` report (2026-09-09, fontsninja-v3-site
|
|
33
|
-
dev-10628-3). `okstra_ctl.report_translation_dispatch` owns the step; an English
|
|
34
|
-
report or an existing sidecar makes it a no-op.
|
|
35
|
-
|
|
36
28
|
Every lead adapter drives Phase 7 through this module: the Codex adapter calls
|
|
37
29
|
it in-process (``codex_dispatch``), and a Claude-led run reaches the same code
|
|
38
30
|
through the ``okstra report-finalize`` CLI. Neither reimplements the sequence.
|
|
@@ -69,12 +61,11 @@ from .handoff_error import HandoffError
|
|
|
69
61
|
from .paths import RunRef, task_dir, task_manifest_file
|
|
70
62
|
from .report_view_artifacts import html_view_path
|
|
71
63
|
from .release_gate import release_handoff_allowed
|
|
72
|
-
from .report_translation_dispatch import TranslateOutcome, translate_report
|
|
73
64
|
from .json_boundary import JsonBoundaryError, load_owned_object, write_owned_object_atomic
|
|
74
65
|
from .stage_integrate import IntegrateError
|
|
75
66
|
from .stage_targets import (
|
|
76
67
|
StageTargetError,
|
|
77
|
-
|
|
68
|
+
teardown_completed_stage_worktrees,
|
|
78
69
|
)
|
|
79
70
|
from .session import observe_lead_session
|
|
80
71
|
from .lead_progress import record_checkpoint
|
|
@@ -92,7 +83,6 @@ from okstra_project.phase_pointer import (
|
|
|
92
83
|
|
|
93
84
|
|
|
94
85
|
STEP_PROJECT_ACTIVITY = "project-activity"
|
|
95
|
-
STEP_TRANSLATE = "translate"
|
|
96
86
|
STEP_TOKEN_USAGE = "token-usage"
|
|
97
87
|
STEP_RENDER_VIEWS = "render-views"
|
|
98
88
|
STEP_SPAWN_FOLLOWUPS = "spawn-followups"
|
|
@@ -105,7 +95,6 @@ STEP_PRUNE_RUN_ARTIFACTS = "prune-run-artifacts"
|
|
|
105
95
|
|
|
106
96
|
STEP_ORDER = (
|
|
107
97
|
STEP_PROJECT_ACTIVITY,
|
|
108
|
-
STEP_TRANSLATE,
|
|
109
98
|
STEP_TOKEN_USAGE,
|
|
110
99
|
STEP_RENDER_VIEWS,
|
|
111
100
|
STEP_SPAWN_FOLLOWUPS,
|
|
@@ -124,8 +113,6 @@ V3_STEP_ORDER = (
|
|
|
124
113
|
STEP_TOKEN_USAGE,
|
|
125
114
|
STEP_PROJECT_ACTIVITY,
|
|
126
115
|
STEP_PREFLIGHT,
|
|
127
|
-
# Before the render that overlays its sidecar.
|
|
128
|
-
STEP_TRANSLATE,
|
|
129
116
|
STEP_RENDER_VIEWS,
|
|
130
117
|
STEP_SPAWN_FOLLOWUPS,
|
|
131
118
|
# Needs the assembled record; `validate-run` requires the row it writes.
|
|
@@ -341,10 +328,6 @@ def build_commands(ctx: FinalizeContext) -> list[tuple[str, list[str]]]:
|
|
|
341
328
|
str(ctx.data_path),
|
|
342
329
|
],
|
|
343
330
|
),
|
|
344
|
-
(
|
|
345
|
-
STEP_TRANSLATE,
|
|
346
|
-
["<in-process>", "translate", str(ctx.data_path)],
|
|
347
|
-
),
|
|
348
331
|
(
|
|
349
332
|
STEP_TOKEN_USAGE,
|
|
350
333
|
[
|
|
@@ -571,38 +554,53 @@ def _teardown_stage_worktrees(
|
|
|
571
554
|
ctx: FinalizeContext,
|
|
572
555
|
command: list[str],
|
|
573
556
|
) -> subprocess.CompletedProcess:
|
|
574
|
-
"""
|
|
575
|
-
|
|
576
|
-
whole-task 진입이 통합만 하고 정리를 남겨두므로(`stage_targets`), 정리는 판정이
|
|
577
|
-
나온 뒤인 여기서 한다. `blocked` 이거나 릴리스를 막는 조건이 남은 판정에서는
|
|
578
|
-
stage 작업물을 그대로 둬서 재작업이 바로 이어지게 한다. 이미 정리된 뒤 재실행돼도
|
|
579
|
-
같은 결과를 낸다 — 병합은 `already_merged` 로, 없는 worktree 는 건너뛴다.
|
|
580
|
-
"""
|
|
557
|
+
"""검증된 보고서와 완료 기록이 일치하는 단계 작업 트리를 회수한다."""
|
|
581
558
|
def _done(payload: Mapping[str, Any]) -> subprocess.CompletedProcess:
|
|
582
559
|
return subprocess.CompletedProcess(command, 0, json.dumps(payload), "")
|
|
583
560
|
|
|
584
|
-
if ctx.task_type
|
|
585
|
-
return _done({"skipped": "not
|
|
561
|
+
if ctx.task_type not in {"implementation", "final-verification"}:
|
|
562
|
+
return _done({"skipped": "not an implementation or final-verification run"})
|
|
586
563
|
try:
|
|
587
564
|
data = load_owned_object(Path(ctx.data_path), artifact="final report record")
|
|
588
565
|
except JsonBoundaryError as exc:
|
|
589
566
|
return subprocess.CompletedProcess(
|
|
590
567
|
command, 1, "", f"cannot read final-report data.json: {exc}")
|
|
591
|
-
if
|
|
592
|
-
return _done({"skipped": "single-stage verification owns no teardown"})
|
|
593
|
-
if not release_handoff_allowed(data):
|
|
568
|
+
if ctx.task_type == "final-verification" and not release_handoff_allowed(data):
|
|
594
569
|
return _done({"skipped": "verdict does not clear the work for release"})
|
|
570
|
+
heads = _stage_cleanup_heads(ctx.task_type, data)
|
|
571
|
+
if heads == {}:
|
|
572
|
+
return _done({"skipped": "report names no completed stage commit"})
|
|
595
573
|
try:
|
|
596
|
-
result =
|
|
574
|
+
result = teardown_completed_stage_worktrees(
|
|
597
575
|
project_root=ctx.project_root,
|
|
598
576
|
task_group=ctx.task_group,
|
|
599
577
|
task_id=ctx.task_id,
|
|
578
|
+
expected_heads=heads,
|
|
600
579
|
)
|
|
601
|
-
except (IntegrateError, StageTargetError, OSError) as exc:
|
|
580
|
+
except (IntegrateError, StageTargetError, JsonBoundaryError, OSError) as exc:
|
|
602
581
|
return subprocess.CompletedProcess(command, 1, "", str(exc))
|
|
603
582
|
return _done(result)
|
|
604
583
|
|
|
605
584
|
|
|
585
|
+
def _stage_cleanup_heads(task_type: str, data: Mapping[str, Any]) -> dict[int, str] | None:
|
|
586
|
+
if task_type == "implementation":
|
|
587
|
+
implementation = data.get("implementation") or {}
|
|
588
|
+
evidence = implementation.get("stageSidecarEvidence") or {}
|
|
589
|
+
stage = evidence.get("stageNumber")
|
|
590
|
+
commits = implementation.get("commitList") or []
|
|
591
|
+
head = commits[-1].get("fullSha", "") if commits else ""
|
|
592
|
+
return {stage: head} if isinstance(stage, int) and head and not evidence.get("withheld") else {}
|
|
593
|
+
if data.get("verificationScope") == "whole-task":
|
|
594
|
+
return None
|
|
595
|
+
verification = data.get("finalVerification") or {}
|
|
596
|
+
stages = verification.get("stageReports") or []
|
|
597
|
+
head = (verification.get("sourceImplementationReport") or {}).get("capturedHeadSha", "")
|
|
598
|
+
if data.get("verificationScope") == "single-stage" and len(stages) == 1 and head:
|
|
599
|
+
stage = stages[0].get("stage")
|
|
600
|
+
return {stage: head} if isinstance(stage, int) else {}
|
|
601
|
+
return {}
|
|
602
|
+
|
|
603
|
+
|
|
606
604
|
def _prune_run_artifacts(
|
|
607
605
|
ctx: FinalizeContext,
|
|
608
606
|
command: list[str],
|
|
@@ -684,8 +682,6 @@ def run_finalize(
|
|
|
684
682
|
if only:
|
|
685
683
|
selected = set(only)
|
|
686
684
|
if ctx.report_contract_version == "3.0":
|
|
687
|
-
if STEP_TRANSLATE in selected:
|
|
688
|
-
selected.add(STEP_PREFLIGHT)
|
|
689
685
|
if selected - {STEP_TOKEN_USAGE}:
|
|
690
686
|
# 부분 재개도 이전 조립본이 아닌 현재 소유자 입력을 소비한다.
|
|
691
687
|
selected.add(STEP_PROJECT_ACTIVITY)
|
|
@@ -799,7 +795,6 @@ def _execute_finalize_steps(
|
|
|
799
795
|
if consumer != STEP_TOKEN_USAGE or ctx.report_contract_version != "3.0":
|
|
800
796
|
blocked[consumer] = name
|
|
801
797
|
elif name == STEP_PREFLIGHT:
|
|
802
|
-
blocked[STEP_TRANSLATE] = name
|
|
803
798
|
blocked[STEP_SPAWN_FOLLOWUPS] = name
|
|
804
799
|
elif name == STEP_RENDER_VIEWS:
|
|
805
800
|
blocked[STEP_VALIDATE_RUN] = name
|
|
@@ -865,8 +860,6 @@ def _finalize_recovery(ctx: FinalizeContext, result: Mapping[str, Any]) -> dict[
|
|
|
865
860
|
"Repair the reported causes in this run using the owning input's correction command, "
|
|
866
861
|
"then execute resumeCommand. Preserve approvals, model choices and completed evidence. "
|
|
867
862
|
"If plan content changes, re-verify the affected items before finalizing. "
|
|
868
|
-
"After a narrative correction, check existing translations with report-translate check-data "
|
|
869
|
-
"and regenerate them if their source no longer matches. "
|
|
870
863
|
"Ask only for an unresolved user decision or an actual external prerequisite."
|
|
871
864
|
),
|
|
872
865
|
}
|
|
@@ -1018,8 +1011,6 @@ def _run_finalize_step(
|
|
|
1018
1011
|
"""한 Phase 7 단계를 실행하고 그 단계의 종료 코드만 돌려준다."""
|
|
1019
1012
|
if name == STEP_PROJECT_ACTIVITY:
|
|
1020
1013
|
return _run_project_activity(ctx, command)
|
|
1021
|
-
if name == STEP_TRANSLATE:
|
|
1022
|
-
return _run_translate(ctx, command)
|
|
1023
1014
|
if name == STEP_TEARDOWN_STAGES:
|
|
1024
1015
|
return _teardown_stage_worktrees(ctx, command)
|
|
1025
1016
|
if name == STEP_PRUNE_RUN_ARTIFACTS:
|
|
@@ -1043,31 +1034,6 @@ def _run_finalize_step(
|
|
|
1043
1034
|
)
|
|
1044
1035
|
|
|
1045
1036
|
|
|
1046
|
-
def _run_translate(
|
|
1047
|
-
ctx: FinalizeContext,
|
|
1048
|
-
command: Sequence[str],
|
|
1049
|
-
) -> subprocess.CompletedProcess[str]:
|
|
1050
|
-
"""비영어 리포트의 번역 사이드카를 만든다. 영어 리포트는 건너뛴다.
|
|
1051
|
-
|
|
1052
|
-
실패는 다른 단계처럼 기록만 하고 시퀀스는 계속 돈다 — `render-views` 는
|
|
1053
|
-
사이드카 없이 영어 열람본을 내고, `validate-run` 은 권고를 남기며, 결과의
|
|
1054
|
-
`--only translate --only render-views …` 재개 힌트가 그 둘을 다시 돌린다.
|
|
1055
|
-
"""
|
|
1056
|
-
try:
|
|
1057
|
-
outcome = translate_report(
|
|
1058
|
-
project_root=ctx.project_root,
|
|
1059
|
-
workspace_root=ctx.workspace_root,
|
|
1060
|
-
manifest_path=ctx.manifest_path,
|
|
1061
|
-
manifest=_load_manifest(ctx.manifest_path),
|
|
1062
|
-
data_path=ctx.data_path,
|
|
1063
|
-
)
|
|
1064
|
-
except (FinalizeError, OSError, JsonBoundaryError) as exc:
|
|
1065
|
-
outcome = TranslateOutcome(1, "", f"translate failed: {exc}")
|
|
1066
|
-
return subprocess.CompletedProcess(
|
|
1067
|
-
command, outcome.returncode, outcome.stdout, outcome.stderr
|
|
1068
|
-
)
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
1037
|
def _run_project_activity(
|
|
1072
1038
|
ctx: FinalizeContext,
|
|
1073
1039
|
command: Sequence[str],
|
|
@@ -1206,18 +1172,13 @@ _CLI_EPILOG = r"""Usage:
|
|
|
1206
1172
|
Runs the Phase 7 steps in their contractual order against one final-report:
|
|
1207
1173
|
|
|
1208
1174
|
1. project-activity project the run's activity events into the data.json
|
|
1209
|
-
2.
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
legacy conditional interactive view
|
|
1217
|
-
5. spawn-followups turn section 4 rows into task stubs
|
|
1218
|
-
6. validate-run validate the finished run artifacts
|
|
1219
|
-
7. record-group-memory / 8. teardown-stages after a clean validation
|
|
1220
|
-
9. prune-run-artifacts remove node_modules and .next under this run
|
|
1175
|
+
2. token-usage substitute real token/cost numbers into the data.json
|
|
1176
|
+
3. render-views write the schema-v2 task-specific *.html sibling;
|
|
1177
|
+
v1 keeps the legacy conditional interactive view
|
|
1178
|
+
4. spawn-followups turn section 4 rows into task stubs
|
|
1179
|
+
5. validate-run validate the finished run artifacts
|
|
1180
|
+
6. record-group-memory / 7. teardown-stages after a clean validation
|
|
1181
|
+
8. prune-run-artifacts remove node_modules and .next under this run
|
|
1221
1182
|
directory, and the dispatch snapshots and publication
|
|
1222
1183
|
locks of a validated run; runs after a failure too
|
|
1223
1184
|
|
|
@@ -3,16 +3,14 @@ from __future__ import annotations
|
|
|
3
3
|
|
|
4
4
|
import hashlib
|
|
5
5
|
import json
|
|
6
|
-
import sys
|
|
7
6
|
from pathlib import Path
|
|
8
7
|
|
|
9
8
|
import okstra_vendor # noqa: F401 # registers vendored dependency aliases
|
|
10
9
|
from jinja2 import Environment, StrictUndefined, select_autoescape
|
|
11
10
|
|
|
12
|
-
from ..final_report_paths import final_report_data_path
|
|
11
|
+
from ..final_report_paths import final_report_data_path
|
|
13
12
|
from ..final_report_schema import load_schema_for_data, validate
|
|
14
13
|
from ..i18n import HTML_DICTIONARY_REL, load_dictionary, make_jinja_global
|
|
15
|
-
from ..report_translation import overlay
|
|
16
14
|
from ..report_view_artifacts import user_responses_dir_for_report
|
|
17
15
|
from ..usage_cells import format_duration_ms
|
|
18
16
|
from ..json_boundary import load_owned_object
|
|
@@ -76,44 +74,8 @@ def _report_meta(data: dict, run_meta: HtmlRunMeta) -> dict[str, object]:
|
|
|
76
74
|
}
|
|
77
75
|
|
|
78
76
|
|
|
79
|
-
def
|
|
80
|
-
""
|
|
81
|
-
|
|
82
|
-
data.json 은 영어 정본이고 고치지 않는다. 사람이 읽는 HTML 만 독자 언어다.
|
|
83
|
-
사이드카가 빠진 포인터는 영어로 남긴다.
|
|
84
|
-
|
|
85
|
-
사이드카 파일 자체가 없으면 요청 언어를 버리고 전부 영어로 렌더한다.
|
|
86
|
-
종전에는 여기서 렌더를 거부했다 — 크롬만 번역된 반쪽 페이지를 내보내지
|
|
87
|
-
않겠다는 이유였고, 그 판단 자체는 옳다. 틀린 것은 남은 선택지였다: 번역
|
|
88
|
-
워커가 못 도는 이유(호스트 승인 게이트, 공급자 부재)는 리포트의 품질과
|
|
89
|
-
무관한데, 거부는 열람본을 통째로 없애고 `validate-run` 이 그 부재를 차단
|
|
90
|
-
실패로 삼아 run 을 `contract-violated` 로 끝냈다 — 분석도 리포트도 끝난
|
|
91
|
-
run 이 그렇게 끝났다(2026-09-08 FontsNinja/app/jobs dev-10784
|
|
92
|
-
error-analysis). 언어를 낮추면 크롬과 본문이 같은 영어라 반쪽 페이지도
|
|
93
|
-
아니고, 독자는 읽을 것을 갖는다. 번역이 나중에 도착하면 다시 렌더해
|
|
94
|
-
덮어쓴다.
|
|
95
|
-
"""
|
|
96
|
-
lang = str((data.get("meta") or {}).get("reportLanguage") or "en")
|
|
97
|
-
if lang == "en":
|
|
98
|
-
return data, lang
|
|
99
|
-
sidecar_file = translation_sidecar_path(data_path, lang)
|
|
100
|
-
if not sidecar_file.is_file():
|
|
101
|
-
sys.stdout.write(
|
|
102
|
-
f"no {lang} translation sidecar at {sidecar_file.name}; "
|
|
103
|
-
"rendering the English source\n"
|
|
104
|
-
)
|
|
105
|
-
return data, "en"
|
|
106
|
-
payload = load_owned_object(sidecar_file, artifact="translation sidecar")
|
|
107
|
-
strings = payload.get("strings")
|
|
108
|
-
if not isinstance(strings, dict):
|
|
109
|
-
raise HtmlRenderError(f"translation sidecar has no 'strings' object: {sidecar_file}")
|
|
110
|
-
localized, report = overlay(data, strings)
|
|
111
|
-
sys.stdout.write(
|
|
112
|
-
f"translated {report.applied} string(s) into {lang}"
|
|
113
|
-
f" ({len(report.untranslated)} left in English"
|
|
114
|
-
f", {len(report.unresolved)} unresolved)\n"
|
|
115
|
-
)
|
|
116
|
-
return localized, lang
|
|
77
|
+
def _report_language(data: dict) -> str:
|
|
78
|
+
return str((data.get("meta") or {}).get("reportLanguage") or "en")
|
|
117
79
|
|
|
118
80
|
|
|
119
81
|
def _html_path(data_path: Path) -> Path:
|
|
@@ -140,9 +102,7 @@ def render_v2_html_view(
|
|
|
140
102
|
errors = validate(data, load_schema_for_data(data))
|
|
141
103
|
if errors:
|
|
142
104
|
raise HtmlRenderError("invalid v2 final-report data: " + "; ".join(errors[:5]))
|
|
143
|
-
|
|
144
|
-
# has no say in whether the report is well-formed.
|
|
145
|
-
data, lang = _localize(data, data_path)
|
|
105
|
+
lang = _report_language(data)
|
|
146
106
|
route = resolve_html_route(run_meta.task_type)
|
|
147
107
|
view = route.view_builder(data)
|
|
148
108
|
root = _templates_root(templates_root)
|
|
@@ -6,9 +6,9 @@ the run manifest, which report assembly copies into `meta.reportLanguage` and
|
|
|
6
6
|
the HTML renderer reads. When only the prompt called this, a project could ask
|
|
7
7
|
for `ko`, get a Korean-instructed writer, and still ship an English report.
|
|
8
8
|
|
|
9
|
-
The value is a language tag (`en`, `ko`, `fr`, `pt-BR`, …). The
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
The value is a language tag (`en`, `ko`, `fr`, `pt-BR`, …). The report writer
|
|
10
|
+
authors the report prose in that language; nothing here knows any particular
|
|
11
|
+
language. Precedence: project config → global config → `en`.
|
|
12
12
|
"""
|
|
13
13
|
from __future__ import annotations
|
|
14
14
|
|