@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.
- package/bin/capture-deep-persona-signals.mjs +143 -0
- package/bin/ensure-phase-files.mjs +7 -0
- package/bin/log-bash-exit-1.mjs +7 -0
- package/bin/parse-review-threads.mjs +7 -0
- package/package.json +78 -0
- package/src/analysis/change-classifier.mjs +146 -0
- package/src/analysis/diff-analyzer.mjs +285 -0
- package/src/bash-exit-one.mjs +130 -0
- package/src/cli/helpers.mjs +22 -0
- package/src/cli/primitives.mjs +70 -0
- package/src/cli/retry-wrapper.mjs +169 -0
- package/src/cli/subcommand-runner.mjs +246 -0
- package/src/config/config.mjs +965 -0
- package/src/debt/cluster.mjs +240 -0
- package/src/debt/debt-finding.mjs +68 -0
- package/src/debt/debt-signal.mjs +46 -0
- package/src/debt/deep-persona-signals.mjs +266 -0
- package/src/debt/remediation-to-issue.mjs +121 -0
- package/src/debt/score.mjs +127 -0
- package/src/debt/shape.mjs +214 -0
- package/src/github/copilot-helpers.mjs +343 -0
- package/src/github/repo-slug.mjs +105 -0
- package/src/github/review-threads.mjs +343 -0
- package/src/harness/adapter.mjs +57 -0
- package/src/harness/index.mjs +3 -0
- package/src/harness/noop-adapter.mjs +22 -0
- package/src/harness/pi-adapter.mjs +47 -0
- package/src/loop/async-start-contract.mjs +170 -0
- package/src/loop/conductor-routing.mjs +817 -0
- package/src/loop/copilot-ci-status.mjs +255 -0
- package/src/loop/copilot-loop-iterations.mjs +161 -0
- package/src/loop/copilot-loop-state.mjs +510 -0
- package/src/loop/handoff-envelope.mjs +800 -0
- package/src/loop/issue-refinement-artifact.mjs +268 -0
- package/src/loop/lifecycle-state.mjs +342 -0
- package/src/loop/phase-files.mjs +187 -0
- package/src/loop/policy-constants.mjs +17 -0
- package/src/loop/pr-gate-coordination.mjs +1278 -0
- package/src/loop/public-dev-loop-routing-contract.mjs +277 -0
- package/src/loop/public-dev-loop-routing.mjs +1746 -0
- package/src/loop/queue-board-ordering.mjs +38 -0
- package/src/loop/queue-board-sync.mjs +223 -0
- package/src/loop/queue-driver.mjs +164 -0
- package/src/loop/queue-parallel.mjs +190 -0
- package/src/loop/queue-state.mjs +230 -0
- package/src/loop/retrospective-checkpoint.mjs +178 -0
- package/src/loop/reviewer-loop-state.mjs +456 -0
- package/src/loop/run-inspection.mjs +604 -0
- package/src/loop/steering.mjs +793 -0
- package/src/loop/timeout-policy.mjs +73 -0
- package/src/loop/tracker-first-loop-state.mjs +87 -0
- package/src/loop/tracker-pr-state.mjs +301 -0
- package/src/loop/worktree-guard.mjs +141 -0
- package/src/refinement/ac-dod-matrix.mjs +95 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Queue state machine — durable .pi/dev-loop-queue.json read/write/transition.
|
|
3
|
+
*
|
|
4
|
+
* Entry lifecycle:
|
|
5
|
+
* queued → running → waiting_review → gates_passing → merging → done
|
|
6
|
+
* any state → blocked | failed
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { readFile, writeFile, rename, mkdir } from "node:fs/promises";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
|
|
12
|
+
// ── Schema constants ────────────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
export const QUEUE_VERSION = 1;
|
|
15
|
+
|
|
16
|
+
export const ENTRY_STATUS = Object.freeze([
|
|
17
|
+
"queued",
|
|
18
|
+
"running",
|
|
19
|
+
"waiting_review",
|
|
20
|
+
"gates_passing",
|
|
21
|
+
"merging",
|
|
22
|
+
"done",
|
|
23
|
+
"blocked",
|
|
24
|
+
"failed",
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
export const VALID_TRANSITIONS = Object.freeze({
|
|
28
|
+
queued: ["running", "blocked"],
|
|
29
|
+
running: ["waiting_review", "blocked", "failed", "done"],
|
|
30
|
+
waiting_review: ["running", "gates_passing", "blocked", "failed", "done"],
|
|
31
|
+
gates_passing: ["merging", "blocked", "failed", "done"],
|
|
32
|
+
merging: ["done", "failed", "blocked"],
|
|
33
|
+
done: [],
|
|
34
|
+
blocked: ["queued", "running", "failed"],
|
|
35
|
+
failed: ["queued"],
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
export const RECOVERABLE_FAILURES = new Set([
|
|
39
|
+
"acceptance_report_parse_failure",
|
|
40
|
+
"round_cap_reached",
|
|
41
|
+
"timeout",
|
|
42
|
+
]);
|
|
43
|
+
|
|
44
|
+
// ── Default queue shape ─────────────────────────────────────────────
|
|
45
|
+
|
|
46
|
+
function emptyQueue() {
|
|
47
|
+
return { version: QUEUE_VERSION, entries: [] };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ── File I/O ─────────────────────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
export function queueFilePath(repoRoot) {
|
|
53
|
+
return path.join(repoRoot, ".pi", "dev-loop-queue.json");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function readQueue(repoRoot) {
|
|
57
|
+
const fp = queueFilePath(repoRoot);
|
|
58
|
+
try {
|
|
59
|
+
const raw = await readFile(fp, "utf8");
|
|
60
|
+
const parsed = JSON.parse(raw);
|
|
61
|
+
if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.entries)) {
|
|
62
|
+
return emptyQueue();
|
|
63
|
+
}
|
|
64
|
+
return { version: parsed.version ?? QUEUE_VERSION, entries: parsed.entries };
|
|
65
|
+
} catch {
|
|
66
|
+
return emptyQueue();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export async function writeQueue(repoRoot, queue) {
|
|
71
|
+
const fp = queueFilePath(repoRoot);
|
|
72
|
+
await mkdir(path.dirname(fp), { recursive: true });
|
|
73
|
+
// Atomic write: write to temp file then rename
|
|
74
|
+
const tmp = fp + ".tmp." + Date.now();
|
|
75
|
+
await writeFile(tmp, JSON.stringify(queue, null, 2) + "\n", "utf8");
|
|
76
|
+
await rename(tmp, fp);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ── Entry helpers ────────────────────────────────────────────────────
|
|
80
|
+
|
|
81
|
+
export function createEntry(target, kind, dependsOn = []) {
|
|
82
|
+
return {
|
|
83
|
+
target,
|
|
84
|
+
kind, // "issue" | "pr"
|
|
85
|
+
status: "queued",
|
|
86
|
+
dependsOn: Array.isArray(dependsOn) ? dependsOn : [],
|
|
87
|
+
pr: null,
|
|
88
|
+
runId: null,
|
|
89
|
+
retrospectiveWritten: false,
|
|
90
|
+
failureReason: null,
|
|
91
|
+
failureKind: null,
|
|
92
|
+
retryCount: 0,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function snapshotEntry(entry) {
|
|
97
|
+
return { ...entry };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function findEntry(queue, target) {
|
|
101
|
+
return queue.entries.find((e) => e.target === target);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function findEntryIndex(queue, target) {
|
|
105
|
+
return queue.entries.findIndex((e) => e.target === target);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ── State machine ────────────────────────────────────────────────────
|
|
109
|
+
|
|
110
|
+
export function isValidTransition(from, to) {
|
|
111
|
+
const allowed = VALID_TRANSITIONS[from];
|
|
112
|
+
if (!allowed) return false;
|
|
113
|
+
return allowed.includes(to);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function transitionEntry(entry, to, metadata = {}) {
|
|
117
|
+
if (!isValidTransition(entry.status, to)) {
|
|
118
|
+
throw new Error(
|
|
119
|
+
`Invalid transition: ${entry.status} → ${to} for entry ${entry.target}`
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
entry.status = to;
|
|
123
|
+
if (metadata.pr != null) entry.pr = metadata.pr;
|
|
124
|
+
if (metadata.runId != null) entry.runId = metadata.runId;
|
|
125
|
+
if (metadata.retrospectiveWritten != null) entry.retrospectiveWritten = metadata.retrospectiveWritten;
|
|
126
|
+
if (metadata.failureReason != null) entry.failureReason = metadata.failureReason;
|
|
127
|
+
if (metadata.failureKind != null) entry.failureKind = metadata.failureKind;
|
|
128
|
+
return entry;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ── Dependency resolution ────────────────────────────────────────────
|
|
132
|
+
|
|
133
|
+
export function entryDependenciesSatisfied(queue, entry) {
|
|
134
|
+
if (!entry.dependsOn || entry.dependsOn.length === 0) return true;
|
|
135
|
+
return entry.dependsOn.every((depTarget) => {
|
|
136
|
+
const dep = findEntry(queue, depTarget);
|
|
137
|
+
return dep && dep.status === "done";
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ── Queue ordering ───────────────────────────────────────────────────
|
|
142
|
+
|
|
143
|
+
export function topologicalOrder(entries) {
|
|
144
|
+
const visited = new Set();
|
|
145
|
+
const result = [];
|
|
146
|
+
|
|
147
|
+
function visit(entry, path = new Set()) {
|
|
148
|
+
if (visited.has(entry.target)) return;
|
|
149
|
+
if (path.has(entry.target)) {
|
|
150
|
+
throw new Error(`Circular dependency detected involving ${entry.target}`);
|
|
151
|
+
}
|
|
152
|
+
path.add(entry.target);
|
|
153
|
+
for (const depTarget of entry.dependsOn || []) {
|
|
154
|
+
const dep = entries.find((e) => e.target === depTarget);
|
|
155
|
+
if (dep) visit(dep, new Set(path));
|
|
156
|
+
}
|
|
157
|
+
path.delete(entry.target);
|
|
158
|
+
visited.add(entry.target);
|
|
159
|
+
result.push(entry);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
for (const entry of entries) {
|
|
163
|
+
visit(entry);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return result;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function applyOrderHint(ordered, orderHint) {
|
|
170
|
+
if (orderHint.length === 0) return ordered;
|
|
171
|
+
const hinted = new Set(orderHint);
|
|
172
|
+
const hintIndex = new Map(orderHint.map((target, i) => [target, i]));
|
|
173
|
+
const inHint = [];
|
|
174
|
+
const rest = [];
|
|
175
|
+
for (const entry of ordered) {
|
|
176
|
+
if (hinted.has(entry.target)) {
|
|
177
|
+
inHint.push(entry);
|
|
178
|
+
} else {
|
|
179
|
+
rest.push(entry);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
inHint.sort((a, b) => hintIndex.get(a.target) - hintIndex.get(b.target));
|
|
183
|
+
return [...inHint, ...rest];
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function nextReadyEntry(queue, maxRetries = 1, orderHint = []) {
|
|
187
|
+
const ordered = topologicalOrder(queue.entries);
|
|
188
|
+
const sorted = applyOrderHint(ordered, orderHint);
|
|
189
|
+
for (const entry of sorted) {
|
|
190
|
+
if (entry.status === "queued" && entryDependenciesSatisfied(queue, entry)) {
|
|
191
|
+
return entry;
|
|
192
|
+
}
|
|
193
|
+
if (
|
|
194
|
+
entry.status === "failed" &&
|
|
195
|
+
RECOVERABLE_FAILURES.has(entry.failureKind) &&
|
|
196
|
+
(entry.retryCount ?? 0) < maxRetries
|
|
197
|
+
) {
|
|
198
|
+
return entry;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function allDone(queue) {
|
|
205
|
+
return queue.entries.every((e) => e.status === "done" || e.status === "blocked");
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function pendingEntries(queue) {
|
|
209
|
+
return queue.entries.filter(
|
|
210
|
+
(e) => e.status !== "done" && e.status !== "blocked"
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// ── Bug injection ────────────────────────────────────────────────────
|
|
215
|
+
|
|
216
|
+
export function appendBugIssue(queue, issueNumber, dependsOn = null) {
|
|
217
|
+
const entry = createEntry(issueNumber, "issue", dependsOn ? [dependsOn] : []);
|
|
218
|
+
entry.status = "queued";
|
|
219
|
+
queue.entries.push(entry);
|
|
220
|
+
return entry;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// ── Serialization helpers ────────────────────────────────────────────
|
|
224
|
+
|
|
225
|
+
export function serializeQueue(queue) {
|
|
226
|
+
return {
|
|
227
|
+
version: queue.version,
|
|
228
|
+
entries: queue.entries.map((e) => ({ ...e })),
|
|
229
|
+
};
|
|
230
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Post-run behavioral retrospective checkpoint contract.
|
|
3
|
+
*
|
|
4
|
+
* Defines the enforcement seam for the required post-run behavioral retrospective
|
|
5
|
+
* after qualifying async dev-loop completions in this repository.
|
|
6
|
+
*
|
|
7
|
+
* This module is intentionally pure and side-effect free. Callers are responsible
|
|
8
|
+
* for reading/writing the durable checkpoint artifact and passing the resolved
|
|
9
|
+
* checkpoint state to the enforcement gate.
|
|
10
|
+
*
|
|
11
|
+
* Relationship to formal dev mode:
|
|
12
|
+
* - Formal local dev mode is scoped to local implementation/self-improvement work.
|
|
13
|
+
* - The required post-run behavioral retrospective applies to qualifying async
|
|
14
|
+
* GitHub-first dev-loop completions, independent of whether that run was in
|
|
15
|
+
* formal local dev mode.
|
|
16
|
+
* - These are related but distinct requirements.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Stable state constants for the post-run behavioral retrospective checkpoint.
|
|
21
|
+
*
|
|
22
|
+
* These represent the state that a caller derives from the durable checkpoint
|
|
23
|
+
* artifact on disk, then passes to the enforcement gate.
|
|
24
|
+
*
|
|
25
|
+
* Mapping from durable artifact to checkpoint state:
|
|
26
|
+
* - No artifact file → NONE (no qualifying completion has occurred)
|
|
27
|
+
* - Artifact file with state "required" → MISSING (completion detected, retrospective pending)
|
|
28
|
+
* - Artifact file with state "complete" → COMPLETE (retrospective recorded)
|
|
29
|
+
* - Artifact file with state "skipped" → SKIPPED (explicitly skipped with reason)
|
|
30
|
+
*/
|
|
31
|
+
export const RETROSPECTIVE_CHECKPOINT_STATE = Object.freeze({
|
|
32
|
+
/** No qualifying async dev-loop completion has occurred; no retrospective is required. */
|
|
33
|
+
NONE: "none",
|
|
34
|
+
/** The required retrospective has been completed and recorded. */
|
|
35
|
+
COMPLETE: "complete",
|
|
36
|
+
/** The required retrospective was explicitly skipped with a stated reason. */
|
|
37
|
+
SKIPPED: "skipped",
|
|
38
|
+
/** A qualifying async dev-loop completion was detected but no retrospective checkpoint exists. */
|
|
39
|
+
MISSING: "missing",
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The set of internal dev-loop strategy gate names that represent qualifying
|
|
44
|
+
* GitHub-first async completions in this repository.
|
|
45
|
+
*
|
|
46
|
+
* A post-run behavioral retrospective is required before the next dev-loop
|
|
47
|
+
* start/resume when the previous run used one of these gates.
|
|
48
|
+
*
|
|
49
|
+
* Qualifying gates:
|
|
50
|
+
* - copilot_pr_followup: Copilot-owned PR follow-up (primary routed GitHub-first path)
|
|
51
|
+
* - issue_intake: Copilot-first issue intake (GitHub-first issue assignment path)
|
|
52
|
+
*/
|
|
53
|
+
export const RETROSPECTIVE_QUALIFYING_GATES = Object.freeze([
|
|
54
|
+
"copilot_pr_followup",
|
|
55
|
+
"issue_intake",
|
|
56
|
+
]);
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Normalizes an external retrospective checkpoint-state input to one of the
|
|
60
|
+
* stable RETROSPECTIVE_CHECKPOINT_STATE values. Returns null when the value is
|
|
61
|
+
* absent or unrecognized.
|
|
62
|
+
*
|
|
63
|
+
* @param {unknown} value
|
|
64
|
+
* @returns {"none"|"complete"|"skipped"|"missing"|null}
|
|
65
|
+
*/
|
|
66
|
+
export function normalizeRetrospectiveCheckpointState(value) {
|
|
67
|
+
if (value === undefined || value === null) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const normalized = typeof value === "string" ? value.trim().toLowerCase() : "";
|
|
72
|
+
return Object.values(RETROSPECTIVE_CHECKPOINT_STATE).includes(normalized) ? normalized : null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Returns true if a routing result represents a qualifying GitHub-first async
|
|
77
|
+
* dev-loop completion that requires a post-run behavioral retrospective before
|
|
78
|
+
* the next start/resume.
|
|
79
|
+
*
|
|
80
|
+
* A qualifying completion is one that:
|
|
81
|
+
* - has a `selectedGate` in RETROSPECTIVE_QUALIFYING_GATES
|
|
82
|
+
* - with `routeKind === "route"` (inspect/status-only results do not qualify)
|
|
83
|
+
*/
|
|
84
|
+
export function isQualifyingAsyncCompletion(routingResult) {
|
|
85
|
+
if (!routingResult || typeof routingResult !== "object") return false;
|
|
86
|
+
const { routeKind, selectedGate } = routingResult;
|
|
87
|
+
if (routeKind !== "route") {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
if (typeof selectedGate !== "string") return false;
|
|
91
|
+
return RETROSPECTIVE_QUALIFYING_GATES.includes(selectedGate);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Enforcement gate for the required post-run behavioral retrospective.
|
|
96
|
+
*
|
|
97
|
+
* Evaluates whether a proposed dev-loop routing result should proceed or be
|
|
98
|
+
* blocked due to a missing retrospective checkpoint from the previous qualifying
|
|
99
|
+
* async completion.
|
|
100
|
+
*
|
|
101
|
+
* Pass-through cases (proposed routing is returned unchanged):
|
|
102
|
+
* - checkpoint state is NONE (no qualifying completion has happened; no requirement exists)
|
|
103
|
+
* - checkpoint state is COMPLETE (retrospective was recorded; requirement satisfied)
|
|
104
|
+
* - checkpoint state is SKIPPED (explicitly skipped with reason; requirement satisfied)
|
|
105
|
+
* - proposed routing is already a stop or needs_reconcile result
|
|
106
|
+
* - proposed routing is an inspect-only result
|
|
107
|
+
*
|
|
108
|
+
* Fail-closed case:
|
|
109
|
+
* - checkpoint state is MISSING: returns a needs_reconcile result that blocks start/resume
|
|
110
|
+
* - unrecognized checkpoint state: returns a needs_reconcile result
|
|
111
|
+
*
|
|
112
|
+
* @param {object} input
|
|
113
|
+
* @param {string} input.checkpointState - One of the RETROSPECTIVE_CHECKPOINT_STATE values
|
|
114
|
+
* @param {object} input.proposedRouting - The routing result from evaluatePublicDevLoopRouting
|
|
115
|
+
* @returns {object} The original or replacement routing result
|
|
116
|
+
*/
|
|
117
|
+
export function evaluateRetrospectiveGate({ checkpointState, proposedRouting } = {}) {
|
|
118
|
+
if (!proposedRouting || typeof proposedRouting !== "object") {
|
|
119
|
+
return {
|
|
120
|
+
publicEntrypoint: "dev-loop",
|
|
121
|
+
routeKind: "needs_reconcile",
|
|
122
|
+
selectedGate: "fail_closed_reconcile",
|
|
123
|
+
selectedStrategy: null,
|
|
124
|
+
executionMode: "bounded_handoff",
|
|
125
|
+
waitSemantics: "default",
|
|
126
|
+
canonicalState: null,
|
|
127
|
+
issueAssignmentSeam: "not_applicable",
|
|
128
|
+
nextAction: "Reconcile the retrospective checkpoint state before routing.",
|
|
129
|
+
reason: "Missing or invalid proposed routing result for retrospective gate evaluation.",
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Already a terminal/inspect result — pass through regardless of checkpoint state.
|
|
134
|
+
if (
|
|
135
|
+
proposedRouting.routeKind === "stop" ||
|
|
136
|
+
proposedRouting.routeKind === "needs_reconcile" ||
|
|
137
|
+
proposedRouting.routeKind === "inspect"
|
|
138
|
+
) {
|
|
139
|
+
return proposedRouting;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// No qualifying completion, or retrospective satisfied — pass through.
|
|
143
|
+
if (
|
|
144
|
+
checkpointState === RETROSPECTIVE_CHECKPOINT_STATE.NONE ||
|
|
145
|
+
checkpointState === RETROSPECTIVE_CHECKPOINT_STATE.COMPLETE ||
|
|
146
|
+
checkpointState === RETROSPECTIVE_CHECKPOINT_STATE.SKIPPED
|
|
147
|
+
) {
|
|
148
|
+
return proposedRouting;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Missing retrospective checkpoint — fail closed.
|
|
152
|
+
if (checkpointState === RETROSPECTIVE_CHECKPOINT_STATE.MISSING) {
|
|
153
|
+
return {
|
|
154
|
+
...proposedRouting,
|
|
155
|
+
routeKind: "needs_reconcile",
|
|
156
|
+
selectedGate: "fail_closed_reconcile",
|
|
157
|
+
selectedStrategy: null,
|
|
158
|
+
waitSemantics: proposedRouting.waitSemantics ?? "default",
|
|
159
|
+
issueAssignmentSeam: proposedRouting.issueAssignmentSeam ?? "not_applicable",
|
|
160
|
+
nextAction:
|
|
161
|
+
"Complete or explicitly skip the required post-run behavioral retrospective before starting or resuming the next dev-loop run.",
|
|
162
|
+
reason:
|
|
163
|
+
"The previous qualifying async dev-loop completion is missing its required behavioral retrospective checkpoint.",
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Unrecognized checkpoint state — fail closed.
|
|
168
|
+
return {
|
|
169
|
+
...proposedRouting,
|
|
170
|
+
routeKind: "needs_reconcile",
|
|
171
|
+
selectedGate: "fail_closed_reconcile",
|
|
172
|
+
selectedStrategy: null,
|
|
173
|
+
waitSemantics: proposedRouting.waitSemantics ?? "default",
|
|
174
|
+
issueAssignmentSeam: proposedRouting.issueAssignmentSeam ?? "not_applicable",
|
|
175
|
+
nextAction: "Reconcile the retrospective checkpoint state before routing.",
|
|
176
|
+
reason: `Unrecognized retrospective checkpoint state: "${String(checkpointState)}".`,
|
|
177
|
+
};
|
|
178
|
+
}
|