okstra 0.122.0 → 0.123.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.
Files changed (53) hide show
  1. package/README.md +4 -2
  2. package/docs/architecture/storage-model.md +14 -0
  3. package/docs/architecture.md +34 -6
  4. package/docs/cli.md +45 -5
  5. package/docs/for-ai/README.md +2 -2
  6. package/docs/for-ai/skills/okstra-rollup.md +1 -1
  7. package/docs/for-ai/skills/{okstra-schedule.md → okstra-schedule-gen.md} +3 -3
  8. package/docs/project-structure-overview.md +3 -2
  9. package/docs/task-process/implementation-planning.md +33 -9
  10. package/docs/task-process/implementation.md +21 -2
  11. package/package.json +1 -1
  12. package/runtime/BUILD.json +2 -2
  13. package/runtime/bin/lib/okstra/usage.sh +3 -3
  14. package/runtime/prompts/launch.template.md +5 -2
  15. package/runtime/prompts/lead/convergence.md +1 -1
  16. package/runtime/prompts/lead/okstra-lead-contract.md +2 -2
  17. package/runtime/prompts/lead/plan-body-verification.md +29 -0
  18. package/runtime/prompts/lead/report-writer.md +9 -3
  19. package/runtime/prompts/profiles/_common-contract.md +1 -1
  20. package/runtime/prompts/profiles/_implementation-deliverable.md +3 -1
  21. package/runtime/prompts/profiles/_implementation-executor.md +5 -0
  22. package/runtime/prompts/profiles/_implementation-verifier.md +1 -1
  23. package/runtime/prompts/profiles/final-verification.md +2 -0
  24. package/runtime/prompts/profiles/implementation-planning.md +11 -1
  25. package/runtime/prompts/wizard/prompts.ko.json +44 -0
  26. package/runtime/python/okstra_ctl/codex_dispatch.py +23 -1
  27. package/runtime/python/okstra_ctl/design_prep.py +1462 -0
  28. package/runtime/python/okstra_ctl/design_surfaces.py +243 -0
  29. package/runtime/python/okstra_ctl/final_report_schema.py +33 -1
  30. package/runtime/python/okstra_ctl/implementation_stage.py +35 -0
  31. package/runtime/python/okstra_ctl/incremental_carry.py +294 -21
  32. package/runtime/python/okstra_ctl/incremental_scope.py +51 -5
  33. package/runtime/python/okstra_ctl/material.py +1 -1
  34. package/runtime/python/okstra_ctl/model_discovery.py +98 -0
  35. package/runtime/python/okstra_ctl/models.py +8 -3
  36. package/runtime/python/okstra_ctl/render.py +5 -0
  37. package/runtime/python/okstra_ctl/run.py +53 -5
  38. package/runtime/python/okstra_ctl/user_response.py +67 -2
  39. package/runtime/python/okstra_ctl/wizard.py +283 -3
  40. package/runtime/python/okstra_token_usage/report.py +11 -0
  41. package/runtime/schemas/final-report-v1.0.schema.json +336 -0
  42. package/runtime/skills/okstra-inspect/SKILL.md +2 -2
  43. package/runtime/skills/okstra-rollup/SKILL.md +1 -1
  44. package/runtime/skills/{okstra-schedule → okstra-schedule-gen}/SKILL.md +2 -2
  45. package/runtime/skills/okstra-user-response/SKILL.md +8 -6
  46. package/runtime/templates/reports/final-report.template.md +67 -0
  47. package/runtime/templates/reports/i18n/en.json +31 -0
  48. package/runtime/templates/reports/i18n/ko.json +31 -0
  49. package/runtime/validators/validate-run.py +426 -5
  50. package/runtime/validators/validate-schedule.py +4 -4
  51. package/src/cli-registry.mjs +7 -0
  52. package/src/commands/inspect/design-prep.mjs +23 -0
  53. package/src/lib/skill-catalog.mjs +2 -1
@@ -45,6 +45,18 @@ from okstra_ctl.md_table import ( # noqa: E402
45
45
  split_pipe_row as _split_pipe_row,
46
46
  )
47
47
  from okstra_ctl.final_report_paths import final_report_data_path as _data_path_for # noqa: E402
48
+ from okstra_ctl.design_prep import ( # noqa: E402
49
+ DesignPrepError,
50
+ _planning_seq as _design_prep_planning_seq,
51
+ _render_request as _render_design_prep_request,
52
+ _report_language as _design_prep_report_language,
53
+ )
54
+ from okstra_ctl.design_surfaces import ( # noqa: E402
55
+ RULES as DESIGN_SURFACE_RULES,
56
+ DesignSurfaceError,
57
+ detect_design_surfaces,
58
+ expected_prep_plan_item_id,
59
+ )
48
60
 
49
61
  TERMINAL_STATUSES = {"completed", "timeout", "error", "not-run"}
50
62
  ATTEMPTED_STATUSES = {"completed", "timeout", "error"}
@@ -1530,7 +1542,12 @@ def _load_final_report_data(report_path: Path) -> dict:
1530
1542
  return {}
1531
1543
 
1532
1544
 
1533
- def validate_final_report_data(report_path: Path, failures: list[str]) -> None:
1545
+ def validate_final_report_data(
1546
+ report_path: Path,
1547
+ failures: list[str],
1548
+ *,
1549
+ report_contracts: set[str] | None = None,
1550
+ ) -> None:
1534
1551
  """Validate the final-report data.json against the v1.0 schema.
1535
1552
 
1536
1553
  The data.json is the source-of-truth that the renderer reads to
@@ -1584,15 +1601,29 @@ def validate_final_report_data(report_path: Path, failures: list[str]) -> None:
1584
1601
  if task_type == "final-verification":
1585
1602
  _validate_final_verification_consistency(data, failures)
1586
1603
  elif task_type == "implementation-planning":
1604
+ active_report_contracts = report_contracts or set()
1605
+ enforce_design_prep = _DESIGN_PREP_CONTRACT in active_report_contracts
1587
1606
  _validate_implementation_planning_cross_project(data, failures)
1588
1607
  _validate_implementation_planning_decision_drafts(data, failures)
1589
1608
  _validate_plan_body_gate_recompute(data, failures)
1590
- _validate_plan_item_extraction_completeness(data, failures)
1609
+ _validate_plan_item_extraction_completeness(
1610
+ data,
1611
+ failures,
1612
+ enforce_design_prep=enforce_design_prep,
1613
+ )
1591
1614
  _validate_plan_item_subject_substance(data, failures)
1592
1615
  _validate_plan_body_clarification_matching(data, failures)
1593
1616
  _validate_disagree_has_fixability(data, failures)
1594
1617
  _validate_self_fix_before_clarification(data, failures)
1595
1618
  _validate_requirement_coverage_covered_by(data, failures)
1619
+ warnings = _validate_design_prep_contract(
1620
+ data,
1621
+ report_path,
1622
+ active_report_contracts,
1623
+ failures,
1624
+ )
1625
+ for warning in warnings:
1626
+ print(f"validate-run: warning: {warning}", file=sys.stderr)
1596
1627
 
1597
1628
 
1598
1629
  # path:line (foo.service.ts:268), an in-report ID (C-001 / E-006 / P-004), a
@@ -1867,7 +1898,12 @@ _PLAN_ITEM_SOURCES = (
1867
1898
  )
1868
1899
 
1869
1900
 
1870
- def _validate_plan_item_extraction_completeness(data: dict, failures: list[str]) -> None:
1901
+ def _validate_plan_item_extraction_completeness(
1902
+ data: dict,
1903
+ failures: list[str],
1904
+ *,
1905
+ enforce_design_prep: bool = False,
1906
+ ) -> None:
1871
1907
  """H2 — when a verification round ran, every plan-body deliverable item must
1872
1908
  appear as a matching `P-*` verdict item. Closes the omission hole where the
1873
1909
  lead silently drops a weak item (e.g. an out-of-order rollback) from
@@ -1903,6 +1939,384 @@ def _validate_plan_item_extraction_completeness(data: dict, failures: list[str])
1903
1939
  "extracted for cross-verification — a dropped item escapes the "
1904
1940
  "gate (plan-body-verification.md Plan-item extraction)."
1905
1941
  )
1942
+ if enforce_design_prep:
1943
+ _validate_expected_prep_plan_items(ip, extracted_ids, failures)
1944
+
1945
+
1946
+ def _validate_expected_prep_plan_items(
1947
+ planning: dict,
1948
+ extracted_ids: list[str],
1949
+ failures: list[str],
1950
+ ) -> None:
1951
+ try:
1952
+ expected_prep_ids = {}
1953
+ for trigger in detect_design_surfaces(planning):
1954
+ item_id = expected_prep_plan_item_id(trigger.stage, trigger.kind)
1955
+ expected_prep_ids[item_id.upper()] = item_id
1956
+ except DesignSurfaceError as exc:
1957
+ failures.append(
1958
+ f"final-report data.json: design-surface detection failed: {exc}"
1959
+ )
1960
+ return
1961
+ except Exception as exc: # noqa: BLE001
1962
+ failures.append(
1963
+ "final-report data.json: design-surface detection failed on "
1964
+ f"malformed input: {exc}"
1965
+ )
1966
+ return
1967
+ actual_prep_ids = [
1968
+ item_id for item_id in extracted_ids if item_id.startswith("P-PREP-")
1969
+ ]
1970
+ missing_prep_ids = [
1971
+ expected_prep_ids[item_id]
1972
+ for item_id in sorted(set(expected_prep_ids) - set(actual_prep_ids))
1973
+ ]
1974
+ if missing_prep_ids:
1975
+ failures.append(
1976
+ "final-report data.json: planBodyVerification.planItems is missing "
1977
+ "detector-required design-prep verdict item(s): "
1978
+ + ", ".join(missing_prep_ids)
1979
+ )
1980
+ unexpected_prep_ids = sorted(set(actual_prep_ids) - set(expected_prep_ids))
1981
+ if unexpected_prep_ids:
1982
+ failures.append(
1983
+ "final-report data.json: planBodyVerification.planItems contains "
1984
+ "unexpected design-prep verdict item(s) not produced by the "
1985
+ "detector: "
1986
+ + ", ".join(unexpected_prep_ids)
1987
+ )
1988
+ duplicate_prep_ids = sorted(
1989
+ item_id
1990
+ for item_id in set(actual_prep_ids)
1991
+ if actual_prep_ids.count(item_id) > 1
1992
+ )
1993
+ if duplicate_prep_ids:
1994
+ failures.append(
1995
+ "final-report data.json: planBodyVerification.planItems contains "
1996
+ "duplicate detector-required design-prep verdict item(s): "
1997
+ + ", ".join(duplicate_prep_ids)
1998
+ )
1999
+
2000
+
2001
+ _DESIGN_PREP_CONTRACT = "implementation-design-prep-v1"
2002
+ _DESIGN_PREP_REQUEST_STATUSES = {"provisional", "blocked"}
2003
+ _DESIGN_PREP_TERMINAL_STATUSES = {"ready", "not-applicable"}
2004
+
2005
+
2006
+ def _normalize_report_contracts(raw_contracts: object) -> set[str]:
2007
+ if isinstance(raw_contracts, str):
2008
+ candidates = [raw_contracts]
2009
+ elif isinstance(raw_contracts, (list, tuple, set, frozenset)):
2010
+ candidates = raw_contracts
2011
+ else:
2012
+ return set()
2013
+ return {
2014
+ value.strip()
2015
+ for value in candidates
2016
+ if isinstance(value, str) and value.strip()
2017
+ }
2018
+
2019
+
2020
+ def _design_prep_rows(
2021
+ planning: dict,
2022
+ failures: list[str],
2023
+ ) -> tuple[list[dict], dict[str, dict]] | None:
2024
+ preparation = planning.get("designPreparation")
2025
+ if not isinstance(preparation, dict):
2026
+ failures.append(
2027
+ "final-report data.json: implementationPlanning.designPreparation "
2028
+ "is malformed; expected an object"
2029
+ )
2030
+ return None
2031
+ raw_items = preparation.get("items")
2032
+ if not isinstance(raw_items, list) or any(
2033
+ not isinstance(item, dict) for item in raw_items
2034
+ ):
2035
+ failures.append(
2036
+ "final-report data.json: implementationPlanning.designPreparation.items "
2037
+ "is malformed; expected an array of objects"
2038
+ )
2039
+ return None
2040
+ items = list(raw_items)
2041
+ items_by_id: dict[str, dict] = {}
2042
+ for item in items:
2043
+ item_id = item.get("id")
2044
+ if not isinstance(item_id, str) or not item_id:
2045
+ failures.append(
2046
+ "final-report data.json: designPreparation item has malformed id"
2047
+ )
2048
+ continue
2049
+ if item_id in items_by_id:
2050
+ failures.append(
2051
+ f"final-report data.json: duplicate designPreparation item {item_id}"
2052
+ )
2053
+ items_by_id[item_id] = item
2054
+ return items, items_by_id
2055
+
2056
+
2057
+ def _design_surface_coverage(
2058
+ planning: dict,
2059
+ failures: list[str],
2060
+ ) -> dict[tuple[int, str], list[dict]] | None:
2061
+ raw_stages = planning.get("stages")
2062
+ if not isinstance(raw_stages, list):
2063
+ failures.append(
2064
+ "final-report data.json: implementationPlanning.stages is malformed"
2065
+ )
2066
+ return None
2067
+ coverage_by_key: dict[tuple[int, str], list[dict]] = {}
2068
+ for stage in raw_stages:
2069
+ if not isinstance(stage, dict) or not isinstance(stage.get("stage"), int):
2070
+ failures.append("final-report data.json: planning stage is malformed")
2071
+ continue
2072
+ rows = stage.get("designSurfaceCoverage", [])
2073
+ if not isinstance(rows, list):
2074
+ failures.append(
2075
+ f"final-report data.json: Stage {stage['stage']} "
2076
+ "designSurfaceCoverage is malformed"
2077
+ )
2078
+ continue
2079
+ for row in rows:
2080
+ if not isinstance(row, dict) or not isinstance(row.get("kind"), str):
2081
+ failures.append(
2082
+ f"final-report data.json: Stage {stage['stage']} has malformed "
2083
+ "designSurfaceCoverage row"
2084
+ )
2085
+ continue
2086
+ key = (stage["stage"], row["kind"])
2087
+ coverage_by_key.setdefault(key, []).append(row)
2088
+ return coverage_by_key
2089
+
2090
+
2091
+ def _trigger_evidence_identity(raw_evidence: object) -> set[tuple[object, object, object]]:
2092
+ if not isinstance(raw_evidence, (list, tuple)):
2093
+ return set()
2094
+ return {
2095
+ (row.get("step"), row.get("field"), row.get("match"))
2096
+ for row in raw_evidence
2097
+ if isinstance(row, dict)
2098
+ }
2099
+
2100
+
2101
+ def _validate_detector_coverage(
2102
+ planning: dict,
2103
+ coverage_by_key: dict[tuple[int, str], list[dict]],
2104
+ failures: list[str],
2105
+ ) -> None:
2106
+ triggers = detect_design_surfaces(planning)
2107
+ expected_by_key = {(trigger.stage, trigger.kind): trigger for trigger in triggers}
2108
+ detector_kinds = {rule.kind for rule in DESIGN_SURFACE_RULES}
2109
+ for key, trigger in expected_by_key.items():
2110
+ stage, kind = key
2111
+ if kind == "manual-user-test":
2112
+ failures.append(
2113
+ "final-report data.json: detector must not generate "
2114
+ f"manual-user-test for Stage {stage}"
2115
+ )
2116
+ continue
2117
+ rows = coverage_by_key.get(key, [])
2118
+ if len(rows) != 1:
2119
+ failures.append(
2120
+ f"final-report data.json: detector trigger Stage {stage} kind "
2121
+ f"{kind} requires exactly one designSurfaceCoverage row; "
2122
+ f"found {len(rows)}"
2123
+ )
2124
+ continue
2125
+ expected_evidence = {
2126
+ (evidence.step, evidence.field, evidence.match)
2127
+ for evidence in trigger.evidence
2128
+ }
2129
+ actual_evidence = _trigger_evidence_identity(rows[0].get("triggerEvidence"))
2130
+ if actual_evidence != expected_evidence:
2131
+ failures.append(
2132
+ f"final-report data.json: Stage {stage} {kind} triggerEvidence "
2133
+ "does not match detector output"
2134
+ )
2135
+ for stage, kind in coverage_by_key:
2136
+ if kind in detector_kinds and (stage, kind) not in expected_by_key:
2137
+ failures.append(
2138
+ f"final-report data.json: Stage {stage} {kind} coverage has no "
2139
+ "matching detector trigger"
2140
+ )
2141
+
2142
+
2143
+ def _coverage_prep_ids(row: dict) -> list[str]:
2144
+ if isinstance(row.get("prepItemId"), str):
2145
+ return [row["prepItemId"]]
2146
+ raw_ids = row.get("prepItemIds")
2147
+ if isinstance(raw_ids, list):
2148
+ return [item_id for item_id in raw_ids if isinstance(item_id, str)]
2149
+ return []
2150
+
2151
+
2152
+ def _validate_prep_references(
2153
+ items: list[dict],
2154
+ items_by_id: dict[str, dict],
2155
+ coverage_by_key: dict[tuple[int, str], list[dict]],
2156
+ failures: list[str],
2157
+ ) -> None:
2158
+ referenced: set[tuple[str, int, str]] = set()
2159
+ for (stage, kind), rows in coverage_by_key.items():
2160
+ for row in rows:
2161
+ if row.get("disposition") != "prep-item":
2162
+ continue
2163
+ for item_id in _coverage_prep_ids(row):
2164
+ item = items_by_id.get(item_id)
2165
+ if item is None:
2166
+ failures.append(
2167
+ f"final-report data.json: coverage references missing {item_id}"
2168
+ )
2169
+ continue
2170
+ if stage not in (item.get("stageRefs") or []):
2171
+ failures.append(
2172
+ f"final-report data.json: {item_id}.stageRefs does not "
2173
+ f"include coverage Stage {stage}"
2174
+ )
2175
+ if item.get("kind") != kind:
2176
+ failures.append(
2177
+ f"final-report data.json: {item_id}.kind does not match "
2178
+ f"coverage kind {kind}"
2179
+ )
2180
+ referenced.add((item_id, stage, kind))
2181
+ for item in items:
2182
+ item_id = item.get("id")
2183
+ kind = item.get("kind")
2184
+ stage_refs = item.get("stageRefs")
2185
+ if not isinstance(item_id, str) or not isinstance(kind, str):
2186
+ continue
2187
+ if not isinstance(stage_refs, list):
2188
+ failures.append(
2189
+ f"final-report data.json: {item_id}.stageRefs is malformed"
2190
+ )
2191
+ continue
2192
+ for stage in stage_refs:
2193
+ if (item_id, stage, kind) not in referenced:
2194
+ failures.append(
2195
+ f"final-report data.json: {item_id} Stage {stage} kind {kind} "
2196
+ "has no matching prep-item coverage reference"
2197
+ )
2198
+
2199
+
2200
+ def _validate_design_prep_states(items: list[dict], failures: list[str]) -> None:
2201
+ for item in items:
2202
+ item_id = str(item.get("id") or "<missing>")
2203
+ stage_refs = item.get("stageRefs") or []
2204
+ review_at = item.get("reviewAt")
2205
+ if isinstance(review_at, dict) and "stage" in review_at:
2206
+ if review_at.get("stage") not in stage_refs:
2207
+ failures.append(
2208
+ f"final-report data.json: {item_id}.reviewAt.stage must belong "
2209
+ "to stageRefs"
2210
+ )
2211
+ status = item.get("status")
2212
+ if status == "blocked" and (
2213
+ not isinstance(item.get("humanConfirmation"), dict)
2214
+ or item["humanConfirmation"].get("required") is not True
2215
+ ):
2216
+ failures.append(
2217
+ f"final-report data.json: blocked {item_id} requires "
2218
+ "humanConfirmation.required=true"
2219
+ )
2220
+ if status in _DESIGN_PREP_REQUEST_STATUSES and not isinstance(
2221
+ item.get("requestPath"), str
2222
+ ):
2223
+ failures.append(
2224
+ f"final-report data.json: {status} {item_id} requires requestPath"
2225
+ )
2226
+ if status in _DESIGN_PREP_TERMINAL_STATUSES and "requestPath" in item:
2227
+ failures.append(
2228
+ f"final-report data.json: terminal {item_id} must not carry requestPath"
2229
+ )
2230
+
2231
+
2232
+ def _validate_design_prep_requests(
2233
+ data: dict,
2234
+ report_path: Path,
2235
+ items: list[dict],
2236
+ failures: list[str],
2237
+ ) -> None:
2238
+ data_path = _data_path_for(report_path)
2239
+ planning_seq = _design_prep_planning_seq(data_path)
2240
+ report_language = _design_prep_report_language(data)
2241
+ for item in items:
2242
+ if item.get("status") not in _DESIGN_PREP_REQUEST_STATUSES:
2243
+ continue
2244
+ item_id = str(item.get("id") or "<missing>")
2245
+ target, expected = _render_design_prep_request(
2246
+ data_path=data_path,
2247
+ planning_seq=planning_seq,
2248
+ report_language=report_language,
2249
+ item=item,
2250
+ )
2251
+ if not target.is_file():
2252
+ failures.append(
2253
+ f"final-report data.json: design-prep request is missing for {item_id}: "
2254
+ f"{target}"
2255
+ )
2256
+ continue
2257
+ try:
2258
+ actual = target.read_bytes()
2259
+ except OSError as exc:
2260
+ failures.append(
2261
+ f"final-report data.json: cannot read design-prep request for "
2262
+ f"{item_id}: {exc}"
2263
+ )
2264
+ continue
2265
+ if actual != expected:
2266
+ failures.append(
2267
+ f"final-report data.json: design-prep request content or "
2268
+ f"fingerprint is stale for {item_id}: {target}"
2269
+ )
2270
+
2271
+
2272
+ def _validate_design_prep_contract(
2273
+ data: dict,
2274
+ report_path: Path | None,
2275
+ report_contracts: set[str],
2276
+ failures: list[str],
2277
+ ) -> list[str]:
2278
+ warnings: list[str] = []
2279
+ planning = data.get("implementationPlanning")
2280
+ if not isinstance(planning, dict):
2281
+ if _DESIGN_PREP_CONTRACT in report_contracts:
2282
+ failures.append(
2283
+ "final-report data.json: implementationPlanning is malformed"
2284
+ )
2285
+ return warnings
2286
+ preparation = planning.get("designPreparation")
2287
+ if not isinstance(preparation, dict):
2288
+ if _DESIGN_PREP_CONTRACT in report_contracts:
2289
+ failures.append(
2290
+ "final-report data.json: marker implementation-design-prep-v1 "
2291
+ "requires implementationPlanning.designPreparation"
2292
+ )
2293
+ else:
2294
+ warnings.append("legacy-unassessed")
2295
+ return warnings
2296
+ if _DESIGN_PREP_CONTRACT not in report_contracts:
2297
+ return warnings
2298
+ try:
2299
+ parsed = _design_prep_rows(planning, failures)
2300
+ coverage_by_key = _design_surface_coverage(planning, failures)
2301
+ if parsed is None or coverage_by_key is None:
2302
+ return warnings
2303
+ items, items_by_id = parsed
2304
+ _validate_detector_coverage(planning, coverage_by_key, failures)
2305
+ _validate_prep_references(items, items_by_id, coverage_by_key, failures)
2306
+ _validate_design_prep_states(items, failures)
2307
+ if report_path is not None:
2308
+ _validate_design_prep_requests(data, report_path, items, failures)
2309
+ except (DesignSurfaceError, DesignPrepError, KeyError, TypeError, ValueError) as exc:
2310
+ failures.append(
2311
+ "final-report data.json: design-preparation contract is malformed: "
2312
+ f"{exc}"
2313
+ )
2314
+ except Exception as exc: # noqa: BLE001
2315
+ failures.append(
2316
+ "final-report data.json: design-preparation validation failed closed "
2317
+ f"on malformed input: {exc}"
2318
+ )
2319
+ return warnings
1906
2320
 
1907
2321
 
1908
2322
  # A `subject` this short or shaped like a bare `P-Opt-1` id is a placeholder,
@@ -2927,7 +3341,14 @@ def main() -> int:
2927
3341
  # contain every required section. Substring checks below are a
2928
3342
  # safety net for hand-edited or pre-v1.0 reports.
2929
3343
  task_type = effective_run_task_type(run_manifest, task_manifest)
2930
- validate_final_report_data(report_path, failures)
3344
+ report_contracts = _normalize_report_contracts(
3345
+ run_manifest.get("reportContracts")
3346
+ )
3347
+ validate_final_report_data(
3348
+ report_path,
3349
+ failures,
3350
+ report_contracts=report_contracts,
3351
+ )
2931
3352
  _validate_fix_cycle(
2932
3353
  run_manifest, _load_final_report_data(report_path), failures
2933
3354
  )
@@ -2994,7 +3415,7 @@ def main() -> int:
2994
3415
  write_json(task_manifest_path, task_manifest)
2995
3416
 
2996
3417
  # Best-effort: regenerate discovery/task-catalog.json so downstream
2997
- # tools (okstra-schedule, FleetView listings, etc.) don't read a stale
3418
+ # tools (okstra-schedule-gen, FleetView listings, etc.) don't read a stale
2998
3419
  # snapshot frozen at instruction-set generation time.
2999
3420
  catalog_ok, catalog_msg = _refresh_task_catalog(project_root, task_manifest)
3000
3421
  if catalog_ok:
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env python3
2
2
  """
3
3
  Validate that an okstra schedule Markdown file conforms to the Section Contract
4
- defined in skills/okstra-schedule/SKILL.md.
4
+ defined in skills/okstra-schedule-gen/SKILL.md.
5
5
 
6
6
  Usage:
7
7
  python3 validators/validate-schedule.py <path-to-schedule.md>
8
8
 
9
9
  Exits 0 if compliant, 1 with a list of violations otherwise. Intended to be
10
- called by the okstra-schedule skill (self-validation step) and by humans /
10
+ called by the okstra-schedule-gen skill (self-validation step) and by humans /
11
11
  hooks before committing a schedule.
12
12
  """
13
13
 
@@ -658,13 +658,13 @@ def main(argv: list[str]) -> int:
658
658
  path = Path(argv[1])
659
659
  violations = validate(path)
660
660
  if not violations:
661
- print(f"OK: {path} conforms to okstra-schedule Section Contract")
661
+ print(f"OK: {path} conforms to okstra-schedule-gen Section Contract")
662
662
  return 0
663
663
  print(f"FAIL: {path}", file=sys.stderr)
664
664
  for v in violations:
665
665
  print(f" - {v}", file=sys.stderr)
666
666
  print(
667
- "\nFix per skills/okstra-schedule/SKILL.md "
667
+ "\nFix per skills/okstra-schedule-gen/SKILL.md "
668
668
  "(Section Contract).",
669
669
  file=sys.stderr,
670
670
  )
@@ -187,6 +187,13 @@ export const COMMAND_REGISTRY = [
187
187
  category: "introspection",
188
188
  summary: ["Answer a task's open clarification questions in-session and write the response sidecar"],
189
189
  },
190
+ {
191
+ name: "design-prep",
192
+ module: "./commands/inspect/design-prep.mjs",
193
+ export: "run",
194
+ category: "introspection",
195
+ summary: ["Review and record AI-prepared implementation design inputs"],
196
+ },
190
197
  {
191
198
  name: "rollup",
192
199
  module: "./commands/inspect/rollup.mjs",
@@ -0,0 +1,23 @@
1
+ import { runPythonModule } from "../../lib/python-helper.mjs";
2
+
3
+ const USAGE = `okstra design-prep — review AI-prepared implementation design inputs
4
+
5
+ Usage:
6
+ okstra design-prep list [--project-root <path>] [--report <path>]
7
+ okstra design-prep show --report <path> [--item PREP-001]
8
+ okstra design-prep write --report <path> --item PREP-001 \\
9
+ --decision <accept-draft|modify-draft|reject-draft|defer> \\
10
+ [--overrides <json-object>] [--notes <text>] --confirmed
11
+ `;
12
+
13
+ export async function run(args) {
14
+ if (args.includes("--help") || args.includes("-h")) {
15
+ process.stdout.write(USAGE);
16
+ return 0;
17
+ }
18
+ const { code } = await runPythonModule({
19
+ module: "okstra_ctl.design_prep",
20
+ args,
21
+ });
22
+ return code ?? 1;
23
+ }
@@ -13,7 +13,7 @@ export const USER_SKILL_NAMES = Object.freeze([
13
13
  "okstra-inspect",
14
14
  "okstra-rollup",
15
15
  "okstra-usage",
16
- "okstra-schedule",
16
+ "okstra-schedule-gen",
17
17
  "okstra-container-build",
18
18
  "okstra-graphify",
19
19
  "okstra-pr-gen",
@@ -33,6 +33,7 @@ export const OBSOLETE_SKILL_NAMES = Object.freeze([
33
33
  "okstra-coding-preflight",
34
34
  "okstra-container",
35
35
  "okstra-brief",
36
+ "okstra-schedule",
36
37
  // Pre-merge status/history/logs/report/time skills, since folded into
37
38
  // okstra-inspect. Absent here, upgrades left them stranded in agent skill
38
39
  // homes and their pre-.okstra-migration bodies kept triggering.