okstra 0.201.3 → 0.204.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.
- package/README.md +3 -3
- package/dist/cli-registry.mjs +7 -7
- package/dist/cli-registry.mjs.map +1 -1
- package/dist/commands/lifecycle/install.mjs +50 -124
- package/dist/commands/lifecycle/install.mjs.map +1 -1
- package/dist/commands/lifecycle/setup.mjs +15 -0
- package/dist/commands/lifecycle/setup.mjs.map +1 -1
- package/dist/commands/memory/memory.mjs +41 -8
- package/dist/commands/memory/memory.mjs.map +1 -1
- package/dist/lib/citation-guidance.d.mts +21 -0
- package/dist/lib/citation-guidance.mjs +79 -0
- package/dist/lib/citation-guidance.mjs.map +1 -0
- package/dist/lib/install-assets.mjs +3 -0
- package/dist/lib/install-assets.mjs.map +1 -1
- package/dist/lib/runtime-manifest.mjs +2 -1
- package/dist/lib/runtime-manifest.mjs.map +1 -1
- package/dist/lib/types.d.mts +2 -1
- package/docs/architecture/storage-model.md +17 -10
- package/docs/architecture.md +26 -20
- package/docs/cli.md +16 -13
- package/docs/contributor-change-matrix.md +3 -2
- package/docs/performance-improvement-plan-v2.md +2 -3
- package/docs/project-structure-overview.md +38 -9
- package/docs/task-process/README.md +1 -1
- package/docs/task-process/common-flow.md +1 -1
- package/docs/task-process/final-verification.md +3 -1
- package/docs/task-process/implementation.md +1 -1
- package/docs/task-process/release-handoff.md +36 -39
- package/package.json +1 -2
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/common.json +28 -0
- package/runtime/agents/operations/code-review.json +6 -0
- package/runtime/agents/operations/report-translation.json +6 -0
- package/runtime/agents/operations/schedule-verification.json +6 -0
- package/runtime/agents/roles/analyser.json +18 -0
- package/runtime/agents/roles/critic.json +18 -0
- package/runtime/agents/roles/designer.json +18 -0
- package/runtime/agents/roles/implementer.json +20 -0
- package/runtime/agents/roles/leader.json +20 -0
- package/runtime/agents/roles/planner.json +18 -0
- package/runtime/agents/roles/report-writer.json +19 -0
- package/runtime/agents/roles/translator.json +19 -0
- package/runtime/agents/roles/verifier.json +18 -0
- package/runtime/bin/lib/okstra/usage.sh +5 -5
- package/runtime/prompts/duties/acceptance-critic.json +32 -0
- package/runtime/prompts/duties/acceptance-verifier.json +32 -0
- package/runtime/prompts/duties/analysis-worker.json +32 -0
- package/runtime/prompts/duties/code-reviewer.json +32 -0
- package/runtime/prompts/duties/diagnosis-worker.json +32 -0
- package/runtime/prompts/duties/direction-selection-worker.json +32 -0
- package/runtime/prompts/duties/discovery-worker.json +32 -0
- package/runtime/prompts/duties/implementation-executor.json +32 -0
- package/runtime/prompts/duties/implementation-verifier.json +32 -0
- package/runtime/prompts/duties/lead.json +32 -0
- package/runtime/prompts/duties/planning-worker.json +36 -0
- package/runtime/prompts/duties/report-writer.json +32 -0
- package/runtime/prompts/duties/reverification-worker.json +32 -0
- package/runtime/prompts/duties/schedule-verifier.json +32 -0
- package/runtime/prompts/duties/scope-critic.json +32 -0
- package/runtime/prompts/duties/technical-verification-worker.json +32 -0
- package/runtime/prompts/duties/translator.json +32 -0
- package/runtime/prompts/launch.template.md +3 -2
- package/runtime/prompts/lead/adapters/cmux.md +1 -1
- package/runtime/prompts/lead/convergence.md +4 -4
- package/runtime/prompts/lead/okstra-lead-contract.md +115 -6
- package/runtime/prompts/lead/plan-body-verification.md +6 -6
- package/runtime/prompts/lead/report-writer.md +3 -3
- package/runtime/prompts/profiles/_common-contract.md +2 -2
- package/runtime/prompts/profiles/_implementation-executor.md +4 -1
- package/runtime/prompts/profiles/_implementation-verifier.md +3 -3
- package/runtime/prompts/profiles/change-impact-analysis.json +31 -0
- package/runtime/prompts/profiles/change-impact-analysis.md +0 -20
- package/runtime/prompts/profiles/error-analysis.json +39 -0
- package/runtime/prompts/profiles/error-analysis.md +0 -25
- package/runtime/prompts/profiles/feature-analysis.json +31 -0
- package/runtime/prompts/profiles/feature-analysis.md +0 -20
- package/runtime/prompts/profiles/final-verification.json +30 -0
- package/runtime/prompts/profiles/final-verification.md +3 -22
- package/runtime/prompts/profiles/forbidden-actions.json +4 -3
- package/runtime/prompts/profiles/implementation-option-selection.json +31 -0
- package/runtime/prompts/profiles/implementation-option-selection.md +0 -20
- package/runtime/prompts/profiles/implementation-planning.json +40 -0
- package/runtime/prompts/profiles/implementation-planning.md +6 -29
- package/runtime/prompts/profiles/implementation.json +30 -0
- package/runtime/prompts/profiles/implementation.md +1 -20
- package/runtime/prompts/profiles/improvement-discovery.json +31 -0
- package/runtime/prompts/profiles/improvement-discovery.md +0 -20
- package/runtime/prompts/profiles/project-analysis.json +31 -0
- package/runtime/prompts/profiles/project-analysis.md +0 -20
- package/runtime/prompts/profiles/release-handoff.json +5 -0
- package/runtime/prompts/profiles/release-handoff.md +71 -73
- package/runtime/prompts/profiles/requirements-discovery.json +39 -0
- package/runtime/prompts/profiles/requirements-discovery.md +0 -25
- package/runtime/prompts/profiles/technical-verification.json +39 -0
- package/runtime/prompts/profiles/technical-verification.md +0 -25
- package/runtime/prompts/wizard/prompts.ko.json +12 -17
- package/runtime/python/okstra_ctl/adapters/hosts/antigravity/relay.md +1 -0
- package/runtime/python/okstra_ctl/adapters/hosts/claude-code/adapter.py +3 -0
- package/runtime/python/okstra_ctl/adapters/hosts/claude-code/manifest.json +1 -1
- package/runtime/python/okstra_ctl/adapters/hosts/claude-code/relay.md +4 -3
- package/runtime/python/okstra_ctl/adapters/hosts/claude-code/worker-session.md +108 -0
- package/runtime/python/okstra_ctl/adapters/hosts/codex/relay.md +1 -0
- package/runtime/python/okstra_ctl/adapters/hosts/grok/relay.md +2 -0
- package/runtime/python/okstra_ctl/adapters/hosts/kimi/relay.md +2 -0
- package/runtime/python/okstra_ctl/adapters/providers/antigravity/adapter.py +8 -1
- package/runtime/python/okstra_ctl/adapters/providers/claude/adapter.py +8 -0
- package/runtime/python/okstra_ctl/adapters/providers/codex/adapter.py +23 -6
- package/runtime/python/okstra_ctl/adapters/providers/grok/adapter.py +6 -2
- package/runtime/python/okstra_ctl/agent/invocation.py +168 -113
- package/runtime/python/okstra_ctl/agent/prompt_cli/cli.py +120 -0
- package/runtime/python/okstra_ctl/agent/prompt_cli/materialize.py +107 -2
- package/runtime/python/okstra_ctl/agent/prompt_cli/run_identity.py +0 -49
- package/runtime/python/okstra_ctl/analysis_packet.py +4 -1
- package/runtime/python/okstra_ctl/application/open_worker.py +6 -1
- package/runtime/python/okstra_ctl/assignment_resolver.py +16 -5
- package/runtime/python/okstra_ctl/cmux.py +69 -20
- package/runtime/python/okstra_ctl/code_review_target.py +16 -8
- package/runtime/python/okstra_ctl/conformance.py +43 -0
- package/runtime/python/okstra_ctl/consumers.py +6 -3
- package/runtime/python/okstra_ctl/container.py +31 -8
- package/runtime/python/okstra_ctl/context_cost.py +11 -15
- package/runtime/python/okstra_ctl/contract_refreeze.py +156 -0
- package/runtime/python/okstra_ctl/convergence_critic_prompt.py +4 -6
- package/runtime/python/okstra_ctl/convergence_provenance.py +81 -18
- package/runtime/python/okstra_ctl/design_prep.py +34 -1
- package/runtime/python/okstra_ctl/dispatch_core.py +53 -27
- package/runtime/python/okstra_ctl/domain/host.py +5 -0
- package/runtime/python/okstra_ctl/domain/worker_runtime.py +10 -0
- package/runtime/python/okstra_ctl/error_report.py +4 -3
- package/runtime/python/okstra_ctl/execution_manifest.py +71 -18
- package/runtime/python/okstra_ctl/execution_mutation_audit.py +21 -21
- package/runtime/python/okstra_ctl/handoff.py +167 -277
- package/runtime/python/okstra_ctl/implementation_stage.py +9 -0
- package/runtime/python/okstra_ctl/initial_prompt_materialization.py +113 -0
- package/runtime/python/okstra_ctl/lead_progress.py +1 -1
- package/runtime/python/okstra_ctl/legacy_model_selection.py +2 -2
- package/runtime/python/okstra_ctl/manager_cli.py +175 -14
- package/runtime/python/okstra_ctl/manager_launch.py +41 -19
- package/runtime/python/okstra_ctl/manager_paths.py +22 -3
- package/runtime/python/okstra_ctl/manager_split.py +474 -0
- package/runtime/python/okstra_ctl/manager_store.py +331 -21
- package/runtime/python/okstra_ctl/manager_sync.py +37 -16
- package/runtime/python/okstra_ctl/manager_view.py +217 -0
- package/runtime/python/okstra_ctl/model_discovery.py +30 -0
- package/runtime/python/okstra_ctl/model_io/lines.py +14 -1
- package/runtime/python/okstra_ctl/model_io/renderers.py +4 -3
- package/runtime/python/okstra_ctl/models.py +1 -1
- package/runtime/python/okstra_ctl/next_phase.py +16 -6
- package/runtime/python/okstra_ctl/operation_invocation.py +86 -0
- package/runtime/python/okstra_ctl/option_comparison.py +168 -0
- package/runtime/python/okstra_ctl/path_hints.py +9 -0
- package/runtime/python/okstra_ctl/paths.py +3 -0
- package/runtime/python/okstra_ctl/plan_items_cli.py +6 -1
- package/runtime/python/okstra_ctl/profile_show.py +42 -1
- package/runtime/python/okstra_ctl/qa_commands.py +15 -0
- package/runtime/python/okstra_ctl/registry/host_discovery.py +20 -12
- package/runtime/python/okstra_ctl/registry/host_registry.py +11 -0
- package/runtime/python/okstra_ctl/render.py +50 -0
- package/runtime/python/okstra_ctl/report_contract.py +1 -1
- package/runtime/python/okstra_ctl/report_finalize.py +13 -6
- package/runtime/python/okstra_ctl/report_html/view_models/final_verification.py +2 -21
- package/runtime/python/okstra_ctl/report_html/view_models/release_handoff.py +21 -3
- package/runtime/python/okstra_ctl/report_html/visualizations.py +0 -5
- package/runtime/python/okstra_ctl/report_synthesis_packet.py +177 -17
- package/runtime/python/okstra_ctl/report_translation.py +2 -1
- package/runtime/python/okstra_ctl/report_translation_dispatch.py +69 -9
- package/runtime/python/okstra_ctl/role_requirements.py +142 -129
- package/runtime/python/okstra_ctl/rollup.py +3 -1
- package/runtime/python/okstra_ctl/run.py +76 -29
- package/runtime/python/okstra_ctl/schedule_semantics.py +17 -6
- package/runtime/python/okstra_ctl/stage_fix_carry.py +23 -4
- package/runtime/python/okstra_ctl/stage_integrate.py +178 -18
- package/runtime/python/okstra_ctl/stage_map.py +16 -2
- package/runtime/python/okstra_ctl/stage_targets.py +209 -43
- package/runtime/python/okstra_ctl/team.py +22 -13
- package/runtime/python/okstra_ctl/time_report.py +2 -1
- package/runtime/python/okstra_ctl/usage_report.py +3 -1
- package/runtime/python/okstra_ctl/verification_target.py +13 -2
- package/runtime/python/okstra_ctl/wizard/confirmation.py +3 -9
- package/runtime/python/okstra_ctl/wizard/ids.py +1 -1
- package/runtime/python/okstra_ctl/wizard/registry.py +1 -1
- package/runtime/python/okstra_ctl/wizard/state.py +3 -5
- package/runtime/python/okstra_ctl/wizard/steps_plan.py +3 -23
- package/runtime/python/okstra_ctl/worker_prompt_contract.py +5 -1
- package/runtime/python/okstra_ctl/worker_prompt_headers.py +35 -7
- package/runtime/python/okstra_ctl/worker_prompt_policy.py +66 -48
- package/runtime/python/okstra_ctl/workflow.py +1 -1
- package/runtime/python/okstra_ctl/worktree/__init__.py +3 -1
- package/runtime/python/okstra_ctl/worktree/naming.py +9 -0
- package/runtime/python/okstra_ctl/worktree_registry.py +38 -9
- package/runtime/python/okstra_token_usage/pricing.py +6 -4
- package/runtime/schemas/agent-common-v1.schema.json +34 -0
- package/runtime/schemas/agent-duty-v1.schema.json +38 -0
- package/runtime/schemas/agent-operation-v1.schema.json +11 -0
- package/runtime/schemas/agent-profile-v1.schema.json +46 -0
- package/runtime/schemas/agent-role-v1.schema.json +29 -0
- package/runtime/schemas/final-report-v2.0.schema.json +118 -97
- package/runtime/schemas/final-report-v3.0.schema.json +118 -97
- package/runtime/skills/okstra-brief-gen/SKILL.md +84 -4
- package/runtime/skills/okstra-chat/SKILL.md +2 -2
- package/runtime/skills/okstra-code-review/SKILL.md +23 -9
- package/runtime/skills/okstra-container-build/SKILL.md +10 -10
- package/runtime/skills/okstra-inspect/SKILL.md +1 -1
- package/runtime/skills/okstra-inspect/facets/cost.md +1 -1
- package/runtime/skills/okstra-inspect/facets/error-zip.md +9 -9
- package/runtime/skills/okstra-inspect/facets/errors.md +16 -16
- package/runtime/skills/okstra-inspect/facets/logs.md +7 -7
- package/runtime/skills/okstra-inspect/facets/recap.md +2 -2
- package/runtime/skills/okstra-inspect/facets/report.md +1 -1
- package/runtime/skills/okstra-inspect/facets/status.md +4 -3
- package/runtime/skills/okstra-inspect/facets/time.md +11 -10
- package/runtime/skills/okstra-manager/SKILL.md +70 -5
- package/runtime/skills/okstra-pr-gen/SKILL.md +6 -5
- package/runtime/skills/okstra-rollup/SKILL.md +5 -5
- package/runtime/skills/okstra-run/SKILL.md +32 -13
- package/runtime/skills/okstra-schedule-gen/SKILL.md +19 -14
- package/runtime/skills/okstra-setup/SKILL.md +21 -10
- package/runtime/skills/okstra-setup/references/project-config.md +7 -6
- package/runtime/skills/okstra-usage/SKILL.md +1 -1
- package/runtime/skills/okstra-user-response/SKILL.md +1 -1
- package/runtime/templates/manager/view.template.html +109 -0
- package/runtime/templates/report-writer-prompt-preamble.md +8 -0
- package/runtime/templates/reports/brief.template.md +14 -4
- package/runtime/templates/reports/html/i18n/en.json +7 -4
- package/runtime/templates/reports/html/i18n/ko.json +7 -4
- package/runtime/templates/reports/html/tasks/final-verification.template.html +2 -2
- package/runtime/templates/reports/html/tasks/release-handoff.template.html +8 -5
- package/runtime/templates/reports/i18n/en.json +1 -1
- package/runtime/templates/reports/md/tasks/release-handoff.template.md +1 -1
- package/runtime/templates/reports/release-handoff-input.template.md +6 -4
- package/runtime/templates/translator-prompt-preamble.md +36 -0
- package/runtime/validators/checks/validate-assets-01.py +7 -8
- package/runtime/validators/validate-brief.py +77 -2
- package/runtime/validators/validate-implementation-plan-stages.py +2 -1
- package/runtime/validators/validate-run.py +59 -9
- package/runtime/validators/validate-schedule.py +9 -0
- package/docs/for-ai/README.md +0 -68
- package/docs/for-ai/skills/okstra-brief-gen.md +0 -262
- package/docs/for-ai/skills/okstra-chat.md +0 -34
- package/docs/for-ai/skills/okstra-code-review.md +0 -57
- package/docs/for-ai/skills/okstra-container-build.md +0 -129
- package/docs/for-ai/skills/okstra-inspect.md +0 -262
- package/docs/for-ai/skills/okstra-manager.md +0 -69
- package/docs/for-ai/skills/okstra-memory.md +0 -126
- package/docs/for-ai/skills/okstra-pr-gen.md +0 -49
- package/docs/for-ai/skills/okstra-rollup.md +0 -114
- package/docs/for-ai/skills/okstra-run.md +0 -250
- package/docs/for-ai/skills/okstra-schedule-gen.md +0 -240
- package/docs/for-ai/skills/okstra-setup.md +0 -158
- package/docs/for-ai/skills/okstra-usage.md +0 -29
- package/docs/for-ai/skills/okstra-user-response.md +0 -72
- package/runtime/agents/workers/claude-worker.md +0 -128
- package/runtime/agents/workers/report-writer-worker.md +0 -37
- package/runtime/agents/workers/translator-worker.md +0 -63
- package/runtime/prompts/duties/acceptance-critic.md +0 -44
- package/runtime/prompts/duties/acceptance-verifier.md +0 -44
- package/runtime/prompts/duties/analysis-worker.md +0 -44
- package/runtime/prompts/duties/code-reviewer.md +0 -44
- package/runtime/prompts/duties/common.md +0 -39
- package/runtime/prompts/duties/diagnosis-worker.md +0 -44
- package/runtime/prompts/duties/direction-selection-worker.md +0 -44
- package/runtime/prompts/duties/discovery-worker.md +0 -44
- package/runtime/prompts/duties/implementation-executor.md +0 -44
- package/runtime/prompts/duties/implementation-verifier.md +0 -44
- package/runtime/prompts/duties/lead.md +0 -44
- package/runtime/prompts/duties/planning-worker.md +0 -52
- package/runtime/prompts/duties/report-writer.md +0 -44
- package/runtime/prompts/duties/reverification-worker.md +0 -44
- package/runtime/prompts/duties/schedule-verifier.md +0 -44
- package/runtime/prompts/duties/scope-critic.md +0 -44
- package/runtime/prompts/duties/technical-verification-worker.md +0 -44
- package/runtime/prompts/duties/translator.md +0 -44
- package/runtime/python/okstra_ctl/pane_title.py +0 -154
|
@@ -647,6 +647,9 @@ def compute_run_paths(
|
|
|
647
647
|
"REPORT_WRITER_PREAMBLE_PATH": str(
|
|
648
648
|
runtime_home / "templates" / "report-writer-prompt-preamble.md"
|
|
649
649
|
),
|
|
650
|
+
"TRANSLATOR_PREAMBLE_PATH": str(
|
|
651
|
+
runtime_home / "templates" / "translator-prompt-preamble.md"
|
|
652
|
+
),
|
|
650
653
|
"WORKER_ERROR_CONTRACT_PATH": str(
|
|
651
654
|
runtime_home / "templates" / "worker-error-contract.md"
|
|
652
655
|
),
|
|
@@ -26,6 +26,7 @@ from .convergence_store import write_json_atomic
|
|
|
26
26
|
from .incremental_carry import sync_prepared_dispatch_queue
|
|
27
27
|
from .json_boundary import JsonBoundaryError, load_owned_object
|
|
28
28
|
from .report_finalize import task_manifest_path
|
|
29
|
+
from .qa_commands import split_verification_command
|
|
29
30
|
from .plan_derivations import extract_tokens, find_derivations
|
|
30
31
|
from .plan_items import (
|
|
31
32
|
NextDispatch,
|
|
@@ -577,7 +578,11 @@ def _render_scalar_or_list(key: str, value: object, indent: str = "") -> list[st
|
|
|
577
578
|
if isinstance(value, Mapping):
|
|
578
579
|
raise PlanItemContractError(f"plan item {key} must be scalar")
|
|
579
580
|
if key in {"command", "commandOrObservation"} and isinstance(value, str):
|
|
580
|
-
|
|
581
|
+
command, annotation = split_verification_command(value)
|
|
582
|
+
rows = _render_literal(_label(key), command, indent)
|
|
583
|
+
if annotation is not None:
|
|
584
|
+
rows.append(indent + line("Command annotation (not executable)", annotation))
|
|
585
|
+
return rows
|
|
581
586
|
return [indent + line(_label(key), value)]
|
|
582
587
|
|
|
583
588
|
|
|
@@ -19,6 +19,7 @@ import sys
|
|
|
19
19
|
from pathlib import Path
|
|
20
20
|
|
|
21
21
|
from .paths import find_asset_root
|
|
22
|
+
from .role_requirements import RoleProfileError, load_role_profile
|
|
22
23
|
from .run import PrepareError, _expand_profile_includes
|
|
23
24
|
|
|
24
25
|
_PROFILES_REL = ("prompts", "profiles")
|
|
@@ -82,9 +83,49 @@ def sidecar_bodies(root: Path, profile_text: str) -> list[str]:
|
|
|
82
83
|
def render(root: Path, task_type: str, *, resolved: bool) -> str:
|
|
83
84
|
path = profile_path(root, task_type)
|
|
84
85
|
if not resolved:
|
|
86
|
+
# 계약상 이 출력은 디스크의 파일 그대로다. 역할 구조는 이제 `.json` 에
|
|
87
|
+
# 있으므로 `--resolved` 가 렌더한다.
|
|
85
88
|
return path.read_text(encoding="utf-8")
|
|
86
89
|
expanded = _expand_profile_includes(path)
|
|
87
|
-
|
|
90
|
+
body = expanded + "".join(sidecar_bodies(root, expanded))
|
|
91
|
+
return body + _role_requirements_section(path)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _role_requirements_section(profile_path: Path) -> str:
|
|
95
|
+
"""Render the profile's role structure from its contract.
|
|
96
|
+
|
|
97
|
+
The structure moved out of the Markdown into the `.json` sibling, so a
|
|
98
|
+
reader of `okstra profile show` would otherwise no longer see which roles
|
|
99
|
+
the phase runs — which is most of what the command is read for.
|
|
100
|
+
"""
|
|
101
|
+
try:
|
|
102
|
+
profile = load_role_profile(profile_path)
|
|
103
|
+
except RoleProfileError as exc:
|
|
104
|
+
return f"\n## Role requirements\n\nunavailable: {exc}\n"
|
|
105
|
+
if not profile.roles:
|
|
106
|
+
return "\n## Role requirements\n\nThis phase runs a single lead and no workers.\n"
|
|
107
|
+
lines = [
|
|
108
|
+
"",
|
|
109
|
+
"## Role requirements",
|
|
110
|
+
"",
|
|
111
|
+
"| Role | Duty | Count (min/recommended/max) | Activation |",
|
|
112
|
+
"|---|---|---|---|",
|
|
113
|
+
]
|
|
114
|
+
for requirement in profile.roles:
|
|
115
|
+
if requirement.dynamic:
|
|
116
|
+
sources = ", ".join(f"`{role}`" for role in requirement.source_role_ids)
|
|
117
|
+
counts = f"one per activated {sources} slot"
|
|
118
|
+
activation = requirement.activation
|
|
119
|
+
else:
|
|
120
|
+
counts = (
|
|
121
|
+
f"{requirement.min_count} / {requirement.recommended_count} / "
|
|
122
|
+
f"{requirement.max_count}"
|
|
123
|
+
)
|
|
124
|
+
activation = "static"
|
|
125
|
+
lines.append(
|
|
126
|
+
f"| `{requirement.role}` | `{requirement.duty}` | {counts} | {activation} |"
|
|
127
|
+
)
|
|
128
|
+
return "\n".join(lines) + "\n"
|
|
88
129
|
|
|
89
130
|
|
|
90
131
|
def _write_stdout(text: str) -> None:
|
|
@@ -98,6 +98,21 @@ def find_unfrozen_installs(cmd: str) -> list[str]:
|
|
|
98
98
|
return found
|
|
99
99
|
|
|
100
100
|
|
|
101
|
+
def split_verification_command(command: str) -> tuple[str, str | None]:
|
|
102
|
+
"""기존 계획의 파일 인용 설명만 분리하며 일반 셸 구문은 보존한다."""
|
|
103
|
+
match = re.search(r";\s*(existing helper:\s*[^;&|<>$`\n]+:\d+(?:-\d+)?)\s*$", command)
|
|
104
|
+
if match is None:
|
|
105
|
+
return command, None
|
|
106
|
+
executable = command[:match.start()].rstrip()
|
|
107
|
+
try:
|
|
108
|
+
words = shlex.split(executable)
|
|
109
|
+
except ValueError:
|
|
110
|
+
return command, None
|
|
111
|
+
if not words:
|
|
112
|
+
return command, None
|
|
113
|
+
return executable, match.group(1).strip()
|
|
114
|
+
|
|
115
|
+
|
|
101
116
|
def verification_command_defects(command: str) -> list[str]:
|
|
102
117
|
"""선언된 작업 폴더를 셸에서 다른 체크아웃으로 바꾸는 명령을 거부한다."""
|
|
103
118
|
try:
|
|
@@ -18,6 +18,8 @@ class HostManifest:
|
|
|
18
18
|
native_provider_id: str
|
|
19
19
|
required_executables: tuple[str, ...]
|
|
20
20
|
relay_contract: Path
|
|
21
|
+
# 선언하지 않은 호스트는 None. 이 호스트 세션 안에서 도는 워커가 읽는 계약이다.
|
|
22
|
+
worker_session_contract: Path | None = None
|
|
21
23
|
|
|
22
24
|
|
|
23
25
|
def discover_host_manifests(roots: Iterable[Path]) -> tuple[HostManifest, ...]:
|
|
@@ -54,7 +56,15 @@ def _read_manifest(manifest_path: Path) -> HostManifest:
|
|
|
54
56
|
native_provider_id = _optional_id(raw, "nativeProviderId", manifest_path)
|
|
55
57
|
required_executables = _string_tuple(raw, "requiredExecutables", manifest_path)
|
|
56
58
|
relay_ref = _required_string(raw, "relayContract", manifest_path)
|
|
57
|
-
relay_contract =
|
|
59
|
+
relay_contract = _resolve_adapter_file(manifest_path, relay_ref, "relay contract")
|
|
60
|
+
session_ref = raw.get("workerSessionContract")
|
|
61
|
+
if session_ref is not None and (not isinstance(session_ref, str) or not session_ref):
|
|
62
|
+
raise HostAdapterContractError(f"invalid host manifest: {manifest_path}")
|
|
63
|
+
worker_session_contract = (
|
|
64
|
+
_resolve_adapter_file(manifest_path, session_ref, "worker session contract")
|
|
65
|
+
if session_ref is not None
|
|
66
|
+
else None
|
|
67
|
+
)
|
|
58
68
|
return HostManifest(
|
|
59
69
|
path=manifest_path,
|
|
60
70
|
host_id=host_id,
|
|
@@ -62,6 +72,7 @@ def _read_manifest(manifest_path: Path) -> HostManifest:
|
|
|
62
72
|
native_provider_id=native_provider_id,
|
|
63
73
|
required_executables=required_executables,
|
|
64
74
|
relay_contract=relay_contract,
|
|
75
|
+
worker_session_contract=worker_session_contract,
|
|
65
76
|
)
|
|
66
77
|
|
|
67
78
|
|
|
@@ -111,15 +122,12 @@ def _string_tuple(
|
|
|
111
122
|
return tuple(value)
|
|
112
123
|
|
|
113
124
|
|
|
114
|
-
def
|
|
125
|
+
def _resolve_adapter_file(manifest_path: Path, ref: str, label: str) -> Path:
|
|
126
|
+
"""어댑터 디렉터리 안의 파일 하나를 해석한다. 밖을 가리키면 거절한다."""
|
|
115
127
|
adapter_root = manifest_path.parent.resolve()
|
|
116
|
-
if Path(
|
|
117
|
-
raise HostAdapterContractError(
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
raise HostAdapterContractError(
|
|
123
|
-
f"relay contract escapes adapter root: {relay_ref!r}"
|
|
124
|
-
)
|
|
125
|
-
return relay_contract
|
|
128
|
+
if Path(ref).is_absolute():
|
|
129
|
+
raise HostAdapterContractError(f"{label} escapes adapter root: {ref!r}")
|
|
130
|
+
resolved = (adapter_root / ref).resolve()
|
|
131
|
+
if adapter_root not in resolved.parents or not resolved.is_file():
|
|
132
|
+
raise HostAdapterContractError(f"{label} escapes adapter root: {ref!r}")
|
|
133
|
+
return resolved
|
|
@@ -248,6 +248,15 @@ def _validated_descriptor(adapter: object, manifest: HostManifest) -> HostDescri
|
|
|
248
248
|
raise HostAdapterContractError(
|
|
249
249
|
f"relay contract mismatch for {manifest.host_id!r}"
|
|
250
250
|
)
|
|
251
|
+
declared = (
|
|
252
|
+
Path(descriptor.worker_session_contract).resolve()
|
|
253
|
+
if descriptor.worker_session_contract
|
|
254
|
+
else None
|
|
255
|
+
)
|
|
256
|
+
if declared != manifest.worker_session_contract:
|
|
257
|
+
raise HostAdapterContractError(
|
|
258
|
+
f"worker session contract mismatch for {manifest.host_id!r}"
|
|
259
|
+
)
|
|
251
260
|
return descriptor
|
|
252
261
|
|
|
253
262
|
|
|
@@ -286,6 +295,8 @@ def _copy_descriptor(raw: object, host_id: str) -> HostDescriptor:
|
|
|
286
295
|
session_accounting=raw.session_accounting,
|
|
287
296
|
has_claude_session=raw.has_claude_session,
|
|
288
297
|
relay_contract=raw.relay_contract,
|
|
298
|
+
worker_session_contract=getattr(raw, "worker_session_contract", ""),
|
|
299
|
+
initial_prompt_delivery_mode=raw.initial_prompt_delivery_mode,
|
|
289
300
|
)
|
|
290
301
|
except (AttributeError, TypeError) as exc:
|
|
291
302
|
raise HostAdapterContractError(
|
|
@@ -455,6 +455,10 @@ def _worker_assignments(ctx: dict) -> list[dict]:
|
|
|
455
455
|
# 다른데 반환값만 봐서는 구별되지 않는다.
|
|
456
456
|
print(f"render: WORKER_ASSIGNMENTS_JSON is not valid JSON, "
|
|
457
457
|
f"falling back to the worker catalog ({exc})", file=sys.stderr)
|
|
458
|
+
# 여기 도달하면 이 run 에는 해소된 배정이 없다 — 준비 경로는 언제나
|
|
459
|
+
# `WORKER_ASSIGNMENTS_JSON` 을 채운다(`run.py` `_model_tokens`). 남은 정보가
|
|
460
|
+
# 워커 id 뿐이라 역할을 복원할 수 없고, 아래 값은 분석 run 로스터의 모양을
|
|
461
|
+
# 가정한 것이다. 이 로스터로 implementation 프롬프트를 고를 수는 없다.
|
|
458
462
|
catalog = _worker_catalog(ctx, include_assignments=False)
|
|
459
463
|
return [
|
|
460
464
|
{
|
|
@@ -690,9 +694,13 @@ def _active_runtime_resources(ctx: dict) -> dict:
|
|
|
690
694
|
or str(runtime_home / "templates" / "implementation-worker-preamble.md"),
|
|
691
695
|
"report-writer": ctx.get("REPORT_WRITER_PREAMBLE_PATH")
|
|
692
696
|
or str(runtime_home / "templates" / "report-writer-prompt-preamble.md"),
|
|
697
|
+
"translator": ctx.get("TRANSLATOR_PREAMBLE_PATH")
|
|
698
|
+
or str(runtime_home / "templates" / "translator-prompt-preamble.md"),
|
|
693
699
|
},
|
|
694
700
|
"workerErrorContractPath": ctx.get("WORKER_ERROR_CONTRACT_PATH")
|
|
695
701
|
or str(runtime_home / "templates" / "worker-error-contract.md"),
|
|
702
|
+
"hostSessionContractPath": ctx.get("HOST_SESSION_CONTRACT_PATH")
|
|
703
|
+
or _host_session_contract_path(ctx),
|
|
696
704
|
}
|
|
697
705
|
|
|
698
706
|
|
|
@@ -2019,6 +2027,9 @@ _V2_INVOCATION_IDENTITY_KEYS = {
|
|
|
2019
2027
|
}
|
|
2020
2028
|
_AGENT_CONTRACT_KEYS = {
|
|
2021
2029
|
"schemaVersion",
|
|
2030
|
+
# 이 run 이 동결한 계약의 형식. 없는 run 은 Markdown 직무를 동결했고, 그
|
|
2031
|
+
# 형식의 리더는 제거됐다.
|
|
2032
|
+
"contractFormatVersion",
|
|
2022
2033
|
"dutyRootPath",
|
|
2023
2034
|
"catalogDigest",
|
|
2024
2035
|
"invocationReservationRootPath",
|
|
@@ -2610,9 +2621,15 @@ def inject_lead_prompt_computed_tokens(ctx: dict) -> None:
|
|
|
2610
2621
|
report_writer_preamble = ctx.get("REPORT_WRITER_PREAMBLE_PATH") or str(
|
|
2611
2622
|
runtime_templates / "report-writer-prompt-preamble.md"
|
|
2612
2623
|
)
|
|
2624
|
+
translator_preamble = ctx.get("TRANSLATOR_PREAMBLE_PATH") or str(
|
|
2625
|
+
runtime_templates / "translator-prompt-preamble.md"
|
|
2626
|
+
)
|
|
2613
2627
|
worker_error_contract = ctx.get("WORKER_ERROR_CONTRACT_PATH") or str(
|
|
2614
2628
|
runtime_templates / "worker-error-contract.md"
|
|
2615
2629
|
)
|
|
2630
|
+
host_session_contract = ctx.get(
|
|
2631
|
+
"HOST_SESSION_CONTRACT_PATH"
|
|
2632
|
+
) or _host_session_contract_path(ctx)
|
|
2616
2633
|
okstra_runtime_resources_block = (
|
|
2617
2634
|
"## Okstra Runtime Resources\n"
|
|
2618
2635
|
"\n"
|
|
@@ -2629,8 +2646,14 @@ def inject_lead_prompt_computed_tokens(ctx: dict) -> None:
|
|
|
2629
2646
|
f"- Analysis worker preamble: `{analysis_preamble}`\n"
|
|
2630
2647
|
f"- Implementation worker preamble: `{implementation_preamble}`\n"
|
|
2631
2648
|
f"- Report writer preamble: `{report_writer_preamble}`\n"
|
|
2649
|
+
f"- Translator preamble: `{translator_preamble}`\n"
|
|
2650
|
+
|
|
2632
2651
|
f"- Shared worker error contract: `{worker_error_contract}`"
|
|
2633
2652
|
)
|
|
2653
|
+
if host_session_contract:
|
|
2654
|
+
okstra_runtime_resources_block += (
|
|
2655
|
+
f"\n- Host worker session contract: `{host_session_contract}`"
|
|
2656
|
+
)
|
|
2634
2657
|
|
|
2635
2658
|
def fmt_assignment(role: str, model: str, execution: str) -> str:
|
|
2636
2659
|
if execution and execution != model:
|
|
@@ -2814,6 +2837,23 @@ def inject_lead_prompt_computed_tokens(ctx: dict) -> None:
|
|
|
2814
2837
|
ctx["EXECUTION_STATUS_TABLE_ROWS"] = "\n".join(execution_status_table_lines)
|
|
2815
2838
|
|
|
2816
2839
|
|
|
2840
|
+
def _host_session_contract_path(ctx: Mapping[str, object]) -> str:
|
|
2841
|
+
"""이 run 의 호스트가 선언한 워커 세션 계약의 절대 경로. 없으면 빈 문자열.
|
|
2842
|
+
|
|
2843
|
+
호스트를 못 찾는 것은 여기서 실패할 일이 아니다 — 계약이 없는 호스트와
|
|
2844
|
+
같게 다루고, 헤더를 빼면 그 워커는 세션 규칙 없이 돈다. 호스트 해석 실패
|
|
2845
|
+
자체는 이 함수를 부르기 전에 이미 run 준비를 막는다.
|
|
2846
|
+
"""
|
|
2847
|
+
host_id = str(ctx.get("HOST_RUNTIME", "") or _lead_runtime(ctx) or "").strip()
|
|
2848
|
+
if not host_id:
|
|
2849
|
+
return ""
|
|
2850
|
+
try:
|
|
2851
|
+
descriptor = default_host_registry().resolve(host_id).descriptor
|
|
2852
|
+
except Exception:
|
|
2853
|
+
return ""
|
|
2854
|
+
return descriptor.worker_session_contract or ""
|
|
2855
|
+
|
|
2856
|
+
|
|
2817
2857
|
def apply_lead_prompt_defaults(ctx: dict) -> None:
|
|
2818
2858
|
"""Apply default values for optional lead-prompt ctx fields.
|
|
2819
2859
|
|
|
@@ -2853,10 +2893,20 @@ def apply_lead_prompt_defaults(ctx: dict) -> None:
|
|
|
2853
2893
|
"REPORT_WRITER_PREAMBLE_PATH",
|
|
2854
2894
|
str(runtime_home / "templates" / "report-writer-prompt-preamble.md"),
|
|
2855
2895
|
)
|
|
2896
|
+
ctx.setdefault(
|
|
2897
|
+
"TRANSLATOR_PREAMBLE_PATH",
|
|
2898
|
+
str(runtime_home / "templates" / "translator-prompt-preamble.md"),
|
|
2899
|
+
)
|
|
2856
2900
|
ctx.setdefault(
|
|
2857
2901
|
"WORKER_ERROR_CONTRACT_PATH",
|
|
2858
2902
|
str(runtime_home / "templates" / "worker-error-contract.md"),
|
|
2859
2903
|
)
|
|
2904
|
+
# 호스트가 선언한 워커 세션 계약. 선언하지 않은 호스트는 빈 문자열이고,
|
|
2905
|
+
# 그러면 헤더가 방출되지 않는다.
|
|
2906
|
+
ctx.setdefault(
|
|
2907
|
+
"HOST_SESSION_CONTRACT_PATH",
|
|
2908
|
+
_host_session_contract_path(ctx),
|
|
2909
|
+
)
|
|
2860
2910
|
# Lead resource paths the launch template references directly. paths.py
|
|
2861
2911
|
# seeds the production values; setdefault backfills callers that render the
|
|
2862
2912
|
# template without the full path ctx.
|
|
@@ -123,7 +123,7 @@ TASK_TYPE_REQUIRED_HUMAN_FIELDS = {
|
|
|
123
123
|
"release-handoff": (
|
|
124
124
|
"releaseHandoff.userSelections",
|
|
125
125
|
"releaseHandoff.featureBranchState",
|
|
126
|
-
"releaseHandoff.
|
|
126
|
+
"releaseHandoff.pullRequestOutcomes",
|
|
127
127
|
"releaseHandoff.mergeConflictProbe",
|
|
128
128
|
),
|
|
129
129
|
}
|
|
@@ -737,6 +737,8 @@ def _closeout_report_paths(ctx: FinalizeContext) -> dict[str, Any]:
|
|
|
737
737
|
답장을 마크다운으로 그리므로 괄호 안에 경로가 들어간 형태만 클릭되고,
|
|
738
738
|
백틱으로 감싼 경로는 글자로만 남아 사용자가 직접 옮겨 적어야 한다. 리드가
|
|
739
739
|
그 형태를 매번 다시 만들면 어긋날 자리가 생기므로 완성된 문자열을 준다.
|
|
740
|
+
링크 목적지는 절대 경로다 — 프로젝트 기준 상대 경로는 호스트 cwd 가 다르거나
|
|
741
|
+
외부 프로그램으로 열 때 해석되지 않는다. 공백이 든 경로는 `<...>` 로 감싼다.
|
|
740
742
|
"""
|
|
741
743
|
def rel(path: Path) -> str:
|
|
742
744
|
try:
|
|
@@ -744,17 +746,22 @@ def _closeout_report_paths(ctx: FinalizeContext) -> dict[str, Any]:
|
|
|
744
746
|
except ValueError:
|
|
745
747
|
return str(path)
|
|
746
748
|
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
"
|
|
750
|
-
|
|
749
|
+
def link_target(path: Path) -> str:
|
|
750
|
+
target = path.resolve().as_posix()
|
|
751
|
+
return f"<{target}>" if " " in target else target
|
|
752
|
+
|
|
753
|
+
sources = {
|
|
754
|
+
"humanReport": html_view_path(ctx.data_path),
|
|
755
|
+
"reportRecord": ctx.data_path,
|
|
756
|
+
"teamState": ctx.team_state_path,
|
|
751
757
|
}
|
|
758
|
+
paths = {key: rel(path) for key, path in sources.items()}
|
|
752
759
|
return {
|
|
753
760
|
**paths,
|
|
754
761
|
"renderFullCopy": f"okstra render-final-report {paths['reportRecord']}",
|
|
755
762
|
"markdown": {
|
|
756
|
-
key: f"[{
|
|
757
|
-
for key,
|
|
763
|
+
key: f"[{path.name}]({link_target(path)})"
|
|
764
|
+
for key, path in sources.items()
|
|
758
765
|
},
|
|
759
766
|
}
|
|
760
767
|
|
|
@@ -3,8 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
|
|
4
4
|
from ...release_gate import release_handoff_allowed
|
|
5
5
|
from ..common import evidence_index
|
|
6
|
-
from ..models import HumanReportView
|
|
7
|
-
from ..visualizations import coverage_figure
|
|
6
|
+
from ..models import HumanReportView
|
|
8
7
|
|
|
9
8
|
# Record fields this template anchors as `id-<row id>` (see
|
|
10
9
|
# `HumanReportView.anchored_fields`); ids elsewhere land in the ledger.
|
|
@@ -17,25 +16,8 @@ ANCHORED_FIELDS = (
|
|
|
17
16
|
)
|
|
18
17
|
|
|
19
18
|
|
|
20
|
-
def _coverage_nodes(final: dict) -> tuple[VisualNode, ...]:
|
|
21
|
-
return tuple(
|
|
22
|
-
VisualNode(
|
|
23
|
-
row["id"],
|
|
24
|
-
row["requirement"],
|
|
25
|
-
"requirement",
|
|
26
|
-
row["status"],
|
|
27
|
-
row["artifact"],
|
|
28
|
-
note=row["status"],
|
|
29
|
-
)
|
|
30
|
-
for row in final["validationEvidence"]
|
|
31
|
-
)
|
|
32
|
-
|
|
33
|
-
|
|
34
19
|
def build_final_verification_view(data: dict) -> HumanReportView:
|
|
35
20
|
final = data["finalVerification"]
|
|
36
|
-
figure = coverage_figure(
|
|
37
|
-
rows=_coverage_nodes(final), title="Requirement verification coverage"
|
|
38
|
-
)
|
|
39
21
|
verdict_token = data["finalVerdict"]["verdictToken"]
|
|
40
22
|
context = {
|
|
41
23
|
"humanSummary": data["humanSummary"],
|
|
@@ -44,7 +26,6 @@ def build_final_verification_view(data: dict) -> HumanReportView:
|
|
|
44
26
|
"finalVerdict": data["finalVerdict"],
|
|
45
27
|
"final": final,
|
|
46
28
|
"narrative": final["userNarrative"],
|
|
47
|
-
"coverageFigure": figure,
|
|
48
29
|
"releaseAllowed": release_handoff_allowed(data),
|
|
49
30
|
"evidenceIndex": evidence_index(data, ANCHORED_FIELDS),
|
|
50
31
|
}
|
|
@@ -52,6 +33,6 @@ def build_final_verification_view(data: dict) -> HumanReportView:
|
|
|
52
33
|
"final-verification",
|
|
53
34
|
"html/tasks/final-verification.template.html",
|
|
54
35
|
context,
|
|
55
|
-
(
|
|
36
|
+
(),
|
|
56
37
|
anchored_fields=ANCHORED_FIELDS,
|
|
57
38
|
)
|
|
@@ -18,16 +18,31 @@ def _safe_http_url(value: str) -> str:
|
|
|
18
18
|
return value if parsed.scheme in {"http", "https"} and parsed.netloc else ""
|
|
19
19
|
|
|
20
20
|
|
|
21
|
+
def _pr_summary(outcomes: list) -> str:
|
|
22
|
+
"""PR 은 stage 당 하나라 결과도 여러 개다. 타임라인 한 칸에는 종류별 건수로
|
|
23
|
+
접어 넣는다 — 대표 하나를 고르면 나머지 stage 의 결과가 사라진다."""
|
|
24
|
+
if not outcomes:
|
|
25
|
+
return "no-action"
|
|
26
|
+
counts: dict[str, int] = {}
|
|
27
|
+
for row in outcomes:
|
|
28
|
+
kind = row.get("kind", "")
|
|
29
|
+
counts[kind] = counts.get(kind, 0) + 1
|
|
30
|
+
return ", ".join(f"{kind} {n}" for kind, n in sorted(counts.items()))
|
|
31
|
+
|
|
32
|
+
|
|
21
33
|
def _timeline_nodes(handoff: dict) -> tuple[VisualNode, ...]:
|
|
22
34
|
selection = handoff["userSelections"]["h1"]
|
|
23
35
|
branch = handoff["featureBranchState"]["branchName"]
|
|
24
36
|
conflict = handoff["mergeConflictProbe"]["kind"]
|
|
25
|
-
|
|
37
|
+
outcomes = handoff["pullRequestOutcomes"]
|
|
38
|
+
kinds = {row.get("kind", "") for row in outcomes}
|
|
39
|
+
status = kinds.pop() if len(kinds) == 1 else "mixed"
|
|
26
40
|
return (
|
|
27
41
|
VisualNode("selection", selection, "handoff", "completed", "User-selected handoff"),
|
|
28
42
|
VisualNode("branch", branch, "handoff", "completed", "Feature branch state"),
|
|
29
43
|
VisualNode("conflict", conflict, "handoff", conflict, "Merge conflict probe"),
|
|
30
|
-
VisualNode("pull-request",
|
|
44
|
+
VisualNode("pull-request", _pr_summary(outcomes), "handoff", status,
|
|
45
|
+
"Pull request outcome, one per stage"),
|
|
31
46
|
)
|
|
32
47
|
|
|
33
48
|
|
|
@@ -41,7 +56,10 @@ def build_release_handoff_view(data: dict) -> HumanReportView:
|
|
|
41
56
|
"handoff": handoff,
|
|
42
57
|
"narrative": handoff["userNarrative"],
|
|
43
58
|
"handoffFigure": figure,
|
|
44
|
-
"
|
|
59
|
+
"prOutcomes": [
|
|
60
|
+
{**row, "url": _safe_http_url(row.get("url", ""))}
|
|
61
|
+
for row in handoff["pullRequestOutcomes"]
|
|
62
|
+
],
|
|
45
63
|
"evidenceIndex": evidence_index(data, ANCHORED_FIELDS),
|
|
46
64
|
}
|
|
47
65
|
return HumanReportView(
|
|
@@ -356,11 +356,6 @@ def matrix_figure(*, items: Iterable[VisualNode], title: str, x_label: str, y_la
|
|
|
356
356
|
return replace(dependency_figure(nodes=nodes, edges=(), title=title), kind="matrix", figure_id="matrix", summary=summary)
|
|
357
357
|
|
|
358
358
|
|
|
359
|
-
def coverage_figure(*, rows: Iterable[VisualNode], title: str) -> FigureModel:
|
|
360
|
-
nodes = tuple(rows)
|
|
361
|
-
return replace(dependency_figure(nodes=nodes, edges=(), title=title), kind="coverage", figure_id="coverage")
|
|
362
|
-
|
|
363
|
-
|
|
364
359
|
def timeline_figure(*, events: Iterable[VisualNode], title: str) -> FigureModel:
|
|
365
360
|
nodes = tuple(events)
|
|
366
361
|
edges = tuple(VisualEdge(nodes[i].id, nodes[i + 1].id, "next", "sequence") for i in range(len(nodes) - 1))
|