@dev-loops/core 0.1.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 (54) hide show
  1. package/bin/capture-deep-persona-signals.mjs +143 -0
  2. package/bin/ensure-phase-files.mjs +7 -0
  3. package/bin/log-bash-exit-1.mjs +7 -0
  4. package/bin/parse-review-threads.mjs +7 -0
  5. package/package.json +78 -0
  6. package/src/analysis/change-classifier.mjs +146 -0
  7. package/src/analysis/diff-analyzer.mjs +285 -0
  8. package/src/bash-exit-one.mjs +130 -0
  9. package/src/cli/helpers.mjs +22 -0
  10. package/src/cli/primitives.mjs +70 -0
  11. package/src/cli/retry-wrapper.mjs +169 -0
  12. package/src/cli/subcommand-runner.mjs +246 -0
  13. package/src/config/config.mjs +965 -0
  14. package/src/debt/cluster.mjs +240 -0
  15. package/src/debt/debt-finding.mjs +68 -0
  16. package/src/debt/debt-signal.mjs +46 -0
  17. package/src/debt/deep-persona-signals.mjs +266 -0
  18. package/src/debt/remediation-to-issue.mjs +121 -0
  19. package/src/debt/score.mjs +127 -0
  20. package/src/debt/shape.mjs +214 -0
  21. package/src/github/copilot-helpers.mjs +343 -0
  22. package/src/github/repo-slug.mjs +105 -0
  23. package/src/github/review-threads.mjs +343 -0
  24. package/src/harness/adapter.mjs +57 -0
  25. package/src/harness/index.mjs +3 -0
  26. package/src/harness/noop-adapter.mjs +22 -0
  27. package/src/harness/pi-adapter.mjs +47 -0
  28. package/src/loop/async-start-contract.mjs +170 -0
  29. package/src/loop/conductor-routing.mjs +817 -0
  30. package/src/loop/copilot-ci-status.mjs +255 -0
  31. package/src/loop/copilot-loop-iterations.mjs +161 -0
  32. package/src/loop/copilot-loop-state.mjs +510 -0
  33. package/src/loop/handoff-envelope.mjs +800 -0
  34. package/src/loop/issue-refinement-artifact.mjs +268 -0
  35. package/src/loop/lifecycle-state.mjs +342 -0
  36. package/src/loop/phase-files.mjs +187 -0
  37. package/src/loop/policy-constants.mjs +17 -0
  38. package/src/loop/pr-gate-coordination.mjs +1278 -0
  39. package/src/loop/public-dev-loop-routing-contract.mjs +277 -0
  40. package/src/loop/public-dev-loop-routing.mjs +1746 -0
  41. package/src/loop/queue-board-ordering.mjs +38 -0
  42. package/src/loop/queue-board-sync.mjs +223 -0
  43. package/src/loop/queue-driver.mjs +164 -0
  44. package/src/loop/queue-parallel.mjs +190 -0
  45. package/src/loop/queue-state.mjs +230 -0
  46. package/src/loop/retrospective-checkpoint.mjs +178 -0
  47. package/src/loop/reviewer-loop-state.mjs +456 -0
  48. package/src/loop/run-inspection.mjs +604 -0
  49. package/src/loop/steering.mjs +793 -0
  50. package/src/loop/timeout-policy.mjs +73 -0
  51. package/src/loop/tracker-first-loop-state.mjs +87 -0
  52. package/src/loop/tracker-pr-state.mjs +301 -0
  53. package/src/loop/worktree-guard.mjs +141 -0
  54. package/src/refinement/ac-dod-matrix.mjs +95 -0
@@ -0,0 +1,604 @@
1
+ /**
2
+ * Pure snapshot composer for the Copilot PR outer-loop run inspection surface.
3
+ *
4
+ * This module provides a read-only, JSON-first inspection snapshot for one
5
+ * explicitly targeted run in the Copilot PR outer-loop family.
6
+ *
7
+ * It composes already-fetched inner-loop facts into the canonical inspection
8
+ * shape without performing any I/O, checkpoint writes, or state mutations.
9
+ *
10
+ * Schema version: 1
11
+ *
12
+ * Always-present output fields:
13
+ * ok, schemaVersion, target, inspectedAt, activeStateFamily,
14
+ * outerAction, activeFamilyState, statusClass, needsAttention,
15
+ * sourceMode, trust, evidence, markers
16
+ *
17
+ * Best-effort output fields:
18
+ * loopIterations, layers (copilot, reviewer, steering drill-down)
19
+ *
20
+ * Source precedence:
21
+ * 1. Authoritative live detector-backed facts
22
+ * 2. Bounded local checkpoint artifacts
23
+ * 3. Unknown/unavailable markers when neither is sufficient
24
+ */
25
+
26
+ import { summarizeLoopInterpretation } from "./copilot-loop-state.mjs";
27
+ import { isKnownOuterState } from "./conductor-routing.mjs";
28
+ import { getAllowedTransitions, lifecyclePhaseForCopilotState, resolveLifecycleState } from "./lifecycle-state.mjs";
29
+
30
+ // ---------------------------------------------------------------------------
31
+ // Constants
32
+ // ---------------------------------------------------------------------------
33
+
34
+ export const SCHEMA_VERSION = 1;
35
+
36
+ /** The workflow family this module inspects. */
37
+ export const ACTIVE_STATE_FAMILY = "copilot-pr-outer-loop";
38
+
39
+ export function deriveRunIdForInspectionTarget({ pr }) {
40
+ return `pr-${pr}`;
41
+ }
42
+
43
+ /** Top-level status class values. */
44
+ export const STATUS_CLASS = Object.freeze({
45
+ ACTIVE: "active",
46
+ WAITING: "waiting",
47
+ BLOCKED: "blocked",
48
+ DONE: "done",
49
+ UNKNOWN: "unknown",
50
+ });
51
+
52
+ /** Source mode values reflecting evidence availability. */
53
+ export const SOURCE_MODE = Object.freeze({
54
+ /** Both inner-loop detectors returned live facts. */
55
+ LIVE_DETECTOR_BACKED: "live-detector-backed",
56
+ /** Live detection failed for all inner loops; checkpoint-backed drill-down remains available, but the top-level state stays unknown. */
57
+ CHECKPOINT_ONLY: "checkpoint-only",
58
+ /** Degraded mode: mixed live + checkpoint fallback keeps the top-level state unknown; complete caller-supplied current-state inputs can still derive a top-level state. */
59
+ PARTIAL: "partial",
60
+ /** No live facts and no valid checkpoint available. */
61
+ UNAVAILABLE: "unavailable",
62
+ });
63
+
64
+ /** Trust classification for the inspection output. */
65
+ export const TRUST = Object.freeze({
66
+ /** All facts come from live authoritative sources. */
67
+ AUTHORITATIVE: "authoritative",
68
+ /** Facts come from a previously persisted checkpoint (advisory only). */
69
+ CHECKPOINT: "checkpoint",
70
+ /** Facts are a mix of live and checkpoint, or only partial live coverage. */
71
+ DEGRADED: "degraded",
72
+ /** No trustworthy evidence available. */
73
+ UNAVAILABLE: "unavailable",
74
+ });
75
+
76
+ // ---------------------------------------------------------------------------
77
+ // Status-class mapping
78
+ // ---------------------------------------------------------------------------
79
+
80
+ /**
81
+ * Map an outerAction value to a top-level statusClass.
82
+ *
83
+ * Mapping:
84
+ * continue_wait → waiting
85
+ * reenter_copilot_loop → active
86
+ * reenter_reviewer_loop → active
87
+ * stop → blocked
88
+ * done → done
89
+ * anything else / undefined → unknown
90
+ *
91
+ * @param {string | undefined} outerAction
92
+ * @returns {string} one of STATUS_CLASS values
93
+ */
94
+ export function mapOuterActionToStatusClass(outerAction) {
95
+ switch (outerAction) {
96
+ case "continue_wait":
97
+ return STATUS_CLASS.WAITING;
98
+ case "reenter_copilot_loop":
99
+ case "reenter_reviewer_loop":
100
+ return STATUS_CLASS.ACTIVE;
101
+ case "stop":
102
+ return STATUS_CLASS.BLOCKED;
103
+ case "done":
104
+ return STATUS_CLASS.DONE;
105
+ default:
106
+ return STATUS_CLASS.UNKNOWN;
107
+ }
108
+ }
109
+
110
+ function buildReviewerScope(snapshotLike) {
111
+ if (snapshotLike?.reviewerScope === "single_reviewer") {
112
+ return {
113
+ mode: "single_reviewer",
114
+ reviewerLogin: typeof snapshotLike?.reviewerLogin === "string" ? snapshotLike.reviewerLogin : null,
115
+ };
116
+ }
117
+
118
+ return {
119
+ mode: "all_reviewers",
120
+ reviewerLogin: null,
121
+ };
122
+ }
123
+
124
+ const LIVE_STEERING_TERMINAL_STATES = new Set([
125
+ "no_pr",
126
+ "done",
127
+ "review_request_unavailable",
128
+ "blocked_needs_user_decision",
129
+ ]);
130
+
131
+ function evaluateLiveSteeringAvailability({
132
+ sourceMode,
133
+ trust,
134
+ markers,
135
+ statusClass,
136
+ copilotCurrentState,
137
+ }) {
138
+ if (sourceMode !== SOURCE_MODE.LIVE_DETECTOR_BACKED) {
139
+ return { status: "unavailable", reason: "live_steering_unavailable_source_mode" };
140
+ }
141
+
142
+ if (trust !== TRUST.AUTHORITATIVE) {
143
+ return { status: "unavailable", reason: "live_steering_unavailable_trust" };
144
+ }
145
+
146
+ if (
147
+ markers.missing.length > 0
148
+ || markers.stale.length > 0
149
+ || markers.conflicts.length > 0
150
+ ) {
151
+ return { status: "unavailable", reason: "live_steering_unavailable_markers" };
152
+ }
153
+
154
+ if (statusClass === STATUS_CLASS.UNKNOWN || typeof copilotCurrentState !== "string") {
155
+ return { status: "unavailable", reason: "live_steering_unavailable_unknown_state" };
156
+ }
157
+
158
+ if (
159
+ statusClass === STATUS_CLASS.DONE
160
+ || statusClass === STATUS_CLASS.BLOCKED
161
+ || LIVE_STEERING_TERMINAL_STATES.has(copilotCurrentState)
162
+ ) {
163
+ return { status: "unavailable", reason: "live_steering_unavailable_terminal_state" };
164
+ }
165
+
166
+ return { status: "available", reason: null };
167
+ }
168
+
169
+ // ---------------------------------------------------------------------------
170
+ // Snapshot composer
171
+ // ---------------------------------------------------------------------------
172
+
173
+ /**
174
+ * Compose a read-only run inspection snapshot for one explicitly targeted run
175
+ * in the Copilot PR outer-loop family.
176
+ *
177
+ * This function is pure: it performs no I/O and does not mutate any files or
178
+ * checkpoints. All evidence must be gathered by the caller before invoking it.
179
+ *
180
+ * @param {object} params
181
+ * @param {{ repo: string, pr: number }} params.target
182
+ * Explicit target identity.
183
+ * @param {string} params.inspectedAt
184
+ * ISO 8601 timestamp of when the inspection was initiated.
185
+ * @param {string | undefined} [params.outerState]
186
+ * Authoritative outer-loop state derived by the caller from a complete
187
+ * current-state picture, or undefined when the outer state cannot be determined.
188
+ * @param {string[] | undefined} [params.outerAllowedTransitions]
189
+ * Authoritative allowed next outer states when `outerState` is available.
190
+ * @param {string | undefined} params.outerAction
191
+ * Backward-compatible outer-loop action projection derived by the caller from
192
+ * the authoritative outer interpretation, or undefined when the outer action
193
+ * cannot be determined.
194
+ * @param {string | undefined} [params.outerReason]
195
+ * Optional stop reason string from the authoritative outer-loop interpretation
196
+ * (for example `interpretOuterLoopState(...).stopReason`, such as
197
+ * "copilot_blocked").
198
+ * @param {{ snapshot: object, interpretation: { state: string, allowedTransitions: string[], nextAction: string } } | null} params.copilotEvidence
199
+ * Live copilot inner-loop facts. null when live detection was unavailable.
200
+ * @param {{ snapshot: object, interpretation: { state: string, allowedTransitions: string[], nextAction: string } } | null} params.reviewerEvidence
201
+ * Live reviewer inner-loop facts. null when live detection was unavailable.
202
+ * @param {object | null} params.existingCheckpoint
203
+ * Previously persisted outer-loop checkpoint (read-only). null when not found.
204
+ * @param {string | null} [params.checkpointEvidencePath]
205
+ * Concrete checkpoint file path used by the caller when a checkpoint was found.
206
+ * @param {{ copilot: "ok"|"failed", reviewer: "ok"|"failed" }} params.liveAvailability
207
+ * Tracks whether each detector/interpreter path succeeded ("ok") or failed ("failed").
208
+ * @param {{ copilot?: "live"|"input", reviewer?: "live"|"input" }} [params.evidenceSourceKinds]
209
+ * Indicates whether successful evidence came from live detection or caller-supplied snapshot input.
210
+ * @param {boolean} [params.explicitTargetMissing]
211
+ * True when the explicit target was not found by one or more detector inputs.
212
+ * @param {string | null} [params.steeringLocatorPath]
213
+ * Path to the steering state file, when explicitly provided by the caller.
214
+ * null means no locator was given.
215
+ * @param {object | null} [params.steeringEvidence]
216
+ * Loaded and normalized steering state, or null when file not found.
217
+ * @param {boolean} [params.steeringLoadFailed]
218
+ * true when a steering locator was provided but loading the file failed.
219
+ * @param {string | null} [params.steeringUnavailableReason]
220
+ * Optional explicit unavailable reason when a steering file was supplied but
221
+ * cannot be trusted for this inspected target.
222
+ * @param {object | null} [params.steeringReadback]
223
+ * Precomputed steering readback summary for the inspection surface.
224
+ * @param {object} [params.loopIterations]
225
+ * Best-effort Copilot remote-loop iteration summary. This is intended for
226
+ * GitHub-backed PR loops where durable review/timeline facts are available.
227
+ * @returns {object} inspection snapshot with always-present and best-effort fields
228
+ */
229
+ export function composeRunInspectionSnapshot({
230
+ target,
231
+ inspectedAt,
232
+ outerState,
233
+ outerAllowedTransitions,
234
+ outerAction,
235
+ outerReason,
236
+ copilotEvidence,
237
+ reviewerEvidence,
238
+ existingCheckpoint,
239
+ checkpointEvidencePath = null,
240
+ liveAvailability,
241
+ evidenceSourceKinds = { copilot: "live", reviewer: "live" },
242
+ explicitTargetMissing = false,
243
+ steeringLocatorPath = null,
244
+ steeringEvidence = null,
245
+ steeringLoadFailed = false,
246
+ steeringUnavailableReason = null,
247
+ steeringReadback = null,
248
+ loopIterations = {
249
+ available: false,
250
+ source: "github_pr_timeline",
251
+ reason: "unavailable",
252
+ },
253
+ }) {
254
+ const { repo, pr } = target;
255
+ const runId = deriveRunIdForInspectionTarget(target);
256
+ const markers = { missing: [], stale: [], conflicts: [] };
257
+
258
+ const copilotLiveOk = liveAvailability.copilot === "ok";
259
+ const reviewerLiveOk = liveAvailability.reviewer === "ok";
260
+ const copilotLiveFailed = liveAvailability.copilot === "failed";
261
+ const reviewerLiveFailed = liveAvailability.reviewer === "failed";
262
+ const bothLiveOk = copilotLiveOk && reviewerLiveOk;
263
+ const copilotSourceKind = evidenceSourceKinds.copilot ?? "live";
264
+ const reviewerSourceKind = evidenceSourceKinds.reviewer ?? "live";
265
+ const bothSourceKindsLive = copilotSourceKind === "live" && reviewerSourceKind === "live";
266
+ const inputSnapshotMode = bothLiveOk && !bothSourceKindsLive;
267
+
268
+ const evidenceAuthoritative = [];
269
+ const evidenceCheckpoint = [];
270
+
271
+ // -------------------------------------------------------------------------
272
+ // Determine source mode and trust
273
+ // -------------------------------------------------------------------------
274
+
275
+ let sourceMode;
276
+ let trust;
277
+
278
+ if (explicitTargetMissing) {
279
+ sourceMode = SOURCE_MODE.UNAVAILABLE;
280
+ trust = TRUST.UNAVAILABLE;
281
+ markers.missing.push("explicit target PR was not found");
282
+ } else if (bothLiveOk && bothSourceKindsLive) {
283
+ sourceMode = SOURCE_MODE.LIVE_DETECTOR_BACKED;
284
+ trust = TRUST.AUTHORITATIVE;
285
+ evidenceAuthoritative.push("live Copilot loop detector", "live reviewer loop detector");
286
+ } else if (inputSnapshotMode) {
287
+ sourceMode = SOURCE_MODE.PARTIAL;
288
+ trust = TRUST.DEGRADED;
289
+ } else if (!copilotLiveOk && !reviewerLiveOk) {
290
+ if (existingCheckpoint !== null && typeof existingCheckpoint?.outerAction === "string") {
291
+ sourceMode = SOURCE_MODE.CHECKPOINT_ONLY;
292
+ trust = TRUST.CHECKPOINT;
293
+ } else {
294
+ sourceMode = SOURCE_MODE.UNAVAILABLE;
295
+ trust = TRUST.UNAVAILABLE;
296
+ }
297
+ if (copilotLiveFailed) {
298
+ markers.missing.push("live Copilot loop state (detection failed)");
299
+ }
300
+ if (reviewerLiveFailed) {
301
+ markers.missing.push("live reviewer loop state (detection failed)");
302
+ }
303
+ } else {
304
+ // Partial: one live ok, one failed
305
+ sourceMode = SOURCE_MODE.PARTIAL;
306
+ trust = TRUST.DEGRADED;
307
+ if (copilotLiveOk) {
308
+ evidenceAuthoritative.push("live Copilot loop detector");
309
+ } else {
310
+ if (copilotLiveFailed) {
311
+ markers.missing.push("live Copilot loop state (detection failed)");
312
+ }
313
+ if (existingCheckpoint?.copilotState !== undefined) {
314
+ markers.stale.push("copilot loop state (checkpoint-derived; live detection failed)");
315
+ }
316
+ }
317
+ if (reviewerLiveOk) {
318
+ evidenceAuthoritative.push("live reviewer loop detector");
319
+ } else {
320
+ if (reviewerLiveFailed) {
321
+ markers.missing.push("live reviewer loop state (detection failed)");
322
+ }
323
+ if (existingCheckpoint?.reviewerState !== undefined) {
324
+ markers.stale.push("reviewer loop state (checkpoint-derived; live detection failed)");
325
+ }
326
+ }
327
+ }
328
+
329
+ // -------------------------------------------------------------------------
330
+ // Checkpoint evidence and conflict detection
331
+ // -------------------------------------------------------------------------
332
+
333
+ if (existingCheckpoint !== null) {
334
+ if (checkpointEvidencePath !== null) {
335
+ evidenceCheckpoint.push(checkpointEvidencePath);
336
+ }
337
+
338
+ if (bothLiveOk && outerAction !== undefined) {
339
+ // Check for conflicts between live-derived action and checkpoint
340
+ const ckptOuterAction = existingCheckpoint.outerAction;
341
+ if (typeof ckptOuterAction === "string" && ckptOuterAction !== outerAction) {
342
+ markers.conflicts.push(
343
+ `checkpoint outerAction '${ckptOuterAction}' differs from live-derived '${outerAction}'`,
344
+ );
345
+ }
346
+
347
+ const ckptCopilotState = existingCheckpoint.copilotState;
348
+ if (
349
+ copilotEvidence !== null
350
+ && typeof ckptCopilotState === "string"
351
+ && ckptCopilotState !== copilotEvidence.interpretation.state
352
+ ) {
353
+ markers.conflicts.push(
354
+ `checkpoint copilotState '${ckptCopilotState}' differs from live '${copilotEvidence.interpretation.state}'`,
355
+ );
356
+ }
357
+
358
+ const ckptReviewerState = existingCheckpoint.reviewerState;
359
+ if (
360
+ reviewerEvidence !== null
361
+ && typeof ckptReviewerState === "string"
362
+ && ckptReviewerState !== reviewerEvidence.interpretation.state
363
+ ) {
364
+ markers.conflicts.push(
365
+ `checkpoint reviewerState '${ckptReviewerState}' differs from live '${reviewerEvidence.interpretation.state}'`,
366
+ );
367
+ }
368
+ }
369
+ }
370
+
371
+ // -------------------------------------------------------------------------
372
+ // Top-level outer-state / outerAction surfacing eligibility
373
+ // -------------------------------------------------------------------------
374
+
375
+ // Top-level outer fields are only surfaced when the caller derived them from
376
+ // a complete current evidence set. Checkpoint-backed or mixed fallback remains
377
+ // available only as advisory drill-down evidence in this chunk.
378
+ const effectiveOuterState = isKnownOuterState(outerState) ? outerState : undefined;
379
+ const effectiveOuterAllowedTransitions = effectiveOuterState !== undefined && Array.isArray(outerAllowedTransitions)
380
+ ? [...outerAllowedTransitions]
381
+ : undefined;
382
+ const effectiveOuterAction = outerAction;
383
+ const effectiveOuterReason = outerReason;
384
+
385
+ // -------------------------------------------------------------------------
386
+ // Determine statusClass
387
+ // -------------------------------------------------------------------------
388
+
389
+ let statusClass;
390
+ if (sourceMode === SOURCE_MODE.UNAVAILABLE || effectiveOuterAction === undefined) {
391
+ statusClass = STATUS_CLASS.UNKNOWN;
392
+ } else {
393
+ statusClass = mapOuterActionToStatusClass(effectiveOuterAction);
394
+ }
395
+
396
+ // -------------------------------------------------------------------------
397
+ // Determine needsAttention
398
+ // -------------------------------------------------------------------------
399
+
400
+ const needsAttention =
401
+ effectiveOuterAction === "stop"
402
+ || markers.conflicts.length > 0
403
+ || markers.missing.length > 0
404
+ || sourceMode === SOURCE_MODE.CHECKPOINT_ONLY
405
+ || sourceMode === SOURCE_MODE.UNAVAILABLE
406
+ || (sourceMode === SOURCE_MODE.PARTIAL && !inputSnapshotMode);
407
+
408
+ // -------------------------------------------------------------------------
409
+ // Build evidence summary
410
+ // -------------------------------------------------------------------------
411
+
412
+ let evidenceSummary;
413
+ if (explicitTargetMissing) {
414
+ evidenceSummary = "The explicit target PR was not found; no current run state could be determined.";
415
+ } else if (sourceMode === SOURCE_MODE.LIVE_DETECTOR_BACKED) {
416
+ if (effectiveOuterState === "stay_with_current_live_owner") {
417
+ evidenceSummary = "Live detectors agree a live owner already controls this run, so the orchestrator should not issue a new handoff yet.";
418
+ } else if (effectiveOuterState === "needs_reconcile") {
419
+ evidenceSummary = "Live detectors found ambiguous or conflicting state, so the orchestrator must reconcile before continuing.";
420
+ } else if (effectiveOuterState === "stop_needs_human") {
421
+ evidenceSummary = `Live detectors indicate a blocked outer state that needs human intervention${effectiveOuterReason !== undefined ? ` (reason: ${effectiveOuterReason})` : ""}.`;
422
+ } else if (effectiveOuterState === "done_terminal") {
423
+ evidenceSummary = "Live detectors agree the PR is complete.";
424
+ } else if (effectiveOuterState === "continue_current_wait") {
425
+ evidenceSummary = "Live detectors agree the orchestrator is in its durable wait state.";
426
+ } else if (effectiveOuterState === "handoff_to_copilot_loop") {
427
+ evidenceSummary = "Live detectors indicate the next meaningful work belongs to the Copilot loop.";
428
+ } else if (effectiveOuterState === "handoff_to_reviewer_loop") {
429
+ evidenceSummary = "Live detectors indicate the next meaningful work belongs to the reviewer loop.";
430
+ } else if (effectiveOuterAction !== undefined) {
431
+ evidenceSummary = `Live detectors returned results, but only the compatibility outerAction could be determined (outerAction: ${effectiveOuterAction}).`;
432
+ } else {
433
+ evidenceSummary = "Live detectors returned results but outer state could not be determined.";
434
+ }
435
+ if (markers.conflicts.length > 0) {
436
+ evidenceSummary += " Checkpoint state conflicts with live facts.";
437
+ }
438
+ } else if (sourceMode === SOURCE_MODE.CHECKPOINT_ONLY) {
439
+ evidenceSummary =
440
+ "No live detector facts are available. Checkpoint state is shown as advisory drill-down only, and the current top-level run state could not be confirmed.";
441
+ } else if (sourceMode === SOURCE_MODE.PARTIAL) {
442
+ evidenceSummary = inputSnapshotMode
443
+ ? "One or more caller-supplied snapshot inputs were used. The result reflects the complete current-state picture provided to inspection, but remains degraded because it was not fully live-detector-backed."
444
+ : "Only partial live evidence is available. Any checkpoint-backed state is advisory only, so the current top-level run state could not be confirmed.";
445
+ } else {
446
+ evidenceSummary = "No evidence available to determine current run state.";
447
+ }
448
+
449
+ // -------------------------------------------------------------------------
450
+ // Build layers (best-effort drill-down)
451
+ // -------------------------------------------------------------------------
452
+
453
+ const layers = {};
454
+
455
+ if (copilotLiveOk && copilotEvidence !== null) {
456
+ const copilotSummary = summarizeLoopInterpretation(copilotEvidence.interpretation);
457
+ layers.copilot = {
458
+ currentState: copilotEvidence.interpretation.state,
459
+ allowedTransitions: copilotEvidence.interpretation.allowedTransitions,
460
+ sameHeadCleanConverged: copilotEvidence.interpretation.sameHeadCleanConverged === true,
461
+ loopDisposition: copilotSummary.loopDisposition,
462
+ terminal: copilotSummary.terminal,
463
+ };
464
+ } else if (typeof existingCheckpoint?.copilotState === "string") {
465
+ layers.copilot = {
466
+ currentState: existingCheckpoint.copilotState,
467
+ source: "checkpoint",
468
+ };
469
+ }
470
+
471
+ if (reviewerLiveOk && reviewerEvidence !== null) {
472
+ const reviewerSubmittedReviewState = reviewerEvidence.snapshot.submittedReviewState ?? null;
473
+ const reviewerApprovedOnCurrentHead = reviewerEvidence.snapshot.submittedReviewPresent === true
474
+ && reviewerSubmittedReviewState === "APPROVED"
475
+ && reviewerEvidence.snapshot.prHeadSha !== null
476
+ && reviewerEvidence.snapshot.submittedReviewCommitSha !== null
477
+ && reviewerEvidence.snapshot.prHeadSha === reviewerEvidence.snapshot.submittedReviewCommitSha;
478
+
479
+ layers.reviewer = {
480
+ currentState: reviewerEvidence.interpretation.state,
481
+ allowedTransitions: reviewerEvidence.interpretation.allowedTransitions,
482
+ scope: buildReviewerScope(reviewerEvidence.snapshot),
483
+ submittedReviewState: reviewerSubmittedReviewState,
484
+ approvedOnCurrentHead: reviewerApprovedOnCurrentHead,
485
+ };
486
+ } else if (typeof existingCheckpoint?.reviewerState === "string") {
487
+ layers.reviewer = {
488
+ currentState: existingCheckpoint.reviewerState,
489
+ source: "checkpoint",
490
+ scope: buildReviewerScope(existingCheckpoint),
491
+ };
492
+ }
493
+
494
+ // Steering layer (best-effort; only when an explicit locator is provided)
495
+ if (steeringLocatorPath === null || steeringLocatorPath === undefined) {
496
+ layers.steering = {
497
+ status: "unavailable",
498
+ reason: "no_steering_locator",
499
+ };
500
+ } else if (steeringLoadFailed) {
501
+ layers.steering = {
502
+ status: "unavailable",
503
+ reason: "load_failed",
504
+ };
505
+ } else if (steeringUnavailableReason !== null) {
506
+ layers.steering = {
507
+ status: "unavailable",
508
+ reason: steeringUnavailableReason,
509
+ };
510
+ } else if (steeringEvidence === null) {
511
+ layers.steering = {
512
+ status: "unavailable",
513
+ reason: "no_steering_file",
514
+ };
515
+ } else {
516
+ const liveSteering = evaluateLiveSteeringAvailability({
517
+ sourceMode,
518
+ trust,
519
+ markers,
520
+ statusClass,
521
+ copilotCurrentState: layers.copilot?.currentState,
522
+ });
523
+
524
+ if (liveSteering.status === "available") {
525
+ layers.steering = {
526
+ status: "available",
527
+ ...(steeringReadback ?? {}),
528
+ liveSteering,
529
+ };
530
+ } else {
531
+ layers.steering = {
532
+ status: "unavailable",
533
+ reason: liveSteering.reason,
534
+ liveSteering,
535
+ };
536
+ }
537
+ }
538
+
539
+ // -------------------------------------------------------------------------
540
+ // Lifecycle phase resolution (best-effort from available PR facts)
541
+ // -------------------------------------------------------------------------
542
+
543
+ let lifecyclePhase = null;
544
+ let lifecycleAllowedTransitions = null;
545
+
546
+ if (copilotLiveOk && copilotEvidence !== null) {
547
+ const copilotState = copilotEvidence.interpretation.state;
548
+ const mappedPhase = lifecyclePhaseForCopilotState(copilotState);
549
+ if (mappedPhase) {
550
+ lifecyclePhase = mappedPhase;
551
+ lifecycleAllowedTransitions = getAllowedTransitions(mappedPhase);
552
+ }
553
+ }
554
+
555
+ if (lifecyclePhase === null) {
556
+ // Fallback: derive from available PR facts
557
+ const loopIter = loopIterations ?? {};
558
+ const hasUnresolvedThreads = typeof loopIter.unresolvedReviewThreads === "number"
559
+ && loopIter.unresolvedReviewThreads > 0;
560
+ const copilotState = copilotLiveOk && copilotEvidence !== null
561
+ ? copilotEvidence.interpretation.state
562
+ : null;
563
+ const prIsDraft = copilotState === "pr_draft";
564
+
565
+ const resolved = resolveLifecycleState({
566
+ hasLinkedPr: !explicitTargetMissing,
567
+ prIsDraft,
568
+ hasUnresolvedThreads,
569
+ });
570
+ lifecyclePhase = resolved.state;
571
+ lifecycleAllowedTransitions = resolved.allowedTransitions;
572
+ }
573
+
574
+ // -------------------------------------------------------------------------
575
+ // Assemble final snapshot
576
+ // -------------------------------------------------------------------------
577
+
578
+ return {
579
+ ok: true,
580
+ schemaVersion: SCHEMA_VERSION,
581
+ target: { repo, pr },
582
+ runId,
583
+ inspectedAt,
584
+ activeStateFamily: ACTIVE_STATE_FAMILY,
585
+ outerState: effectiveOuterState ?? "unknown",
586
+ ...(effectiveOuterAllowedTransitions !== undefined ? { allowedTransitions: effectiveOuterAllowedTransitions } : {}),
587
+ outerAction: effectiveOuterAction ?? "unknown",
588
+ activeFamilyState: effectiveOuterAction ?? "unknown",
589
+ statusClass,
590
+ needsAttention,
591
+ sourceMode,
592
+ trust,
593
+ evidence: {
594
+ summary: evidenceSummary,
595
+ authoritative: evidenceAuthoritative,
596
+ checkpoint: evidenceCheckpoint,
597
+ },
598
+ markers,
599
+ loopIterations,
600
+ layers,
601
+ lifecyclePhase,
602
+ lifecycleAllowedTransitions,
603
+ };
604
+ }