okstra 0.155.0 → 0.156.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/package.json
CHANGED
package/runtime/BUILD.json
CHANGED
|
@@ -110,11 +110,30 @@ def task_id(manifest: Mapping[str, Any]) -> str:
|
|
|
110
110
|
raise FinalizeError(f"cannot infer task id from taskKey: {task_key}")
|
|
111
111
|
|
|
112
112
|
|
|
113
|
-
def
|
|
113
|
+
def _category_seq(manifest: Mapping[str, Any], category: str) -> str:
|
|
114
114
|
seqs = manifest.get("runSequencesByCategory")
|
|
115
115
|
if not isinstance(seqs, Mapping):
|
|
116
116
|
raise FinalizeError("run manifest has no runSequencesByCategory object")
|
|
117
|
-
return require_string(seqs,
|
|
117
|
+
return require_string(seqs, category)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def run_seq(manifest: Mapping[str, Any]) -> str:
|
|
121
|
+
"""How many times this task type has run — what a lead event records."""
|
|
122
|
+
return _category_seq(manifest, "manifests")
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def report_seq(manifest: Mapping[str, Any]) -> str:
|
|
126
|
+
"""The `reports` sequence — the one the final-report filename carries.
|
|
127
|
+
|
|
128
|
+
Categories advance independently (`paths.compute_run_paths`), so a run whose
|
|
129
|
+
predecessor produced no report reuses the free report slot and `run_seq`
|
|
130
|
+
outruns this one. This value reaches `render-report-views --seq`, which
|
|
131
|
+
stamps it into the HTML `runMeta` naming the exported
|
|
132
|
+
`user-response-<task-type>-<seq>.md`; `user_response.write_sidecar` derives
|
|
133
|
+
that same name from the report path, so anything but `reports` makes the two
|
|
134
|
+
answer paths write different files for one report.
|
|
135
|
+
"""
|
|
136
|
+
return _category_seq(manifest, "reports")
|
|
118
137
|
|
|
119
138
|
|
|
120
139
|
def task_manifest_path(project_root: Path, manifest: Mapping[str, Any]) -> Path:
|
|
@@ -204,7 +223,7 @@ class FinalizeContext:
|
|
|
204
223
|
task_key=require_string(manifest, "taskKey"),
|
|
205
224
|
task_type=require_string(manifest, "taskType"),
|
|
206
225
|
task_group=task_group(manifest),
|
|
207
|
-
seq=
|
|
226
|
+
seq=report_seq(manifest),
|
|
208
227
|
final_status_path=resolve_optional_path(
|
|
209
228
|
project_root, manifest.get("finalStatusPath")
|
|
210
229
|
),
|
|
@@ -218,6 +218,103 @@ def parse_stage_map_file(path: Path) -> list[StageMapStage]:
|
|
|
218
218
|
return parse_stage_map_text(text, source_plan_path=str(resolved))
|
|
219
219
|
|
|
220
220
|
|
|
221
|
+
def _require_data_positive_int(
|
|
222
|
+
value: Any, field: str, row_number: int, source_plan_path: str,
|
|
223
|
+
) -> int:
|
|
224
|
+
if not isinstance(value, int) or isinstance(value, bool) or value < 1:
|
|
225
|
+
raise StageMapError(
|
|
226
|
+
"stage_map",
|
|
227
|
+
f"structured Stage Map row {row_number} has invalid {field} {value!r}",
|
|
228
|
+
source_plan_path,
|
|
229
|
+
)
|
|
230
|
+
return value
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def _require_data_text(
|
|
234
|
+
value: Any, field: str, row_number: int, source_plan_path: str,
|
|
235
|
+
) -> str:
|
|
236
|
+
if not isinstance(value, str) or not value.strip():
|
|
237
|
+
raise StageMapError(
|
|
238
|
+
"stage_map",
|
|
239
|
+
f"structured Stage Map row {row_number} has invalid {field} {value!r}",
|
|
240
|
+
source_plan_path,
|
|
241
|
+
)
|
|
242
|
+
return value
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def _parse_data_stage_map_row(
|
|
246
|
+
value: Any, row_number: int, source_plan_path: str,
|
|
247
|
+
) -> StageMapStage:
|
|
248
|
+
if not isinstance(value, dict):
|
|
249
|
+
raise StageMapError(
|
|
250
|
+
"stage_map",
|
|
251
|
+
f"structured Stage Map row {row_number} must be an object",
|
|
252
|
+
source_plan_path,
|
|
253
|
+
)
|
|
254
|
+
stage_number = _require_data_positive_int(
|
|
255
|
+
value.get("stage"), "stage", row_number, source_plan_path
|
|
256
|
+
)
|
|
257
|
+
step_count = _require_data_positive_int(
|
|
258
|
+
value.get("stepCount"), "stepCount", row_number, source_plan_path
|
|
259
|
+
)
|
|
260
|
+
title = _require_data_text(
|
|
261
|
+
value.get("title"), "title", row_number, source_plan_path
|
|
262
|
+
)
|
|
263
|
+
depends_on = _require_data_text(
|
|
264
|
+
value.get("dependsOn"), "dependsOn", row_number, source_plan_path
|
|
265
|
+
)
|
|
266
|
+
exit_summary = _require_data_text(
|
|
267
|
+
value.get("exitContractSummary"),
|
|
268
|
+
"exitContractSummary",
|
|
269
|
+
row_number,
|
|
270
|
+
source_plan_path,
|
|
271
|
+
)
|
|
272
|
+
return StageMapStage(
|
|
273
|
+
stage_number,
|
|
274
|
+
title,
|
|
275
|
+
_parse_depends_on(depends_on.strip(), row_number, source_plan_path),
|
|
276
|
+
step_count,
|
|
277
|
+
exit_summary,
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def _parse_schema_v2_stage_map(
|
|
282
|
+
data: dict[str, Any], source_plan_path: str,
|
|
283
|
+
) -> list[StageMapStage]:
|
|
284
|
+
planning = data.get("implementationPlanning")
|
|
285
|
+
stage_map = planning.get("stageMap") if isinstance(planning, dict) else None
|
|
286
|
+
if not isinstance(stage_map, list) or not stage_map:
|
|
287
|
+
raise StageMapError(
|
|
288
|
+
"stage_map",
|
|
289
|
+
"structured report requires a non-empty implementationPlanning.stageMap",
|
|
290
|
+
source_plan_path,
|
|
291
|
+
)
|
|
292
|
+
stages = [
|
|
293
|
+
_parse_data_stage_map_row(value, row_number, source_plan_path)
|
|
294
|
+
for row_number, value in enumerate(stage_map, start=1)
|
|
295
|
+
]
|
|
296
|
+
_validate_stage_numbers(stages, source_plan_path)
|
|
297
|
+
return stages
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def _parse_stage_map_source(markdown_path: Path) -> list[StageMapStage]:
|
|
301
|
+
resolved = markdown_path.resolve()
|
|
302
|
+
data_path = resolved.with_suffix(".data.json")
|
|
303
|
+
if not data_path.exists():
|
|
304
|
+
return parse_stage_map_file(resolved)
|
|
305
|
+
try:
|
|
306
|
+
data = json.loads(data_path.read_text(encoding="utf-8"))
|
|
307
|
+
except (OSError, UnicodeError, json.JSONDecodeError) as exc:
|
|
308
|
+
raise StageMapError("stage_map", str(exc), str(data_path)) from exc
|
|
309
|
+
if not isinstance(data, dict):
|
|
310
|
+
raise StageMapError(
|
|
311
|
+
"stage_map", "structured report must be an object", str(data_path)
|
|
312
|
+
)
|
|
313
|
+
if data.get("schemaVersion") != "2.0":
|
|
314
|
+
return parse_stage_map_file(resolved)
|
|
315
|
+
return _parse_schema_v2_stage_map(data, str(data_path))
|
|
316
|
+
|
|
317
|
+
|
|
221
318
|
def stage_map_records(stages: Iterable[StageMapStage]) -> list[dict[str, Any]]:
|
|
222
319
|
return [
|
|
223
320
|
{
|
|
@@ -256,7 +353,7 @@ def load_task_stage_map(
|
|
|
256
353
|
return StageMapSnapshot(
|
|
257
354
|
"ready",
|
|
258
355
|
str(resolved),
|
|
259
|
-
stage_map_records(
|
|
356
|
+
stage_map_records(_parse_stage_map_source(resolved)),
|
|
260
357
|
)
|
|
261
358
|
|
|
262
359
|
|