okstra 0.128.0 → 0.129.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/docs/architecture/storage-model.md +15 -2
- package/docs/architecture.md +19 -3
- package/docs/cli.md +8 -0
- package/docs/project-structure-overview.md +14 -5
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/antigravity-worker.md +3 -3
- package/runtime/agents/workers/claude-worker.md +4 -4
- package/runtime/agents/workers/codex-worker.md +3 -3
- package/runtime/agents/workers/report-writer-worker.md +5 -5
- package/runtime/prompts/lead/adapters/claude-code.md +2 -1
- package/runtime/prompts/lead/adapters/codex.md +1 -0
- package/runtime/prompts/lead/adapters/external.md +1 -0
- package/runtime/prompts/lead/convergence.md +42 -84
- package/runtime/prompts/lead/okstra-lead-contract.md +10 -8
- package/runtime/prompts/lead/report-writer.md +3 -2
- package/runtime/prompts/lead/team-contract.md +11 -9
- package/runtime/prompts/profiles/_common-contract.md +1 -1
- package/runtime/prompts/profiles/error-analysis.md +1 -1
- package/runtime/prompts/profiles/implementation-planning.md +5 -0
- package/runtime/prompts/profiles/improvement-discovery.md +11 -1
- package/runtime/prompts/profiles/requirements-discovery.md +8 -1
- package/runtime/python/okstra_ctl/analysis_packet.py +7 -13
- package/runtime/python/okstra_ctl/codex_dispatch.py +42 -97
- package/runtime/python/okstra_ctl/context_cost.py +82 -7
- package/runtime/python/okstra_ctl/convergence.py +223 -0
- package/runtime/python/okstra_ctl/convergence_engine.py +1152 -0
- package/runtime/python/okstra_ctl/convergence_migration.py +184 -0
- package/runtime/python/okstra_ctl/convergence_store.py +85 -0
- package/runtime/python/okstra_ctl/dispatch_core.py +53 -14
- package/runtime/python/okstra_ctl/improvement_assignment.py +61 -0
- package/runtime/python/okstra_ctl/path_hints.py +23 -1
- package/runtime/python/okstra_ctl/paths.py +10 -1
- package/runtime/python/okstra_ctl/render.py +56 -11
- package/runtime/python/okstra_ctl/worker_prompt_body.py +107 -0
- package/runtime/python/okstra_ctl/worker_prompt_contract.py +102 -9
- package/runtime/python/okstra_ctl/worker_prompt_headers.py +79 -9
- package/runtime/python/okstra_ctl/worker_prompt_policy.py +158 -0
- package/runtime/templates/implementation-worker-preamble.md +51 -0
- package/runtime/templates/operating-standard.md +4 -4
- package/runtime/templates/report-writer-prompt-preamble.md +37 -0
- package/runtime/templates/worker-error-contract.md +46 -0
- package/runtime/templates/worker-prompt-preamble.md +28 -234
- package/runtime/validators/lib/fixtures.sh +27 -12
- package/runtime/validators/validate-run.py +201 -72
- package/src/cli-registry.mjs +7 -0
- package/src/commands/execute/convergence.mjs +34 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""Shared initial analysis-worker prompt body rendering."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import Any, Mapping, Sequence
|
|
5
|
+
|
|
6
|
+
from .worker_prompt_policy import PromptPlan
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
ANALYSIS_WORKER_LABELS = {
|
|
10
|
+
"claude": "Claude worker",
|
|
11
|
+
"codex": "Codex worker",
|
|
12
|
+
"antigravity": "Antigravity worker",
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def analysis_prompt_body(
|
|
17
|
+
manifest: Mapping[str, Any],
|
|
18
|
+
active_context: Mapping[str, Any],
|
|
19
|
+
worker_id: str,
|
|
20
|
+
model: str,
|
|
21
|
+
role: str,
|
|
22
|
+
plan: PromptPlan,
|
|
23
|
+
) -> list[str]:
|
|
24
|
+
"""Render the provider-neutral body for an initial analysis audience."""
|
|
25
|
+
label = ANALYSIS_WORKER_LABELS.get(worker_id, f"{worker_id} worker")
|
|
26
|
+
return [
|
|
27
|
+
f"**Model:** {label}, {model}",
|
|
28
|
+
f"**Pane role:** {role}",
|
|
29
|
+
"",
|
|
30
|
+
f"# {label} Dispatch",
|
|
31
|
+
"",
|
|
32
|
+
"## Role",
|
|
33
|
+
(
|
|
34
|
+
f"You are the {label} for okstra cross-verification. "
|
|
35
|
+
"Produce an independent worker result."
|
|
36
|
+
),
|
|
37
|
+
"",
|
|
38
|
+
"## Task",
|
|
39
|
+
f"- Task key: `{_require_string(manifest, 'taskKey')}`",
|
|
40
|
+
f"- Task type: `{_require_string(manifest, 'taskType')}`",
|
|
41
|
+
"",
|
|
42
|
+
"## Inputs",
|
|
43
|
+
*analysis_input_lines(manifest, active_context, plan),
|
|
44
|
+
"",
|
|
45
|
+
mcp_pointer_line(),
|
|
46
|
+
"",
|
|
47
|
+
"## Output Contract",
|
|
48
|
+
"- Read the Worker Preamble Path end-to-end before analysis.",
|
|
49
|
+
"- Write the worker result to Result Path and no other canonical result path.",
|
|
50
|
+
"- Write the audit sidecar and any tool-failure entries as the preamble requires.",
|
|
51
|
+
"- Cite evidence with file paths and line numbers whenever you make a claim.",
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def analysis_input_lines(
|
|
56
|
+
manifest: Mapping[str, Any],
|
|
57
|
+
active_context: Mapping[str, Any],
|
|
58
|
+
plan: PromptPlan,
|
|
59
|
+
) -> list[str]:
|
|
60
|
+
if plan.packet_only:
|
|
61
|
+
packet_path = instruction_path(manifest, active_context, "analysisPacketPath")
|
|
62
|
+
return existing_input_lines((("Primary analysis packet", packet_path),))
|
|
63
|
+
inputs = [
|
|
64
|
+
("Primary analysis packet", instruction_path(manifest, active_context, "analysisPacketPath")),
|
|
65
|
+
("Task brief", instruction_path(manifest, active_context, "taskBriefPath")),
|
|
66
|
+
("Analysis profile", instruction_path(manifest, active_context, "analysisProfilePath")),
|
|
67
|
+
("Analysis material", instruction_path(manifest, active_context, "analysisMaterialPath")),
|
|
68
|
+
("Reference expectations", instruction_path(manifest, active_context, "referenceExpectationsPath")),
|
|
69
|
+
("Clarification response", instruction_path(manifest, active_context, "clarificationResponsePath")),
|
|
70
|
+
]
|
|
71
|
+
return existing_input_lines(inputs)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def existing_input_lines(inputs: Sequence[tuple[str, str]]) -> list[str]:
|
|
75
|
+
lines = [f"- {label}: `{path}`" for label, path in inputs if path]
|
|
76
|
+
return lines or ["- No input paths were recorded in active-run-context."]
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def instruction_path(
|
|
80
|
+
manifest: Mapping[str, Any],
|
|
81
|
+
active_context: Mapping[str, Any],
|
|
82
|
+
key: str,
|
|
83
|
+
) -> str:
|
|
84
|
+
instruction_set = active_context.get("instructionSet")
|
|
85
|
+
if isinstance(instruction_set, Mapping):
|
|
86
|
+
value = _string_value(instruction_set.get(key))
|
|
87
|
+
if value:
|
|
88
|
+
return value
|
|
89
|
+
return _string_value(manifest.get(key))
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def mcp_pointer_line() -> str:
|
|
93
|
+
return (
|
|
94
|
+
'**MCP servers:** follow the task brief\'s "## Available MCP Servers" '
|
|
95
|
+
"section (already in your Required reading)."
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _require_string(payload: Mapping[str, Any], key: str) -> str:
|
|
100
|
+
value = _string_value(payload.get(key))
|
|
101
|
+
if not value:
|
|
102
|
+
raise ValueError(f"missing required string field: {key}")
|
|
103
|
+
return value
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _string_value(value: Any) -> str:
|
|
107
|
+
return value.strip() if isinstance(value, str) else ""
|
|
@@ -2,14 +2,18 @@
|
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
4
|
import re
|
|
5
|
+
from dataclasses import dataclass
|
|
5
6
|
from pathlib import Path
|
|
6
|
-
from typing import Mapping
|
|
7
|
+
from typing import Any, Mapping, Sequence
|
|
8
|
+
|
|
9
|
+
from .worker_prompt_policy import PromptPlan, resolve_prompt_plan_for_manifest
|
|
7
10
|
|
|
8
11
|
|
|
9
12
|
MAX_FINAL_VERIFICATION_DIRECTIVE_LINES = 40
|
|
10
13
|
MAX_FINAL_VERIFICATION_BODY_LINES = 96
|
|
11
14
|
|
|
12
15
|
_DIRECTIVE_HEADING = "## Run-specific directive"
|
|
16
|
+
_WORKER_ERROR_CONTRACT_HEADER = "**Worker Error Contract Path:**"
|
|
13
17
|
_PRIMARY_PACKET_RE = re.compile(
|
|
14
18
|
r"(?im)^-\s+Primary analysis packet:\s+`[^`\n]*analysis-packet\.md`\s*$"
|
|
15
19
|
)
|
|
@@ -63,6 +67,13 @@ _WORKER_LABEL_RE = re.compile(
|
|
|
63
67
|
)
|
|
64
68
|
|
|
65
69
|
|
|
70
|
+
@dataclass(frozen=True)
|
|
71
|
+
class PromptRecord:
|
|
72
|
+
worker_id: str
|
|
73
|
+
dispatch_kind: str
|
|
74
|
+
path: Path
|
|
75
|
+
|
|
76
|
+
|
|
66
77
|
def validate_final_verification_initial_prompt(text: str) -> list[str]:
|
|
67
78
|
"""Return deterministic compact-prompt contract violations."""
|
|
68
79
|
errors: list[str] = []
|
|
@@ -155,23 +166,105 @@ def validate_final_verification_prompt_paths(
|
|
|
155
166
|
prompt_paths: Mapping[str, Path],
|
|
156
167
|
) -> list[str]:
|
|
157
168
|
"""Validate persisted initial prompts and their normalized equality."""
|
|
158
|
-
|
|
169
|
+
records = [
|
|
170
|
+
PromptRecord(worker_id, "initial", path)
|
|
171
|
+
for worker_id, path in sorted(prompt_paths.items())
|
|
172
|
+
]
|
|
173
|
+
return validate_initial_prompt_records(
|
|
174
|
+
manifest={"taskType": "final-verification"},
|
|
175
|
+
records=records,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def validate_initial_prompt_records(
|
|
180
|
+
*,
|
|
181
|
+
manifest: Mapping[str, Any],
|
|
182
|
+
records: Sequence[PromptRecord],
|
|
183
|
+
) -> list[str]:
|
|
184
|
+
"""Validate prompt audiences and compare their normalized equality groups."""
|
|
159
185
|
errors: list[str] = []
|
|
160
|
-
|
|
186
|
+
equality_groups: dict[str, dict[str, str]] = {}
|
|
187
|
+
for record in records:
|
|
188
|
+
plan = _resolve_record_plan(manifest, record, errors)
|
|
189
|
+
if plan is None or plan.audience in {"lead-only", "reverify"}:
|
|
190
|
+
continue
|
|
161
191
|
try:
|
|
162
|
-
text = path.read_text(encoding="utf-8")
|
|
192
|
+
text = record.path.read_text(encoding="utf-8")
|
|
163
193
|
except OSError as exc:
|
|
164
|
-
errors.append(
|
|
194
|
+
errors.append(
|
|
195
|
+
f"{record.worker_id}: cannot read prompt {record.path}: {exc}"
|
|
196
|
+
)
|
|
165
197
|
continue
|
|
166
|
-
prompts[worker_id] = text
|
|
167
198
|
errors.extend(
|
|
168
|
-
f"{worker_id}: {error}"
|
|
169
|
-
for error in
|
|
199
|
+
f"{record.worker_id}: {error}"
|
|
200
|
+
for error in _validate_prompt_for_plan(text, plan, manifest)
|
|
170
201
|
)
|
|
171
|
-
|
|
202
|
+
if plan.equality_group:
|
|
203
|
+
group = equality_groups.setdefault(plan.equality_group, {})
|
|
204
|
+
group[record.worker_id] = text
|
|
205
|
+
for prompts in equality_groups.values():
|
|
206
|
+
errors.extend(validate_analysis_prompt_set(prompts))
|
|
172
207
|
return errors
|
|
173
208
|
|
|
174
209
|
|
|
210
|
+
def _resolve_record_plan(
|
|
211
|
+
manifest: Mapping[str, Any],
|
|
212
|
+
record: PromptRecord,
|
|
213
|
+
errors: list[str],
|
|
214
|
+
) -> PromptPlan | None:
|
|
215
|
+
try:
|
|
216
|
+
return resolve_prompt_plan_for_manifest(
|
|
217
|
+
manifest=manifest,
|
|
218
|
+
worker_id=record.worker_id,
|
|
219
|
+
dispatch_kind=record.dispatch_kind,
|
|
220
|
+
)
|
|
221
|
+
except ValueError as exc:
|
|
222
|
+
errors.append(f"{record.worker_id}: {exc}")
|
|
223
|
+
return None
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _validate_prompt_for_plan(
|
|
227
|
+
text: str,
|
|
228
|
+
plan: PromptPlan,
|
|
229
|
+
manifest: Mapping[str, Any],
|
|
230
|
+
) -> list[str]:
|
|
231
|
+
errors: list[str] = []
|
|
232
|
+
_require_non_empty_header(text, _WORKER_ERROR_CONTRACT_HEADER, errors)
|
|
233
|
+
if manifest.get("taskType") == "final-verification" and plan.audience == "analysis":
|
|
234
|
+
return [*errors, *validate_final_verification_initial_prompt(text)]
|
|
235
|
+
if not plan.allow_coding_preflight:
|
|
236
|
+
_reject_literal(
|
|
237
|
+
text,
|
|
238
|
+
"**Coding preflight pack:**",
|
|
239
|
+
"Coding preflight pack is forbidden for this prompt audience",
|
|
240
|
+
errors,
|
|
241
|
+
)
|
|
242
|
+
for prefix in plan.required_headers:
|
|
243
|
+
_require_non_empty_header(text, prefix, errors)
|
|
244
|
+
if plan.audience == "analysis":
|
|
245
|
+
packet_count = len(_PRIMARY_PACKET_RE.findall(text))
|
|
246
|
+
if packet_count != 1:
|
|
247
|
+
errors.append(
|
|
248
|
+
"exactly one Primary analysis packet path is required "
|
|
249
|
+
f"(found {packet_count})"
|
|
250
|
+
)
|
|
251
|
+
return errors
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def _require_non_empty_header(
|
|
255
|
+
text: str,
|
|
256
|
+
prefix: str,
|
|
257
|
+
errors: list[str],
|
|
258
|
+
) -> None:
|
|
259
|
+
matches = [
|
|
260
|
+
line.strip()
|
|
261
|
+
for line in text.splitlines()
|
|
262
|
+
if line.strip().startswith(prefix)
|
|
263
|
+
]
|
|
264
|
+
if len(matches) != 1 or not matches[0][len(prefix):].strip():
|
|
265
|
+
errors.append(f"exactly one non-empty {prefix} header is required")
|
|
266
|
+
|
|
267
|
+
|
|
175
268
|
def _reject_literal(
|
|
176
269
|
text: str,
|
|
177
270
|
literal: str,
|
|
@@ -5,6 +5,12 @@ from pathlib import Path
|
|
|
5
5
|
from typing import Any, Mapping
|
|
6
6
|
|
|
7
7
|
from .paths import okstra_home
|
|
8
|
+
from .worker_prompt_policy import (
|
|
9
|
+
PromptPlan,
|
|
10
|
+
WORKER_ERROR_CONTRACT_FILENAME,
|
|
11
|
+
WORKER_PREAMBLE_FILENAME_BY_AUDIENCE,
|
|
12
|
+
resolve_prompt_plan_for_manifest,
|
|
13
|
+
)
|
|
8
14
|
|
|
9
15
|
|
|
10
16
|
class WorkerPromptHeaderError(Exception):
|
|
@@ -36,30 +42,42 @@ def worker_prompt_headers(
|
|
|
36
42
|
prompt_rel: str,
|
|
37
43
|
result_rel: str,
|
|
38
44
|
worker_id: str,
|
|
45
|
+
dispatch_kind: str,
|
|
39
46
|
manifest: Mapping[str, Any],
|
|
40
47
|
active_context: Mapping[str, Any],
|
|
41
48
|
) -> list[str]:
|
|
42
49
|
"""Render the team-contract worker prompt anchor headers."""
|
|
43
50
|
prompt_path = _resolve_project_path(project_root, prompt_rel)
|
|
51
|
+
errors_log_path = _errors_log_path(project_root, manifest, active_context)
|
|
52
|
+
errors_sidecar_path = _worker_errors_sidecar_path(
|
|
53
|
+
project_root,
|
|
54
|
+
manifest,
|
|
55
|
+
active_context,
|
|
56
|
+
worker_id,
|
|
57
|
+
)
|
|
58
|
+
plan = _prompt_plan(manifest, worker_id, dispatch_kind)
|
|
44
59
|
headers = [
|
|
45
60
|
f"**Project Root:** {project_root}",
|
|
46
61
|
f"**Prompt History Path:** {prompt_rel}",
|
|
47
62
|
f"**Result Path:** {result_rel}",
|
|
48
63
|
f"Assigned worker prompt history path: {prompt_path}",
|
|
49
|
-
f"**Worker Preamble Path:** {_worker_preamble_path()}",
|
|
64
|
+
f"**Worker Preamble Path:** {_worker_preamble_path(plan, active_context)}",
|
|
65
|
+
f"**Worker Error Contract Path:** {_worker_error_contract_path(active_context)}",
|
|
50
66
|
]
|
|
51
|
-
if
|
|
67
|
+
if plan.allow_coding_preflight:
|
|
52
68
|
headers.append(
|
|
53
69
|
f"**Coding preflight pack:** {_coding_preflight_pack_path(active_context)}"
|
|
54
70
|
)
|
|
55
71
|
headers.extend([
|
|
56
|
-
f"**Errors log path:** {
|
|
57
|
-
|
|
58
|
-
f"**Errors sidecar path:** "
|
|
59
|
-
f"{_worker_errors_sidecar_path(project_root, manifest, active_context, worker_id)}"
|
|
60
|
-
),
|
|
72
|
+
f"**Errors log path:** {errors_log_path}",
|
|
73
|
+
f"**Errors sidecar path:** {errors_sidecar_path}",
|
|
61
74
|
READ_SCOPE_HEADER,
|
|
62
75
|
])
|
|
76
|
+
if _string_value(manifest.get("taskType")) == "improvement-discovery":
|
|
77
|
+
headers.append(
|
|
78
|
+
f"**Phase 1.5 Grilling Log:** "
|
|
79
|
+
f"{_improvement_grilling_log_path(project_root, manifest)}"
|
|
80
|
+
)
|
|
63
81
|
if _string_value(manifest.get("taskType")) == "final-verification":
|
|
64
82
|
headers.extend(_final_verification_target_headers(active_context))
|
|
65
83
|
return headers
|
|
@@ -86,8 +104,31 @@ def _final_verification_target_headers(
|
|
|
86
104
|
]
|
|
87
105
|
|
|
88
106
|
|
|
89
|
-
def _worker_preamble_path(
|
|
90
|
-
|
|
107
|
+
def _worker_preamble_path(
|
|
108
|
+
plan: PromptPlan,
|
|
109
|
+
active_context: Mapping[str, Any],
|
|
110
|
+
) -> Path:
|
|
111
|
+
runtime_resources = active_context.get("runtimeResources")
|
|
112
|
+
if isinstance(runtime_resources, Mapping):
|
|
113
|
+
paths = runtime_resources.get("workerPreamblePathByAudience")
|
|
114
|
+
if isinstance(paths, Mapping):
|
|
115
|
+
value = _string_value(paths.get(plan.audience))
|
|
116
|
+
if value:
|
|
117
|
+
return Path(value)
|
|
118
|
+
filename = WORKER_PREAMBLE_FILENAME_BY_AUDIENCE.get(
|
|
119
|
+
plan.audience,
|
|
120
|
+
WORKER_PREAMBLE_FILENAME_BY_AUDIENCE["analysis"],
|
|
121
|
+
)
|
|
122
|
+
return okstra_home() / "templates" / filename
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _worker_error_contract_path(active_context: Mapping[str, Any]) -> Path:
|
|
126
|
+
runtime_resources = active_context.get("runtimeResources")
|
|
127
|
+
if isinstance(runtime_resources, Mapping):
|
|
128
|
+
value = _string_value(runtime_resources.get("workerErrorContractPath"))
|
|
129
|
+
if value:
|
|
130
|
+
return Path(value)
|
|
131
|
+
return okstra_home() / "templates" / WORKER_ERROR_CONTRACT_FILENAME
|
|
91
132
|
|
|
92
133
|
|
|
93
134
|
def _coding_preflight_pack_path(active_context: Mapping[str, Any]) -> Path:
|
|
@@ -99,6 +140,35 @@ def _coding_preflight_pack_path(active_context: Mapping[str, Any]) -> Path:
|
|
|
99
140
|
return okstra_home() / "prompts" / "coding-preflight"
|
|
100
141
|
|
|
101
142
|
|
|
143
|
+
def _prompt_plan(
|
|
144
|
+
manifest: Mapping[str, Any],
|
|
145
|
+
worker_id: str,
|
|
146
|
+
dispatch_kind: str,
|
|
147
|
+
) -> PromptPlan:
|
|
148
|
+
try:
|
|
149
|
+
return resolve_prompt_plan_for_manifest(
|
|
150
|
+
manifest=manifest,
|
|
151
|
+
worker_id=worker_id,
|
|
152
|
+
dispatch_kind=dispatch_kind,
|
|
153
|
+
)
|
|
154
|
+
except ValueError as exc:
|
|
155
|
+
raise WorkerPromptHeaderError(str(exc)) from exc
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _improvement_grilling_log_path(
|
|
159
|
+
project_root: Path,
|
|
160
|
+
manifest: Mapping[str, Any],
|
|
161
|
+
) -> Path:
|
|
162
|
+
path = (
|
|
163
|
+
_run_directory_path(project_root, manifest)
|
|
164
|
+
/ "state"
|
|
165
|
+
/ "phase-1.5-grilling.md"
|
|
166
|
+
)
|
|
167
|
+
if not path.is_file():
|
|
168
|
+
raise WorkerPromptHeaderError(f"Phase 1.5 grilling log not found: {path}")
|
|
169
|
+
return path
|
|
170
|
+
|
|
171
|
+
|
|
102
172
|
def _errors_log_path(
|
|
103
173
|
project_root: Path,
|
|
104
174
|
manifest: Mapping[str, Any],
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"""Resolve worker prompt requirements from functional audience."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import Any, Literal, Mapping
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
PromptAudience = Literal[
|
|
9
|
+
"analysis",
|
|
10
|
+
"implementation-executor",
|
|
11
|
+
"implementation-verifier",
|
|
12
|
+
"report-writer",
|
|
13
|
+
"reverify",
|
|
14
|
+
"lead-only",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
FINAL_VERIFICATION_HEADERS = (
|
|
18
|
+
"**Worktree:**",
|
|
19
|
+
"**Verification scope:**",
|
|
20
|
+
"**Verification base ref:**",
|
|
21
|
+
"**Verification head ref:**",
|
|
22
|
+
"**Verification target path:**",
|
|
23
|
+
"**Verification target digest:**",
|
|
24
|
+
)
|
|
25
|
+
SUPPORTED_TASK_TYPES = frozenset({
|
|
26
|
+
"requirements-discovery",
|
|
27
|
+
"error-analysis",
|
|
28
|
+
"implementation-planning",
|
|
29
|
+
"improvement-discovery",
|
|
30
|
+
"implementation",
|
|
31
|
+
"final-verification",
|
|
32
|
+
"release-handoff",
|
|
33
|
+
})
|
|
34
|
+
WORKER_PREAMBLE_FILENAME_BY_AUDIENCE = {
|
|
35
|
+
"analysis": "worker-prompt-preamble.md",
|
|
36
|
+
"implementation-executor": "implementation-worker-preamble.md",
|
|
37
|
+
"implementation-verifier": "implementation-worker-preamble.md",
|
|
38
|
+
"report-writer": "report-writer-prompt-preamble.md",
|
|
39
|
+
}
|
|
40
|
+
WORKER_ERROR_CONTRACT_FILENAME = "worker-error-contract.md"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass(frozen=True)
|
|
44
|
+
class PromptPlan:
|
|
45
|
+
audience: PromptAudience
|
|
46
|
+
equality_group: str | None
|
|
47
|
+
packet_only: bool
|
|
48
|
+
allow_coding_preflight: bool
|
|
49
|
+
required_headers: tuple[str, ...]
|
|
50
|
+
max_body_lines: int | None
|
|
51
|
+
max_directive_lines: int | None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def resolve_prompt_plan(
|
|
55
|
+
*,
|
|
56
|
+
task_type: str,
|
|
57
|
+
worker_id: str,
|
|
58
|
+
executor_worker_id: str | None,
|
|
59
|
+
dispatch_kind: str,
|
|
60
|
+
) -> PromptPlan:
|
|
61
|
+
"""Resolve prompt policy without consulting provider or model identity."""
|
|
62
|
+
if task_type not in SUPPORTED_TASK_TYPES:
|
|
63
|
+
raise ValueError(f"unsupported task type: {task_type}")
|
|
64
|
+
if task_type == "release-handoff":
|
|
65
|
+
return _plan("lead-only")
|
|
66
|
+
if not worker_id.strip():
|
|
67
|
+
raise ValueError("worker ID is required")
|
|
68
|
+
if dispatch_kind.startswith("reverify-r"):
|
|
69
|
+
return _plan("reverify")
|
|
70
|
+
if task_type == "implementation" and not executor_worker_id:
|
|
71
|
+
raise ValueError("implementation executor worker ID is required")
|
|
72
|
+
if worker_id == "report-writer":
|
|
73
|
+
return _plan("report-writer")
|
|
74
|
+
if task_type == "implementation" and worker_id == executor_worker_id:
|
|
75
|
+
return _plan(
|
|
76
|
+
"implementation-executor",
|
|
77
|
+
allow_coding_preflight=True,
|
|
78
|
+
required_headers=("**Worktree:**",),
|
|
79
|
+
)
|
|
80
|
+
if task_type == "implementation":
|
|
81
|
+
return _plan(
|
|
82
|
+
"implementation-verifier",
|
|
83
|
+
equality_group="implementation-verifier-core",
|
|
84
|
+
allow_coding_preflight=True,
|
|
85
|
+
required_headers=("**Worktree:**",),
|
|
86
|
+
)
|
|
87
|
+
if task_type == "final-verification":
|
|
88
|
+
return _plan(
|
|
89
|
+
"analysis",
|
|
90
|
+
equality_group="analysis-core",
|
|
91
|
+
packet_only=True,
|
|
92
|
+
required_headers=FINAL_VERIFICATION_HEADERS,
|
|
93
|
+
max_body_lines=96,
|
|
94
|
+
max_directive_lines=40,
|
|
95
|
+
)
|
|
96
|
+
if task_type == "improvement-discovery":
|
|
97
|
+
return _plan(
|
|
98
|
+
"analysis",
|
|
99
|
+
equality_group="analysis-core",
|
|
100
|
+
packet_only=True,
|
|
101
|
+
required_headers=("**Phase 1.5 Grilling Log:**",),
|
|
102
|
+
)
|
|
103
|
+
return _plan("analysis", equality_group="analysis-core", packet_only=True)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def resolve_prompt_plan_for_manifest(
|
|
107
|
+
*,
|
|
108
|
+
manifest: Mapping[str, Any],
|
|
109
|
+
worker_id: str,
|
|
110
|
+
dispatch_kind: str,
|
|
111
|
+
) -> PromptPlan:
|
|
112
|
+
"""Resolve policy from canonical task and executor manifest fields."""
|
|
113
|
+
task_type = _required_string(manifest, "taskType")
|
|
114
|
+
return resolve_prompt_plan(
|
|
115
|
+
task_type=task_type,
|
|
116
|
+
worker_id=worker_id,
|
|
117
|
+
executor_worker_id=_executor_worker_id(manifest),
|
|
118
|
+
dispatch_kind=dispatch_kind,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _plan(
|
|
123
|
+
audience: PromptAudience,
|
|
124
|
+
*,
|
|
125
|
+
equality_group: str | None = None,
|
|
126
|
+
packet_only: bool = False,
|
|
127
|
+
allow_coding_preflight: bool = False,
|
|
128
|
+
required_headers: tuple[str, ...] = (),
|
|
129
|
+
max_body_lines: int | None = None,
|
|
130
|
+
max_directive_lines: int | None = None,
|
|
131
|
+
) -> PromptPlan:
|
|
132
|
+
return PromptPlan(
|
|
133
|
+
audience=audience,
|
|
134
|
+
equality_group=equality_group,
|
|
135
|
+
packet_only=packet_only,
|
|
136
|
+
allow_coding_preflight=allow_coding_preflight,
|
|
137
|
+
required_headers=required_headers,
|
|
138
|
+
max_body_lines=max_body_lines,
|
|
139
|
+
max_directive_lines=max_directive_lines,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _executor_worker_id(manifest: Mapping[str, Any]) -> str | None:
|
|
144
|
+
team_contract = manifest.get("teamContract")
|
|
145
|
+
if not isinstance(team_contract, Mapping):
|
|
146
|
+
return None
|
|
147
|
+
executor = team_contract.get("executor")
|
|
148
|
+
if not isinstance(executor, Mapping):
|
|
149
|
+
return None
|
|
150
|
+
provider = executor.get("provider")
|
|
151
|
+
return provider.strip() if isinstance(provider, str) and provider.strip() else None
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _required_string(payload: Mapping[str, Any], key: str) -> str:
|
|
155
|
+
value = payload.get(key)
|
|
156
|
+
if not isinstance(value, str) or not value.strip():
|
|
157
|
+
raise ValueError(f"missing required string field: {key}")
|
|
158
|
+
return value.strip()
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Implementation Worker Prompt Preamble (canonical)
|
|
2
|
+
|
|
3
|
+
This file is shared by the `implementation-executor` and `implementation-verifier` audiences. Read it end-to-end, then read the role sidecar enumerated by the prompt. Before work, also read the shared file named by `**Worker Error Contract Path:**`; error rules live only there.
|
|
4
|
+
|
|
5
|
+
## Operating standard (read before any output)
|
|
6
|
+
|
|
7
|
+
Work like a senior engineer who owns this result, not a commentator on it.
|
|
8
|
+
- Evidence over assertion — back every claim with a file:line, or mark it an explicit assumption. Never state the unverified as fact.
|
|
9
|
+
- Read before you reason — read each required input end to end; when you lack basis, write "insufficient evidence" instead of filling the gap plausibly.
|
|
10
|
+
- Shortest sound path — chase the most likely cause first; don't re-verify what is settled or pad with restatement.
|
|
11
|
+
- Decide, don't survey — when options exist, give the trade-off and one recommendation, not an exhaustive list.
|
|
12
|
+
- Fit what's here — match the surrounding code and prose; size the response to the request.
|
|
13
|
+
- Hold your own line — reason from your independent angle; do not drift toward the other workers' likely answers or echo the user. Triangulation fails if your view isn't genuinely yours.
|
|
14
|
+
|
|
15
|
+
## Required reading
|
|
16
|
+
|
|
17
|
+
- Read the implementation role sidecar named by the prompt end-to-end. The executor sidecar owns mutation behavior; the verifier sidecar owns read-only review behavior.
|
|
18
|
+
- Read `overview.md` and `clean-code.md` under `**Coding preflight pack:**`, then follow every matching language, framework, and architecture route before editing or verification.
|
|
19
|
+
- Read the approved implementation deliverable and any effective design-preparation block enumerated by the prompt.
|
|
20
|
+
|
|
21
|
+
## Worktree and command discipline
|
|
22
|
+
|
|
23
|
+
- `**Worktree:**` is the canonical checkout. Project commands run with that directory as cwd.
|
|
24
|
+
- `.okstra/**` artifacts remain anchored at `**Project Root:**`; the worktree may not contain them.
|
|
25
|
+
- Only the executor may mutate source files. Verifiers remain read-only except for okstra result/audit artifacts and project-declared QA commands.
|
|
26
|
+
- Execute the role sidecar's pre-write, post-write, QA, and return gates without substituting analysis Sections 1–6 or report-authoring instructions.
|
|
27
|
+
|
|
28
|
+
## Anchor headers
|
|
29
|
+
|
|
30
|
+
Every initial implementation prompt begins with these generated common anchors in this exact order, before its implementation-specific body:
|
|
31
|
+
|
|
32
|
+
1. `**Project Root:** <absolute-path>`
|
|
33
|
+
2. `**Prompt History Path:** <project-relative-path>`
|
|
34
|
+
3. `**Result Path:** <project-relative-path>`
|
|
35
|
+
4. `Assigned worker prompt history path: <absolute-path>`
|
|
36
|
+
5. `**Worker Preamble Path:** <absolute-path>`
|
|
37
|
+
6. `**Worker Error Contract Path:** <absolute-path>`
|
|
38
|
+
7. `**Coding preflight pack:** <absolute-path>`
|
|
39
|
+
8. `**Errors log path:** <absolute-path>`
|
|
40
|
+
9. `**Errors sidecar path:** <absolute-path>`
|
|
41
|
+
10. `**Read scope:** <allowlist>`
|
|
42
|
+
|
|
43
|
+
The implementation body additionally carries `**Worktree:**` and its role-sidecar inputs. Do not synthesize any missing path.
|
|
44
|
+
|
|
45
|
+
## Return message to the lead
|
|
46
|
+
|
|
47
|
+
Begin the inline return with the exact `**Model:** <Role>, <modelExecutionValue>` line from the prompt, followed by the role-sidecar status summary. Never invent or abbreviate the model.
|
|
48
|
+
|
|
49
|
+
## Writing style
|
|
50
|
+
|
|
51
|
+
Use concise evidence-backed prose. Keep identifiers, paths, symbols, model names, CLI flags, and status tokens in English. Translate meaning rather than dictionary words.
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# Operating Standard (canonical)
|
|
2
2
|
|
|
3
3
|
This file is the single source of truth for the operating standard injected into
|
|
4
|
-
every okstra agent. The "Common core" block below is inlined verbatim into
|
|
5
|
-
`templates
|
|
6
|
-
`prompts/lead/okstra-lead-contract.md` (lead). A unit test asserts
|
|
7
|
-
|
|
4
|
+
every okstra agent. The "Common core" block below is inlined verbatim into the
|
|
5
|
+
three audience preambles under `templates/` and
|
|
6
|
+
`prompts/lead/okstra-lead-contract.md` (lead). A unit test asserts every inlined
|
|
7
|
+
copy matches this core — update all copies together.
|
|
8
8
|
|
|
9
9
|
## Common core
|
|
10
10
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Report Writer Prompt Preamble (canonical)
|
|
2
|
+
|
|
3
|
+
This file is the audience-specific contract for `report-writer`. Read it end-to-end. Before work, also read the shared file named by `**Worker Error Contract Path:**`; error rules live only there.
|
|
4
|
+
|
|
5
|
+
## Operating standard (read before any output)
|
|
6
|
+
|
|
7
|
+
Work like a senior engineer who owns this result, not a commentator on it.
|
|
8
|
+
- Evidence over assertion — back every claim with a file:line, or mark it an explicit assumption. Never state the unverified as fact.
|
|
9
|
+
- Read before you reason — read each required input end to end; when you lack basis, write "insufficient evidence" instead of filling the gap plausibly.
|
|
10
|
+
- Shortest sound path — chase the most likely cause first; don't re-verify what is settled or pad with restatement.
|
|
11
|
+
- Decide, don't survey — when options exist, give the trade-off and one recommendation, not an exhaustive list.
|
|
12
|
+
- Fit what's here — match the surrounding code and prose; size the response to the request.
|
|
13
|
+
|
|
14
|
+
## Required reading
|
|
15
|
+
|
|
16
|
+
Read every input enumerated by the Phase 6 dispatch end-to-end: task/analysis inputs, worker results, convergence state, the instruction-set-local `final-report-template.md`, and the task-type excerpt `final-report-schema.json`. Do not pull the full repository template or schema when the scoped instruction-set copies are provided.
|
|
17
|
+
|
|
18
|
+
Write Reading Confirmation to the report-writer audit sidecar, not the rendered final report. Resolve `.okstra/**` paths against `**Project Root:**`.
|
|
19
|
+
|
|
20
|
+
## Report authoring handoff
|
|
21
|
+
|
|
22
|
+
- Author the data.json at `**Result Path:**` and the audit file at `**Worker Result Path:**`.
|
|
23
|
+
- Follow the task-type schema excerpt and Phase 6 report-writer contract. Do not perform independent analysis, edit source code, or load implementation coding-preflight resources.
|
|
24
|
+
- Invoke `okstra render-final-report <Result Path>` after writing data.json and verify the markdown sibling exists before returning.
|
|
25
|
+
- Preserve source item IDs, convergence classifications, round history, and unresolved dissent; do not recompute them from intuition.
|
|
26
|
+
|
|
27
|
+
## Anchor headers
|
|
28
|
+
|
|
29
|
+
The generated prompt selects this file through `**Worker Preamble Path:**` and includes `**Worker Error Contract Path:**`, error paths, and read scope. It never includes `**Coding preflight pack:**`.
|
|
30
|
+
|
|
31
|
+
## Return message to the lead
|
|
32
|
+
|
|
33
|
+
Begin the inline return with the exact `**Model:** Report writer worker, <modelExecutionValue>` line from the prompt, followed by the artifact status. Never invent or abbreviate the model.
|
|
34
|
+
|
|
35
|
+
## Writing style
|
|
36
|
+
|
|
37
|
+
Use concise reader-facing prose and honor the report language. Keep identifiers, paths, symbols, model names, CLI flags, and status tokens in English. Translate meaning rather than dictionary words.
|