okstra 0.220.0 → 0.221.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/dist/lib/paths.mjs +1 -1
- package/dist/lib/paths.mjs.map +1 -1
- package/docs/architecture.md +2 -0
- package/docs/cli.md +7 -3
- package/docs/project-structure-overview.md +1 -1
- 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 +12 -8
- 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 +10 -2
- package/runtime/python/okstra_ctl/correction_cli.py +44 -5
- package/runtime/python/okstra_ctl/correction_round.py +40 -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/initial_prompt_materialization.py +3 -6
- package/runtime/python/okstra_ctl/lead_progress.py +56 -11
- package/runtime/python/okstra_ctl/manager_cli.py +13 -1
- package/runtime/python/okstra_ctl/manager_launch.py +23 -2
- 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/phases/implementation/instructions/_implementation-deliverable.md +4 -3
- 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/release_handoff/boundary.json +2 -2
- package/runtime/python/okstra_ctl/phases/release_handoff/profile.md +2 -2
- package/runtime/python/okstra_ctl/phases/release_handoff/spec.md +1 -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/render.py +7 -48
- package/runtime/python/okstra_ctl/report_assembly.py +5 -5
- package/runtime/python/okstra_ctl/report_inputs.py +10 -1
- package/runtime/python/okstra_ctl/report_language.py +4 -5
- package/runtime/python/okstra_ctl/run.py +29 -0
- package/runtime/python/okstra_ctl/stage_done.py +37 -3
- package/runtime/python/okstra_ctl/wizard/engine.py +1 -2
- 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 +3 -2
- package/runtime/skills/okstra-run/SKILL.md +3 -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/task-brief.template.md +3 -3
- package/runtime/validators/validate-run.py +13 -0
|
@@ -33,7 +33,7 @@ When a predecessor advances, `okstra_ctl.stage_rebase.synchronize_stage_stack` r
|
|
|
33
33
|
|
|
34
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.
|
|
35
35
|
|
|
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
|
|
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 report narrative inline; `okstra report-finalize` assembles the final report from that narrative.
|
|
37
37
|
|
|
38
38
|
## 2. okstra-run wizard flow
|
|
39
39
|
|
|
@@ -98,3 +98,40 @@ def materialize_technical_input_packet(payload_json: str, run_root: Path, seq: s
|
|
|
98
98
|
"- Read the input before planning experiments; copy each fact identity verbatim into results.\n"
|
|
99
99
|
+ "\n```json\n" + payload_json + "\n```\n"
|
|
100
100
|
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def technical_evidence_packet_block(source: Path, project_root: Path) -> str:
|
|
104
|
+
"""carry-in 이 시험 결과 기록이면 후속 비교 워커가 읽을 증거 위치를 렌더한다.
|
|
105
|
+
|
|
106
|
+
clarification carry-in 은 §1 행만 싣고 원문은 `Source report:` 한 줄로만
|
|
107
|
+
가리키므로, 이 블록이 없으면 워커는 시험 결과와 로그에 닿지 못한다.
|
|
108
|
+
"""
|
|
109
|
+
from okstra_ctl.path_resolve import relative_to_project_root
|
|
110
|
+
from okstra_ctl.phases.technical_verification.validation import REPORT_NAME_RE
|
|
111
|
+
|
|
112
|
+
match = REPORT_NAME_RE.fullmatch(source.name)
|
|
113
|
+
if match is None or not source.is_file():
|
|
114
|
+
return ""
|
|
115
|
+
data = load_owned_object_snapshot(
|
|
116
|
+
source, artifact="technical verification result"
|
|
117
|
+
).value
|
|
118
|
+
if (data.get("header") or {}).get("taskType") != "technical-verification":
|
|
119
|
+
return ""
|
|
120
|
+
experiments = source.parent.parent / "experiments" / match[1]
|
|
121
|
+
lines = [
|
|
122
|
+
"## Technical Verification Evidence",
|
|
123
|
+
"",
|
|
124
|
+
"The clarification response is a technical-verification record. Apply"
|
|
125
|
+
" `Technical evidence loop` in `## Phase Focus Extract`: read each"
|
|
126
|
+
" check's plan, commands and logs below before authoring a fresh"
|
|
127
|
+
" feasibility vote.",
|
|
128
|
+
"",
|
|
129
|
+
f"- Result record: `{relative_to_project_root(source, project_root)}`",
|
|
130
|
+
"- Experiment plans and logs:"
|
|
131
|
+
f" `{relative_to_project_root(experiments, project_root)}/`",
|
|
132
|
+
]
|
|
133
|
+
for check in (data.get("technicalVerification") or {}).get("checks") or []:
|
|
134
|
+
lines.append(
|
|
135
|
+
f"- `{check.get('id')}` on `{check.get('candidateId')}`: {check.get('status')}"
|
|
136
|
+
)
|
|
137
|
+
return "\n".join(lines)
|
|
@@ -10,6 +10,8 @@ from okstra_ctl.implementation_direction import validate_task_artifact_path
|
|
|
10
10
|
from okstra_ctl.json_boundary import load_owned_object_snapshot
|
|
11
11
|
from okstra_ctl.technical_verification_facts import TechnicalVerificationError
|
|
12
12
|
|
|
13
|
+
REPORT_NAME_RE = re.compile(r"final-report-technical-verification-(\d{3,})\.data\.json")
|
|
14
|
+
|
|
13
15
|
|
|
14
16
|
def _check_execution_evidence(
|
|
15
17
|
check: Mapping[str, Any], project_root: Path, run_root: Path, seq: str
|
|
@@ -51,9 +53,7 @@ def validate_technical_verification_report(
|
|
|
51
53
|
"""발행·최종 검증에서 같은 사실 집합과 실제 시험 산출물을 대조한다."""
|
|
52
54
|
if (data.get("header") or {}).get("taskType") != "technical-verification":
|
|
53
55
|
return []
|
|
54
|
-
match =
|
|
55
|
-
r"final-report-technical-verification-(\d{3,})\.data\.json", report_path.name
|
|
56
|
-
)
|
|
56
|
+
match = REPORT_NAME_RE.fullmatch(report_path.name)
|
|
57
57
|
if match is None:
|
|
58
58
|
return ["technical verification report must use its canonical record path"]
|
|
59
59
|
run_root = report_path.parent.parent
|
|
@@ -26,7 +26,7 @@ import sys
|
|
|
26
26
|
from collections.abc import Mapping
|
|
27
27
|
from pathlib import Path
|
|
28
28
|
|
|
29
|
-
from okstra_project.dirs import TASK_MANIFEST_FILENAME
|
|
29
|
+
from okstra_project.dirs import TASK_MANIFEST_FILENAME
|
|
30
30
|
|
|
31
31
|
# phase 시퀀스 / 기본 next-phase 매핑의 SSOT 는 workflow 모듈이다. 과거
|
|
32
32
|
# render_task_manifest 가 동일한 리스트/딕셔너리를 로컬에 중복 정의했는데,
|
|
@@ -50,6 +50,8 @@ from .registry.provider_registry import default_provider_registry
|
|
|
50
50
|
from .path_hints import compact_active_run_context, hydrate_run_context
|
|
51
51
|
from .paths import task_timeline_file
|
|
52
52
|
from .json_boundary import JsonBoundaryError, load_owned_object, write_owned_object_atomic
|
|
53
|
+
from .mcp_servers import load_declarations as load_mcp_declarations
|
|
54
|
+
from .mcp_servers import render_block as render_mcp_block
|
|
53
55
|
from .report_contract import CURRENT_REPORT_SCHEMA_VERSION
|
|
54
56
|
from .report_language import resolve_report_language
|
|
55
57
|
from .validation_contract import CURRENT_VALIDATION_CONTRACT_VERSION
|
|
@@ -2509,54 +2511,11 @@ def render_task_index(template_path: str, output_path: str, ctx: dict) -> None:
|
|
|
2509
2511
|
# --------------------------------------------------------------------------- #
|
|
2510
2512
|
|
|
2511
2513
|
|
|
2512
|
-
_NO_MCP_SERVERS_LINE = (
|
|
2513
|
-
f"- No MCP servers are declared in `{OKSTRA_DIR_NAME}/project.json`'s "
|
|
2514
|
-
"`mcpServers` array. Treat MCP tools as unavailable for this run. To enable "
|
|
2515
|
-
"them, add entries shaped `{name, description, tools, notes?}` to that array "
|
|
2516
|
-
"and re-render the bundle."
|
|
2517
|
-
)
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
2514
|
def build_available_mcp_servers_block(project_root: Path) -> str:
|
|
2521
|
-
"""
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
so each user/project declares the MCP surface available to their
|
|
2526
|
-
lead+workers. Missing file or empty array yields a generic "none declared"
|
|
2527
|
-
fallback.
|
|
2528
|
-
"""
|
|
2529
|
-
config_path = project_json_path(project_root)
|
|
2530
|
-
try:
|
|
2531
|
-
raw = load_owned_object(config_path, artifact="project config")
|
|
2532
|
-
except JsonBoundaryError:
|
|
2533
|
-
return _NO_MCP_SERVERS_LINE
|
|
2534
|
-
servers = raw.get("mcpServers") if isinstance(raw, dict) else None
|
|
2535
|
-
if not isinstance(servers, list) or not servers:
|
|
2536
|
-
return _NO_MCP_SERVERS_LINE
|
|
2537
|
-
lines: list[str] = []
|
|
2538
|
-
for entry in servers:
|
|
2539
|
-
if not isinstance(entry, dict):
|
|
2540
|
-
continue
|
|
2541
|
-
name = str(entry.get("name", "")).strip()
|
|
2542
|
-
if not name:
|
|
2543
|
-
continue
|
|
2544
|
-
description = str(entry.get("description", "")).strip()
|
|
2545
|
-
tools = entry.get("tools") or []
|
|
2546
|
-
notes = str(entry.get("notes", "")).strip()
|
|
2547
|
-
parts = [f"`mcp__{name}`"]
|
|
2548
|
-
if description:
|
|
2549
|
-
parts.append(description)
|
|
2550
|
-
if isinstance(tools, list) and tools:
|
|
2551
|
-
tool_names = ", ".join(
|
|
2552
|
-
f"`{str(t).strip()}`" for t in tools if str(t).strip()
|
|
2553
|
-
)
|
|
2554
|
-
if tool_names:
|
|
2555
|
-
parts.append(f"Tools: {tool_names}")
|
|
2556
|
-
if notes:
|
|
2557
|
-
parts.append(notes)
|
|
2558
|
-
lines.append("- " + ". ".join(parts) + ".")
|
|
2559
|
-
return "\n".join(lines) if lines else _NO_MCP_SERVERS_LINE
|
|
2515
|
+
"""provider 판정 없이 렌더한다. prepare 는 판정한 블록을 ctx 로 넘기므로
|
|
2516
|
+
이 경로는 prepare 를 거치지 않는 렌더 호출자만 쓴다."""
|
|
2517
|
+
declarations, _problems = load_mcp_declarations(project_root)
|
|
2518
|
+
return render_mcp_block(declarations)
|
|
2560
2519
|
|
|
2561
2520
|
|
|
2562
2521
|
# --------------------------------------------------------------------------- #
|
|
@@ -762,7 +762,7 @@ def _attach_machine_planning(
|
|
|
762
762
|
_sync_human_summary_with_plan_body_gate(data, projection)
|
|
763
763
|
|
|
764
764
|
|
|
765
|
-
def _schema_owner(error: str) -> str:
|
|
765
|
+
def _schema_owner(error: str, narrative_owner: str) -> str:
|
|
766
766
|
field = error.split(" ", 1)[0]
|
|
767
767
|
owners = {
|
|
768
768
|
"executionStatus": "team-state",
|
|
@@ -781,8 +781,8 @@ def _schema_owner(error: str) -> str:
|
|
|
781
781
|
if "planBodyVerification" in error:
|
|
782
782
|
return "convergence"
|
|
783
783
|
if "selectedDirectionRef" in error:
|
|
784
|
-
return
|
|
785
|
-
return owners.get(field.split(".", 1)[0],
|
|
784
|
+
return narrative_owner
|
|
785
|
+
return owners.get(field.split(".", 1)[0], narrative_owner)
|
|
786
786
|
|
|
787
787
|
|
|
788
788
|
def _publish(
|
|
@@ -864,7 +864,7 @@ def assemble_report(
|
|
|
864
864
|
path_errors = planned_path_declaration_errors(data)
|
|
865
865
|
if path_errors:
|
|
866
866
|
raise ReportAssemblyError(tuple(
|
|
867
|
-
AssemblyIssue("
|
|
867
|
+
AssemblyIssue(inputs["narrative"].owner, str(inputs["narrative"].path), error.split(":", 1)[0], error)
|
|
868
868
|
for error in path_errors
|
|
869
869
|
))
|
|
870
870
|
direction_errors = selected_direction_plan_errors(
|
|
@@ -888,7 +888,7 @@ def assemble_report(
|
|
|
888
888
|
raise ReportAssemblyError(
|
|
889
889
|
tuple(
|
|
890
890
|
AssemblyIssue(
|
|
891
|
-
_schema_owner(error),
|
|
891
|
+
_schema_owner(error, inputs["narrative"].owner),
|
|
892
892
|
str(target),
|
|
893
893
|
error.split(" ", 1)[0],
|
|
894
894
|
error,
|
|
@@ -28,6 +28,10 @@ _INPUT_FIELDS = (
|
|
|
28
28
|
("convergence", "convergence", "convergenceStatePath"),
|
|
29
29
|
)
|
|
30
30
|
|
|
31
|
+
# 워커 없이 리드가 리포트까지 쓰는 phase. 서사의 저자가 리드이므로, 서사가
|
|
32
|
+
# 없을 때 조립이 report-writer 를 지목하면 띄울 수 없는 워커를 찾게 된다.
|
|
33
|
+
_LEAD_AUTHORED_NARRATIVE_TASK_TYPES = frozenset({"release-handoff"})
|
|
34
|
+
|
|
31
35
|
_PLANNING_INPUT_FIELDS = (
|
|
32
36
|
("design-preparation", "design-surface-detector", "designPreparationPath"),
|
|
33
37
|
("plan-body-verification", "convergence", "planBodyVerificationPath"),
|
|
@@ -63,8 +67,13 @@ def report_input_paths(
|
|
|
63
67
|
if manifest.get("taskType") == "implementation-planning"
|
|
64
68
|
else ()
|
|
65
69
|
)
|
|
70
|
+
lead_authored = manifest.get("taskType") in _LEAD_AUTHORED_NARRATIVE_TASK_TYPES
|
|
66
71
|
rows = tuple(
|
|
67
|
-
ReportInputPath(
|
|
72
|
+
ReportInputPath(
|
|
73
|
+
key,
|
|
74
|
+
"lead" if lead_authored and key == "narrative" else owner,
|
|
75
|
+
_project_path(project_root, manifest.get(field), field),
|
|
76
|
+
)
|
|
68
77
|
for key, owner, field in fields
|
|
69
78
|
)
|
|
70
79
|
if len({row.key for row in rows}) != len(rows):
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
"""Report-language resolution — the one place the precedence lives.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
for `ko`, get a Korean-instructed writer, and still ship an English report.
|
|
3
|
+
Prepare calls this once and freezes the answer as `reportLanguage` in the run
|
|
4
|
+
manifest. Both report-writer dispatch paths stamp that manifest value as the
|
|
5
|
+
`**Report Language:**` line, and report assembly copies it into
|
|
6
|
+
`meta.reportLanguage`, so a config edit after prepare cannot split the two.
|
|
8
7
|
|
|
9
8
|
The value is a language tag (`en`, `ko`, `fr`, `pt-BR`, …). The report writer
|
|
10
9
|
authors the report prose in that language; nothing here knows any particular
|
|
@@ -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(
|
|
@@ -4055,6 +4067,20 @@ def _prepare_user_authorization(inp: PrepareInputs) -> dict:
|
|
|
4055
4067
|
return record
|
|
4056
4068
|
|
|
4057
4069
|
|
|
4070
|
+
def _prepare_mcp_block(
|
|
4071
|
+
project_root: Path, plan: AssignmentPlan, brief_path: Path,
|
|
4072
|
+
) -> str:
|
|
4073
|
+
brief_text = brief_path.read_text(encoding="utf-8") if brief_path.is_file() else ""
|
|
4074
|
+
prepared = prepare_mcp(
|
|
4075
|
+
project_root,
|
|
4076
|
+
(assignment.provider_id for assignment in plan.assignments),
|
|
4077
|
+
brief_text,
|
|
4078
|
+
)
|
|
4079
|
+
for warning in prepared.warnings:
|
|
4080
|
+
print(f"prepare: {warning}", file=sys.stderr)
|
|
4081
|
+
return prepared.block
|
|
4082
|
+
|
|
4083
|
+
|
|
4058
4084
|
def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
|
|
4059
4085
|
"""Produce a complete okstra task bundle on disk. See module docstring."""
|
|
4060
4086
|
user_authorization = _prepare_user_authorization(inp)
|
|
@@ -4326,6 +4352,9 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
|
|
|
4326
4352
|
"DYNAMIC_EXECUTION_ROLES_JSON": json.dumps(
|
|
4327
4353
|
dynamic_roles, ensure_ascii=False,
|
|
4328
4354
|
),
|
|
4355
|
+
"AVAILABLE_MCP_SERVERS": _prepare_mcp_block(
|
|
4356
|
+
project_root, assignment_plan, inp.brief_path,
|
|
4357
|
+
),
|
|
4329
4358
|
})
|
|
4330
4359
|
refresh_run_context_snapshot(ctx)
|
|
4331
4360
|
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
|
|
|
@@ -96,7 +96,7 @@ def _build_group_prompt(state: WizardState, group_id: str) -> Prompt:
|
|
|
96
96
|
|
|
97
97
|
|
|
98
98
|
def _is_lone_choice_pick(prompt: Prompt) -> bool:
|
|
99
|
-
"""고를 것이 한 줄뿐인 단일
|
|
99
|
+
"""고를 것이 한 줄뿐인 픽인가 — 단일 선택이든 체크박스든.
|
|
100
100
|
|
|
101
101
|
호스트 선택기는 옵션 2개 미만을 받지 않아(`nativeLimits` `minOptions`) 이런
|
|
102
102
|
화면은 번호 1개짜리 목록으로 내려간다 — 사용자는 고를 것이 없는 목록에서
|
|
@@ -106,7 +106,6 @@ def _is_lone_choice_pick(prompt: Prompt) -> bool:
|
|
|
106
106
|
"""
|
|
107
107
|
return (
|
|
108
108
|
prompt.kind == "pick"
|
|
109
|
-
and not prompt.multi
|
|
110
109
|
and len(prompt.options) == 1
|
|
111
110
|
and prompt.options[0].value != _ABORT_OPTION
|
|
112
111
|
)
|
|
@@ -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
|
|
|
@@ -29,7 +29,7 @@ okstra manager task status --manager-id <manager-id> --task-group <task-group> -
|
|
|
29
29
|
okstra manager task sync --manager-id <manager-id> --task-group <task-group> --task-id <task-id>
|
|
30
30
|
okstra manager task assign --manager-id <manager-id> --task-group <task-group> --task-id <task-id> --project-id <project-id> [--child-task-id <child-task-id>] [--role <role>] [--tag <tag>] [--assignment <text>]
|
|
31
31
|
okstra manager task note --manager-id <manager-id> --task-group <task-group> --task-id <task-id> --scope <shared|project> [--project-id <project-id>] --body <text>
|
|
32
|
-
okstra manager task run --manager-id <manager-id> --project-id <project-id> --task-group <task-group> --task-id <task-id> [--child-task-id <child-task-id>]
|
|
32
|
+
okstra manager task run --manager-id <manager-id> --project-id <project-id> --task-group <task-group> --task-id <task-id> [--child-task-id <child-task-id>] [--allow-completed]
|
|
33
33
|
okstra manager task split --manager-id <manager-id> --task-group <task-group> --task-id <task-id> --plan <split-plan.json> [--overwrite]
|
|
34
34
|
okstra manager list managers
|
|
35
35
|
okstra manager list projects --manager-id <manager-id>
|
|
@@ -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
|
|
|
@@ -104,7 +105,7 @@ When the user wants to see a manager at a glance, run `okstra manager task sync
|
|
|
104
105
|
For launching child work:
|
|
105
106
|
|
|
106
107
|
1. Run `okstra manager task sync ...` if the manager snapshot must be refreshed first.
|
|
107
|
-
2. Run `okstra manager task run ...`.
|
|
108
|
+
2. Run `okstra manager task run ...`. It re-reads the child's state from its project, not from the snapshot. When the child is already done there (terminal next phase, or work status `done`), it refuses with the last phase and report path; tell the user that and do not add `--allow-completed` unless the user asks to run the child again.
|
|
108
109
|
3. Read the returned fixed fields `Backend`, `Worker dispatch backend`, `Project root`, `Context path`, `Run command`, every numbered `Run arg N`, and `Shell command`.
|
|
109
110
|
4. Give the user the `Shell command` line verbatim and tell them to run it in a new terminal. It starts the installed launcher in the child project: the launcher fills the task type and brief from the child task manifest or asks for them, prepares the run with the manager context directive, and starts the child lead as a separate host process (`Backend` `spawn-process`).
|
|
110
111
|
|
|
@@ -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`.
|
|
@@ -455,7 +455,7 @@ If `render-bundle` raises Step 5's concurrent-run conflict detection (concurrent
|
|
|
455
455
|
<!-- BEGIN FRAGMENT: wizard-lead-takeover -->
|
|
456
456
|
## Step 6: Take over as Okstra lead
|
|
457
457
|
|
|
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.
|
|
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. 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
459
|
|
|
460
460
|
Then proceed through the phases exactly as the lead prompt directs (Phase 1 context → Phase 2+ worker dispatch → final synthesis → final report).
|
|
461
461
|
|
|
@@ -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
|
|
|
@@ -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(
|