158 source_index: SourceIndex,
159 plan_records: Sequence[PlanRecord] = (),
160 history: Sequence[CommitRecord] = (),
161 repo_root: Path |
None =
None,
162 advances_required_from: str |
None =
None,
163 history_error: str |
None =
None,
165 """Run all three check classes over the supplied inputs.
167 Pure: every input is injected, so the engine is exercised without git and
172 findings: list[Finding] = []
173 findings += check_changelog_status_divergence(source_index, history)
174 advances_findings, links, none_count = check_advances_conformance(
175 source_index, plan_records, advances_required_from=advances_required_from
177 findings += advances_findings
178 findings += check_stale_completion(source_index, repo_root=repo_root)
179 findings.sort(key=_sort_key)
183 record_id=change.record_id,
184 from_status=change.from_status,
185 to_status=change.to_status,
189 for commit
in history
190 for change
in commit.status_changes
193 records_examined = sum(len(commit.added_changelog)
for commit
in history)
195 findings=tuple(findings),
197 transitions=transitions,
198 counts=count_by_kind(findings),
199 advances_none_count=none_count,
200 plans_checked=len(plan_records),
201 changelog_records_checked=records_examined,
202 records_checked=len(source_index.records),
203 unscannable_ids=source_index.unscannable_ids,
204 history_error=history_error,
206 records_examined, len(source_index.changelog), history_error
212 f
"{report.violation_count} violations / {report.advisory_count} advisories",
219 workspace_root: str | Path,
221 source_dir: str | Path |
None =
None,
222 plans_dir: str | Path |
None =
None,
223 since: str |
None =
None,
224 advances_required_from: str |
None =
None,
226 """Load a real workspace and reconcile it."""
228 func =
"reconcile_workspace"
229 root = Path(workspace_root).resolve()
230 source = (Path(source_dir)
if source_dir
else root / DEFAULT_SOURCE_REL).resolve()
231 plans = (Path(plans_dir)
if plans_dir
else root / DEFAULT_PLANS_REL).resolve()
233 _dbg(
"OC_CHAT.MAIN", func, f
"reconciling {root} (scope {source_rel})", 2)
237 history: tuple[CommitRecord, ...] = ()
238 history_error: str |
None =
None
240 history = read_history(root, source_rel=source_rel, since=since)
241 except HistoryError
as error:
242 history_error = str(error)
243 _dbg(
"OC_CHAT.MAIN", func,
"class 1 not evaluated: no git history", 2)
245 source_index=load_source_index(source),
246 plan_records=load_plan_records(plans),
249 advances_required_from=advances_required_from,
250 history_error=history_error,
254@dataclass(frozen=True)
294 links: Sequence[PlanLink],
297 actor_type: ActorType =
"human_operator",
298 system_actor: bool =
True,
299 dry_run: bool =
False,
301 """Persist ``links`` into ``roadmap_plan_links`` after a fail-loud preflight.
303 The two foreign-key parents of this table — ``plans(plan_id)`` and
304 ``roadmap_items_v3(item_id)`` — must both be present in the target store.
305 When either is missing the write is refused whole and the missing ids are
306 named; nothing is fabricated to make the insert succeed.
308 ``actor_type`` defaults to ``human_operator`` because the CLI resolves the
309 store's active *operator* slot, and the backend rejects a slot whose
310 registered type disagrees with the claimed one.
313 func =
"write_plan_links"
317 have_plans =
_existing(registry,
"plans",
"plan_id", {link.plan_id
for link
in links})
319 registry,
"roadmap_items_v3",
"item_id", {link.item_id
for link
in links}
321 missing_plans = tuple(sorted({link.plan_id
for link
in links} - have_plans))
322 missing_items = tuple(sorted({link.item_id
for link
in links} - have_items))
323 if missing_plans
or missing_items:
325 "refused: roadmap_plan_links has two foreign-key parents and this store "
326 f
"is missing {len(missing_plans)} plan row(s) and {len(missing_items)} "
327 "roadmap item row(s). The check reports; it does not invent parents. "
328 "Populate the live store first (queue the plans; `roadmap import` the "
329 "items) or resolve where the join is meant to live."
331 _dbg(
"OC_CHAT.MAIN", func,
"preflight refused the write", 2)
335 return JoinWriteResult(
True, 0, (), (), f
"dry run: {len(links)} link(s) would be written")
338 roadmap_plan_link_create(
340 plan_id=link.plan_id,
341 item_id=link.item_id,
342 contribution=link.contribution,
343 actor_slot=actor_slot,
344 actor_type=actor_type,
345 command=
"roadmap reconcile --write-links",
346 system_actor=system_actor,
348 _dbg(
"OC_CHAT.MAIN", func, f
"wrote {len(links)} plan links", 2)
349 return JoinWriteResult(
True, len(links), (), (), f
"wrote {len(links)} plan link(s)")
JoinWriteResult write_plan_links(Registry registry, Sequence[PlanLink] links, *, str actor_slot, ActorType actor_type="human_operator", bool system_actor=True, bool dry_run=False)
ReconcileReport reconcile_workspace(str|Path workspace_root, *, str|Path|None source_dir=None, str|Path|None plans_dir=None, str|None since=None, str|None advances_required_from=None)