@dev-loops/core 0.6.0 → 0.7.2
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/package.json +7 -7
- package/src/analysis/change-classifier.mjs +50 -6
- package/src/analysis/diff-analyzer.mjs +68 -12
- package/src/claude/hook-decisions.mjs +138 -15
- package/src/config/config.mjs +247 -98
- package/src/config/extension-defaults.yaml +6 -11
- package/src/github/copilot-helpers.mjs +143 -0
- package/src/harness/extension-adapter.mjs +1 -0
- package/src/harness/index.mjs +0 -1
- package/src/loop/bash-command-classify.mjs +333 -29
- package/src/loop/conductor-routing.mjs +0 -27
- package/src/loop/copilot-loop-state.mjs +25 -2
- package/src/loop/gate-fanin.mjs +137 -0
- package/src/loop/handoff-envelope.mjs +142 -70
- package/src/loop/issue-refinement-artifact.mjs +259 -8
- package/src/loop/lifecycle-state.mjs +1 -1
- package/src/loop/pr-gate-coordination.mjs +158 -238
- package/src/loop/pr-lifecycle.mjs +79 -0
- package/src/loop/public-dev-loop-routing.mjs +2 -2
- package/src/loop/queue-board-ordering.mjs +52 -8
- package/src/loop/queue-board-sync.mjs +62 -3
- package/src/loop/queue-driver.mjs +80 -8
- package/src/loop/queue-state.mjs +13 -2
- package/src/loop/reviewer-loop-state.mjs +20 -2
- package/src/projects/list-queue-items.mjs +380 -0
- package/src/projects/move-queue-item.mjs +394 -0
- package/src/projects/resolve-project.mjs +183 -0
- package/bin/capture-deep-persona-signals.mjs +0 -143
- package/src/debt/deep-persona-signals.mjs +0 -266
- package/src/harness/claude-extension-adapter.mjs +0 -102
- package/src/refinement/ac-dod-matrix.mjs +0 -95
|
@@ -1,6 +1,35 @@
|
|
|
1
|
-
import { loadBoardConfig, resolveProjectNumber } from "./queue-board-sync.mjs";
|
|
2
|
-
import { main as listQueueItemsMain } from "
|
|
1
|
+
import { loadBoardConfig, resolveProjectNumber, loadStateColumnMap, LOGICAL_COLUMN } from "./queue-board-sync.mjs";
|
|
2
|
+
import { main as listQueueItemsMain } from "../projects/list-queue-items.mjs";
|
|
3
3
|
|
|
4
|
+
// Canonical fail-closed Next Up tokens — the SINGLE source of truth so the reason
|
|
5
|
+
// codes and the empty-queue message stay byte-identical across every layer that
|
|
6
|
+
// detects them (queue-driver, run-queue, resolve-active-board-item). These strings
|
|
7
|
+
// drifted twice before centralization (#1091); import them, never re-inline them.
|
|
8
|
+
export const REASON_NEXT_UP_EMPTY = "next-up-empty";
|
|
9
|
+
export const REASON_BOARD_QUERY_ERROR = "board-query-error";
|
|
10
|
+
export const REASON_NEXT_UP_TARGET_MISSING_LOCALLY = "next-up-target-missing-locally";
|
|
11
|
+
export const EMPTY_NEXT_UP_MESSAGE = "queue empty — prioritize Backlog items into Next Up";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Resolve the board's "Next Up" pickup order (issue #1091).
|
|
15
|
+
*
|
|
16
|
+
* Next Up is the NORMATIVE, fail-closed pickup source. This resolver reports
|
|
17
|
+
* enough to let the driver distinguish three cases cleanly (it never silently
|
|
18
|
+
* collapses them):
|
|
19
|
+
*
|
|
20
|
+
* - Board NOT configured → `{ ok:true, configured:false, order:[] }`. The
|
|
21
|
+
* Next Up concept does not exist; the driver keeps its legacy local order.
|
|
22
|
+
* - Board configured, Next Up query SUCCEEDS → `{ ok:true, configured:true,
|
|
23
|
+
* order:[…], reason:null }`. `order` may be empty (a genuinely empty Next
|
|
24
|
+
* Up → the driver fails closed / idles, it MUST NOT fall back to Backlog).
|
|
25
|
+
* - Board configured, query ERRORS (unreachable / project unresolvable / API
|
|
26
|
+
* failure) → `{ ok:false, configured:true, order:[], reason:<msg> }`. The
|
|
27
|
+
* driver surfaces the error and stops; it MUST NOT fall back to Backlog.
|
|
28
|
+
*
|
|
29
|
+
* `order` and `reason` are always present so the fail-open membership layer
|
|
30
|
+
* (queue-membership.mjs), which predates the `ok`/`configured` fields, keeps
|
|
31
|
+
* working unchanged (it reads `order`/`reason` only).
|
|
32
|
+
*/
|
|
4
33
|
export async function resolveNextUpOrder(
|
|
5
34
|
repo,
|
|
6
35
|
repoRoot,
|
|
@@ -9,19 +38,32 @@ export async function resolveNextUpOrder(
|
|
|
9
38
|
) {
|
|
10
39
|
const config = loadBoardConfig(repoRoot);
|
|
11
40
|
if (!config.enabled) {
|
|
12
|
-
return { ok: true, order: [], reason: config.reason ?? "board not configured" };
|
|
41
|
+
return { ok: true, configured: false, order: [], reason: config.reason ?? "board not configured" };
|
|
13
42
|
}
|
|
14
43
|
|
|
15
44
|
let projectNumber;
|
|
16
45
|
try {
|
|
17
46
|
projectNumber = await resolveProjectNumber(repo, config, env, dependencies.runChild);
|
|
18
47
|
} catch (err) {
|
|
19
|
-
|
|
48
|
+
// Board IS configured but we cannot resolve/reach it: this is a query ERROR,
|
|
49
|
+
// not an empty Next Up. Fail closed at the driver, never Backlog fallback.
|
|
50
|
+
return { ok: false, configured: true, order: [], reason: err.message ?? "board lookup failed" };
|
|
20
51
|
}
|
|
21
52
|
if (!projectNumber) {
|
|
22
|
-
return { ok: true, order: [], reason: "could not resolve board project" };
|
|
53
|
+
return { ok: false, configured: true, order: [], reason: "could not resolve board project" };
|
|
23
54
|
}
|
|
24
55
|
|
|
56
|
+
// Resolve the logical next_up column through the SAME statusColumns mapping
|
|
57
|
+
// board-sync uses (#1098), so a renamed Next Up column (e.g. "Todo") is
|
|
58
|
+
// queried by its configured display name instead of the literal default.
|
|
59
|
+
// No config-error guard here: loadBoardConfig above already short-circuits any
|
|
60
|
+
// `.devloops` read/parse error to `enabled:false` (early return at the top of
|
|
61
|
+
// this function), so a malformed config never reaches this point. The
|
|
62
|
+
// fail-closed-on-config-error guard lives on the direct-read pickup path
|
|
63
|
+
// (resolve-active-board-item), which does NOT go through loadBoardConfig.
|
|
64
|
+
const { columnNames } = loadStateColumnMap(repoRoot);
|
|
65
|
+
const nextUpColumn = columnNames[LOGICAL_COLUMN.NEXT_UP];
|
|
66
|
+
|
|
25
67
|
const listItems = dependencies.listQueueItems ?? listQueueItemsMain;
|
|
26
68
|
try {
|
|
27
69
|
const result = await listItems(
|
|
@@ -29,14 +71,16 @@ export async function resolveNextUpOrder(
|
|
|
29
71
|
// resolveProjectNumber yields a number, so stringify it. Passing the raw
|
|
30
72
|
// number trips parseProjectRef's `typeof raw !== "string"` guard, which
|
|
31
73
|
// surfaces as a misleading "--project is required" (#901).
|
|
32
|
-
{ repo, project: String(projectNumber), column:
|
|
74
|
+
{ repo, project: String(projectNumber), column: nextUpColumn },
|
|
33
75
|
{ env, runChild: dependencies.runChild },
|
|
34
76
|
);
|
|
35
77
|
const order = (result?.items ?? [])
|
|
36
78
|
.map((it) => it.issueNumber ?? it.prNumber)
|
|
37
79
|
.filter((n) => typeof n === "number");
|
|
38
|
-
|
|
80
|
+
// Successful query — order may be empty (genuinely empty Next Up).
|
|
81
|
+
return { ok: true, configured: true, order, reason: null };
|
|
39
82
|
} catch (err) {
|
|
40
|
-
|
|
83
|
+
// Query ERROR — surface it; the driver stops and never falls back.
|
|
84
|
+
return { ok: false, configured: true, order: [], reason: err.message ?? "Next Up query failed" };
|
|
41
85
|
}
|
|
42
86
|
}
|
|
@@ -2,7 +2,7 @@ import { readFileSync } from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { parse as parseYaml } from "yaml";
|
|
4
4
|
import { runChild as coreRunChild } from "../cli/primitives.mjs";
|
|
5
|
-
import { main as moveQueueItemMain } from "
|
|
5
|
+
import { main as moveQueueItemMain } from "../projects/move-queue-item.mjs";
|
|
6
6
|
|
|
7
7
|
const DEFAULT_NON_SUCCESS_COLUMN = "Backlog";
|
|
8
8
|
|
|
@@ -107,6 +107,61 @@ export function boardColumnForLoopState(loopState, mapping = {}) {
|
|
|
107
107
|
return columnNames[logical] ?? columnNames[DEFAULT_LOGICAL_COLUMN];
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Derive the board's target LOGICAL column for a queue item from live GitHub
|
|
112
|
+
* facts (#1069). Returns LOGICAL_COLUMN.DONE, LOGICAL_COLUMN.IN_PROGRESS, or
|
|
113
|
+
* null when the item should be left where it is (Backlog/Next Up untouched).
|
|
114
|
+
*
|
|
115
|
+
* facts: {
|
|
116
|
+
* itemKind: "issue" | "pr",
|
|
117
|
+
* issueState: "OPEN" | "CLOSED" | null, // for issue items
|
|
118
|
+
* prState: "OPEN" | "CLOSED" | "MERGED" | null, // item PR, or the issue's linked PR
|
|
119
|
+
* prIsDraft: boolean | null,
|
|
120
|
+
* }
|
|
121
|
+
*/
|
|
122
|
+
export function deriveReconcileColumn(facts = {}) {
|
|
123
|
+
const { itemKind, issueState, prState, prIsDraft } = facts;
|
|
124
|
+
// Merged PR (item is a PR, or issue's linked PR merged) => Done.
|
|
125
|
+
if (prState === "MERGED") return LOGICAL_COLUMN.DONE;
|
|
126
|
+
if (itemKind === "issue" && issueState === "CLOSED") return LOGICAL_COLUMN.DONE;
|
|
127
|
+
// Open, ready (non-draft) PR => In Progress.
|
|
128
|
+
if (prState === "OPEN" && prIsDraft === false) return LOGICAL_COLUMN.IN_PROGRESS;
|
|
129
|
+
// Otherwise leave the item untouched (Backlog / Next Up ordering preserved).
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Pure reconcile planner (#1069). Given listed board items, a map of live facts
|
|
135
|
+
* keyed by the item's stable GraphQL node id (`item.itemId`), and the resolved
|
|
136
|
+
* column display names, return the set of moves needed to converge the board and
|
|
137
|
+
* a count of items left unchanged. Idempotent: when every item already sits in
|
|
138
|
+
* its derived column, the moves array is empty.
|
|
139
|
+
*
|
|
140
|
+
* Keying by the stable `itemId` (not the bare issue/PR number) keeps reconcile
|
|
141
|
+
* deterministic on a multi-repo GitHub Projects board, where two items can share
|
|
142
|
+
* a number (repo-A PR #5 vs repo-B issue #5) — number-keying would collide and
|
|
143
|
+
* make moves order-dependent.
|
|
144
|
+
*
|
|
145
|
+
* items: [{ itemId, issueNumber, prNumber, status, ... }] (from list-queue-items)
|
|
146
|
+
* factsByItemId: Map<itemId, factsObject> (facts as consumed by deriveReconcileColumn)
|
|
147
|
+
* columnNames: { in_progress, done, ... } (LOGICAL_COLUMN -> display name)
|
|
148
|
+
*/
|
|
149
|
+
export function planReconcile(items = [], factsByItemId = new Map(), columnNames = {}) {
|
|
150
|
+
const moves = [];
|
|
151
|
+
let unchanged = 0;
|
|
152
|
+
for (const item of items) {
|
|
153
|
+
const facts = factsByItemId.get(item.itemId);
|
|
154
|
+
const logical = facts ? deriveReconcileColumn(facts) : null;
|
|
155
|
+
if (logical == null) { unchanged += 1; continue; }
|
|
156
|
+
const target = columnNames[logical];
|
|
157
|
+
if (!target || item.status === target) { unchanged += 1; continue; }
|
|
158
|
+
// `number` is kept only for reporting; the move is applied by node id.
|
|
159
|
+
const number = item.prNumber != null ? item.prNumber : item.issueNumber;
|
|
160
|
+
moves.push({ itemId: item.itemId, number, from: item.status ?? null, to: target });
|
|
161
|
+
}
|
|
162
|
+
return { moves, unchanged };
|
|
163
|
+
}
|
|
164
|
+
|
|
110
165
|
// ── Local config loader ─────────────────────────────────────────────────
|
|
111
166
|
|
|
112
167
|
function readDevloopsSettings(repoRoot) {
|
|
@@ -169,7 +224,7 @@ export function loadBoardConfig(repoRoot) {
|
|
|
169
224
|
* cannot pollute Object.prototype.
|
|
170
225
|
*/
|
|
171
226
|
export function loadStateColumnMap(repoRoot) {
|
|
172
|
-
const { settings: queue } = readDevloopsSettings(repoRoot);
|
|
227
|
+
const { settings: queue, error } = readDevloopsSettings(repoRoot);
|
|
173
228
|
// Null-prototype objects: untrusted keys can never reach Object.prototype.
|
|
174
229
|
const columnNames = Object.assign(Object.create(null), DEFAULT_STATE_COLUMN_NAMES);
|
|
175
230
|
const stateColumnMap = Object.create(null);
|
|
@@ -200,7 +255,11 @@ export function loadStateColumnMap(repoRoot) {
|
|
|
200
255
|
}
|
|
201
256
|
}
|
|
202
257
|
|
|
203
|
-
|
|
258
|
+
// Surface a non-ENOENT read/parse error (mirrors loadBoardConfig). Callers on
|
|
259
|
+
// the fail-closed next_up pickup path MUST honor it rather than silently
|
|
260
|
+
// querying the default literal column against a stale/renamed board (#1098).
|
|
261
|
+
// Existing `.columnNames`-only callers ignore this field and behave unchanged.
|
|
262
|
+
return { columnNames, stateColumnMap, error: error ?? null };
|
|
204
263
|
}
|
|
205
264
|
|
|
206
265
|
// ── Minimal project lookup (read-only, no create/repair) ────────────────
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
transitionEntry,
|
|
10
10
|
snapshotEntry,
|
|
11
11
|
nextReadyEntry,
|
|
12
|
+
findEntry,
|
|
12
13
|
allDone,
|
|
13
14
|
RECOVERABLE_FAILURES,
|
|
14
15
|
appendBugIssue,
|
|
@@ -19,7 +20,13 @@ import {
|
|
|
19
20
|
boardColumnForLoopState,
|
|
20
21
|
loadStateColumnMap,
|
|
21
22
|
} from "./queue-board-sync.mjs";
|
|
22
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
resolveNextUpOrder,
|
|
25
|
+
REASON_NEXT_UP_EMPTY,
|
|
26
|
+
REASON_BOARD_QUERY_ERROR,
|
|
27
|
+
REASON_NEXT_UP_TARGET_MISSING_LOCALLY,
|
|
28
|
+
EMPTY_NEXT_UP_MESSAGE,
|
|
29
|
+
} from "./queue-board-ordering.mjs";
|
|
23
30
|
|
|
24
31
|
export const DEFAULT_QUEUE_DRIVER_OPTIONS = {
|
|
25
32
|
mergeAuthorized: false,
|
|
@@ -92,23 +99,88 @@ export async function runQueue(repoRoot, repo, options = {}) {
|
|
|
92
99
|
// (e.g. a configured "Ready for Review") still syncs. (#793 round-1 #1)
|
|
93
100
|
const lastSyncedColumn = new Map();
|
|
94
101
|
|
|
95
|
-
//
|
|
96
|
-
//
|
|
97
|
-
//
|
|
98
|
-
|
|
102
|
+
// Next Up is the NORMATIVE, fail-closed pickup source (#1091). When a board is
|
|
103
|
+
// configured, the driver picks ONLY entries whose target is in Next Up, by
|
|
104
|
+
// POSITION ascending; entries absent from Next Up are never auto-picked. It
|
|
105
|
+
// NEVER falls back to Backlog or to the non-board local queue order.
|
|
106
|
+
//
|
|
107
|
+
// Single-issue/PR runs do not reach this gating at all — they run via the
|
|
108
|
+
// dev-loop routing path, not the queue driver — so an explicit --issue/--pr
|
|
109
|
+
// target is inherently unaffected by Next Up.
|
|
110
|
+
const ordering = !allDone(queue)
|
|
99
111
|
? await resolveNextUpOrder(repo, repoRoot, opts.env ?? process.env, opts.queueBoardSyncDependencies ?? {})
|
|
100
|
-
: { ok: true, order: [], reason: "
|
|
101
|
-
|
|
112
|
+
: { ok: true, configured: false, order: [], reason: "queue idle" };
|
|
113
|
+
|
|
114
|
+
// (b) Board-query ERROR → surface it and stop. Do NOT fall back to Backlog
|
|
115
|
+
// or local order (fail-closed). Distinct from an empty Next Up below.
|
|
116
|
+
if (ordering.ok === false) {
|
|
117
|
+
return {
|
|
118
|
+
ok: false,
|
|
119
|
+
stopped: true,
|
|
120
|
+
reason: REASON_BOARD_QUERY_ERROR,
|
|
121
|
+
message: `Next Up query failed (${ordering.reason}); refusing to fall back to Backlog/local order`,
|
|
122
|
+
error: ordering.reason ?? "board query failed",
|
|
123
|
+
results: [],
|
|
124
|
+
queue,
|
|
125
|
+
ordering,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Board-gated only when a board is configured.
|
|
130
|
+
const boardGated = ordering.configured === true;
|
|
131
|
+
const orderHint = ordering.order;
|
|
132
|
+
const allowedTargets = boardGated ? new Set(orderHint) : null;
|
|
133
|
+
|
|
134
|
+
// (a) Empty Next Up (successful query, zero items) → fail CLOSED: idle/stop
|
|
135
|
+
// with an actionable, machine-readable outcome. Never pull from Backlog.
|
|
136
|
+
if (boardGated && orderHint.length === 0) {
|
|
137
|
+
return {
|
|
138
|
+
ok: true,
|
|
139
|
+
idle: true,
|
|
140
|
+
reason: REASON_NEXT_UP_EMPTY,
|
|
141
|
+
message: EMPTY_NEXT_UP_MESSAGE,
|
|
142
|
+
results: [],
|
|
143
|
+
queue,
|
|
144
|
+
ordering,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// (a2) Next Up resolved one or more targets that have NO matching local queue
|
|
149
|
+
// entry (membership reconcile not run/persisted, or the board changed between
|
|
150
|
+
// reconcile and this query). Filtering them out would return a silent empty
|
|
151
|
+
// idle while real Next Up work goes undispatched — so fail CLOSED with an
|
|
152
|
+
// actionable stop instead. Distinct from the genuine empty-Next-Up idle above.
|
|
153
|
+
// Never pull from Backlog. (#1091)
|
|
154
|
+
if (boardGated) {
|
|
155
|
+
const missingTargets = orderHint.filter((t) => !findEntry(queue, t));
|
|
156
|
+
if (missingTargets.length > 0) {
|
|
157
|
+
return {
|
|
158
|
+
ok: false,
|
|
159
|
+
stopped: true,
|
|
160
|
+
reason: REASON_NEXT_UP_TARGET_MISSING_LOCALLY,
|
|
161
|
+
missingTargets,
|
|
162
|
+
message:
|
|
163
|
+
"Next Up contains items with no local queue entry — run membership reconcile / re-add them",
|
|
164
|
+
results: [],
|
|
165
|
+
queue,
|
|
166
|
+
ordering,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
}
|
|
102
170
|
|
|
103
171
|
let autoFiledCount = 0;
|
|
104
172
|
const results = [];
|
|
105
173
|
let incomplete = false;
|
|
106
174
|
|
|
107
175
|
while (!allDone(queue)) {
|
|
108
|
-
const entry = nextReadyEntry(queue, opts.reDispatchMaxRetries, orderHint);
|
|
176
|
+
const entry = nextReadyEntry(queue, opts.reDispatchMaxRetries, orderHint, allowedTargets);
|
|
109
177
|
if (!entry) {
|
|
178
|
+
// When board-gated, entries absent from Next Up are intentionally NOT
|
|
179
|
+
// picked (and are not "blocked by deps") — only unfinished Next Up members
|
|
180
|
+
// count toward an incomplete verdict.
|
|
110
181
|
const remaining = queue.entries.filter(
|
|
111
182
|
(e) => e.status !== "done" && e.status !== "blocked" && e.status !== "failed"
|
|
183
|
+
&& (!allowedTargets || allowedTargets.has(e.target))
|
|
112
184
|
);
|
|
113
185
|
if (remaining.length > 0) {
|
|
114
186
|
incomplete = true;
|
package/src/loop/queue-state.mjs
CHANGED
|
@@ -183,9 +183,20 @@ function applyOrderHint(ordered, orderHint) {
|
|
|
183
183
|
return [...inHint, ...rest];
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
-
|
|
186
|
+
/**
|
|
187
|
+
* Pick the next ready entry.
|
|
188
|
+
*
|
|
189
|
+
* @param {object} queue
|
|
190
|
+
* @param {number} maxRetries
|
|
191
|
+
* @param {number[]} orderHint - preferred order (targets sorted to the front).
|
|
192
|
+
* @param {Set<number>|null} allowedTargets - when non-null, ONLY entries whose
|
|
193
|
+
* target is in this set are eligible. Used for board-gated (Next Up)
|
|
194
|
+
* selection (#1091): entries absent from Next Up are never auto-picked.
|
|
195
|
+
*/
|
|
196
|
+
export function nextReadyEntry(queue, maxRetries = 1, orderHint = [], allowedTargets = null) {
|
|
187
197
|
const ordered = topologicalOrder(queue.entries);
|
|
188
|
-
const
|
|
198
|
+
const restricted = allowedTargets ? ordered.filter((e) => allowedTargets.has(e.target)) : ordered;
|
|
199
|
+
const sorted = applyOrderHint(restricted, orderHint);
|
|
189
200
|
for (const entry of sorted) {
|
|
190
201
|
if (entry.status === "queued" && entryDependenciesSatisfied(queue, entry)) {
|
|
191
202
|
return entry;
|
|
@@ -18,40 +18,55 @@ export const REVIEWER_STATE = Object.freeze({
|
|
|
18
18
|
BLOCKED_NEEDS_USER_DECISION: "blocked_needs_user_decision",
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
+
// The reviewSubmissionStatus guard applies only after the {!prExists, prMerged, prClosed,
|
|
22
|
+
// prDraft} pre-gates, then fires ahead of the state-specific branches: failed ->
|
|
23
|
+
// blocked_needs_user_decision, submitted -> submitted_review. The table declares every such pair.
|
|
21
24
|
export const REVIEWER_TRANSITIONS = Object.freeze({
|
|
22
|
-
[REVIEWER_STATE.WAITING_FOR_REVIEW_REQUEST]: [
|
|
25
|
+
[REVIEWER_STATE.WAITING_FOR_REVIEW_REQUEST]: [
|
|
26
|
+
REVIEWER_STATE.REVIEW_REQUESTED,
|
|
27
|
+
REVIEWER_STATE.SUBMITTED_REVIEW,
|
|
28
|
+
REVIEWER_STATE.BLOCKED_NEEDS_USER_DECISION,
|
|
29
|
+
],
|
|
23
30
|
[REVIEWER_STATE.REVIEW_REQUESTED]: [
|
|
24
31
|
REVIEWER_STATE.DETERMINE_REVIEW_PLAN,
|
|
32
|
+
REVIEWER_STATE.SUBMITTED_REVIEW,
|
|
25
33
|
REVIEWER_STATE.BLOCKED_NEEDS_USER_DECISION,
|
|
26
34
|
],
|
|
27
35
|
[REVIEWER_STATE.DETERMINE_REVIEW_PLAN]: [
|
|
28
36
|
REVIEWER_STATE.REVIEWS_RUNNING,
|
|
37
|
+
REVIEWER_STATE.SUBMITTED_REVIEW,
|
|
29
38
|
REVIEWER_STATE.BLOCKED_NEEDS_USER_DECISION,
|
|
30
39
|
],
|
|
31
40
|
[REVIEWER_STATE.REVIEWS_RUNNING]: [
|
|
32
41
|
REVIEWER_STATE.MERGE_RESULTS,
|
|
42
|
+
REVIEWER_STATE.SUBMITTED_REVIEW,
|
|
33
43
|
REVIEWER_STATE.BLOCKED_NEEDS_USER_DECISION,
|
|
34
44
|
],
|
|
35
45
|
[REVIEWER_STATE.MERGE_RESULTS]: [
|
|
36
46
|
REVIEWER_STATE.DRAFT_REVIEW_READY,
|
|
47
|
+
REVIEWER_STATE.SUBMITTED_REVIEW,
|
|
37
48
|
REVIEWER_STATE.BLOCKED_NEEDS_USER_DECISION,
|
|
38
49
|
],
|
|
39
50
|
[REVIEWER_STATE.DRAFT_REVIEW_READY]: [
|
|
40
51
|
REVIEWER_STATE.DRAFT_REVIEW_POSTED,
|
|
52
|
+
REVIEWER_STATE.SUBMITTED_REVIEW,
|
|
41
53
|
REVIEWER_STATE.BLOCKED_NEEDS_USER_DECISION,
|
|
42
54
|
],
|
|
43
55
|
[REVIEWER_STATE.DRAFT_REVIEW_POSTED]: [
|
|
44
56
|
REVIEWER_STATE.WAITING_FOR_USER_SUBMIT,
|
|
45
57
|
REVIEWER_STATE.REVIEW_INVALIDATED,
|
|
46
58
|
REVIEWER_STATE.SUBMITTED_REVIEW,
|
|
59
|
+
REVIEWER_STATE.BLOCKED_NEEDS_USER_DECISION,
|
|
47
60
|
],
|
|
48
61
|
[REVIEWER_STATE.WAITING_FOR_USER_SUBMIT]: [
|
|
49
62
|
REVIEWER_STATE.SUBMITTED_REVIEW,
|
|
50
63
|
REVIEWER_STATE.REVIEW_INVALIDATED,
|
|
64
|
+
REVIEWER_STATE.BLOCKED_NEEDS_USER_DECISION,
|
|
51
65
|
],
|
|
52
66
|
[REVIEWER_STATE.SUBMITTED_REVIEW]: [
|
|
53
67
|
REVIEWER_STATE.REVIEW_REQUESTED,
|
|
54
68
|
REVIEWER_STATE.WAITING_FOR_REVIEW_REQUEST,
|
|
69
|
+
REVIEWER_STATE.BLOCKED_NEEDS_USER_DECISION,
|
|
55
70
|
],
|
|
56
71
|
[REVIEWER_STATE.WAITING_FOR_AUTHOR_FOLLOWUP]: [
|
|
57
72
|
REVIEWER_STATE.SUBMITTED_REVIEW,
|
|
@@ -62,7 +77,10 @@ export const REVIEWER_TRANSITIONS = Object.freeze({
|
|
|
62
77
|
REVIEWER_STATE.REVIEW_REQUESTED,
|
|
63
78
|
REVIEWER_STATE.SUBMITTED_REVIEW,
|
|
64
79
|
],
|
|
65
|
-
[REVIEWER_STATE.REVIEW_INVALIDATED]: [
|
|
80
|
+
[REVIEWER_STATE.REVIEW_INVALIDATED]: [
|
|
81
|
+
REVIEWER_STATE.REVIEW_REQUESTED,
|
|
82
|
+
REVIEWER_STATE.BLOCKED_NEEDS_USER_DECISION,
|
|
83
|
+
],
|
|
66
84
|
[REVIEWER_STATE.BLOCKED_NEEDS_USER_DECISION]: [],
|
|
67
85
|
});
|
|
68
86
|
|