| 225 | |
| 226 | |
| 227 | def inspect_note_plan(plan_path: Path) -> tuple[bool, list[str]]: |
| 228 | if not plan_path.exists(): |
| 229 | return False, ["planning_artifact_missing"] |
| 230 | |
| 231 | try: |
| 232 | plan = json.loads(plan_path.read_text(encoding="utf-8")) |
| 233 | except (OSError, UnicodeDecodeError, json.JSONDecodeError): |
| 234 | return True, ["planning_artifact_invalid_json"] |
| 235 | |
| 236 | if not isinstance(plan, dict): |
| 237 | return True, ["planning_required_fields_invalid"] |
| 238 | |
| 239 | issues: list[str] = [] |
| 240 | missing_fields = [field for field in NOTE_PLAN_REQUIRED_FIELDS if field not in plan] |
| 241 | if missing_fields: |
| 242 | issues.append("planning_required_fields_missing") |
| 243 | |
| 244 | has_invalid_fields = False |
| 245 | for field in NOTE_PLAN_STRING_FIELDS: |
| 246 | if field in plan and not isinstance(plan[field], str): |
| 247 | has_invalid_fields = True |
| 248 | elif field in plan and not plan[field].strip(): |
| 249 | issues.append(f"planning_{field}_empty") |
| 250 | if isinstance(plan.get("paper_type"), str) and plan["paper_type"].strip(): |
| 251 | if plan["paper_type"].strip() not in PAPER_TYPE_VALUES: |
| 252 | issues.append("planning_paper_type_invalid") |
| 253 | for field in NOTE_PLAN_LIST_FIELDS: |
| 254 | if field in plan and not isinstance(plan[field], list): |
| 255 | has_invalid_fields = True |
| 256 | elif field in plan and not plan[field]: |
| 257 | issues.append(f"planning_{field}_empty") |
| 258 | if has_invalid_fields: |
| 259 | issues.append("planning_required_fields_invalid") |
| 260 | issues.extend(inspect_central_claims_plan(plan.get("central_claims"))) |
| 261 | |
| 262 | return True, issues |
| 263 | |
| 264 | |
| 265 | def inspect_central_claims_plan(value: object) -> list[str]: |