open-research-protocol 0.4.24 → 0.4.25

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/cli/orp.py CHANGED
@@ -134,6 +134,9 @@ DEFAULT_HOSTED_BASE_URL = "https://orp.earth"
134
134
  KERNEL_SCHEMA_VERSION = "1.0.0"
135
135
  FRONTIER_SCHEMA_VERSION = "1.0.0"
136
136
  FRONTIER_BANDS = ("exact", "structured", "horizon")
137
+ FRONTIER_ACTIVE_STATUSES = {"active", "in_progress", "running"}
138
+ FRONTIER_PENDING_STATUSES = {"", "pending", "planned", "ready"}
139
+ FRONTIER_TERMINAL_STATUSES = {"complete", "completed", "done", "skipped", "terminal"}
137
140
  YOUTUBE_SOURCE_SCHEMA_VERSION = "1.0.0"
138
141
  EXCHANGE_REPORT_SCHEMA_VERSION = "1.0.0"
139
142
  MAINTENANCE_STATE_SCHEMA_VERSION = "1.0.0"
@@ -5869,10 +5872,12 @@ def _frontier_paths(repo_root: Path) -> dict[str, Path]:
5869
5872
  "roadmap_json": root / "roadmap.json",
5870
5873
  "checklist_json": root / "checklist.json",
5871
5874
  "stack_json": root / "version-stack.json",
5875
+ "additional_json": root / "additional-items.json",
5872
5876
  "state_md": root / "STATE.md",
5873
5877
  "roadmap_md": root / "ROADMAP.md",
5874
5878
  "checklist_md": root / "CHECKLIST.md",
5875
5879
  "stack_md": root / "VERSION_STACK.md",
5880
+ "additional_md": root / "ADDITIONAL_ITEMS.md",
5876
5881
  }
5877
5882
 
5878
5883
 
@@ -5923,6 +5928,18 @@ def _default_frontier_stack_payload(program_id: str, label: str) -> dict[str, An
5923
5928
  }
5924
5929
 
5925
5930
 
5931
+ def _default_frontier_additional_payload(program_id: str = "", label: str = "") -> dict[str, Any]:
5932
+ return {
5933
+ "schema_version": FRONTIER_SCHEMA_VERSION,
5934
+ "kind": "orp_frontier_additional_items",
5935
+ "program_id": str(program_id).strip(),
5936
+ "label": str(label).strip(),
5937
+ "active_list_id": "",
5938
+ "active_item_id": "",
5939
+ "lists": [],
5940
+ }
5941
+
5942
+
5926
5943
  def _frontier_load_stack(repo_root: Path) -> dict[str, Any]:
5927
5944
  payload = _read_json_if_exists(_frontier_paths(repo_root)["stack_json"])
5928
5945
  if not payload:
@@ -5976,6 +5993,485 @@ def _frontier_find_phase(
5976
5993
  return None
5977
5994
 
5978
5995
 
5996
+ def _frontier_load_additional(repo_root: Path, stack: dict[str, Any] | None = None) -> dict[str, Any]:
5997
+ payload = _read_json_if_exists(_frontier_paths(repo_root)["additional_json"])
5998
+ if not payload:
5999
+ stack_payload = stack if isinstance(stack, dict) else _read_json_if_exists(_frontier_paths(repo_root)["stack_json"])
6000
+ return _default_frontier_additional_payload(
6001
+ str((stack_payload or {}).get("program_id", "")).strip(),
6002
+ str((stack_payload or {}).get("label", "")).strip(),
6003
+ )
6004
+ if not isinstance(payload.get("lists"), list):
6005
+ payload["lists"] = []
6006
+ payload["active_list_id"] = str(payload.get("active_list_id", "")).strip()
6007
+ payload["active_item_id"] = str(payload.get("active_item_id", "")).strip()
6008
+ return payload
6009
+
6010
+
6011
+ def _frontier_find_additional_list(payload: dict[str, Any], list_id: str) -> dict[str, Any] | None:
6012
+ lists = payload.get("lists")
6013
+ if not isinstance(lists, list):
6014
+ return None
6015
+ for row in lists:
6016
+ if isinstance(row, dict) and str(row.get("id", "")).strip() == list_id:
6017
+ return row
6018
+ return None
6019
+
6020
+
6021
+ def _frontier_find_additional_item(item_list: dict[str, Any], item_id: str) -> dict[str, Any] | None:
6022
+ items = item_list.get("items")
6023
+ if not isinstance(items, list):
6024
+ return None
6025
+ for item in items:
6026
+ if isinstance(item, dict) and str(item.get("id", "")).strip() == item_id:
6027
+ return item
6028
+ return None
6029
+
6030
+
6031
+ def _frontier_active_additional_item(payload: dict[str, Any]) -> tuple[dict[str, Any] | None, dict[str, Any] | None]:
6032
+ list_id = str(payload.get("active_list_id", "")).strip()
6033
+ item_id = str(payload.get("active_item_id", "")).strip()
6034
+ if not list_id or not item_id:
6035
+ return (None, None)
6036
+ item_list = _frontier_find_additional_list(payload, list_id)
6037
+ if not isinstance(item_list, dict):
6038
+ return (None, None)
6039
+ item = _frontier_find_additional_item(item_list, item_id)
6040
+ if not isinstance(item, dict):
6041
+ return (None, None)
6042
+ return (item_list, item)
6043
+
6044
+
6045
+ def _frontier_normalize_additional_statuses(payload: dict[str, Any]) -> None:
6046
+ lists = payload.get("lists")
6047
+ if not isinstance(lists, list):
6048
+ payload["lists"] = []
6049
+ return
6050
+ for item_list in lists:
6051
+ if not isinstance(item_list, dict):
6052
+ continue
6053
+ items = item_list.get("items")
6054
+ if not isinstance(items, list):
6055
+ item_list["items"] = []
6056
+ items = item_list["items"]
6057
+ for item in items:
6058
+ if isinstance(item, dict):
6059
+ item["status"] = str(item.get("status", "")).strip() or "pending"
6060
+ if items and all(str(item.get("status", "")).strip() in {"complete", "skipped"} for item in items if isinstance(item, dict)):
6061
+ item_list["status"] = "complete"
6062
+ else:
6063
+ item_list["status"] = str(item_list.get("status", "")).strip() or "pending"
6064
+
6065
+
6066
+ def _frontier_next_pending_additional_item(payload: dict[str, Any]) -> tuple[dict[str, Any] | None, dict[str, Any] | None]:
6067
+ _frontier_normalize_additional_statuses(payload)
6068
+ lists = payload.get("lists")
6069
+ if not isinstance(lists, list):
6070
+ return (None, None)
6071
+ for item_list in lists:
6072
+ if not isinstance(item_list, dict) or str(item_list.get("status", "")).strip() in {"complete", "skipped"}:
6073
+ continue
6074
+ items = item_list.get("items")
6075
+ if not isinstance(items, list):
6076
+ continue
6077
+ for item in items:
6078
+ if isinstance(item, dict) and str(item.get("status", "")).strip() in {"", "pending"}:
6079
+ return (item_list, item)
6080
+ return (None, None)
6081
+
6082
+
6083
+ def _frontier_status(raw: Any, *, default: str = "pending") -> str:
6084
+ text = str(raw or "").strip().lower().replace(" ", "_").replace("-", "_")
6085
+ return text or default
6086
+
6087
+
6088
+ def _frontier_status_is_active(raw: Any) -> bool:
6089
+ return _frontier_status(raw, default="") in FRONTIER_ACTIVE_STATUSES
6090
+
6091
+
6092
+ def _frontier_status_is_pending(raw: Any) -> bool:
6093
+ return _frontier_status(raw, default="") in FRONTIER_PENDING_STATUSES
6094
+
6095
+
6096
+ def _frontier_status_is_terminal(raw: Any) -> bool:
6097
+ return _frontier_status(raw, default="") in FRONTIER_TERMINAL_STATUSES
6098
+
6099
+
6100
+ def _frontier_diagnostic_ok(issues: list[dict[str, Any]], *, strict: bool = False) -> bool:
6101
+ if any(str(issue.get("severity", "")).strip() == "error" for issue in issues):
6102
+ return False
6103
+ if strict and any(str(issue.get("severity", "")).strip() == "warning" for issue in issues):
6104
+ return False
6105
+ return True
6106
+
6107
+
6108
+ def _frontier_stack_summary(stack: dict[str, Any] | None) -> dict[str, int]:
6109
+ summary = {
6110
+ "versions": 0,
6111
+ "milestones": 0,
6112
+ "phases": 0,
6113
+ }
6114
+ if not isinstance(stack, dict):
6115
+ return summary
6116
+ versions = stack.get("versions")
6117
+ if not isinstance(versions, list):
6118
+ return summary
6119
+ for version in versions:
6120
+ if not isinstance(version, dict):
6121
+ continue
6122
+ summary["versions"] += 1
6123
+ milestones = version.get("milestones")
6124
+ if not isinstance(milestones, list):
6125
+ continue
6126
+ for milestone in milestones:
6127
+ if not isinstance(milestone, dict):
6128
+ continue
6129
+ summary["milestones"] += 1
6130
+ phases = milestone.get("phases")
6131
+ if isinstance(phases, list):
6132
+ summary["phases"] += len([phase for phase in phases if isinstance(phase, dict)])
6133
+ return summary
6134
+
6135
+
6136
+ def _frontier_compact_additional_item(item_list: dict[str, Any], item: dict[str, Any]) -> dict[str, str]:
6137
+ return {
6138
+ "list_id": str(item_list.get("id", "")).strip(),
6139
+ "list_label": str(item_list.get("label", "")).strip(),
6140
+ "item_id": str(item.get("id", "")).strip(),
6141
+ "item_label": str(item.get("label", "")).strip(),
6142
+ "status": _frontier_status(item.get("status")),
6143
+ }
6144
+
6145
+
6146
+ def _frontier_additional_summary(payload: dict[str, Any]) -> dict[str, Any]:
6147
+ active_list_id = str(payload.get("active_list_id", "")).strip()
6148
+ active_item_id = str(payload.get("active_item_id", "")).strip()
6149
+ summary: dict[str, Any] = {
6150
+ "lists": 0,
6151
+ "items": 0,
6152
+ "pending_items": 0,
6153
+ "active_items": 0,
6154
+ "complete_items": 0,
6155
+ "skipped_items": 0,
6156
+ "open_items": 0,
6157
+ "active_list_id": active_list_id,
6158
+ "active_item_id": active_item_id,
6159
+ "active_pointer_partial": bool(active_list_id) != bool(active_item_id),
6160
+ "active_pointer_valid": False,
6161
+ "active_item_status": "",
6162
+ "next_pending": None,
6163
+ }
6164
+ lists = payload.get("lists")
6165
+ if not isinstance(lists, list):
6166
+ return summary
6167
+ summary["lists"] = len([row for row in lists if isinstance(row, dict)])
6168
+ for item_list in lists:
6169
+ if not isinstance(item_list, dict):
6170
+ continue
6171
+ items = item_list.get("items")
6172
+ if not isinstance(items, list):
6173
+ continue
6174
+ for item in items:
6175
+ if not isinstance(item, dict):
6176
+ continue
6177
+ status = _frontier_status(item.get("status"))
6178
+ summary["items"] += 1
6179
+ if _frontier_status_is_active(status):
6180
+ summary["active_items"] += 1
6181
+ summary["open_items"] += 1
6182
+ elif status == "skipped":
6183
+ summary["skipped_items"] += 1
6184
+ elif _frontier_status_is_terminal(status):
6185
+ summary["complete_items"] += 1
6186
+ else:
6187
+ summary["pending_items"] += 1
6188
+ summary["open_items"] += 1
6189
+ if summary["next_pending"] is None:
6190
+ summary["next_pending"] = _frontier_compact_additional_item(item_list, item)
6191
+
6192
+ if (
6193
+ active_list_id
6194
+ and active_item_id
6195
+ and str(item_list.get("id", "")).strip() == active_list_id
6196
+ and str(item.get("id", "")).strip() == active_item_id
6197
+ ):
6198
+ summary["active_pointer_valid"] = True
6199
+ summary["active_item_status"] = status
6200
+
6201
+ return summary
6202
+
6203
+
6204
+ def _frontier_terminal_declared(state: dict[str, Any] | None, stack: dict[str, Any] | None) -> bool:
6205
+ state_obj = state if isinstance(state, dict) else {}
6206
+ stack_obj = stack if isinstance(stack, dict) else {}
6207
+ if bool(state_obj.get("terminal")) or bool(stack_obj.get("terminal")):
6208
+ return True
6209
+ completion_status = _frontier_status(
6210
+ state_obj.get("completion_status", state_obj.get("completionStatus", stack_obj.get("completion_status", ""))),
6211
+ default="",
6212
+ )
6213
+ return completion_status in {"complete", "completed", "done", "terminal"}
6214
+
6215
+
6216
+ def _frontier_build_continuation_payload(
6217
+ repo_root: Path,
6218
+ stack: dict[str, Any] | None,
6219
+ state: dict[str, Any] | None,
6220
+ *,
6221
+ include_structural_issues: bool = True,
6222
+ strict: bool = False,
6223
+ ) -> dict[str, Any]:
6224
+ issues: list[dict[str, Any]] = []
6225
+ paths = _frontier_paths(repo_root)
6226
+ stack_obj = stack if isinstance(stack, dict) else None
6227
+ state_obj = state if isinstance(state, dict) else None
6228
+
6229
+ if stack_obj is None and include_structural_issues:
6230
+ issues.append({"severity": "error", "code": "missing_stack", "message": "frontier version stack is missing."})
6231
+ if state_obj is None and include_structural_issues:
6232
+ issues.append({"severity": "error", "code": "missing_state", "message": "frontier state is missing."})
6233
+
6234
+ additional = _frontier_load_additional(repo_root, stack_obj)
6235
+ additional_summary = _frontier_additional_summary(additional)
6236
+ stack_summary = _frontier_stack_summary(stack_obj)
6237
+ blockers = _coerce_string_list(state_obj.get("blocked_by") if isinstance(state_obj, dict) else [])
6238
+ terminal_declared = _frontier_terminal_declared(state_obj, stack_obj)
6239
+ active_primary = False
6240
+ active_primary_status = ""
6241
+ active_primary_kind = ""
6242
+ active_primary_id = ""
6243
+ next_action = str(state_obj.get("next_action", "")).strip() if isinstance(state_obj, dict) else ""
6244
+ suggested_next_command = ""
6245
+
6246
+ if stack_obj is not None and state_obj is not None:
6247
+ active_version = str(state_obj.get("active_version", "")).strip()
6248
+ active_milestone = str(state_obj.get("active_milestone", "")).strip()
6249
+ active_phase = str(state_obj.get("active_phase", "")).strip()
6250
+ version = _frontier_find_version(stack_obj, active_version) if active_version else None
6251
+ _, milestone = _frontier_find_milestone(stack_obj, active_milestone) if active_milestone else (None, None)
6252
+ phase = _frontier_find_phase(milestone, active_phase) if active_phase and isinstance(milestone, dict) else None
6253
+
6254
+ if isinstance(version, dict):
6255
+ version_status = _frontier_status(version.get("status", ""))
6256
+ if _frontier_status_is_terminal(version_status):
6257
+ issues.append(
6258
+ {
6259
+ "severity": "error",
6260
+ "code": "stale_active_version_complete",
6261
+ "message": f"active version `{active_version}` is marked `{version_status}`; advance the frontier or declare terminal completion.",
6262
+ }
6263
+ )
6264
+ elif active_version:
6265
+ active_primary = True
6266
+ active_primary_kind = "version"
6267
+ active_primary_id = active_version
6268
+ active_primary_status = version_status
6269
+
6270
+ if isinstance(milestone, dict):
6271
+ milestone_status = _frontier_status(milestone.get("status", ""))
6272
+ if _frontier_status_is_terminal(milestone_status):
6273
+ issues.append(
6274
+ {
6275
+ "severity": "error",
6276
+ "code": "stale_active_milestone_complete",
6277
+ "message": f"active milestone `{active_milestone}` is marked `{milestone_status}`; advance the frontier or declare terminal completion.",
6278
+ }
6279
+ )
6280
+ if active_primary_kind == "version":
6281
+ active_primary = False
6282
+ elif active_milestone:
6283
+ active_primary = True
6284
+ active_primary_kind = "milestone"
6285
+ active_primary_id = active_milestone
6286
+ active_primary_status = milestone_status
6287
+
6288
+ if isinstance(phase, dict):
6289
+ phase_status = _frontier_status(phase.get("status", ""))
6290
+ if _frontier_status_is_terminal(phase_status):
6291
+ issues.append(
6292
+ {
6293
+ "severity": "error",
6294
+ "code": "stale_active_phase_complete",
6295
+ "message": f"active phase `{active_phase}` is marked `{phase_status}`; record the handoff and move the frontier to the next phase or queue item.",
6296
+ }
6297
+ )
6298
+ active_primary = False
6299
+ elif active_phase:
6300
+ active_primary = True
6301
+ active_primary_kind = "phase"
6302
+ active_primary_id = active_phase
6303
+ active_primary_status = phase_status
6304
+
6305
+ if additional_summary["active_pointer_partial"]:
6306
+ issues.append(
6307
+ {
6308
+ "severity": "error",
6309
+ "code": "partial_active_additional_pointer",
6310
+ "message": "frontier additional queue has only one of active_list_id or active_item_id set.",
6311
+ }
6312
+ )
6313
+
6314
+ active_list_id = str(additional_summary["active_list_id"]).strip()
6315
+ active_item_id = str(additional_summary["active_item_id"]).strip()
6316
+ if active_list_id or active_item_id:
6317
+ active_list, active_item = _frontier_active_additional_item(additional)
6318
+ if active_list is None:
6319
+ issues.append(
6320
+ {"severity": "error", "code": "missing_active_additional_list", "message": f"active additional list `{active_list_id}` does not exist."}
6321
+ )
6322
+ elif active_item is None:
6323
+ issues.append(
6324
+ {
6325
+ "severity": "error",
6326
+ "code": "missing_active_additional_item",
6327
+ "message": f"active additional item `{active_item_id}` does not exist in list `{active_list_id}`.",
6328
+ }
6329
+ )
6330
+ else:
6331
+ active_status = _frontier_status(active_item.get("status", ""))
6332
+ if _frontier_status_is_terminal(active_status):
6333
+ issues.append(
6334
+ {
6335
+ "severity": "error",
6336
+ "code": "stale_active_additional_item",
6337
+ "message": f"active additional item `{active_list_id}/{active_item_id}` is marked `{active_status}`; complete the handoff and activate the next pending item.",
6338
+ }
6339
+ )
6340
+ elif not _frontier_status_is_active(active_status):
6341
+ issues.append(
6342
+ {
6343
+ "severity": "warning",
6344
+ "code": "active_additional_item_not_marked_active",
6345
+ "message": f"active additional item `{active_list_id}/{active_item_id}` is marked `{active_status}` instead of active.",
6346
+ }
6347
+ )
6348
+ if not next_action:
6349
+ next_action = _frontier_additional_item_summary(active_list, active_item)
6350
+
6351
+ if not active_item_id and int(additional_summary["pending_items"]) > 0:
6352
+ issues.append(
6353
+ {
6354
+ "severity": "warning",
6355
+ "code": "pending_additional_without_active_pointer",
6356
+ "message": "frontier additional queue has pending work but no active item; run `orp frontier additional activate-next` before delegating queue work.",
6357
+ }
6358
+ )
6359
+ suggested_next_command = "orp frontier additional activate-next --json"
6360
+ next_pending = additional_summary.get("next_pending")
6361
+ if isinstance(next_pending, dict) and not next_action:
6362
+ next_action = (
6363
+ f"Activate additional item {next_pending.get('list_id', '')}/{next_pending.get('item_id', '')}: "
6364
+ f"{next_pending.get('item_label', '')}"
6365
+ )
6366
+
6367
+ defined_work = int(stack_summary["versions"]) + int(stack_summary["milestones"]) + int(stack_summary["phases"]) + int(additional_summary["items"])
6368
+ if (
6369
+ defined_work > 0
6370
+ and not blockers
6371
+ and not terminal_declared
6372
+ and not active_primary
6373
+ and int(additional_summary["active_items"]) == 0
6374
+ and int(additional_summary["pending_items"]) == 0
6375
+ ):
6376
+ issues.append(
6377
+ {
6378
+ "severity": "warning",
6379
+ "code": "no_frontier_continuation_or_terminal_declaration",
6380
+ "message": "frontier has defined work but no active/open continuation and no terminal completion declaration.",
6381
+ }
6382
+ )
6383
+
6384
+ summary = {
6385
+ "defined_work": defined_work,
6386
+ "blocked": bool(blockers),
6387
+ "terminal_declared": terminal_declared,
6388
+ "active_primary": active_primary,
6389
+ "active_primary_kind": active_primary_kind,
6390
+ "active_primary_id": active_primary_id,
6391
+ "active_primary_status": active_primary_status,
6392
+ "additional": additional_summary,
6393
+ }
6394
+ return {
6395
+ "ok": _frontier_diagnostic_ok(issues, strict=strict),
6396
+ "strict": strict,
6397
+ "issues": issues,
6398
+ "summary": summary,
6399
+ "next_action": next_action,
6400
+ "suggested_next_command": suggested_next_command,
6401
+ "paths": {key: _path_for_state(value, repo_root) for key, value in paths.items()},
6402
+ }
6403
+
6404
+
6405
+ def _frontier_continuation_payload(repo_root: Path, *, strict: bool = False) -> dict[str, Any]:
6406
+ paths = _frontier_paths(repo_root)
6407
+ stack = _read_json_if_exists(paths["stack_json"])
6408
+ state = _read_json_if_exists(paths["state_json"])
6409
+ return _frontier_build_continuation_payload(repo_root, stack, state, include_structural_issues=True, strict=strict)
6410
+
6411
+
6412
+ def _frontier_additional_item_summary(item_list: dict[str, Any], item: dict[str, Any]) -> str:
6413
+ bits = [
6414
+ f"ORP additional item {str(item_list.get('id', '')).strip()}/{str(item.get('id', '')).strip()}: {str(item.get('label', '')).strip()}",
6415
+ ]
6416
+ goal = str(item.get("goal", "")).strip()
6417
+ if goal:
6418
+ bits.append(f"Goal: {goal}")
6419
+ criteria = _coerce_string_list(item.get("success_criteria"))
6420
+ if criteria:
6421
+ bits.append("Success: " + "; ".join(criteria))
6422
+ return " ".join(bits)
6423
+
6424
+
6425
+ def _render_frontier_additional_md(payload: dict[str, Any]) -> str:
6426
+ lines = [
6427
+ f"# Additional Frontier Items: {payload.get('label', '') or payload.get('program_id', '')}",
6428
+ "",
6429
+ f"- active_list_id: `{payload.get('active_list_id', '') or '(none)'}`",
6430
+ f"- active_item_id: `{payload.get('active_item_id', '') or '(none)'}`",
6431
+ "",
6432
+ ]
6433
+ lists = payload.get("lists")
6434
+ if isinstance(lists, list) and lists:
6435
+ for item_list in lists:
6436
+ if not isinstance(item_list, dict):
6437
+ continue
6438
+ lines.append(
6439
+ f"## `{item_list.get('id', '')}` {item_list.get('label', '')} (`{item_list.get('status', '') or 'pending'}`)"
6440
+ )
6441
+ lines.append("")
6442
+ items = item_list.get("items")
6443
+ if isinstance(items, list) and items:
6444
+ for item in items:
6445
+ if not isinstance(item, dict):
6446
+ continue
6447
+ marker = "x" if str(item.get("status", "")).strip() == "complete" else " "
6448
+ lines.append(
6449
+ f"- [{marker}] `{item.get('id', '')}` {item.get('label', '')} (`{item.get('status', '') or 'pending'}`)"
6450
+ )
6451
+ goal = str(item.get("goal", "")).strip()
6452
+ if goal:
6453
+ lines.append(f" - goal: {goal}")
6454
+ else:
6455
+ lines.append("- `(no items)`")
6456
+ lines.append("")
6457
+ else:
6458
+ lines.append("- `(no additional lists yet)`")
6459
+ lines.append("")
6460
+ return "\n".join(lines)
6461
+
6462
+
6463
+ def _frontier_write_additional_views(repo_root: Path, payload: dict[str, Any]) -> dict[str, str]:
6464
+ paths = _frontier_paths(repo_root)
6465
+ paths["root"].mkdir(parents=True, exist_ok=True)
6466
+ _frontier_normalize_additional_statuses(payload)
6467
+ _write_json(paths["additional_json"], payload)
6468
+ _write_text(paths["additional_md"], _render_frontier_additional_md(payload) + "\n")
6469
+ return {
6470
+ "additional_json": _path_for_state(paths["additional_json"], repo_root),
6471
+ "additional_md": _path_for_state(paths["additional_md"], repo_root),
6472
+ }
6473
+
6474
+
5979
6475
  def _frontier_set_current_frontier(stack: dict[str, Any], state: dict[str, Any]) -> None:
5980
6476
  stack["current_frontier"] = {
5981
6477
  "active_version": str(state.get("active_version", "")).strip(),
@@ -6179,6 +6675,11 @@ def _frontier_write_materialized_views(repo_root: Path, stack: dict[str, Any], s
6179
6675
  _frontier_set_current_frontier(stack, state)
6180
6676
  roadmap = _frontier_build_roadmap_payload(stack, state)
6181
6677
  checklist = _frontier_build_checklist_payload(stack, state)
6678
+ additional = _frontier_load_additional(repo_root, stack)
6679
+ if not str(additional.get("program_id", "")).strip():
6680
+ additional["program_id"] = str(stack.get("program_id", "")).strip()
6681
+ if not str(additional.get("label", "")).strip():
6682
+ additional["label"] = str(stack.get("label", "")).strip()
6182
6683
  _write_json(paths["state_json"], state)
6183
6684
  _write_json(paths["stack_json"], stack)
6184
6685
  _write_json(paths["roadmap_json"], roadmap)
@@ -6187,6 +6688,7 @@ def _frontier_write_materialized_views(repo_root: Path, stack: dict[str, Any], s
6187
6688
  _write_text(paths["roadmap_md"], _render_frontier_roadmap_md(roadmap) + "\n")
6188
6689
  _write_text(paths["checklist_md"], _render_frontier_checklist_md(checklist) + "\n")
6189
6690
  _write_text(paths["stack_md"], _render_frontier_stack_md(stack) + "\n")
6691
+ _frontier_write_additional_views(repo_root, additional)
6190
6692
  return {key: _path_for_state(value, repo_root) for key, value in paths.items() if key != "root"}
6191
6693
 
6192
6694
 
@@ -6245,14 +6747,44 @@ def _frontier_doctor_payload(repo_root: Path) -> dict[str, Any]:
6245
6747
  )
6246
6748
  if band == "exact":
6247
6749
  exact_milestones += 1
6750
+ milestone_id = str(milestone_row.get("id", "")).strip()
6751
+ if active_milestone and milestone_id and milestone_id != active_milestone:
6752
+ issues.append(
6753
+ {
6754
+ "severity": "warning",
6755
+ "code": "exact_milestone_not_live",
6756
+ "message": f"milestone `{milestone_id}` is marked exact but the live milestone is `{active_milestone}`.",
6757
+ }
6758
+ )
6248
6759
  if exact_milestones > 1:
6249
6760
  issues.append(
6250
6761
  {"severity": "warning", "code": "multiple_exact_milestones", "message": f"{exact_milestones} milestones are marked exact; the planning rule expects only one live exact milestone."}
6251
6762
  )
6252
6763
 
6764
+ continuation = _frontier_build_continuation_payload(
6765
+ repo_root,
6766
+ stack,
6767
+ state,
6768
+ include_structural_issues=False,
6769
+ )
6770
+ issues.extend(continuation["issues"])
6771
+ else:
6772
+ continuation = _frontier_build_continuation_payload(
6773
+ repo_root,
6774
+ stack if isinstance(stack, dict) else None,
6775
+ state if isinstance(state, dict) else None,
6776
+ include_structural_issues=False,
6777
+ )
6778
+
6253
6779
  return {
6254
- "ok": not any(issue["severity"] == "error" for issue in issues),
6780
+ "ok": _frontier_diagnostic_ok(issues),
6255
6781
  "issues": issues,
6782
+ "continuation": {
6783
+ "ok": continuation["ok"],
6784
+ "summary": continuation["summary"],
6785
+ "next_action": continuation["next_action"],
6786
+ "suggested_next_command": continuation["suggested_next_command"],
6787
+ },
6256
6788
  "paths": {key: _path_for_state(value, repo_root) for key, value in paths.items()},
6257
6789
  }
6258
6790
 
@@ -10769,12 +11301,16 @@ def _about_payload() -> dict[str, Any]:
10769
11301
  },
10770
11302
  {
10771
11303
  "id": "frontier",
10772
- "description": "Version-stack, milestone, phase, and live-frontier control for long-running agent-first research programs.",
11304
+ "description": "Version-stack, milestone, phase, additional-queue, and continuation-preflight control for long-running agent-first research programs.",
10773
11305
  "entrypoints": [
10774
11306
  ["frontier", "init"],
10775
11307
  ["frontier", "state"],
10776
11308
  ["frontier", "roadmap"],
10777
11309
  ["frontier", "checklist"],
11310
+ ["frontier", "continuation-status"],
11311
+ ["frontier", "preflight-delegate"],
11312
+ ["frontier", "additional", "list"],
11313
+ ["frontier", "additional", "activate-next"],
10778
11314
  ["frontier", "stack"],
10779
11315
  ["frontier", "add-version"],
10780
11316
  ["frontier", "add-milestone"],
@@ -10942,6 +11478,13 @@ def _about_payload() -> dict[str, Any]:
10942
11478
  {"name": "frontier_state", "path": ["frontier", "state"], "json_output": True},
10943
11479
  {"name": "frontier_roadmap", "path": ["frontier", "roadmap"], "json_output": True},
10944
11480
  {"name": "frontier_checklist", "path": ["frontier", "checklist"], "json_output": True},
11481
+ {"name": "frontier_continuation_status", "path": ["frontier", "continuation-status"], "json_output": True},
11482
+ {"name": "frontier_preflight_delegate", "path": ["frontier", "preflight-delegate"], "json_output": True},
11483
+ {"name": "frontier_additional_list", "path": ["frontier", "additional", "list"], "json_output": True},
11484
+ {"name": "frontier_additional_add_list", "path": ["frontier", "additional", "add-list"], "json_output": True},
11485
+ {"name": "frontier_additional_add_item", "path": ["frontier", "additional", "add-item"], "json_output": True},
11486
+ {"name": "frontier_additional_activate_next", "path": ["frontier", "additional", "activate-next"], "json_output": True},
11487
+ {"name": "frontier_additional_complete_active", "path": ["frontier", "additional", "complete-active"], "json_output": True},
10945
11488
  {"name": "frontier_stack", "path": ["frontier", "stack"], "json_output": True},
10946
11489
  {"name": "frontier_add_version", "path": ["frontier", "add-version"], "json_output": True},
10947
11490
  {"name": "frontier_add_milestone", "path": ["frontier", "add-milestone"], "json_output": True},
@@ -10973,7 +11516,7 @@ def _about_payload() -> dict[str, Any]:
10973
11516
  "Discovery profiles in ORP are portable search-intent files managed directly by ORP.",
10974
11517
  "Knowledge exchange is a built-in ORP ability exposed through `orp exchange repo synthesize`, producing structured exchange artifacts and transfer maps for local or remote source repositories.",
10975
11518
  "Collaboration is a built-in ORP ability exposed through `orp collaborate ...`.",
10976
- "Frontier control is a built-in ORP ability exposed through `orp frontier ...`, separating the exact live point, the exact active milestone, the near structured checklist, and the farther major-version stack.",
11519
+ "Frontier control is a built-in ORP ability exposed through `orp frontier ...`, separating the exact live point, the exact active milestone, the near structured checklist, the additional work queue, and strict continuation preflight before delegation.",
10977
11520
  "Agent modes are lightweight optional overlays for taste, perspective shifts, fresh movement, and intentional comprehension breakdowns; `orp mode breakdown granular-breakdown --json` gives agents a broad-to-atomic ladder for complex work, while `orp mode nudge granular-breakdown --json` gives a short reminder card.",
10978
11521
  "Project/session linking is a built-in ORP ability exposed through `orp link ...` and stored machine-locally under `.git/orp/link/`.",
10979
11522
  "Secrets are easiest to understand as saved credentials and related login metadata: humans usually run `orp secrets add ...` and paste the value at the prompt, agents usually pipe the value with `--value-stdin`, optional usernames can be stored alongside the secret when a service needs them, and local macOS Keychain caching plus hosted sync are optional layers on top.",
@@ -13532,6 +14075,245 @@ def cmd_frontier_checklist(args: argparse.Namespace) -> int:
13532
14075
  return 0
13533
14076
 
13534
14077
 
14078
+ def cmd_frontier_additional_list(args: argparse.Namespace) -> int:
14079
+ repo_root = Path(args.repo_root).resolve()
14080
+ stack = _frontier_load_stack(repo_root)
14081
+ payload = _frontier_load_additional(repo_root, stack)
14082
+ lists = payload.get("lists")
14083
+ rows = lists if isinstance(lists, list) else []
14084
+ pending_items = 0
14085
+ active_items = 0
14086
+ complete_items = 0
14087
+ for item_list in rows:
14088
+ if not isinstance(item_list, dict):
14089
+ continue
14090
+ items = item_list.get("items")
14091
+ if not isinstance(items, list):
14092
+ continue
14093
+ for item in items:
14094
+ if not isinstance(item, dict):
14095
+ continue
14096
+ status = str(item.get("status", "")).strip() or "pending"
14097
+ if status == "active":
14098
+ active_items += 1
14099
+ elif status == "complete":
14100
+ complete_items += 1
14101
+ else:
14102
+ pending_items += 1
14103
+ result = {
14104
+ "ok": True,
14105
+ **payload,
14106
+ "summary": {
14107
+ "lists": len(rows),
14108
+ "pending_items": pending_items,
14109
+ "active_items": active_items,
14110
+ "complete_items": complete_items,
14111
+ },
14112
+ "paths": {
14113
+ "additional_json": _path_for_state(_frontier_paths(repo_root)["additional_json"], repo_root),
14114
+ "additional_md": _path_for_state(_frontier_paths(repo_root)["additional_md"], repo_root),
14115
+ },
14116
+ }
14117
+ if args.json_output:
14118
+ _print_json(result)
14119
+ else:
14120
+ print(f"lists={len(rows)}")
14121
+ print(f"active_list_id={payload.get('active_list_id', '') or '(none)'}")
14122
+ print(f"active_item_id={payload.get('active_item_id', '') or '(none)'}")
14123
+ print(f"pending_items={pending_items}")
14124
+ print(f"active_items={active_items}")
14125
+ print(f"complete_items={complete_items}")
14126
+ return 0
14127
+
14128
+
14129
+ def cmd_frontier_additional_add_list(args: argparse.Namespace) -> int:
14130
+ repo_root = Path(args.repo_root).resolve()
14131
+ stack = _frontier_load_stack(repo_root)
14132
+ payload = _frontier_load_additional(repo_root, stack)
14133
+ list_id = str(args.id).strip()
14134
+ if _frontier_find_additional_list(payload, list_id) is not None:
14135
+ raise RuntimeError(f"frontier additional list `{list_id}` already exists.")
14136
+ item_list = {
14137
+ "id": list_id,
14138
+ "label": str(args.label).strip(),
14139
+ "status": str(args.status or "pending").strip() or "pending",
14140
+ "items": [],
14141
+ }
14142
+ lists = payload.get("lists")
14143
+ if not isinstance(lists, list):
14144
+ lists = []
14145
+ payload["lists"] = lists
14146
+ lists.append(item_list)
14147
+ written = _frontier_write_additional_views(repo_root, payload)
14148
+ result = {"ok": True, "list": item_list, "paths": written}
14149
+ if args.json_output:
14150
+ _print_json(result)
14151
+ else:
14152
+ print(f"list_id={list_id}")
14153
+ print(f"label={item_list['label']}")
14154
+ return 0
14155
+
14156
+
14157
+ def cmd_frontier_additional_add_item(args: argparse.Namespace) -> int:
14158
+ repo_root = Path(args.repo_root).resolve()
14159
+ stack = _frontier_load_stack(repo_root)
14160
+ payload = _frontier_load_additional(repo_root, stack)
14161
+ list_id = str(args.list).strip()
14162
+ item_list = _frontier_find_additional_list(payload, list_id)
14163
+ if item_list is None:
14164
+ raise RuntimeError(f"frontier additional list `{list_id}` was not found.")
14165
+ item_id = str(args.id).strip()
14166
+ if _frontier_find_additional_item(item_list, item_id) is not None:
14167
+ raise RuntimeError(f"frontier additional item `{item_id}` already exists in list `{list_id}`.")
14168
+ item = {
14169
+ "id": item_id,
14170
+ "label": str(args.label).strip(),
14171
+ "status": str(args.status or "pending").strip() or "pending",
14172
+ "goal": str(args.goal or "").strip(),
14173
+ "depends_on": _coerce_string_list(getattr(args, "depends_on", [])),
14174
+ "requirements": _coerce_string_list(getattr(args, "requirement", [])),
14175
+ "success_criteria": _coerce_string_list(getattr(args, "success_criterion", [])),
14176
+ "plans": _coerce_string_list(getattr(args, "plan", [])),
14177
+ }
14178
+ items = item_list.get("items")
14179
+ if not isinstance(items, list):
14180
+ items = []
14181
+ item_list["items"] = items
14182
+ items.append(item)
14183
+ written = _frontier_write_additional_views(repo_root, payload)
14184
+ result = {"ok": True, "list_id": list_id, "item": item, "paths": written}
14185
+ if args.json_output:
14186
+ _print_json(result)
14187
+ else:
14188
+ print(f"list_id={list_id}")
14189
+ print(f"item_id={item_id}")
14190
+ print(f"label={item['label']}")
14191
+ return 0
14192
+
14193
+
14194
+ def cmd_frontier_additional_activate_next(args: argparse.Namespace) -> int:
14195
+ repo_root = Path(args.repo_root).resolve()
14196
+ stack = _frontier_load_stack(repo_root)
14197
+ payload = _frontier_load_additional(repo_root, stack)
14198
+ active_list, active_item = _frontier_active_additional_item(payload)
14199
+ already_active = active_list is not None and active_item is not None and str(active_item.get("status", "")).strip() == "active"
14200
+ if not already_active:
14201
+ active_list, active_item = _frontier_next_pending_additional_item(payload)
14202
+ if active_list is not None and active_item is not None:
14203
+ active_list["status"] = "active"
14204
+ active_item["status"] = "active"
14205
+ payload["active_list_id"] = str(active_list.get("id", "")).strip()
14206
+ payload["active_item_id"] = str(active_item.get("id", "")).strip()
14207
+ else:
14208
+ payload["active_list_id"] = ""
14209
+ payload["active_item_id"] = ""
14210
+ written = _frontier_write_additional_views(repo_root, payload)
14211
+ activated = active_list is not None and active_item is not None
14212
+ result = {
14213
+ "ok": True,
14214
+ "activated": activated,
14215
+ "already_active": already_active,
14216
+ "active_list_id": payload.get("active_list_id", ""),
14217
+ "active_item_id": payload.get("active_item_id", ""),
14218
+ "list": active_list if activated else None,
14219
+ "item": active_item if activated else None,
14220
+ "next_action": _frontier_additional_item_summary(active_list, active_item) if activated else "",
14221
+ "paths": written,
14222
+ }
14223
+ if args.json_output:
14224
+ _print_json(result)
14225
+ else:
14226
+ print(f"activated={'true' if activated else 'false'}")
14227
+ if activated:
14228
+ print(f"active_list_id={result['active_list_id']}")
14229
+ print(f"active_item_id={result['active_item_id']}")
14230
+ print(f"next_action={result['next_action']}")
14231
+ return 0
14232
+
14233
+
14234
+ def cmd_frontier_additional_complete_active(args: argparse.Namespace) -> int:
14235
+ repo_root = Path(args.repo_root).resolve()
14236
+ stack = _frontier_load_stack(repo_root)
14237
+ payload = _frontier_load_additional(repo_root, stack)
14238
+ active_list, active_item = _frontier_active_additional_item(payload)
14239
+ completed = active_list is not None and active_item is not None
14240
+ list_completed = False
14241
+ if completed:
14242
+ active_item["status"] = str(args.status or "complete").strip() or "complete"
14243
+ items = active_list.get("items")
14244
+ if isinstance(items, list) and all(
14245
+ str(item.get("status", "")).strip() in {"complete", "skipped"} for item in items if isinstance(item, dict)
14246
+ ):
14247
+ active_list["status"] = "complete"
14248
+ list_completed = True
14249
+ payload["active_list_id"] = ""
14250
+ payload["active_item_id"] = ""
14251
+ written = _frontier_write_additional_views(repo_root, payload)
14252
+ result = {
14253
+ "ok": True,
14254
+ "completed": completed,
14255
+ "list_completed": list_completed,
14256
+ "list": active_list if completed else None,
14257
+ "item": active_item if completed else None,
14258
+ "paths": written,
14259
+ }
14260
+ if args.json_output:
14261
+ _print_json(result)
14262
+ else:
14263
+ print(f"completed={'true' if completed else 'false'}")
14264
+ print(f"list_completed={'true' if list_completed else 'false'}")
14265
+ return 0
14266
+
14267
+
14268
+ def _print_frontier_diagnostic_payload(payload: dict[str, Any]) -> None:
14269
+ print(f"ok={'true' if payload.get('ok') else 'false'}")
14270
+ print(f"next_action={payload.get('next_action', '') or '(none)'}")
14271
+ suggested = str(payload.get("suggested_next_command", "")).strip()
14272
+ if suggested:
14273
+ print(f"suggested_next_command={suggested}")
14274
+ summary = payload.get("summary")
14275
+ if isinstance(summary, dict):
14276
+ print(f"active_primary={summary.get('active_primary_kind', '') or '(none)'}:{summary.get('active_primary_id', '') or '(none)'}")
14277
+ additional = summary.get("additional")
14278
+ if isinstance(additional, dict):
14279
+ print(f"pending_additional_items={additional.get('pending_items', 0)}")
14280
+ print(f"active_additional={additional.get('active_list_id', '') or '(none)'}/{additional.get('active_item_id', '') or '(none)'}")
14281
+ for issue in payload.get("issues", []):
14282
+ if isinstance(issue, dict):
14283
+ print(f"issue={issue.get('severity','')}:{issue.get('code','')}:{issue.get('message','')}")
14284
+
14285
+
14286
+ def cmd_frontier_continuation_status(args: argparse.Namespace) -> int:
14287
+ repo_root = Path(args.repo_root).resolve()
14288
+ strict = bool(getattr(args, "strict", False))
14289
+ payload = _frontier_continuation_payload(repo_root, strict=strict)
14290
+ if args.json_output:
14291
+ _print_json(payload)
14292
+ else:
14293
+ _print_frontier_diagnostic_payload(payload)
14294
+ return 0 if payload["ok"] else 1
14295
+
14296
+
14297
+ def cmd_frontier_preflight_delegate(args: argparse.Namespace) -> int:
14298
+ repo_root = Path(args.repo_root).resolve()
14299
+ payload = _frontier_doctor_payload(repo_root)
14300
+ payload["strict"] = True
14301
+ payload["ok"] = _frontier_diagnostic_ok(payload.get("issues", []), strict=True)
14302
+ continuation = payload.get("continuation") if isinstance(payload.get("continuation"), dict) else {}
14303
+ payload["next_action"] = continuation.get("next_action", "")
14304
+ payload["suggested_next_command"] = continuation.get("suggested_next_command", "")
14305
+ payload["summary"] = continuation.get("summary", {})
14306
+ payload["preflight"] = {
14307
+ "ready": bool(payload["ok"]),
14308
+ "purpose": "Block delegation when the frontier cannot prove a single safe continuation or terminal state.",
14309
+ }
14310
+ if args.json_output:
14311
+ _print_json(payload)
14312
+ else:
14313
+ _print_frontier_diagnostic_payload(payload)
14314
+ return 0 if payload["ok"] else 1
14315
+
14316
+
13535
14317
  def cmd_frontier_add_version(args: argparse.Namespace) -> int:
13536
14318
  repo_root = Path(args.repo_root).resolve()
13537
14319
  stack = _frontier_load_stack(repo_root)
@@ -13739,6 +14521,9 @@ def cmd_frontier_render(args: argparse.Namespace) -> int:
13739
14521
  def cmd_frontier_doctor(args: argparse.Namespace) -> int:
13740
14522
  repo_root = Path(args.repo_root).resolve()
13741
14523
  payload = _frontier_doctor_payload(repo_root)
14524
+ strict = bool(getattr(args, "strict", False))
14525
+ payload["strict"] = strict
14526
+ payload["ok"] = _frontier_diagnostic_ok(payload.get("issues", []), strict=strict)
13742
14527
  fixes_applied: list[str] = []
13743
14528
  if args.fix and payload["ok"]:
13744
14529
  stack = _frontier_load_stack(repo_root)
@@ -24592,6 +25377,114 @@ def build_parser() -> argparse.ArgumentParser:
24592
25377
  add_json_flag(s_frontier_checklist)
24593
25378
  s_frontier_checklist.set_defaults(func=cmd_frontier_checklist, json_output=False)
24594
25379
 
25380
+ s_frontier_continuation_status = frontier_sub.add_parser(
25381
+ "continuation-status",
25382
+ help="Show whether the frontier has a safe next continuation, blocker, or terminal declaration",
25383
+ )
25384
+ s_frontier_continuation_status.add_argument(
25385
+ "--strict",
25386
+ action="store_true",
25387
+ help="Return non-zero when continuation warnings are present",
25388
+ )
25389
+ add_json_flag(s_frontier_continuation_status)
25390
+ s_frontier_continuation_status.set_defaults(func=cmd_frontier_continuation_status, json_output=False)
25391
+
25392
+ s_frontier_preflight_delegate = frontier_sub.add_parser(
25393
+ "preflight-delegate",
25394
+ help="Fail delegation when frontier continuation is stale, ambiguous, or not activated",
25395
+ )
25396
+ add_json_flag(s_frontier_preflight_delegate)
25397
+ s_frontier_preflight_delegate.set_defaults(func=cmd_frontier_preflight_delegate, json_output=False)
25398
+
25399
+ s_frontier_additional = frontier_sub.add_parser(
25400
+ "additional",
25401
+ help="Manage queued additional Frontier work that should run after the active delegate objective",
25402
+ )
25403
+ frontier_additional_sub = s_frontier_additional.add_subparsers(dest="frontier_additional_cmd", required=True)
25404
+
25405
+ s_frontier_additional_list = frontier_additional_sub.add_parser(
25406
+ "list",
25407
+ help="Show additional Frontier work lists and their item states",
25408
+ )
25409
+ add_json_flag(s_frontier_additional_list)
25410
+ s_frontier_additional_list.set_defaults(func=cmd_frontier_additional_list, json_output=False)
25411
+
25412
+ s_frontier_additional_add_list = frontier_additional_sub.add_parser(
25413
+ "add-list",
25414
+ help="Add an additional work list that delegates may drain after the active objective",
25415
+ )
25416
+ s_frontier_additional_add_list.add_argument("--id", required=True, help="Additional list id")
25417
+ s_frontier_additional_add_list.add_argument("--label", required=True, help="Additional list label")
25418
+ s_frontier_additional_add_list.add_argument(
25419
+ "--status",
25420
+ default="pending",
25421
+ choices=["pending", "active", "complete"],
25422
+ help="List status (default: pending)",
25423
+ )
25424
+ add_json_flag(s_frontier_additional_add_list)
25425
+ s_frontier_additional_add_list.set_defaults(func=cmd_frontier_additional_add_list, json_output=False)
25426
+
25427
+ s_frontier_additional_add_item = frontier_additional_sub.add_parser(
25428
+ "add-item",
25429
+ help="Add one item to an additional Frontier work list",
25430
+ )
25431
+ s_frontier_additional_add_item.add_argument("--list", required=True, help="Parent additional list id")
25432
+ s_frontier_additional_add_item.add_argument("--id", required=True, help="Additional item id")
25433
+ s_frontier_additional_add_item.add_argument("--label", required=True, help="Additional item label")
25434
+ s_frontier_additional_add_item.add_argument(
25435
+ "--status",
25436
+ default="pending",
25437
+ choices=["pending", "active", "complete", "skipped"],
25438
+ help="Item status (default: pending)",
25439
+ )
25440
+ s_frontier_additional_add_item.add_argument("--goal", default="", help="Optional item goal")
25441
+ s_frontier_additional_add_item.add_argument(
25442
+ "--depends-on",
25443
+ action="append",
25444
+ default=[],
25445
+ help="Dependency item, phase, or milestone id (repeatable)",
25446
+ )
25447
+ s_frontier_additional_add_item.add_argument(
25448
+ "--requirement",
25449
+ action="append",
25450
+ default=[],
25451
+ help="Requirement identifier or note (repeatable)",
25452
+ )
25453
+ s_frontier_additional_add_item.add_argument(
25454
+ "--success-criterion",
25455
+ action="append",
25456
+ default=[],
25457
+ help="Item success criterion (repeatable)",
25458
+ )
25459
+ s_frontier_additional_add_item.add_argument(
25460
+ "--plan",
25461
+ action="append",
25462
+ default=[],
25463
+ help="Plan id or plan note (repeatable)",
25464
+ )
25465
+ add_json_flag(s_frontier_additional_add_item)
25466
+ s_frontier_additional_add_item.set_defaults(func=cmd_frontier_additional_add_item, json_output=False)
25467
+
25468
+ s_frontier_additional_activate_next = frontier_additional_sub.add_parser(
25469
+ "activate-next",
25470
+ help="Activate and print the next pending additional Frontier item",
25471
+ )
25472
+ add_json_flag(s_frontier_additional_activate_next)
25473
+ s_frontier_additional_activate_next.set_defaults(func=cmd_frontier_additional_activate_next, json_output=False)
25474
+
25475
+ s_frontier_additional_complete_active = frontier_additional_sub.add_parser(
25476
+ "complete-active",
25477
+ help="Mark the active additional Frontier item complete",
25478
+ )
25479
+ s_frontier_additional_complete_active.add_argument(
25480
+ "--status",
25481
+ default="complete",
25482
+ choices=["complete", "skipped"],
25483
+ help="Completion status for the active item (default: complete)",
25484
+ )
25485
+ add_json_flag(s_frontier_additional_complete_active)
25486
+ s_frontier_additional_complete_active.set_defaults(func=cmd_frontier_additional_complete_active, json_output=False)
25487
+
24595
25488
  s_frontier_stack = frontier_sub.add_parser(
24596
25489
  "stack",
24597
25490
  help="Show the larger major-version stack",
@@ -24745,6 +25638,11 @@ def build_parser() -> argparse.ArgumentParser:
24745
25638
  action="store_true",
24746
25639
  help="Re-render materialized frontier views when the frontier is otherwise consistent",
24747
25640
  )
25641
+ s_frontier_doctor.add_argument(
25642
+ "--strict",
25643
+ action="store_true",
25644
+ help="Return non-zero when frontier warnings are present",
25645
+ )
24748
25646
  add_json_flag(s_frontier_doctor)
24749
25647
  s_frontier_doctor.set_defaults(func=cmd_frontier_doctor, json_output=False)
24750
25648