okstra 0.220.1 → 0.222.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/dist/cli-registry.mjs +6 -0
- package/dist/cli-registry.mjs.map +1 -1
- package/docs/architecture/storage-model.md +1 -1
- package/docs/architecture.md +6 -4
- package/docs/cli.md +13 -9
- package/docs/project-structure-overview.md +5 -1
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/prompts/launch.template.md +1 -1
- package/runtime/prompts/lead/convergence.md +3 -1
- package/runtime/prompts/lead/okstra-lead-contract.md +10 -6
- package/runtime/prompts/lead/phase-routing.md +1 -1
- package/runtime/prompts/lead/team-contract.md +1 -1
- package/runtime/python/okstra_ctl/adapters/hosts/capability_adapter.py +3 -2
- package/runtime/python/okstra_ctl/adapters/hosts/claude-code/relay.md +3 -3
- package/runtime/python/okstra_ctl/adapters/hosts/claude-code/worker-session.md +4 -2
- package/runtime/python/okstra_ctl/adapters/hosts/grok/relay.md +3 -3
- package/runtime/python/okstra_ctl/adapters/providers/antigravity/adapter.py +27 -0
- package/runtime/python/okstra_ctl/adapters/providers/claude/adapter.py +24 -0
- package/runtime/python/okstra_ctl/adapters/providers/codex/adapter.py +24 -0
- package/runtime/python/okstra_ctl/adapters/providers/grok/adapter.py +25 -0
- package/runtime/python/okstra_ctl/adapters/providers/zai/adapter.py +20 -1
- package/runtime/python/okstra_ctl/agent/prompt_cli/materialize.py +3 -0
- package/runtime/python/okstra_ctl/analysis_packet.py +14 -0
- package/runtime/python/okstra_ctl/business_flow/engine.py +1 -0
- package/runtime/python/okstra_ctl/convergence.py +155 -19
- package/runtime/python/okstra_ctl/convergence_critic_prompt.py +3 -2
- package/runtime/python/okstra_ctl/convergence_migration.py +10 -1
- package/runtime/python/okstra_ctl/convergence_provenance.py +69 -24
- package/runtime/python/okstra_ctl/convergence_reverify_prompt.py +4 -1
- package/runtime/python/okstra_ctl/correction_cli.py +50 -11
- package/runtime/python/okstra_ctl/correction_round.py +80 -11
- package/runtime/python/okstra_ctl/domain/mcp.py +43 -0
- package/runtime/python/okstra_ctl/domain/provider.py +2 -0
- package/runtime/python/okstra_ctl/domain/wizard/interaction.py +89 -8
- package/runtime/python/okstra_ctl/file_size_cap.py +142 -0
- package/runtime/python/okstra_ctl/fix_cycles.py +14 -1
- package/runtime/python/okstra_ctl/git_reconcile.py +62 -8
- package/runtime/python/okstra_ctl/initial_prompt_materialization.py +3 -6
- package/runtime/python/okstra_ctl/manager_cli.py +11 -1
- package/runtime/python/okstra_ctl/manager_sync.py +38 -2
- package/runtime/python/okstra_ctl/mcp_cli.py +179 -0
- package/runtime/python/okstra_ctl/mcp_servers.py +421 -0
- package/runtime/python/okstra_ctl/next_phase.py +22 -9
- package/runtime/python/okstra_ctl/phases/final_verification/target.py +4 -2
- package/runtime/python/okstra_ctl/phases/implementation/entry.py +6 -2
- package/runtime/python/okstra_ctl/phases/implementation/host-rules.md +1 -0
- package/runtime/python/okstra_ctl/phases/implementation/instructions/_implementation-deliverable.md +5 -4
- package/runtime/python/okstra_ctl/phases/implementation/instructions/_implementation-executor.md +1 -0
- package/runtime/python/okstra_ctl/phases/implementation/instructions/_implementation-self-check.md +1 -0
- package/runtime/python/okstra_ctl/phases/implementation/instructions/_implementation-verifier.md +3 -2
- package/runtime/python/okstra_ctl/phases/implementation_option_selection/profile.md +1 -1
- package/runtime/python/okstra_ctl/phases/implementation_planning/authoring.py +19 -0
- package/runtime/python/okstra_ctl/phases/implementation_planning/instructions/plan-body-verification.md +14 -0
- package/runtime/python/okstra_ctl/phases/implementation_planning/profile.md +2 -1
- package/runtime/python/okstra_ctl/phases/technical_verification/entry.py +37 -0
- package/runtime/python/okstra_ctl/phases/technical_verification/validation.py +3 -3
- package/runtime/python/okstra_ctl/process_group.py +4 -0
- package/runtime/python/okstra_ctl/render.py +7 -48
- package/runtime/python/okstra_ctl/report_assembly.py +50 -13
- package/runtime/python/okstra_ctl/report_html/common.py +3 -1
- package/runtime/python/okstra_ctl/report_language.py +4 -5
- package/runtime/python/okstra_ctl/report_projections.py +45 -4
- package/runtime/python/okstra_ctl/report_synthesis_packet.py +22 -1
- package/runtime/python/okstra_ctl/run.py +36 -5
- package/runtime/python/okstra_ctl/stage_done.py +37 -3
- package/runtime/python/okstra_ctl/stage_reconcile.py +49 -1
- package/runtime/python/okstra_ctl/wizard/steps_plan.py +3 -3
- package/runtime/schemas/final-report-v3.0.schema.json +36 -0
- package/runtime/skills/_fragments/wizard-lead-takeover.md +1 -1
- package/runtime/skills/_fragments/wizard-relay-protocol.md +2 -2
- package/runtime/skills/okstra-discussion/SKILL.md +3 -3
- package/runtime/skills/okstra-manager/SKILL.md +1 -0
- package/runtime/skills/okstra-run/SKILL.md +4 -3
- package/runtime/skills/okstra-setup/SKILL.md +2 -0
- package/runtime/skills/okstra-setup/references/project-config.md +34 -0
- package/runtime/templates/report-writer-prompt-preamble.md +1 -1
- package/runtime/templates/reports/html/base.template.html +6 -1
- package/runtime/templates/reports/html/i18n/en.json +1 -0
- package/runtime/templates/reports/html/i18n/ko.json +1 -0
- package/runtime/templates/reports/task-brief.template.md +3 -3
- package/runtime/validators/validate-run.py +18 -2
- package/runtime/validators/validate_session_conformance.py +19 -1
|
@@ -14,6 +14,7 @@ from typing import Any, Mapping
|
|
|
14
14
|
from okstra_token_usage.blocks import accounting_workers
|
|
15
15
|
|
|
16
16
|
from .analysis_packet import reference_source_extracts
|
|
17
|
+
from .correction_round import correction_convergence_states
|
|
17
18
|
from .json_boundary import JsonBoundaryError, load_owned_object, write_owned_object_atomic
|
|
18
19
|
from .phases.implementation_option_selection.authoring import (
|
|
19
20
|
requirement_instructions as selection_requirement_instructions,
|
|
@@ -251,6 +252,17 @@ class ReportSynthesisPacket:
|
|
|
251
252
|
]
|
|
252
253
|
if self.task_type == "implementation-planning":
|
|
253
254
|
return self._planning_requirement_instructions()
|
|
255
|
+
if self.task_type == "implementation" and any(
|
|
256
|
+
source.label.startswith(f"{CONVERGENCE_STATE_LABEL} (correction-r")
|
|
257
|
+
for source in self.sources
|
|
258
|
+
):
|
|
259
|
+
return [
|
|
260
|
+
"Each `Convergence state (correction-r<N>)` source is correction round N's "
|
|
261
|
+
"re-convergence of the verifiers' re-verification; the plain `Convergence "
|
|
262
|
+
"state` is the initial round's. Describe the stage's open findings from the "
|
|
263
|
+
"last round's re-convergence, not the initial one; report what each round "
|
|
264
|
+
"fixed through `implementation.correctionRounds`.",
|
|
265
|
+
]
|
|
254
266
|
if self.task_type != "implementation-option-selection":
|
|
255
267
|
return []
|
|
256
268
|
return selection_requirement_instructions(
|
|
@@ -371,6 +383,7 @@ _SOURCE_FIELDS = (
|
|
|
371
383
|
("Final report schema", "orchestrator", "instructionSet", "finalReportSchemaPath", True),
|
|
372
384
|
("Convergence state", "convergence", "run", "convergenceStatePath", True),
|
|
373
385
|
)
|
|
386
|
+
CONVERGENCE_STATE_LABEL = "Convergence state"
|
|
374
387
|
|
|
375
388
|
|
|
376
389
|
_READ_CHUNK_BYTES = 16_000
|
|
@@ -514,7 +527,7 @@ def _source_markdown(
|
|
|
514
527
|
brief = next((item.content.strip() for item in sources if item.label == "Task brief"), "")
|
|
515
528
|
if len(brief.encode("utf-8")) >= _SHARED_TEXT_MIN_BYTES:
|
|
516
529
|
content = content.replace(brief, 'Read the complete "Source: Task brief" above.')
|
|
517
|
-
if source.label
|
|
530
|
+
if source.label.startswith(CONVERGENCE_STATE_LABEL):
|
|
518
531
|
content = _convergence_view(content)
|
|
519
532
|
if content != source.content.rstrip("\n"):
|
|
520
533
|
view = [
|
|
@@ -815,6 +828,14 @@ def _source_specs(
|
|
|
815
828
|
path = str(Path(instruction_set) / "final-report-schema.json")
|
|
816
829
|
if path or required:
|
|
817
830
|
specs.append((label, owner, path, required))
|
|
831
|
+
if label == CONVERGENCE_STATE_LABEL and path:
|
|
832
|
+
specs.extend(
|
|
833
|
+
(f"{CONVERGENCE_STATE_LABEL} ({scope})", owner,
|
|
834
|
+
_relative(project_root, round_final), True)
|
|
835
|
+
for scope, round_final in correction_convergence_states(
|
|
836
|
+
_resolve(project_root, path)
|
|
837
|
+
)
|
|
838
|
+
)
|
|
818
839
|
if carried_source:
|
|
819
840
|
specs.append((CARRIED_STAGES_LABEL, "orchestrator", carried_source, True))
|
|
820
841
|
specs.extend(_user_response_specs(project_root, narrative_path))
|
|
@@ -23,6 +23,7 @@ from okstra_ctl.phases.implementation_planning.validation import validate_select
|
|
|
23
23
|
from .phases.technical_verification.entry import (
|
|
24
24
|
prepare_technical_verification_input,
|
|
25
25
|
materialize_technical_input_packet,
|
|
26
|
+
technical_evidence_packet_block,
|
|
26
27
|
)
|
|
27
28
|
|
|
28
29
|
from argparse import (
|
|
@@ -55,6 +56,7 @@ from .phases.implementation_planning.entry import (
|
|
|
55
56
|
)
|
|
56
57
|
from . import fix_cycles
|
|
57
58
|
from .analysis_packet import build_analysis_packet
|
|
59
|
+
from .mcp_servers import NO_MCP_SERVERS_LINE, prepare_mcp
|
|
58
60
|
from .coverage_census import (
|
|
59
61
|
CensusError,
|
|
60
62
|
census_filename,
|
|
@@ -3051,6 +3053,16 @@ def _write_instruction_set_sources(
|
|
|
3051
3053
|
),
|
|
3052
3054
|
coverage_census_text=coverage_census_text,
|
|
3053
3055
|
has_selected_direction=(instruction_set / "selected-direction.json").is_file(),
|
|
3056
|
+
mcp_servers_text=(
|
|
3057
|
+
"" if ctx.get("AVAILABLE_MCP_SERVERS", NO_MCP_SERVERS_LINE) == NO_MCP_SERVERS_LINE
|
|
3058
|
+
else ctx["AVAILABLE_MCP_SERVERS"]
|
|
3059
|
+
),
|
|
3060
|
+
technical_evidence_text=(
|
|
3061
|
+
technical_evidence_packet_block(
|
|
3062
|
+
Path(inp.clarification_response_path), Path(ctx["PROJECT_ROOT"]),
|
|
3063
|
+
)
|
|
3064
|
+
if inp.clarification_response_path else ""
|
|
3065
|
+
),
|
|
3054
3066
|
)
|
|
3055
3067
|
if inp.task_type == "technical-verification":
|
|
3056
3068
|
packet += materialize_technical_input_packet(
|
|
@@ -3358,10 +3370,11 @@ def _maybe_open_fix_cycle(inp: PrepareInputs, task_root: Path, existing: dict,
|
|
|
3358
3370
|
f"{', '.join(fix_cycles.FIX_CYCLE_ENTRY_PHASES)})에서만 허용됩니다: "
|
|
3359
3371
|
f"{inp.task_type}")
|
|
3360
3372
|
workflow = existing.get("workflow") or {}
|
|
3361
|
-
if
|
|
3373
|
+
if not fix_cycles.delivery_completed(workflow):
|
|
3362
3374
|
raise PrepareError(
|
|
3363
|
-
"--fix-cycle yes 는 release-handoff
|
|
3364
|
-
"(workflow.lastCompletedPhase
|
|
3375
|
+
"--fix-cycle yes 는 release-handoff 로 전달까지 끝난 task 에만 허용됩니다 "
|
|
3376
|
+
"(workflow.lastCompletedPhase 가 release-handoff 이고, skip·미전달 stage 로 "
|
|
3377
|
+
"다음 phase 가 release-handoff 로 남아 있지 않아야 함)")
|
|
3365
3378
|
brief_text = Path(inp.brief_path).read_text(encoding="utf-8")
|
|
3366
3379
|
fix_cycles.append_opened(
|
|
3367
3380
|
task_root, target_report=str(existing.get("latestReportRecordPath", "")),
|
|
@@ -3373,7 +3386,8 @@ def _record_fix_cycle_events(inp: PrepareInputs, ctx: dict) -> str:
|
|
|
3373
3386
|
"""fix-cycles.jsonl 의 lazy-close → opened → run 기록. cycle id 반환.
|
|
3374
3387
|
|
|
3375
3388
|
- lazy-close: open cycle 에 release-handoff run 이 부착됐고 디스크 manifest 의
|
|
3376
|
-
lastCompletedPhase 가 release-handoff 면 닫는다.
|
|
3389
|
+
lastCompletedPhase 가 release-handoff 면 닫는다. 다음 phase 포인터가 다시
|
|
3390
|
+
release-handoff 를 가리키면(skip·미전달 stage) 닫지 않는다. manifest 는 prepare 가
|
|
3377
3391
|
이번 run 으로 재작성하기 *전* 값이어야 하므로 finalize 직전에 호출한다.
|
|
3378
3392
|
- opened: --fix-cycle yes 일 때만. 완료 task + entry phase 미충족이면
|
|
3379
3393
|
PrepareError.
|
|
@@ -3386,7 +3400,7 @@ def _record_fix_cycle_events(inp: PrepareInputs, ctx: dict) -> str:
|
|
|
3386
3400
|
|
|
3387
3401
|
open_c = fix_cycles.open_cycle(fix_cycles.read_rows(task_root))
|
|
3388
3402
|
|
|
3389
|
-
if open_c and
|
|
3403
|
+
if open_c and fix_cycles.delivery_completed(workflow):
|
|
3390
3404
|
rows = fix_cycles.read_rows(task_root)
|
|
3391
3405
|
handoff_attached = any(
|
|
3392
3406
|
r.get("event") == "run" and r.get("cycle") == open_c["cycle"]
|
|
@@ -4055,6 +4069,20 @@ def _prepare_user_authorization(inp: PrepareInputs) -> dict:
|
|
|
4055
4069
|
return record
|
|
4056
4070
|
|
|
4057
4071
|
|
|
4072
|
+
def _prepare_mcp_block(
|
|
4073
|
+
project_root: Path, plan: AssignmentPlan, brief_path: Path,
|
|
4074
|
+
) -> str:
|
|
4075
|
+
brief_text = brief_path.read_text(encoding="utf-8") if brief_path.is_file() else ""
|
|
4076
|
+
prepared = prepare_mcp(
|
|
4077
|
+
project_root,
|
|
4078
|
+
(assignment.provider_id for assignment in plan.assignments),
|
|
4079
|
+
brief_text,
|
|
4080
|
+
)
|
|
4081
|
+
for warning in prepared.warnings:
|
|
4082
|
+
print(f"prepare: {warning}", file=sys.stderr)
|
|
4083
|
+
return prepared.block
|
|
4084
|
+
|
|
4085
|
+
|
|
4058
4086
|
def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
|
|
4059
4087
|
"""Produce a complete okstra task bundle on disk. See module docstring."""
|
|
4060
4088
|
user_authorization = _prepare_user_authorization(inp)
|
|
@@ -4326,6 +4354,9 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
|
|
|
4326
4354
|
"DYNAMIC_EXECUTION_ROLES_JSON": json.dumps(
|
|
4327
4355
|
dynamic_roles, ensure_ascii=False,
|
|
4328
4356
|
),
|
|
4357
|
+
"AVAILABLE_MCP_SERVERS": _prepare_mcp_block(
|
|
4358
|
+
project_root, assignment_plan, inp.brief_path,
|
|
4359
|
+
),
|
|
4329
4360
|
})
|
|
4330
4361
|
refresh_run_context_snapshot(ctx)
|
|
4331
4362
|
if lead_runtime == "codex":
|
|
@@ -12,6 +12,7 @@ from __future__ import annotations
|
|
|
12
12
|
import argparse
|
|
13
13
|
import json
|
|
14
14
|
import re
|
|
15
|
+
import subprocess
|
|
15
16
|
from datetime import datetime, timezone
|
|
16
17
|
from pathlib import Path
|
|
17
18
|
|
|
@@ -23,6 +24,7 @@ from .consumers import (
|
|
|
23
24
|
carry_head_commit,
|
|
24
25
|
read_consumers,
|
|
25
26
|
)
|
|
27
|
+
from .file_size_cap import file_size_violations
|
|
26
28
|
from .json_boundary import JsonBoundaryError, load_owned_object, write_owned_object_atomic
|
|
27
29
|
from .paths import RunRef
|
|
28
30
|
from .stage_reopen import resolve_planned_stage
|
|
@@ -49,6 +51,34 @@ def _read_carry(carry_file: Path, stage: int) -> dict:
|
|
|
49
51
|
return carry
|
|
50
52
|
|
|
51
53
|
|
|
54
|
+
def _check_file_size(project_root: Path, carry: dict, head: str) -> str:
|
|
55
|
+
"""Refuse to settle a stage whose diff breaks the file-size limit; return what was checked.
|
|
56
|
+
|
|
57
|
+
Verifiers report the limit from `okstra correction file-size`; this re-runs it so a stage
|
|
58
|
+
a verifier did not check still reaches the correction round instead of landing. A range
|
|
59
|
+
git cannot read is reported in the output, not refused: the limit is a code-quality rule
|
|
60
|
+
and must not strand a stage whose verdict already passed.
|
|
61
|
+
"""
|
|
62
|
+
rng = carry.get("stageCommitRange")
|
|
63
|
+
base = rng.get("base") if isinstance(rng, dict) else None
|
|
64
|
+
if not base:
|
|
65
|
+
return "not checked: the carry has no stageCommitRange.base"
|
|
66
|
+
try:
|
|
67
|
+
violations = [
|
|
68
|
+
v for v in file_size_violations(project_root, str(base), head) if v.blocking
|
|
69
|
+
]
|
|
70
|
+
except subprocess.CalledProcessError as exc:
|
|
71
|
+
return f"not checked: git: {exc.stderr.decode(errors='replace').strip()}"
|
|
72
|
+
if violations:
|
|
73
|
+
raise StateError(
|
|
74
|
+
"the stage diff breaks the 400-line file-size limit:\n"
|
|
75
|
+
+ "\n".join(v.line() for v in violations)
|
|
76
|
+
+ f"\nRun a correction round (`okstra correction prompt ... --base {head} "
|
|
77
|
+
f"--stage-base {base}`) so the executor splits these files, or end the run FAIL "
|
|
78
|
+
"with `okstra stage-fail` when no round is left.", stage="file-size")
|
|
79
|
+
return f"pass: {base}..{head}"
|
|
80
|
+
|
|
81
|
+
|
|
52
82
|
def _supersede(target: Path, rows: list[dict], stage: int) -> Path:
|
|
53
83
|
"""Move the sidecar of the run that first settled a reopened stage aside, unchanged."""
|
|
54
84
|
settled = [r for r in rows if r.get("stage") == stage and r.get("status") == "done"]
|
|
@@ -76,6 +106,7 @@ def done_stage(
|
|
|
76
106
|
identity, plan_run_root = resolve_planned_stage(project_root, task_key, stage)
|
|
77
107
|
carry = _read_carry(carry_file, stage)
|
|
78
108
|
head = carry_head_commit(carry)
|
|
109
|
+
file_size_cap = _check_file_size(project_root, carry, head)
|
|
79
110
|
report = report if report.is_absolute() else project_root / report
|
|
80
111
|
try:
|
|
81
112
|
report_rel = report.relative_to(RunRef.from_report_path(report).run_dir).as_posix()
|
|
@@ -118,6 +149,7 @@ def done_stage(
|
|
|
118
149
|
"supersededCarryPath": str(superseded) if superseded else "",
|
|
119
150
|
"reportPath": report_rel,
|
|
120
151
|
"consumersPath": str(plan_run_root / "consumers.jsonl"),
|
|
152
|
+
"fileSizeCap": file_size_cap,
|
|
121
153
|
}
|
|
122
154
|
|
|
123
155
|
|
|
@@ -131,11 +163,13 @@ canonical sidecar `runs/implementation/carry/stage-<N>.json`, then appends the
|
|
|
131
163
|
`done` row with that carry path, the report path relative to its run directory,
|
|
132
164
|
the carry's head and the completion time. A reopened stage's previous sidecar
|
|
133
165
|
is moved to `carry/superseded/stage-<N>-from-<task-type>-<seq>.json` first.
|
|
134
|
-
Refuses a carry for another stage, a failed carry,
|
|
135
|
-
another head
|
|
166
|
+
Refuses a carry for another stage, a failed carry, a stage already done at
|
|
167
|
+
another head, and a carry whose stageCommitRange diff leaves a source or test
|
|
168
|
+
file over the 400-line limit (`okstra correction file-size`). Re-running with the
|
|
169
|
+
same carry writes nothing new.
|
|
136
170
|
|
|
137
171
|
Output: JSON { ok, taskKey, stage, headCommit, carryPath, supersededCarryPath,
|
|
138
|
-
reportPath, consumersPath }. Exit 1 on a refusal, 2 when
|
|
172
|
+
reportPath, consumersPath, fileSizeCap }. Exit 1 on a refusal, 2 when
|
|
139
173
|
PROJECT_ROOT cannot be resolved.
|
|
140
174
|
"""
|
|
141
175
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Git reconciliation shared by stage prepare flows."""
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
4
|
import sys
|
|
@@ -31,3 +31,51 @@ def auto_reconcile_best_effort(inp: Any, plan_run_root: Path) -> None:
|
|
|
31
31
|
)
|
|
32
32
|
except Exception as exc:
|
|
33
33
|
print(f"git-reconcile skipped: {exc}", file=sys.stderr)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class StaleStageRecordsError(RuntimeError):
|
|
37
|
+
"""A recorded stage SHA no longer matches git and only the user can say which commit counts."""
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def require_reconciled_stages(
|
|
41
|
+
inp: Any, plan_run_root: Path, *, stage: int | None = None,
|
|
42
|
+
) -> None:
|
|
43
|
+
"""Apply auto items, then refuse while a confirm item remains.
|
|
44
|
+
|
|
45
|
+
A prepare that restores, rebases or merges stage branches calls this first:
|
|
46
|
+
building on a recorded commit the branch no longer carries integrates work
|
|
47
|
+
nobody verified. ``stage`` narrows the check to that stage's done row.
|
|
48
|
+
"""
|
|
49
|
+
from .git_reconcile import _git, classify_task, guidance
|
|
50
|
+
|
|
51
|
+
auto_reconcile_best_effort(inp, plan_run_root)
|
|
52
|
+
# A project prepared without git (worktree status skipped-not-git) has no
|
|
53
|
+
# history the recorded SHAs could disagree with.
|
|
54
|
+
if _git(Path(inp.project_root), "rev-parse", "--git-dir").returncode != 0:
|
|
55
|
+
return
|
|
56
|
+
coordinates = dict(
|
|
57
|
+
project_id=inp.project_id,
|
|
58
|
+
task_group=inp.task_group,
|
|
59
|
+
task_id=inp.task_id,
|
|
60
|
+
work_category=inp.work_category,
|
|
61
|
+
)
|
|
62
|
+
pending = [
|
|
63
|
+
item
|
|
64
|
+
for item in classify_task(
|
|
65
|
+
project_root=Path(inp.project_root),
|
|
66
|
+
plan_run_root=plan_run_root,
|
|
67
|
+
**coordinates,
|
|
68
|
+
)
|
|
69
|
+
if item.classification == "confirm"
|
|
70
|
+
and (stage is None or item.stage == stage)
|
|
71
|
+
]
|
|
72
|
+
if not pending:
|
|
73
|
+
return
|
|
74
|
+
lines = "\n".join(
|
|
75
|
+
f" {f'stage {item.stage}' if item.kind == 'done' else 'anchor'}: "
|
|
76
|
+
f"{item.reason} (recorded {item.recorded})"
|
|
77
|
+
for item in pending
|
|
78
|
+
)
|
|
79
|
+
raise StaleStageRecordsError(
|
|
80
|
+
f"{lines}\n{guidance(plan_run_root=plan_run_root, **coordinates)}"
|
|
81
|
+
)
|
|
@@ -215,14 +215,14 @@ def _stage_plan_for_confirmation(
|
|
|
215
215
|
|
|
216
216
|
|
|
217
217
|
def _fix_cycle_confirm_required(state: WizardState) -> bool:
|
|
218
|
-
"""
|
|
219
|
-
cycle 이 없을 때만 묻는다."""
|
|
218
|
+
"""전달까지 끝난(release-handoff) task 에 entry phase 로 재진입하고, 아직
|
|
219
|
+
열린 fix cycle 이 없을 때만 묻는다."""
|
|
220
220
|
if state.task_type not in fix_cycles.FIX_CYCLE_ENTRY_PHASES:
|
|
221
221
|
return False
|
|
222
222
|
task_root = task_dir(Path(state.project_root),
|
|
223
223
|
state.task_group, state.task_id)
|
|
224
224
|
workflow = (read_task_manifest(task_root) or {}).get("workflow") or {}
|
|
225
|
-
if
|
|
225
|
+
if not fix_cycles.delivery_completed(workflow):
|
|
226
226
|
return False
|
|
227
227
|
return fix_cycles.open_cycle(fix_cycles.read_rows(task_root)) is None
|
|
228
228
|
|
|
@@ -480,6 +480,42 @@
|
|
|
480
480
|
}
|
|
481
481
|
]
|
|
482
482
|
},
|
|
483
|
+
"correctionRounds": {
|
|
484
|
+
"type": "array",
|
|
485
|
+
"maxItems": 2,
|
|
486
|
+
"description": "Each implementation correction round's re-convergence of the verifiers' re-verification, projected from `convergence-correction-r<N>-<task-type>-<seq>.json`. Row ids continue the initial round's numbering so every row keeps its own anchor.",
|
|
487
|
+
"items": {
|
|
488
|
+
"type": "object",
|
|
489
|
+
"required": [
|
|
490
|
+
"round",
|
|
491
|
+
"consensus",
|
|
492
|
+
"differences"
|
|
493
|
+
],
|
|
494
|
+
"additionalProperties": false,
|
|
495
|
+
"properties": {
|
|
496
|
+
"round": {
|
|
497
|
+
"type": "integer",
|
|
498
|
+
"minimum": 1,
|
|
499
|
+
"maximum": 2
|
|
500
|
+
},
|
|
501
|
+
"roundHistory": {
|
|
502
|
+
"$ref": "#/properties/crossVerification/properties/roundHistory"
|
|
503
|
+
},
|
|
504
|
+
"consensus": {
|
|
505
|
+
"type": "array",
|
|
506
|
+
"items": {
|
|
507
|
+
"$ref": "#/$defs/ConsensusRow"
|
|
508
|
+
}
|
|
509
|
+
},
|
|
510
|
+
"differences": {
|
|
511
|
+
"type": "array",
|
|
512
|
+
"items": {
|
|
513
|
+
"$ref": "#/$defs/DifferenceRow"
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
},
|
|
483
519
|
"criticGaps": {
|
|
484
520
|
"type": "object",
|
|
485
521
|
"description": "Coverage-critic gap accounting. `proposed - merged - unverified` is the count the voters judged and rejected, so a gap that vanished shows up as a mismatch instead of as nothing. Every `unverified` gap needs a matching `missingInformation` row with source `critic-unverified` (prompts/lead/convergence.md \"Gap verification\").",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
## Step 6: Take over as Okstra lead
|
|
2
2
|
|
|
3
|
-
Read `resources.leadExecutionPromptPath` from the run manifest at `<RUN_MANIFEST_PATH>` — the same manifest Step 5 already opened for `resources.leadPromptMetadataPath` — then read that file verbatim and take over as `Okstra lead` in the current host-native session. Do not construct the path yourself: the prompt is written per run under `runs/<task-type>/prompts/lead-execution-prompt-<task-type>-<seq>.md`, not in the instruction set, and the `<seq>` is assigned at prepare time. The prompt selects exactly one runtime adapter and points to compact intake artifacts first (`active-run-context`, `analysis-profile.md`, and `analysis-packet.md`); full source files such as `analysis-material.md`, `reference-expectations.md`, and `final-report-template.md` are lazy/fallback inputs. Follow the rendered prompt order, do not preempt it.
|
|
3
|
+
Read `resources.leadExecutionPromptPath` from the run manifest at `<RUN_MANIFEST_PATH>` — the same manifest Step 5 already opened for `resources.leadPromptMetadataPath` — then read that file verbatim and take over as `Okstra lead` in the current host-native session. Do not construct the path yourself: the prompt is written per run under `runs/<task-type>/prompts/lead-execution-prompt-<task-type>-<seq>.md`, not in the instruction set, and the `<seq>` is assigned at prepare time. The prompt selects exactly one runtime adapter and points to compact intake artifacts first (`active-run-context`, `analysis-profile.md`, and `analysis-packet.md`); full source files such as `analysis-material.md`, `reference-expectations.md`, and `final-report-template.md` are lazy/fallback inputs. Follow the rendered prompt order, do not preempt it. The run manifest's `reportLanguage` sets the language of okstra report artifacts; a host, project or global instruction file (`CLAUDE.md`, `AGENTS.md`) does not override it, and the lead never tells the report writer to use another language.
|
|
4
4
|
|
|
5
5
|
Then proceed through the phases exactly as the lead prompt directs (Phase 1 context → Phase 2+ worker dispatch → final synthesis → final report).
|
|
6
6
|
|
|
@@ -20,8 +20,8 @@ On `ok: false`, re-prompt with the same `current.step` using the error message.
|
|
|
20
20
|
The wizard tells you which relay operation to use via `next.interaction.kind`. Step 1 loads the current registered host's relay contract. Use the matching entry under its `interactions` object, including the runtime-generated navigation and completion options. If the entry is absent, stop instead of inventing a host function or falling back silently. Use the explicit `Legacy text relay compatibility` mapping only when the preflight response has no `relayContract`.
|
|
21
21
|
|
|
22
22
|
- `native-single` → use the current registered host's relay contract for its native single-select function. Pass every option in its original order and submit the selected `options[].value`.
|
|
23
|
-
- `native-multi` → use the relay contract's native multi-select function. Pass every option in its original order and submit the selected
|
|
24
|
-
- `native-group` → use the relay contract's native grouped-question function once. Preserve every question and option, then submit one JSON object keyed by `questions[].step`;
|
|
23
|
+
- `native-multi` → use the relay contract's native multi-select function. Pass every option in its original order and submit the host's selected-labels string unchanged (labels may contain `, `; the wizard decodes it against the option list); submit an empty selection as `--answer ""`.
|
|
24
|
+
- `native-group` → use the relay contract's native grouped-question function once. Preserve every question and option, then submit one JSON object keyed by `questions[].step`; a single-select step value is the selected option value, and a multi-select step value is the host's selected-labels string unchanged, not a JSON array.
|
|
25
25
|
- `numbered-single` → render every option as a 1-based numbered Markdown list and submit the user's next message unchanged.
|
|
26
26
|
- `numbered-multi` → render every option as a 1-based numbered Markdown list and submit the user's comma-separated reply unchanged.
|
|
27
27
|
- `sequential-group` → render every question in order, preserve each complete option list, collect every answer, and submit one JSON object keyed by `questions[].step`.
|
|
@@ -47,8 +47,8 @@ On `ok: false`, re-prompt with the same `current.step` using the error message.
|
|
|
47
47
|
The wizard tells you which relay operation to use via `next.interaction.kind`. Step 1 loads the current registered host's relay contract. Use the matching entry under its `interactions` object, including the runtime-generated navigation and completion options. If the entry is absent, stop instead of inventing a host function or falling back silently. Use the explicit `Legacy text relay compatibility` mapping only when the preflight response has no `relayContract`.
|
|
48
48
|
|
|
49
49
|
- `native-single` → use the current registered host's relay contract for its native single-select function. Pass every option in its original order and submit the selected `options[].value`.
|
|
50
|
-
- `native-multi` → use the relay contract's native multi-select function. Pass every option in its original order and submit the selected
|
|
51
|
-
- `native-group` → use the relay contract's native grouped-question function once. Preserve every question and option, then submit one JSON object keyed by `questions[].step`;
|
|
50
|
+
- `native-multi` → use the relay contract's native multi-select function. Pass every option in its original order and submit the host's selected-labels string unchanged (labels may contain `, `; the wizard decodes it against the option list); submit an empty selection as `--answer ""`.
|
|
51
|
+
- `native-group` → use the relay contract's native grouped-question function once. Preserve every question and option, then submit one JSON object keyed by `questions[].step`; a single-select step value is the selected option value, and a multi-select step value is the host's selected-labels string unchanged, not a JSON array.
|
|
52
52
|
- `numbered-single` → render every option as a 1-based numbered Markdown list and submit the user's next message unchanged.
|
|
53
53
|
- `numbered-multi` → render every option as a 1-based numbered Markdown list and submit the user's comma-separated reply unchanged.
|
|
54
54
|
- `sequential-group` → render every question in order, preserve each complete option list, collect every answer, and submit one JSON object keyed by `questions[].step`.
|
|
@@ -290,7 +290,7 @@ You can delete the literal state-file path after this point — its job is done.
|
|
|
290
290
|
<!-- BEGIN FRAGMENT: wizard-lead-takeover -->
|
|
291
291
|
## Step 6: Take over as Okstra lead
|
|
292
292
|
|
|
293
|
-
Read `resources.leadExecutionPromptPath` from the run manifest at `<RUN_MANIFEST_PATH>` — the same manifest Step 5 already opened for `resources.leadPromptMetadataPath` — then read that file verbatim and take over as `Okstra lead` in the current host-native session. Do not construct the path yourself: the prompt is written per run under `runs/<task-type>/prompts/lead-execution-prompt-<task-type>-<seq>.md`, not in the instruction set, and the `<seq>` is assigned at prepare time. The prompt selects exactly one runtime adapter and points to compact intake artifacts first (`active-run-context`, `analysis-profile.md`, and `analysis-packet.md`); full source files such as `analysis-material.md`, `reference-expectations.md`, and `final-report-template.md` are lazy/fallback inputs. Follow the rendered prompt order, do not preempt it.
|
|
293
|
+
Read `resources.leadExecutionPromptPath` from the run manifest at `<RUN_MANIFEST_PATH>` — the same manifest Step 5 already opened for `resources.leadPromptMetadataPath` — then read that file verbatim and take over as `Okstra lead` in the current host-native session. Do not construct the path yourself: the prompt is written per run under `runs/<task-type>/prompts/lead-execution-prompt-<task-type>-<seq>.md`, not in the instruction set, and the `<seq>` is assigned at prepare time. The prompt selects exactly one runtime adapter and points to compact intake artifacts first (`active-run-context`, `analysis-profile.md`, and `analysis-packet.md`); full source files such as `analysis-material.md`, `reference-expectations.md`, and `final-report-template.md` are lazy/fallback inputs. Follow the rendered prompt order, do not preempt it. The run manifest's `reportLanguage` sets the language of okstra report artifacts; a host, project or global instruction file (`CLAUDE.md`, `AGENTS.md`) does not override it, and the lead never tells the report writer to use another language.
|
|
294
294
|
|
|
295
295
|
Then proceed through the phases exactly as the lead prompt directs (Phase 1 context → Phase 2+ worker dispatch → final synthesis → final report).
|
|
296
296
|
|
|
@@ -52,6 +52,7 @@ okstra manager view --manager-id <manager-id>
|
|
|
52
52
|
|
|
53
53
|
`remove project`, `remove task`, `remove task-group` and `task remove-child` change nothing without `--confirm`; they print what would be removed (`Removed: no — dry run`). Run the command without `--confirm` first, show the user the listed project, path, tasks or kept children, and rerun with `--confirm` only after the user approves that exact target. These commands touch manager files only; project `.okstra/` state and briefs written by `task split` stay.
|
|
54
54
|
|
|
55
|
+
- `task status` shows the snapshot as of `Synced at` with its `Sync age`; a child marked `changed since sync: yes` has a project task manifest written after that sync, so run `task sync` before reporting its state.
|
|
55
56
|
- `remove project` unregisters the project and keeps its child tasks. `task status` shows them with `project linked: no`, the view marks them `unlinked`, and `task run` refuses them until the project is registered again with `new project`.
|
|
56
57
|
- `task update` changes only the fields given and needs no confirmation. `new task` still refuses to change an existing task's objective, common brief or progress mode.
|
|
57
58
|
|
|
@@ -47,8 +47,8 @@ On `ok: false`, re-prompt with the same `current.step` using the error message.
|
|
|
47
47
|
The wizard tells you which relay operation to use via `next.interaction.kind`. Step 1 loads the current registered host's relay contract. Use the matching entry under its `interactions` object, including the runtime-generated navigation and completion options. If the entry is absent, stop instead of inventing a host function or falling back silently. Use the explicit `Legacy text relay compatibility` mapping only when the preflight response has no `relayContract`.
|
|
48
48
|
|
|
49
49
|
- `native-single` → use the current registered host's relay contract for its native single-select function. Pass every option in its original order and submit the selected `options[].value`.
|
|
50
|
-
- `native-multi` → use the relay contract's native multi-select function. Pass every option in its original order and submit the selected
|
|
51
|
-
- `native-group` → use the relay contract's native grouped-question function once. Preserve every question and option, then submit one JSON object keyed by `questions[].step`;
|
|
50
|
+
- `native-multi` → use the relay contract's native multi-select function. Pass every option in its original order and submit the host's selected-labels string unchanged (labels may contain `, `; the wizard decodes it against the option list); submit an empty selection as `--answer ""`.
|
|
51
|
+
- `native-group` → use the relay contract's native grouped-question function once. Preserve every question and option, then submit one JSON object keyed by `questions[].step`; a single-select step value is the selected option value, and a multi-select step value is the host's selected-labels string unchanged, not a JSON array.
|
|
52
52
|
- `numbered-single` → render every option as a 1-based numbered Markdown list and submit the user's next message unchanged.
|
|
53
53
|
- `numbered-multi` → render every option as a 1-based numbered Markdown list and submit the user's comma-separated reply unchanged.
|
|
54
54
|
- `sequential-group` → render every question in order, preserve each complete option list, collect every answer, and submit one JSON object keyed by `questions[].step`.
|
|
@@ -439,6 +439,7 @@ If `render-bundle` fails with a `PrepareError` containing `Recorded stage SHAs n
|
|
|
439
439
|
- **Enter a different ref directly** — the user names a commit/branch/tag.
|
|
440
440
|
- **Abort** — stop the run without recovering.
|
|
441
441
|
3. Run `okstra git-reconcile … --apply --stage <N> --use-ref <ref>` with the chosen ref, then retry the failed `render-bundle` with the same arguments.
|
|
442
|
+
If that output carries a `Warning` naming the implementation anchor, the chosen head no longer contains the anchor: show the warning to the user and run its printed `--reset-anchor` command only after they confirm the new base, before the retry.
|
|
442
443
|
|
|
443
444
|
If the anchor (`implementation_base_commit`) is reported unresolvable, run the same command's `--reset-anchor <ref>` after user confirmation. Correcting a confirm item without the picker is forbidden — the runtime also rejects a confirm correction without `--use-ref`.
|
|
444
445
|
|
|
@@ -455,7 +456,7 @@ If `render-bundle` raises Step 5's concurrent-run conflict detection (concurrent
|
|
|
455
456
|
<!-- BEGIN FRAGMENT: wizard-lead-takeover -->
|
|
456
457
|
## Step 6: Take over as Okstra lead
|
|
457
458
|
|
|
458
|
-
Read `resources.leadExecutionPromptPath` from the run manifest at `<RUN_MANIFEST_PATH>` — the same manifest Step 5 already opened for `resources.leadPromptMetadataPath` — then read that file verbatim and take over as `Okstra lead` in the current host-native session. Do not construct the path yourself: the prompt is written per run under `runs/<task-type>/prompts/lead-execution-prompt-<task-type>-<seq>.md`, not in the instruction set, and the `<seq>` is assigned at prepare time. The prompt selects exactly one runtime adapter and points to compact intake artifacts first (`active-run-context`, `analysis-profile.md`, and `analysis-packet.md`); full source files such as `analysis-material.md`, `reference-expectations.md`, and `final-report-template.md` are lazy/fallback inputs. Follow the rendered prompt order, do not preempt it.
|
|
459
|
+
Read `resources.leadExecutionPromptPath` from the run manifest at `<RUN_MANIFEST_PATH>` — the same manifest Step 5 already opened for `resources.leadPromptMetadataPath` — then read that file verbatim and take over as `Okstra lead` in the current host-native session. Do not construct the path yourself: the prompt is written per run under `runs/<task-type>/prompts/lead-execution-prompt-<task-type>-<seq>.md`, not in the instruction set, and the `<seq>` is assigned at prepare time. The prompt selects exactly one runtime adapter and points to compact intake artifacts first (`active-run-context`, `analysis-profile.md`, and `analysis-packet.md`); full source files such as `analysis-material.md`, `reference-expectations.md`, and `final-report-template.md` are lazy/fallback inputs. Follow the rendered prompt order, do not preempt it. The run manifest's `reportLanguage` sets the language of okstra report artifacts; a host, project or global instruction file (`CLAUDE.md`, `AGENTS.md`) does not override it, and the lead never tells the report writer to use another language.
|
|
459
460
|
|
|
460
461
|
Then proceed through the phases exactly as the lead prompt directs (Phase 1 context → Phase 2+ worker dispatch → final synthesis → final report).
|
|
461
462
|
|
|
@@ -174,6 +174,8 @@ matching section:
|
|
|
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.
|
|
177
|
+
- **H. MCP servers** (`mcpServers`) — `okstra mcp add` declares a server,
|
|
178
|
+
`okstra mcp check` shows which provider CLIs reach it.
|
|
177
179
|
|
|
178
180
|
## Step 4: Verify
|
|
179
181
|
|
|
@@ -301,3 +301,37 @@ preserves it:
|
|
|
301
301
|
records that it read no rules and the run still passes. Whether a pack that
|
|
302
302
|
does resolve was actually read and applied stays the phase's own
|
|
303
303
|
`project-review-rules:` record — no machine check reads a worker's reasoning.
|
|
304
|
+
|
|
305
|
+
## H. MCP servers (`mcpServers`)
|
|
306
|
+
|
|
307
|
+
`mcpServers` declares the MCP servers a run may use — a Figma file server for a
|
|
308
|
+
visual check, for example. Without a declaration the launch prompt and the
|
|
309
|
+
analysis packet tell every worker that MCP is unavailable, even when a provider
|
|
310
|
+
CLI has the server configured.
|
|
311
|
+
|
|
312
|
+
Register each server with `okstra mcp add`. `--tool` repeats per tool;
|
|
313
|
+
`--alias` repeats per name a provider CLI lists the server under (one provider
|
|
314
|
+
may list Figma as `claude.ai Figma`, another as `figma-desktop`):
|
|
315
|
+
|
|
316
|
+
```bash
|
|
317
|
+
okstra mcp add --name figma --description "Figma design files" --tool get_screenshot --tool get_metadata --alias "claude.ai Figma" --alias figma-desktop
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
Then check which providers can reach it:
|
|
321
|
+
|
|
322
|
+
```bash
|
|
323
|
+
okstra mcp check
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
Each provider adapter lists its own CLI's MCP servers without an inference
|
|
327
|
+
call. `reachable` means the provider listed the server enabled; `disabled`
|
|
328
|
+
means listed but disabled or awaiting authentication; `missing` means not
|
|
329
|
+
listed; `unknown` means the adapter cannot list. Show the result to the user.
|
|
330
|
+
For a provider that is not `reachable`, tell the user to add the server to that
|
|
331
|
+
provider CLI's own MCP configuration — okstra never edits a provider's
|
|
332
|
+
configuration. `okstra mcp list` and `okstra mcp remove --name <name>` manage
|
|
333
|
+
existing entries.
|
|
334
|
+
|
|
335
|
+
prepare repeats the check for the providers a run selects and prints a warning
|
|
336
|
+
for each gap; it never refuses the run. It also warns when the task brief cites
|
|
337
|
+
a Figma URL and no declared server name or alias contains `figma`.
|
|
@@ -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. 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.
|
|
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. A host, project or global instruction file (`CLAUDE.md`, `AGENTS.md`) does not change that language for report artifacts. 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
|
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
</section>
|
|
71
71
|
{% endif %}
|
|
72
72
|
{% set no_cross_model_check = taskType != "release-handoff" and (crossVerification.get("roundHistory") or {}).get("round2SkippedReason") == "single-analyser-only" %}
|
|
73
|
-
{% if crossVerification.get("consensus") or crossVerification.get("differences") or no_cross_model_check %}
|
|
73
|
+
{% if crossVerification.get("consensus") or crossVerification.get("differences") or crossVerification.get("correctionRounds") or no_cross_model_check %}
|
|
74
74
|
<section data-report-section="cross-check">
|
|
75
75
|
<h2>{{ t('base.cross-check') }}</h2>
|
|
76
76
|
{% if no_cross_model_check %}<p class="section-lede">{{ t('base.no-cross-model-check') }}</p>{% endif %}
|
|
@@ -82,6 +82,11 @@
|
|
|
82
82
|
<h3>{{ t('base.workers-disagreed') }}</h3>
|
|
83
83
|
<div class="summary-grid">{% for row in crossVerification.differences %}<article class="summary-card tone-important" id="id-xv-{{ row.id }}"><p class="eyebrow">{{ row.id | inline_code }}</p><h3>{{ row.disagreement | inline_code }}</h3><p>{% for pos in row.get("workersPosition", []) %}{{ pos.worker | inline_code }} · {{ pos.position | inline_code }}{% if not loop.last %} · {% endif %}{% endfor %}</p><p class="evidence-refs">{{ t('macros.layout.evidence') }} {{ row.evidence | inline_code }}</p></article>{% endfor %}</div>
|
|
84
84
|
{% endif %}
|
|
85
|
+
{% for correction in crossVerification.get("correctionRounds", []) %}
|
|
86
|
+
<h3>{{ t('base.cross-check-correction-round') | replace('{round}', correction.round | string) }}</h3>
|
|
87
|
+
{% if correction.consensus %}<div class="summary-grid">{% for row in correction.consensus %}<article class="summary-card" id="id-xv-{{ row.id }}"><p class="eyebrow">{{ row.id | inline_code }} · {{ t('base.agreed-across-workers') }}</p><h3>{{ row.statement | inline_code }}</h3><p class="evidence-refs">{{ t('macros.layout.evidence') }} {{ row.evidence | inline_code }}</p></article>{% endfor %}</div>{% endif %}
|
|
88
|
+
{% if correction.differences %}<div class="summary-grid">{% for row in correction.differences %}<article class="summary-card tone-important" id="id-xv-{{ row.id }}"><p class="eyebrow">{{ row.id | inline_code }} · {{ t('base.workers-disagreed') }}</p><h3>{{ row.disagreement | inline_code }}</h3><p>{% for pos in row.get("workersPosition", []) %}{{ pos.worker | inline_code }} · {{ pos.position | inline_code }}{% if not loop.last %} · {% endif %}{% endfor %}</p><p class="evidence-refs">{{ t('macros.layout.evidence') }} {{ row.evidence | inline_code }}</p></article>{% endfor %}</div>{% endif %}
|
|
89
|
+
{% endfor %}
|
|
85
90
|
</section>
|
|
86
91
|
{% endif %}
|
|
87
92
|
{% if agentActivity %}
|
|
@@ -242,6 +242,7 @@
|
|
|
242
242
|
"no-cross-model-check": "No cross-model check ran: fewer than two analysis workers were selected, so no other model verified these findings.",
|
|
243
243
|
"agreed-across-workers": "Agreed across workers",
|
|
244
244
|
"workers-disagreed": "Workers disagreed",
|
|
245
|
+
"cross-check-correction-round": "Re-converged after correction round {round}",
|
|
245
246
|
"source": "Source",
|
|
246
247
|
"confidence": "Confidence",
|
|
247
248
|
"export-my-answers": "Export my answers",
|
|
@@ -242,6 +242,7 @@
|
|
|
242
242
|
"no-cross-model-check": "교차 모델 검증을 하지 않았습니다. 분석 작업자를 두 명 미만으로 골라 다른 모델이 이 결과를 검증하지 않았습니다.",
|
|
243
243
|
"agreed-across-workers": "작업자 간 합의",
|
|
244
244
|
"workers-disagreed": "작업자 간 이견",
|
|
245
|
+
"cross-check-correction-round": "수정 라운드 {round} 뒤 재수렴",
|
|
245
246
|
"source": "출처",
|
|
246
247
|
"confidence": "확신도",
|
|
247
248
|
"export-my-answers": "내 답변 내보내기",
|
|
@@ -154,13 +154,13 @@ taskType: "{{FM_TASK_TYPE}}"
|
|
|
154
154
|
|
|
155
155
|
## Available MCP Servers
|
|
156
156
|
|
|
157
|
-
The MCP servers available to this run are declared in `.okstra/project.json`'s `mcpServers` array
|
|
157
|
+
The MCP servers available to this run are declared in `.okstra/project.json`'s `mcpServers` array. Prepare renders them, with the providers that list each server, into the lead's launch prompt and the analysis packet under `## Available MCP Servers`.
|
|
158
158
|
|
|
159
|
-
To declare
|
|
159
|
+
To declare a server, run `okstra mcp add --name <server> --description <text> --tool <tool>... [--alias <name-as-a-provider-lists-it>...]`, then `okstra mcp check` to see which providers reach it. If nothing is declared, treat MCP as unavailable for this run.
|
|
160
160
|
|
|
161
161
|
How to invoke (worker-by-worker):
|
|
162
162
|
|
|
163
|
-
- **Native-session assignments**: invoke an MCP tool directly
|
|
163
|
+
- **Native-session assignments**: invoke an MCP tool directly through the host's tool interface, under the name the host adapter exposes, only when it exposes the server. Do not call an MCP tool name through a shell.
|
|
164
164
|
- **CLI-wrapper assignments**: use only the MCP or tool configuration explicitly supported and forwarded by that provider wrapper. If the wrapper has no matching MCP configuration, record `MCP not available in this CLI` in `Missing Information or Assumptions`; do not attempt a shell fallback.
|
|
165
165
|
- All workers: cite the exact server, tool, and SELECT (or `WHERE` filters) used in the result file. Tool-call failures must be logged in the worker's `*-errors.json` (commandKind `mcp_call`) so the lead can decide whether to retry under a different worker.
|
|
166
166
|
|
|
@@ -2342,6 +2342,15 @@ def _validate_conformance_surfaces(
|
|
|
2342
2342
|
)
|
|
2343
2343
|
|
|
2344
2344
|
|
|
2345
|
+
def _stage_ended_without_a_commit(report_data: dict) -> bool:
|
|
2346
|
+
implementation = report_data.get("implementation")
|
|
2347
|
+
if not isinstance(implementation, dict):
|
|
2348
|
+
return False
|
|
2349
|
+
evidence = implementation.get("stageSidecarEvidence")
|
|
2350
|
+
withheld = isinstance(evidence, dict) and str(evidence.get("withheld") or "").strip()
|
|
2351
|
+
return bool(withheld) and not implementation.get("commitList")
|
|
2352
|
+
|
|
2353
|
+
|
|
2345
2354
|
def _validate_conformance(
|
|
2346
2355
|
report_path: Path,
|
|
2347
2356
|
failures: list[str],
|
|
@@ -2389,6 +2398,10 @@ def _validate_conformance(
|
|
|
2389
2398
|
)
|
|
2390
2399
|
if declared_manifest is None:
|
|
2391
2400
|
return warnings
|
|
2401
|
+
# 스크립트와 manifest 항목은 stage 를 실행한 run 이 쓴다. 커밋 없이 carry 를
|
|
2402
|
+
# 보류한 run(첫 편집 전 정지 후 재계획)은 그것을 쓸 기회가 없었다.
|
|
2403
|
+
if _stage_ended_without_a_commit(report_data):
|
|
2404
|
+
declared_manifest = None
|
|
2392
2405
|
if declared_manifest is not None:
|
|
2393
2406
|
scoped_declared = _scope_manifest_entries(declared_manifest, stage_name)
|
|
2394
2407
|
for error in missing_declared_scripts(
|
|
@@ -4841,8 +4854,10 @@ def _validate_convergence_group_provenance(run_dir, failures, suffix=None) -> No
|
|
|
4841
4854
|
# provider token (`claude` vs `claude-worker`), depending on how the roster was
|
|
4842
4855
|
# seeded — worker resolution below accepts both. `-reverify-r` never occurs
|
|
4843
4856
|
# inside a slug, so the non-greedy slug capture stops at that literal anchor.
|
|
4857
|
+
# A correction round's re-convergence puts `correction-r<N>-` before
|
|
4858
|
+
# `reverify-r`, and its plans carry the same token before `<task-type>`.
|
|
4844
4859
|
_REVERIFY_PROMPT_BASENAME_RE = re.compile(
|
|
4845
|
-
r"^(?P<slug>[a-z][a-z0-9-]*?)-reverify-r(?P<round>\d+)"
|
|
4860
|
+
r"^(?P<slug>[a-z][a-z0-9-]*?)(?:-(?P<scope>correction-r\d+))?-reverify-r(?P<round>\d+)"
|
|
4846
4861
|
r"-(?P<task_type>[a-z][a-z-]*?)-(?P<seq>\d{3})\.md$"
|
|
4847
4862
|
)
|
|
4848
4863
|
|
|
@@ -5305,8 +5320,9 @@ def _validate_reverify_prompt_matches_plan(run_dir, failures, suffix=None) -> No
|
|
|
5305
5320
|
if match is None:
|
|
5306
5321
|
continue
|
|
5307
5322
|
round_n = match.group("round")
|
|
5323
|
+
scope = f"{match.group('scope')}-" if match.group("scope") else ""
|
|
5308
5324
|
plan_path = state_dir / (
|
|
5309
|
-
f"convergence-round-{round_n}-plan-"
|
|
5325
|
+
f"convergence-round-{round_n}-plan-{scope}"
|
|
5310
5326
|
f"{match.group('task_type')}-{match.group('seq')}.json"
|
|
5311
5327
|
)
|
|
5312
5328
|
if not plan_path.is_file():
|