okstra 0.6.1 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.kr.md +7 -1
- package/README.md +7 -1
- package/docs/kr/architecture.md +4 -3
- package/docs/kr/cli.md +26 -3
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/SKILL.md +20 -4
- package/runtime/agents/TODO.md +15 -2
- package/runtime/agents/workers/claude-worker.md +2 -2
- package/runtime/agents/workers/report-writer-worker.md +2 -2
- package/runtime/bin/okstra.sh +5 -0
- package/runtime/prompts/launch.template.md +2 -2
- package/runtime/prompts/profiles/error-analysis.md +2 -2
- package/runtime/prompts/profiles/final-verification.md +20 -1
- package/runtime/prompts/profiles/implementation-planning.md +3 -3
- package/runtime/prompts/profiles/implementation.md +17 -7
- package/runtime/prompts/profiles/requirements-discovery.md +1 -1
- package/runtime/python/lib/okstra/cli.sh +17 -1
- package/runtime/python/lib/okstra/globals.sh +3 -0
- package/runtime/python/lib/okstra/usage.sh +19 -2
- package/runtime/python/okstra_ctl/render.py +77 -3
- package/runtime/python/okstra_ctl/run.py +141 -9
- package/runtime/python/okstra_ctl/workflow.py +4 -1
- package/runtime/skills/okstra-history/SKILL.md +1 -0
- package/runtime/skills/okstra-run/SKILL.md +3 -1
- package/runtime/skills/okstra-setup/SKILL.md +1 -1
- package/runtime/skills/okstra-status/SKILL.md +11 -2
- package/runtime/skills/okstra-team-contract/SKILL.md +1 -0
- package/runtime/templates/reports/final-report.template.md +15 -3
- package/runtime/templates/reports/settings.template.json +1 -13
- package/runtime/templates/reports/task-brief.template.md +3 -14
- package/runtime/validators/validate-run.py +275 -3
- package/src/install.mjs +133 -2
- package/src/setup.mjs +1 -1
- package/src/uninstall.mjs +46 -9
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
usage() {
|
|
4
4
|
cat >&2 <<USAGE_EOF
|
|
5
5
|
usage:
|
|
6
|
-
$DISPLAY_COMMAND_NAME [--render-only] [--yes] [--refresh-assets] --task-type <task-type> [--workers worker1,worker2] [--lead-model <model>] [--claude-model <model>] [--codex-model <model>] [--gemini-model <model>] [--report-writer-model <model>] [--related-tasks taskA,taskB] --project-id <project-id> [--project-root <path>] --task-group <task-group> --task-id <task-id> --task-brief <brief-path> [--directive <directive>]
|
|
6
|
+
$DISPLAY_COMMAND_NAME [--render-only] [--yes] [--refresh-assets] --task-type <task-type> [--workers worker1,worker2] [--lead-model <model>] [--claude-model <model>] [--codex-model <model>] [--gemini-model <model>] [--report-writer-model <model>] [--executor claude|codex|gemini] [--related-tasks taskA,taskB] --project-id <project-id> [--project-root <path>] --task-group <task-group> --task-id <task-id> --task-brief <brief-path> [--directive <directive>]
|
|
7
7
|
|
|
8
8
|
summary:
|
|
9
9
|
$DISPLAY_TOOL_NAME prepares a task-keyed instruction bundle for Claude Code and launches an interactive Claude session by default.
|
|
@@ -15,7 +15,7 @@ summary:
|
|
|
15
15
|
permissions are injected via 'claude --settings' at launch time.
|
|
16
16
|
|
|
17
17
|
required arguments:
|
|
18
|
-
--project-id Globally unique project ID. Example:
|
|
18
|
+
--project-id Globally unique project ID. Example: sample-project-v2-api.
|
|
19
19
|
Each project is registered at <project-root>/.project-docs/okstra/project.json
|
|
20
20
|
on first run; subsequent runs verify the projectId there matches.
|
|
21
21
|
--task-group Logical task group. Example: backend-api, bugfix, linear-8858
|
|
@@ -39,6 +39,12 @@ optional arguments:
|
|
|
39
39
|
should prefer --resume-clarification, which wraps this flag.
|
|
40
40
|
--approved-plan Path to the approved final-report.md from a prior implementation-planning run.
|
|
41
41
|
Required when --task-type=implementation; the file MUST contain a recorded user approval marker.
|
|
42
|
+
--approve Treat the user's CLI invocation itself as the plan-approval signal. Only meaningful
|
|
43
|
+
together with --approved-plan and --task-type=implementation. The runtime updates the
|
|
44
|
+
top "User Approval Request" block of the approved-plan file: flips
|
|
45
|
+
\`- [ ] Approved\` to \`- [x] Approved\` and appends an approval audit line
|
|
46
|
+
(timestamp + "CLI --approve"). Use this for scripted/CI flows or when you want a
|
|
47
|
+
single command to both approve and launch the next phase.
|
|
42
48
|
--task-key <project-id:task-group:task-id>
|
|
43
49
|
Shorthand for --project-id/--task-group/--task-id. When the matching task-manifest.json
|
|
44
50
|
exists, brief-path and task-type are auto-filled from it (taskBriefPath and
|
|
@@ -66,7 +72,17 @@ options:
|
|
|
66
72
|
--gemini-model Model for Gemini worker. Default: OKSTRA_DEFAULT_GEMINI_MODEL or auto
|
|
67
73
|
--report-writer-model
|
|
68
74
|
Model for report writer worker. Default: OKSTRA_DEFAULT_REPORT_WRITER_MODEL or lead model default
|
|
75
|
+
--executor Provider that performs the Executor role during --task-type=implementation.
|
|
76
|
+
One of: claude | codex | gemini. Default: OKSTRA_DEFAULT_EXECUTOR or claude.
|
|
77
|
+
The Executor is the only worker allowed to mutate project files; the other two
|
|
78
|
+
providers are dispatched as read-only verifiers regardless of this selection.
|
|
79
|
+
Has no effect on other task types.
|
|
69
80
|
--related-tasks Optional comma-separated related task identifiers. Example: auth-token-refresh,frontend-login-ui
|
|
81
|
+
--work-category Work-category classification for this task. One of:
|
|
82
|
+
bugfix | feature | refactor | ops | improvement | unknown.
|
|
83
|
+
Defaults to 'unknown' when omitted. Use this when the
|
|
84
|
+
lifecycle skipped --task-type=requirements-discovery
|
|
85
|
+
(where work-category would otherwise be inferred).
|
|
70
86
|
--task-type Set the task purpose for this run and select the matching profile file.
|
|
71
87
|
-h, --help Show this help.
|
|
72
88
|
|
|
@@ -76,6 +92,7 @@ model defaults:
|
|
|
76
92
|
Claude worker: OKSTRA_DEFAULT_CLAUDE_MODEL or sonnet
|
|
77
93
|
Codex worker: OKSTRA_DEFAULT_CODEX_MODEL or gpt-5.5
|
|
78
94
|
Gemini worker: OKSTRA_DEFAULT_GEMINI_MODEL or auto
|
|
95
|
+
Implementation executor: OKSTRA_DEFAULT_EXECUTOR or claude (one of: claude | codex | gemini)
|
|
79
96
|
|
|
80
97
|
output:
|
|
81
98
|
Stable task bundles are stored under:
|
|
@@ -438,10 +438,19 @@ def render_task_manifest(manifest_path: str, ctx: dict) -> None:
|
|
|
438
438
|
if current_phase:
|
|
439
439
|
phase_states[current_phase] = current_phase_state
|
|
440
440
|
work_category = existing.get("workCategory") or ctx.get("WORKFLOW_WORK_CATEGORY", "unknown")
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
441
|
+
# Compute the canonical next phase from current_phase deterministically.
|
|
442
|
+
# Only preserve `existing.workflow.nextRecommendedPhase` when it is a
|
|
443
|
+
# legitimate forward pointer — i.e. NOT equal to `current_phase` itself
|
|
444
|
+
# (which would mean the lifecycle pointer has stalled on the current
|
|
445
|
+
# phase and would loop forever).
|
|
446
|
+
canonical_next = default_next_phase.get(
|
|
447
|
+
current_phase, ctx.get("WORKFLOW_NEXT_RECOMMENDED_PHASE", "unknown")
|
|
444
448
|
)
|
|
449
|
+
existing_next = workflow.get("nextRecommendedPhase") or ""
|
|
450
|
+
if existing_next and existing_next != current_phase:
|
|
451
|
+
next_recommended_phase = existing_next
|
|
452
|
+
else:
|
|
453
|
+
next_recommended_phase = canonical_next
|
|
445
454
|
last_completed_phase = workflow.get("lastCompletedPhase") or ctx.get("WORKFLOW_LAST_COMPLETED_PHASE", "")
|
|
446
455
|
routing_status = workflow.get("routingStatus") or ctx.get("WORKFLOW_ROUTING_STATUS", "not-applicable")
|
|
447
456
|
awaiting_approval = workflow.get("awaitingApproval")
|
|
@@ -681,6 +690,14 @@ def render_run_manifest(run_manifest_path: str, ctx: dict) -> None:
|
|
|
681
690
|
"disallowLeadSoloAnalysisAsWorkerResult": True,
|
|
682
691
|
"disallowGenericParallelOnlyExecution": True,
|
|
683
692
|
"preferredCompletedWorkerResults": len(reviewers),
|
|
693
|
+
"executor": {
|
|
694
|
+
"provider": ctx.get("EXECUTOR_PROVIDER", ""),
|
|
695
|
+
"displayName": ctx.get("EXECUTOR_DISPLAY_NAME", ""),
|
|
696
|
+
"workerAgent": ctx.get("EXECUTOR_WORKER_AGENT", ""),
|
|
697
|
+
"model": ctx.get("EXECUTOR_MODEL_DISPLAY", ""),
|
|
698
|
+
"modelExecutionValue": ctx.get("EXECUTOR_MODEL_EXECUTION_VALUE", ""),
|
|
699
|
+
"appliesTo": "implementation",
|
|
700
|
+
},
|
|
684
701
|
},
|
|
685
702
|
"validation": {
|
|
686
703
|
"required": True,
|
|
@@ -857,6 +874,59 @@ def render_task_index(template_path: str, output_path: str, ctx: dict) -> None:
|
|
|
857
874
|
_write_text(Path(output_path), rendered.rstrip() + "\n")
|
|
858
875
|
|
|
859
876
|
|
|
877
|
+
# --------------------------------------------------------------------------- #
|
|
878
|
+
# Available MCP servers block
|
|
879
|
+
# --------------------------------------------------------------------------- #
|
|
880
|
+
|
|
881
|
+
|
|
882
|
+
_NO_MCP_SERVERS_LINE = (
|
|
883
|
+
"- No MCP servers are declared in `.project-docs/okstra/project.json`'s "
|
|
884
|
+
"`mcpServers` array. Treat MCP tools as unavailable for this run. To enable "
|
|
885
|
+
"them, add entries shaped `{name, description, tools, notes?}` to that array "
|
|
886
|
+
"and re-render the bundle."
|
|
887
|
+
)
|
|
888
|
+
|
|
889
|
+
|
|
890
|
+
def build_available_mcp_servers_block(project_root: Path) -> str:
|
|
891
|
+
"""Render the `## Available MCP Servers` first bullet from project.json.
|
|
892
|
+
|
|
893
|
+
The MCP server list used to be hardcoded for one specific environment.
|
|
894
|
+
It now comes from the project's `.project-docs/okstra/project.json`
|
|
895
|
+
(`mcpServers` array), so each user/project declares the MCP surface
|
|
896
|
+
available to their lead+workers. Missing file or empty array yields a
|
|
897
|
+
generic "none declared" fallback.
|
|
898
|
+
"""
|
|
899
|
+
config_path = project_root / ".project-docs" / "okstra" / "project.json"
|
|
900
|
+
try:
|
|
901
|
+
raw = json.loads(config_path.read_text(encoding="utf-8"))
|
|
902
|
+
except (FileNotFoundError, json.JSONDecodeError):
|
|
903
|
+
return _NO_MCP_SERVERS_LINE
|
|
904
|
+
servers = raw.get("mcpServers") if isinstance(raw, dict) else None
|
|
905
|
+
if not isinstance(servers, list) or not servers:
|
|
906
|
+
return _NO_MCP_SERVERS_LINE
|
|
907
|
+
lines: list[str] = []
|
|
908
|
+
for entry in servers:
|
|
909
|
+
if not isinstance(entry, dict):
|
|
910
|
+
continue
|
|
911
|
+
name = str(entry.get("name", "")).strip()
|
|
912
|
+
if not name:
|
|
913
|
+
continue
|
|
914
|
+
description = str(entry.get("description", "")).strip()
|
|
915
|
+
tools = entry.get("tools") or []
|
|
916
|
+
notes = str(entry.get("notes", "")).strip()
|
|
917
|
+
parts = [f"`mcp__{name}`"]
|
|
918
|
+
if description:
|
|
919
|
+
parts.append(description)
|
|
920
|
+
if isinstance(tools, list) and tools:
|
|
921
|
+
tool_names = ", ".join(f"`{str(t).strip()}`" for t in tools if str(t).strip())
|
|
922
|
+
if tool_names:
|
|
923
|
+
parts.append(f"Tools: {tool_names}")
|
|
924
|
+
if notes:
|
|
925
|
+
parts.append(notes)
|
|
926
|
+
lines.append("- " + ". ".join(parts) + ".")
|
|
927
|
+
return "\n".join(lines) if lines else _NO_MCP_SERVERS_LINE
|
|
928
|
+
|
|
929
|
+
|
|
860
930
|
# --------------------------------------------------------------------------- #
|
|
861
931
|
# launch.template.md rendering
|
|
862
932
|
# --------------------------------------------------------------------------- #
|
|
@@ -1003,6 +1073,10 @@ def render_template_file(template_path: str, output_path: str, ctx: dict) -> Non
|
|
|
1003
1073
|
"{{WORKFLOW_NEXT_RECOMMENDED_PHASE}}": ctx.get("WORKFLOW_NEXT_RECOMMENDED_PHASE", ""),
|
|
1004
1074
|
"{{PHASE_ALLOWED_OUTPUTS}}": ctx.get("PHASE_ALLOWED_OUTPUTS", ""),
|
|
1005
1075
|
"{{PHASE_FORBIDDEN_ACTIONS}}": ctx.get("PHASE_FORBIDDEN_ACTIONS", ""),
|
|
1076
|
+
"{{AVAILABLE_MCP_SERVERS}}": ctx.get(
|
|
1077
|
+
"AVAILABLE_MCP_SERVERS",
|
|
1078
|
+
build_available_mcp_servers_block(Path(ctx.get("PROJECT_ROOT", "."))),
|
|
1079
|
+
),
|
|
1006
1080
|
}
|
|
1007
1081
|
rendered = template
|
|
1008
1082
|
for k, v in mapping.items():
|
|
@@ -21,6 +21,7 @@ import re
|
|
|
21
21
|
import shutil
|
|
22
22
|
import subprocess
|
|
23
23
|
from dataclasses import dataclass, field
|
|
24
|
+
from datetime import datetime, timezone
|
|
24
25
|
from pathlib import Path
|
|
25
26
|
from typing import Optional
|
|
26
27
|
|
|
@@ -55,7 +56,7 @@ from .workers import normalize_workers, resolve_profile_workers
|
|
|
55
56
|
from .workflow import compute_workflow_state
|
|
56
57
|
|
|
57
58
|
APPROVED_PLAN_PATTERN = re.compile(
|
|
58
|
-
r"^[ \t]*(APPROVED([ \t]|:|$)|\[x\][ \t]*Approved|"
|
|
59
|
+
r"^[ \t]*(?:[-*+][ \t]+)?(APPROVED([ \t]|:|$)|\[x\][ \t]*Approved|"
|
|
59
60
|
r"User[ \t]+Approval[ \t]*:[ \t]*(APPROVED|granted|yes))",
|
|
60
61
|
re.IGNORECASE | re.MULTILINE,
|
|
61
62
|
)
|
|
@@ -81,11 +82,14 @@ class PrepareInputs:
|
|
|
81
82
|
codex_model: str = ""
|
|
82
83
|
gemini_model: str = ""
|
|
83
84
|
report_writer_model: str = ""
|
|
85
|
+
executor: str = ""
|
|
84
86
|
related_tasks_raw: str = ""
|
|
87
|
+
work_category: str = ""
|
|
85
88
|
approved_plan_path: str = ""
|
|
86
89
|
clarification_response_path: str = "" # absolute or empty
|
|
87
90
|
render_only: bool = False
|
|
88
91
|
refresh_assets: bool = False
|
|
92
|
+
approve_plan_ack: bool = False
|
|
89
93
|
|
|
90
94
|
|
|
91
95
|
@dataclass
|
|
@@ -107,11 +111,65 @@ def _validate_approved_plan(path: str) -> None:
|
|
|
107
111
|
if not APPROVED_PLAN_PATTERN.search(p.read_text(encoding="utf-8", errors="replace")):
|
|
108
112
|
raise PrepareError(
|
|
109
113
|
f"approved plan has no recognised user-approval marker: {path}\n"
|
|
110
|
-
'
|
|
111
|
-
'
|
|
114
|
+
' canonical form (single line, top-of-report block): "- [x] Approved"\n'
|
|
115
|
+
' also accepted (case-insensitive, line-anchored, optional leading bullet): '
|
|
116
|
+
'"APPROVED", "[x] Approved", "User Approval: APPROVED|granted|yes"\n'
|
|
117
|
+
" shortcut: re-run okstra with --approve to have the CLI itself "
|
|
118
|
+
"record the approval marker on this file."
|
|
112
119
|
)
|
|
113
120
|
|
|
114
121
|
|
|
122
|
+
# `- [ ] Approved` 라인을 정확히 한 번만 매치한다. 좌측 leading whitespace 와
|
|
123
|
+
# 옵션 bullet 은 보존된 채 체크박스 안쪽 공백만 `x` 로 갱신된다.
|
|
124
|
+
APPROVAL_UNCHECKED_PATTERN = re.compile(
|
|
125
|
+
r"^([ \t]*(?:[-*+][ \t]+)?)\[[ \t]\][ \t]*Approved[ \t]*$",
|
|
126
|
+
re.IGNORECASE | re.MULTILINE,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _apply_cli_approval(path: str) -> str:
|
|
131
|
+
"""`--approve` 가 지정된 경우 approved-plan 파일에 사용자 승인 마커를 새겨 넣는다.
|
|
132
|
+
|
|
133
|
+
Returns a short human-readable description of the action taken (used in the
|
|
134
|
+
runtime audit line). Idempotent: if the file already carries a valid
|
|
135
|
+
approval marker, no edits are written and `"already-approved"` is returned.
|
|
136
|
+
"""
|
|
137
|
+
p = Path(path)
|
|
138
|
+
if not p.is_file():
|
|
139
|
+
raise PrepareError(f"approved plan file not found: {path}")
|
|
140
|
+
body = p.read_text(encoding="utf-8", errors="replace")
|
|
141
|
+
|
|
142
|
+
audit_iso = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
143
|
+
audit_line = (
|
|
144
|
+
f"- 승인 일시 (CLI ack): {audit_iso} — recorded by `okstra --approve` "
|
|
145
|
+
"(user CLI invocation treated as approval signal)"
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
if APPROVED_PLAN_PATTERN.search(body):
|
|
149
|
+
# 이미 사용자(또는 이전 --approve 호출)가 마커를 남긴 상태. audit 라인이
|
|
150
|
+
# 없으면 보조적으로 한 줄만 추가하고 마커 자체는 건드리지 않는다.
|
|
151
|
+
if audit_line.split(" — ")[1] in body:
|
|
152
|
+
return "already-approved"
|
|
153
|
+
new_body = body.rstrip("\n") + "\n" + audit_line + "\n"
|
|
154
|
+
p.write_text(new_body, encoding="utf-8")
|
|
155
|
+
return "already-approved-audit-appended"
|
|
156
|
+
|
|
157
|
+
if APPROVAL_UNCHECKED_PATTERN.search(body):
|
|
158
|
+
new_body, count = APPROVAL_UNCHECKED_PATTERN.subn(
|
|
159
|
+
lambda m: f"{m.group(1)}[x] Approved", body, count=1,
|
|
160
|
+
)
|
|
161
|
+
new_body = new_body.rstrip("\n") + "\n" + audit_line + "\n"
|
|
162
|
+
p.write_text(new_body, encoding="utf-8")
|
|
163
|
+
return "checkbox-flipped"
|
|
164
|
+
|
|
165
|
+
raise PrepareError(
|
|
166
|
+
f"--approve was given but the approved-plan file has no `User Approval Request` "
|
|
167
|
+
f"checkbox to flip: {path}\n"
|
|
168
|
+
" expected a line of the form `- [ ] Approved` near the top of the report "
|
|
169
|
+
"(see templates/reports/final-report.template.md)."
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
|
|
115
173
|
def _ensure_task_directories(ctx: dict) -> None:
|
|
116
174
|
for key in (
|
|
117
175
|
"TASK_ROOT", "INSTRUCTION_SET_DIR", "RUNS_DIR", "HISTORY_DIR",
|
|
@@ -265,7 +323,9 @@ def _canonical_argv(inp: PrepareInputs, ctx: dict) -> list[str]:
|
|
|
265
323
|
("--codex-model", inp.codex_model or ctx.get("CODEX_WORKER_MODEL_DISPLAY", "")),
|
|
266
324
|
("--gemini-model", inp.gemini_model or ctx.get("GEMINI_WORKER_MODEL_DISPLAY", "")),
|
|
267
325
|
("--report-writer-model", inp.report_writer_model or ctx.get("REPORT_WRITER_MODEL_DISPLAY", "")),
|
|
326
|
+
("--executor", inp.executor or ctx.get("EXECUTOR_PROVIDER", "")),
|
|
268
327
|
("--related-tasks", inp.related_tasks_raw),
|
|
328
|
+
("--work-category", inp.work_category),
|
|
269
329
|
]
|
|
270
330
|
argv: list[str] = []
|
|
271
331
|
for flag, val in pairs:
|
|
@@ -328,7 +388,19 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
|
|
|
328
388
|
raise PrepareError(
|
|
329
389
|
"task-type implementation requires --approved-plan <path-to-final-report.md>"
|
|
330
390
|
)
|
|
391
|
+
if inp.approve_plan_ack:
|
|
392
|
+
# 사용자가 직접 `--approve` 를 입력한 행위 자체를 승인 의사로 모델링한다.
|
|
393
|
+
# 파일을 먼저 갱신한 뒤 동일한 검증 경로를 그대로 통과시킨다 — 검증
|
|
394
|
+
# 책임을 단일 지점(`_validate_approved_plan`)으로 유지한다.
|
|
395
|
+
_apply_cli_approval(inp.approved_plan_path)
|
|
331
396
|
_validate_approved_plan(inp.approved_plan_path)
|
|
397
|
+
elif inp.approve_plan_ack:
|
|
398
|
+
# implementation 외 task-type 에서 `--approve` 는 의미가 없다. 사용자에게
|
|
399
|
+
# 정확한 시점을 알려주기 위해 조용히 무시하지 않고 즉시 거부한다.
|
|
400
|
+
raise PrepareError(
|
|
401
|
+
"--approve is only meaningful with --task-type implementation "
|
|
402
|
+
"and --approved-plan <path>"
|
|
403
|
+
)
|
|
332
404
|
if inp.clarification_response_path and not Path(inp.clarification_response_path).is_file():
|
|
333
405
|
raise PrepareError(
|
|
334
406
|
f"clarification response file not found: {inp.clarification_response_path}"
|
|
@@ -383,6 +455,22 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
|
|
|
383
455
|
default_display=report_writer_default, default_execution=report_writer_default,
|
|
384
456
|
)
|
|
385
457
|
|
|
458
|
+
# ---- executor binding (implementation phase only; recorded universally for manifest consistency) ----
|
|
459
|
+
executor_default = _default("OKSTRA_DEFAULT_EXECUTOR", "claude")
|
|
460
|
+
executor_provider = (inp.executor or executor_default).strip().lower()
|
|
461
|
+
if executor_provider not in ("claude", "codex", "gemini"):
|
|
462
|
+
raise PrepareError(
|
|
463
|
+
f"--executor must be one of: claude, codex, gemini (got: {executor_provider!r})"
|
|
464
|
+
)
|
|
465
|
+
executor_provider_to_meta = {
|
|
466
|
+
"claude": ("Claude executor", "claude-worker", cw),
|
|
467
|
+
"codex": ("Codex executor", "codex-worker", co),
|
|
468
|
+
"gemini": ("Gemini executor", "gemini-worker", ge),
|
|
469
|
+
}
|
|
470
|
+
executor_display_name, executor_worker_agent, executor_model_meta = (
|
|
471
|
+
executor_provider_to_meta[executor_provider]
|
|
472
|
+
)
|
|
473
|
+
|
|
386
474
|
# ---- paths under per-task mutex (writes run-context-*.json) ----
|
|
387
475
|
# OKSTRA_RUN_SEQ_OVERRIDE: okstra-ctl rerun / 테스트 hook 이 미리 reserve
|
|
388
476
|
# 한 seq 를 강제하는 user-knob 환경 변수.
|
|
@@ -444,6 +532,11 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
|
|
|
444
532
|
"GEMINI_WORKER_MODEL_EXECUTION_VALUE": ge.execution,
|
|
445
533
|
"REPORT_WRITER_MODEL_DISPLAY": rw.display,
|
|
446
534
|
"REPORT_WRITER_MODEL_EXECUTION_VALUE": rw.execution,
|
|
535
|
+
"EXECUTOR_PROVIDER": executor_provider,
|
|
536
|
+
"EXECUTOR_DISPLAY_NAME": executor_display_name,
|
|
537
|
+
"EXECUTOR_WORKER_AGENT": executor_worker_agent,
|
|
538
|
+
"EXECUTOR_MODEL_DISPLAY": executor_model_meta.display,
|
|
539
|
+
"EXECUTOR_MODEL_EXECUTION_VALUE": executor_model_meta.execution,
|
|
447
540
|
"RELATED_TASKS_JSON": related_tasks_json_str,
|
|
448
541
|
"RELATED_TASKS_BULLETS": bullets,
|
|
449
542
|
"RELATED_TASKS_INLINE": inline,
|
|
@@ -464,16 +557,29 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
|
|
|
464
557
|
cleanup_obsolete_generated_docs(
|
|
465
558
|
project_root=project_root, instruction_set_dir=Path(ctx["INSTRUCTION_SET_DIR"]),
|
|
466
559
|
)
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
560
|
+
# Always materialise the resume command script. Even in --render-only
|
|
561
|
+
# preparation flows the user (or a later non-interactive runner) may
|
|
562
|
+
# invoke it manually; deferring its creation until interactive launch
|
|
563
|
+
# leaves runs/<phase>/sessions/ empty and the manifest pointing at a
|
|
564
|
+
# path that does not exist.
|
|
565
|
+
write_claude_resume_command_file(
|
|
566
|
+
resume_command_path=Path(ctx["CLAUDE_RESUME_COMMAND_FILE"]),
|
|
567
|
+
project_root=project_root, claude_session_id=claude_session_id,
|
|
568
|
+
)
|
|
472
569
|
|
|
473
570
|
# ---- write instruction-set scaffolding ----
|
|
474
571
|
instruction_set = Path(ctx["INSTRUCTION_SET_DIR"])
|
|
475
572
|
instruction_set.mkdir(parents=True, exist_ok=True)
|
|
476
|
-
|
|
573
|
+
profile_rendered = profile_content
|
|
574
|
+
for key in (
|
|
575
|
+
"EXECUTOR_PROVIDER",
|
|
576
|
+
"EXECUTOR_DISPLAY_NAME",
|
|
577
|
+
"EXECUTOR_WORKER_AGENT",
|
|
578
|
+
"EXECUTOR_MODEL_DISPLAY",
|
|
579
|
+
"EXECUTOR_MODEL_EXECUTION_VALUE",
|
|
580
|
+
):
|
|
581
|
+
profile_rendered = profile_rendered.replace("{{" + key + "}}", ctx.get(key, ""))
|
|
582
|
+
(instruction_set / "analysis-profile.md").write_text(profile_rendered, encoding="utf-8")
|
|
477
583
|
(instruction_set / "analysis-material.md").write_text(review_material, encoding="utf-8")
|
|
478
584
|
shutil.copyfile(inp.brief_path, instruction_set / "task-brief.md")
|
|
479
585
|
if inp.clarification_response_path:
|
|
@@ -512,6 +618,7 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
|
|
|
512
618
|
"codexModel": co.display,
|
|
513
619
|
"geminiModel": ge.display,
|
|
514
620
|
"reportWriterModel": rw.display,
|
|
621
|
+
"executor": executor_provider,
|
|
515
622
|
"relatedTasks": inp.related_tasks_raw,
|
|
516
623
|
"approvedPlanPath": inp.approved_plan_path,
|
|
517
624
|
"clarificationResponsePath": inp.clarification_response_path,
|
|
@@ -536,6 +643,7 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
|
|
|
536
643
|
current_run_status=ctx["CURRENT_RUN_STATUS"],
|
|
537
644
|
current_task_status=ctx["CURRENT_TASK_STATUS"],
|
|
538
645
|
render_only=inp.render_only,
|
|
646
|
+
work_category=inp.work_category,
|
|
539
647
|
))
|
|
540
648
|
render_team_state(ctx["TEAM_STATE_FILE"], ctx)
|
|
541
649
|
render_task_manifest(ctx["TASK_MANIFEST_FILE"], ctx)
|
|
@@ -603,11 +711,32 @@ def main(argv: list[str]) -> int:
|
|
|
603
711
|
p.add_argument("--codex-model", default="")
|
|
604
712
|
p.add_argument("--gemini-model", default="")
|
|
605
713
|
p.add_argument("--report-writer-model", default="")
|
|
714
|
+
p.add_argument("--executor", default="")
|
|
606
715
|
p.add_argument("--related-tasks", default="", dest="related_tasks_raw")
|
|
607
716
|
p.add_argument("--approved-plan", default="", dest="approved_plan_path")
|
|
717
|
+
p.add_argument(
|
|
718
|
+
"--approve",
|
|
719
|
+
action="store_true",
|
|
720
|
+
dest="approve_plan_ack",
|
|
721
|
+
help=(
|
|
722
|
+
"Treat the CLI invocation itself as the plan approval signal. "
|
|
723
|
+
"Flips `- [ ] Approved` to `- [x] Approved` in the --approved-plan file "
|
|
724
|
+
"and appends an audit line."
|
|
725
|
+
),
|
|
726
|
+
)
|
|
608
727
|
p.add_argument("--clarification-response", default="", dest="clarification_response_path")
|
|
609
728
|
p.add_argument("--render-only", action="store_true", dest="render_only")
|
|
610
729
|
p.add_argument("--refresh-assets", action="store_true", dest="refresh_assets")
|
|
730
|
+
p.add_argument(
|
|
731
|
+
"--work-category",
|
|
732
|
+
default="",
|
|
733
|
+
dest="work_category",
|
|
734
|
+
help=(
|
|
735
|
+
"Work-category classification for this task "
|
|
736
|
+
"(bugfix / feature / refactor / ops / improvement). "
|
|
737
|
+
"Falls back to `unknown` when omitted."
|
|
738
|
+
),
|
|
739
|
+
)
|
|
611
740
|
args = p.parse_args(argv)
|
|
612
741
|
|
|
613
742
|
project_root = Path(args.project_root).expanduser().resolve()
|
|
@@ -641,11 +770,14 @@ def main(argv: list[str]) -> int:
|
|
|
641
770
|
codex_model=args.codex_model,
|
|
642
771
|
gemini_model=args.gemini_model,
|
|
643
772
|
report_writer_model=args.report_writer_model,
|
|
773
|
+
executor=args.executor,
|
|
644
774
|
related_tasks_raw=args.related_tasks_raw,
|
|
775
|
+
work_category=args.work_category,
|
|
645
776
|
approved_plan_path=args.approved_plan_path,
|
|
646
777
|
clarification_response_path=clarification_abs,
|
|
647
778
|
render_only=args.render_only,
|
|
648
779
|
refresh_assets=args.refresh_assets,
|
|
780
|
+
approve_plan_ack=args.approve_plan_ack,
|
|
649
781
|
)
|
|
650
782
|
try:
|
|
651
783
|
out = prepare_task_bundle(inputs)
|
|
@@ -138,6 +138,7 @@ def compute_workflow_state(
|
|
|
138
138
|
current_run_status: str,
|
|
139
139
|
current_task_status: str,
|
|
140
140
|
render_only: bool,
|
|
141
|
+
work_category: str = "",
|
|
141
142
|
) -> dict:
|
|
142
143
|
"""WORKFLOW_* + PHASE_* 값을 dict 로 돌려준다."""
|
|
143
144
|
if current_run_status == "in-progress":
|
|
@@ -170,8 +171,10 @@ def compute_workflow_state(
|
|
|
170
171
|
rules = PHASE_RULES.get(task_type, PHASE_RULES_UNKNOWN)
|
|
171
172
|
last_completed = task_type if current_run_status == "completed" else ""
|
|
172
173
|
|
|
174
|
+
resolved_work_category = (work_category or "").strip() or "unknown"
|
|
175
|
+
|
|
173
176
|
return {
|
|
174
|
-
"WORKFLOW_WORK_CATEGORY":
|
|
177
|
+
"WORKFLOW_WORK_CATEGORY": resolved_work_category,
|
|
175
178
|
"WORKFLOW_CURRENT_PHASE": task_type,
|
|
176
179
|
"WORKFLOW_CURRENT_PHASE_STATE": phase_state,
|
|
177
180
|
"WORKFLOW_NEXT_RECOMMENDED_PHASE": default_next_phase_for(task_type),
|
|
@@ -103,6 +103,7 @@ To re-run a specific run:
|
|
|
103
103
|
- `recommendedWorkers` → `--workers` (comma-separated)
|
|
104
104
|
- `relatedTasks` → `--related-tasks` (if present)
|
|
105
105
|
- model overrides → `--claude-model`, `--codex-model`, `--gemini-model` (if different from default)
|
|
106
|
+
- for `taskType: implementation`: `teamContract.executor.provider` → `--executor <claude|codex|gemini>` (if different from `claude`)
|
|
106
107
|
4. Display the assembled command:
|
|
107
108
|
|
|
108
109
|
```bash
|
|
@@ -128,8 +128,9 @@ Validate that slugified `task_group` and `task_id` each contain at least one alp
|
|
|
128
128
|
|
|
129
129
|
For existing tasks, present `nextRecommendedPhase` as the first option (recommended default).
|
|
130
130
|
|
|
131
|
-
If `implementation` chosen, ask
|
|
131
|
+
If `implementation` chosen, ask two more `AskUserQuestion` in order:
|
|
132
132
|
- `"Path to the approved final-report.md (must contain APPROVED marker)"` — the underlying python `prepare_task_bundle` re-validates the marker, but you can pre-check with `grep`.
|
|
133
|
+
- `"Executor provider for this run (claude | codex | gemini)?"` — only this provider mutates project files; the other two run as read-only verifiers. Default `claude` (or `OKSTRA_DEFAULT_EXECUTOR` if set). Pass the answer through `PrepareInputs.executor`.
|
|
133
134
|
|
|
134
135
|
## Step 5: Brief path
|
|
135
136
|
|
|
@@ -176,6 +177,7 @@ out = prepare_task_bundle(PrepareInputs(
|
|
|
176
177
|
lead_model="...", claude_model="...", codex_model="...",
|
|
177
178
|
gemini_model="...", report_writer_model="...",
|
|
178
179
|
related_tasks_raw="...",
|
|
180
|
+
executor="<claude|codex|gemini or empty>", # implementation only; empty → default (claude / OKSTRA_DEFAULT_EXECUTOR)
|
|
179
181
|
approved_plan_path="<approved-plan-or-empty>",
|
|
180
182
|
clarification_response_path=str(clarification_abs) if clarification_abs else "",
|
|
181
183
|
render_only=True,
|
|
@@ -96,7 +96,7 @@ so overwriting requires manually deleting the file first.
|
|
|
96
96
|
|
|
97
97
|
If the file does NOT exist, ask via `AskUserQuestion`:
|
|
98
98
|
|
|
99
|
-
- **Question**: `"Project id for okstra (e.g. INV-1234,
|
|
99
|
+
- **Question**: `"Project id for okstra (e.g. INV-1234, my-app, okstra)"`
|
|
100
100
|
- **Validate**: slugified must contain at least one alphanumeric character.
|
|
101
101
|
|
|
102
102
|
Then create the file:
|
|
@@ -43,7 +43,7 @@ Extract the following fields from each task.
|
|
|
43
43
|
|------|------|
|
|
44
44
|
| `taskKey` | `<project-id>:<task-group>:<task-id>` |
|
|
45
45
|
| `taskType` | latest task type |
|
|
46
|
-
| `workCategory` | bugfix / feature / improvement / refactor / ops-change / unknown |
|
|
46
|
+
| `workCategory` | bugfix / feature / improvement / refactor / ops-change / unknown. Display `unknown` as-is, but flag with `(unset)` annotation so the reader knows the requirements-discovery classification was skipped or no `--work-category` flag was passed. |
|
|
47
47
|
| `currentStatus` | task-level status |
|
|
48
48
|
| `currentPhase` | lifecycle current phase |
|
|
49
49
|
| `currentPhaseState` | lifecycle phase state |
|
|
@@ -213,7 +213,16 @@ Accepted `<status>` values: `todo`, `in-progress`, `blocked`, `done`.
|
|
|
213
213
|
|
|
214
214
|
### Default Value Convention
|
|
215
215
|
|
|
216
|
-
If `workStatus` is missing or empty in any manifest,
|
|
216
|
+
If `workStatus` is missing or empty in any manifest, infer the display value from the lifecycle state — DO NOT default to a static `in-progress`:
|
|
217
|
+
|
|
218
|
+
| Manifest state | Inferred display |
|
|
219
|
+
|---|---|
|
|
220
|
+
| `currentStatus == "completed"` AND `workflow.nextRecommendedPhase` in (`done-or-follow-up`, empty) | `done` (inferred) |
|
|
221
|
+
| `currentStatus == "completed"` AND `workflow.currentPhaseState == "completed"` | `phase-done` (inferred) |
|
|
222
|
+
| `currentStatus == "contract-violated"` OR `workflow.currentPhaseState == "blocked"` | `blocked` (inferred) |
|
|
223
|
+
| anything else | `in-progress` (default) |
|
|
224
|
+
|
|
225
|
+
Annotate inferred values with `(inferred)` or `(default)` in the rendered output so the reader can see which value is user-set vs. system-inferred. Do not back-fill the field on read; only write it when the user explicitly issues an update.
|
|
217
226
|
|
|
218
227
|
### Catalog Sync Note
|
|
219
228
|
|
|
@@ -42,6 +42,7 @@ Only workers selected from `recommendedWorkers` in `task-manifest.json` and `res
|
|
|
42
42
|
|
|
43
43
|
## Operating Rules
|
|
44
44
|
|
|
45
|
+
0. **TeamCreate ordering (BLOCKING).** Before issuing any `Agent` dispatch that includes `team_name`, Lead MUST have called `TeamCreate(team_name: "okstra-<task-key>", ...)` in this run and recorded the outcome in team-state as `teamCreate: { attempted: true, status: "ok"|"error", error?: <message> }`. If the Agent tool rejects a dispatch with `"team must be created first or call without team_name"` / `"team을 먼저 생성하거나 team_name 없이 호출해야 합니다"`, the correct response is to go back to Phase 3 and call `TeamCreate` — NOT to strip `team_name` and retry. The no-`team_name` Phase 5 fallback is only legal when `teamCreate.status == "error"` is already recorded; otherwise stripping `team_name` silently degrades the run to in-process background dispatch and loses the Teams split-pane behavior. See [okstra agent SKILL.md Phase 3](../../agents/SKILL.md) for the full team-creation sequence.
|
|
45
46
|
1. `Claude lead` is responsible for orchestration, convergence supervision, and final-report review/approval. It never overrides worker analysis results, and it never authors the final-report file when `Report writer worker` is in the roster.
|
|
46
47
|
2. `Report writer worker` is NOT an analysis worker. It is excluded from Phase 4/5 (initial analysis) and Phase 5.5 (convergence re-verification). It is spawned only in Phase 6 and is the **author** of the final-report file at `runs/<task-type>/reports/final-report-<task-type>-<seq>.md`.
|
|
47
48
|
3. When `Report writer worker` is in the roster, Lead MUST dispatch it in Phase 6. The only legal lead-authored fallback is when a dispatch was attempted and recorded a terminal status of `error` / `timeout` / `not-run` with a concrete logged reason. Speculative reasons such as "session resume constraint" or "team is no longer alive" are NOT valid — Lead can always dispatch a fresh subagent (omit `team_name` if the team is gone).
|
|
@@ -6,6 +6,18 @@
|
|
|
6
6
|
- Lead Model: `{{LEAD_MODEL}}`
|
|
7
7
|
- Clarification Response Carried In: `{{CLARIFICATION_RESPONSE_RELATIVE_PATH}}`
|
|
8
8
|
|
|
9
|
+
## User Approval Request (사용자 승인 게이트)
|
|
10
|
+
|
|
11
|
+
> **이 블록은 `task-type` = `implementation-planning` 결과에서만 의미를 가집니다.** 다른 task-type의 보고서에서는 이 섹션 전체를 삭제하세요.
|
|
12
|
+
>
|
|
13
|
+
> 다음 `implementation` run은 아래 체크박스가 `[x]`로 표시되어 있을 때에만 진입할 수 있습니다 (`okstra_ctl.run._validate_approved_plan` 가 이 마커를 line-anchored 정규식으로 검사하여 통과/거부합니다). 본문(`Sections 1`–`4.5`)을 끝까지 읽고, `4.5.9 Open Questions`가 비어 있거나 모두 해소된 뒤 승인해 주세요.
|
|
14
|
+
|
|
15
|
+
- 승인 여부 (사용자가 직접 편집): `- [ ] Approved` ← 승인하려면 `[ ]` 를 `[x]` 로 변경하여 저장하세요.
|
|
16
|
+
- 승인 후 다음 단계 명령어 (방법 A — 수동 편집): `okstra --task-key {{TASK_KEY}} --task-type implementation --approved-plan <이 보고서 경로>`
|
|
17
|
+
- 승인 + 실행 한 번에 (방법 B — CLI 자체를 승인 의사로): `okstra --task-key {{TASK_KEY}} --task-type implementation --approved-plan <이 보고서 경로> --approve`
|
|
18
|
+
- 방법 B 는 `--approve` 입력 행위 자체를 승인 의사로 모델링합니다. 런타임이 본 블록의 체크박스를 자동으로 `[x]` 로 바꾸고, 본 섹션 하단에 `승인 일시 (CLI ack): <ISO8601>` audit 라인을 한 줄 덧붙입니다.
|
|
19
|
+
- 승인을 보류하거나 거부하려면 체크박스는 `[ ]` 로 두고 `--approve` 도 사용하지 마세요. 필요한 변경 사항은 `4.5.9 Open Questions` 또는 `Section 5 Clarification Requests` 에 기록한 뒤 같은 phase 를 재실행해 주세요.
|
|
20
|
+
|
|
9
21
|
## 0. Clarification Response Carried In From Previous Run
|
|
10
22
|
- Source file: `{{CLARIFICATION_RESPONSE_RELATIVE_PATH}}`
|
|
11
23
|
- If the source path is empty, write `- No prior clarification response was provided for this run.` and skip the rest of this section.
|
|
@@ -89,9 +101,9 @@
|
|
|
89
101
|
### 4.5.7 Rollback Strategy (롤백 전략)
|
|
90
102
|
- 정확한 revert 경로(commits, flags, migrations 등)와 롤백을 트리거하는 신호(에러율, latency, 사용자 보고 등)를 명시합니다.
|
|
91
103
|
|
|
92
|
-
### 4.5.8 User Approval Request (사용자 승인 요청)
|
|
93
|
-
-
|
|
94
|
-
- 승인
|
|
104
|
+
### 4.5.8 User Approval Request (사용자 승인 요청 — 본 보고서 상단 참조)
|
|
105
|
+
- 실제 승인 게이트는 본 보고서 **상단 `User Approval Request (사용자 승인 게이트)` 블록**에 있습니다. 이 하위 섹션은 validator가 요구하는 영문 키워드(`User Approval Request`)와 본문 구조 일관성을 위해 남겨 둡니다.
|
|
106
|
+
- 본 섹션에는 승인 결정에 영향을 주는 *플랜 측 보충 메모*만 적습니다(예: 위험을 줄이기 위한 사전 작업, 승인 전 사용자가 확인해 두어야 할 사항). 승인 마커는 본 섹션이 아니라 상단 블록의 체크박스로만 부여합니다.
|
|
95
107
|
|
|
96
108
|
### 4.5.9 Open Questions
|
|
97
109
|
- pre-planning에서 발견된 모든 모호점을 항목으로 남겨, 사용자가 승인 전에 해소해야 할 질문 목록으로 사용합니다.
|
|
@@ -83,19 +83,7 @@
|
|
|
83
83
|
"Bash(curl:*)",
|
|
84
84
|
"Bash(wget:*)",
|
|
85
85
|
"mcp__test-context7__resolve-library-id",
|
|
86
|
-
"mcp__test-context7__query-docs"
|
|
87
|
-
"mcp__mysql-fontsninja-common__mysql_list_tables",
|
|
88
|
-
"mcp__mysql-fontsninja-common__mysql_describe_table",
|
|
89
|
-
"mcp__mysql-fontsninja-common__mysql_select_data",
|
|
90
|
-
"mcp__mysql-fontsninja-fontradar__mysql_list_tables",
|
|
91
|
-
"mcp__mysql-fontsninja-fontradar__mysql_describe_table",
|
|
92
|
-
"mcp__mysql-fontsninja-fontradar__mysql_select_data",
|
|
93
|
-
"mcp__mysql-fontsninja-fontsninja__mysql_list_tables",
|
|
94
|
-
"mcp__mysql-fontsninja-fontsninja__mysql_describe_table",
|
|
95
|
-
"mcp__mysql-fontsninja-fontsninja__mysql_select_data",
|
|
96
|
-
"mcp__mysql-fontsninja-fonthelper__mysql_list_tables",
|
|
97
|
-
"mcp__mysql-fontsninja-fonthelper__mysql_describe_table",
|
|
98
|
-
"mcp__mysql-fontsninja-fonthelper__mysql_select_data"
|
|
86
|
+
"mcp__test-context7__query-docs"
|
|
99
87
|
]
|
|
100
88
|
}
|
|
101
89
|
}
|
|
@@ -127,24 +127,13 @@
|
|
|
127
127
|
|
|
128
128
|
## Available MCP Servers
|
|
129
129
|
|
|
130
|
-
The
|
|
130
|
+
The MCP servers available to this run are declared in `.project-docs/okstra/project.json`'s `mcpServers` array and rendered into the Claude lead's launch prompt under `## Available MCP Servers`. They may be invoked **as needed** by Claude lead, Claude worker, and Report writer worker. The lead is responsible for forwarding the rendered list verbatim into the worker prompts (Phase 2) so workers know which tools they are allowed to call.
|
|
131
131
|
|
|
132
|
-
|
|
133
|
-
|--------|--------------|------|-------------|
|
|
134
|
-
| `mcp__mysql-fontsninja-common` | local Docker MySQL `common` schema | read-only | shared lookups, code list, reference data |
|
|
135
|
-
| `mcp__mysql-fontsninja-fontradar` | local Docker MySQL `fontradar` schema | read-only | fontradar service data inspection |
|
|
136
|
-
| `mcp__mysql-fontsninja-fontsninja` | local Docker MySQL `fontsninja` schema | read-only | fontsninja service data inspection |
|
|
137
|
-
| `mcp__mysql-fontsninja-fonthelper` | local Docker MySQL `fonthelper` schema | read-only | fonthelper service data inspection |
|
|
138
|
-
|
|
139
|
-
Available tools per server (all read-only — write tools are disabled at the server):
|
|
140
|
-
|
|
141
|
-
- `mysql_list_tables`
|
|
142
|
-
- `mysql_describe_table`
|
|
143
|
-
- `mysql_select_data`
|
|
132
|
+
To declare servers, add entries shaped `{ "name": "<server>", "description": "...", "tools": ["..."], "notes": "..." }` to that array. If the array is empty or absent, treat MCP as unavailable for this run.
|
|
144
133
|
|
|
145
134
|
How to invoke (worker-by-worker):
|
|
146
135
|
|
|
147
|
-
- **Claude lead / Claude worker / Report writer worker**: invoke the MCP tool **directly by its tool name** (e.g. `
|
|
136
|
+
- **Claude lead / Claude worker / Report writer worker**: invoke the MCP tool **directly by its tool name** (e.g. `mcp__<server>__<tool>`) through the host's tool interface. **Do NOT call it via `Bash`** — these names are MCP tools, not shell commands; running them in a shell will always fail with `command not found` regardless of permission settings.
|
|
148
137
|
- **Codex worker / Gemini worker**: invoke through the external CLI's own MCP transport (e.g. `codex mcp call <server> <tool> <args>` for Codex CLI; the equivalent Gemini CLI MCP invocation for Gemini). If the worker's CLI has no matching MCP config, treat the server as unavailable for this run and record `MCP not available in this CLI` in `Missing Information or Assumptions` — do **not** attempt a shell fallback such as `mysql -h ...` or piping a tool name into `bash`.
|
|
149
138
|
- 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.
|
|
150
139
|
|