@codyswann/lisa 4.87.2 → 4.87.4
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/all/copy-overwrite/scripts/lisa-hooks/parity-safety-net.sh +36 -21
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +10 -0
- package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
- package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +20 -14
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/dist/opencode/plugin-templates/parity-safety-net.sh +36 -21
- package/package.json +4 -4
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-parity-code-review/SKILL.md +3 -2
- package/plugins/lisa/.codex-plugin/skills/lisa-parity-code-simplifier/SKILL.md +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-parity-coderabbit/SKILL.md +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/SKILL.md +25 -6
- package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/cli-human.mjs +37 -10
- package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/cli-setup.mjs +22 -6
- package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/cli-usage.mjs +22 -12
- package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/cli-workers.mjs +21 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/human-answer.mjs +76 -21
- package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/ledger.mjs +6 -2
- package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/token-migrate.mjs +15 -3
- package/plugins/lisa/hooks/parity-safety-net.sh +36 -21
- package/plugins/lisa/skills/lisa-parity-code-review/SKILL.md +3 -2
- package/plugins/lisa/skills/lisa-parity-code-simplifier/SKILL.md +1 -1
- package/plugins/lisa/skills/lisa-parity-coderabbit/SKILL.md +1 -1
- package/plugins/lisa/skills/lisa-session-manager/SKILL.md +25 -6
- package/plugins/lisa/skills/lisa-session-manager/scripts/cli-human.mjs +37 -10
- package/plugins/lisa/skills/lisa-session-manager/scripts/cli-setup.mjs +22 -6
- package/plugins/lisa/skills/lisa-session-manager/scripts/cli-usage.mjs +22 -12
- package/plugins/lisa/skills/lisa-session-manager/scripts/cli-workers.mjs +21 -0
- package/plugins/lisa/skills/lisa-session-manager/scripts/human-answer.mjs +76 -21
- package/plugins/lisa/skills/lisa-session-manager/scripts/ledger.mjs +6 -2
- package/plugins/lisa/skills/lisa-session-manager/scripts/token-migrate.mjs +15 -3
- package/plugins/lisa-agy/hooks/parity-safety-net.sh +36 -21
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/skills/lisa-parity-code-review/SKILL.md +3 -2
- package/plugins/lisa-agy/skills/lisa-parity-code-simplifier/SKILL.md +1 -1
- package/plugins/lisa-agy/skills/lisa-parity-coderabbit/SKILL.md +1 -1
- package/plugins/lisa-agy/skills/lisa-session-manager/SKILL.md +25 -6
- package/plugins/lisa-agy/skills/lisa-session-manager/scripts/cli-human.mjs +37 -10
- package/plugins/lisa-agy/skills/lisa-session-manager/scripts/cli-setup.mjs +22 -6
- package/plugins/lisa-agy/skills/lisa-session-manager/scripts/cli-usage.mjs +22 -12
- package/plugins/lisa-agy/skills/lisa-session-manager/scripts/cli-workers.mjs +21 -0
- package/plugins/lisa-agy/skills/lisa-session-manager/scripts/human-answer.mjs +76 -21
- package/plugins/lisa-agy/skills/lisa-session-manager/scripts/ledger.mjs +6 -2
- package/plugins/lisa-agy/skills/lisa-session-manager/scripts/token-migrate.mjs +15 -3
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-devin/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-devin/.devin-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/hooks/parity-safety-net.sh +36 -21
- package/plugins/lisa-copilot/skills/lisa-parity-code-review/SKILL.md +3 -2
- package/plugins/lisa-copilot/skills/lisa-parity-code-simplifier/SKILL.md +1 -1
- package/plugins/lisa-copilot/skills/lisa-parity-coderabbit/SKILL.md +1 -1
- package/plugins/lisa-copilot/skills/lisa-session-manager/SKILL.md +25 -6
- package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/cli-human.mjs +37 -10
- package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/cli-setup.mjs +22 -6
- package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/cli-usage.mjs +22 -12
- package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/cli-workers.mjs +21 -0
- package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/human-answer.mjs +76 -21
- package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/ledger.mjs +6 -2
- package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/token-migrate.mjs +15 -3
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/hooks/parity-safety-net.sh +36 -21
- package/plugins/lisa-cursor/skills/lisa-parity-code-review/SKILL.md +3 -2
- package/plugins/lisa-cursor/skills/lisa-parity-code-simplifier/SKILL.md +1 -1
- package/plugins/lisa-cursor/skills/lisa-parity-coderabbit/SKILL.md +1 -1
- package/plugins/lisa-cursor/skills/lisa-session-manager/SKILL.md +25 -6
- package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/cli-human.mjs +37 -10
- package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/cli-setup.mjs +22 -6
- package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/cli-usage.mjs +22 -12
- package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/cli-workers.mjs +21 -0
- package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/human-answer.mjs +76 -21
- package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/ledger.mjs +6 -2
- package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/token-migrate.mjs +15 -3
- package/plugins/lisa-devin/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-devin/.devin-plugin/plugin.json +1 -1
- package/plugins/lisa-devin/hooks/parity-safety-net.sh +36 -21
- package/plugins/lisa-devin/skills/lisa-parity-code-review/SKILL.md +3 -2
- package/plugins/lisa-devin/skills/lisa-parity-code-simplifier/SKILL.md +1 -1
- package/plugins/lisa-devin/skills/lisa-parity-coderabbit/SKILL.md +1 -1
- package/plugins/lisa-devin/skills/lisa-session-manager/SKILL.md +25 -6
- package/plugins/lisa-devin/skills/lisa-session-manager/scripts/cli-human.mjs +37 -10
- package/plugins/lisa-devin/skills/lisa-session-manager/scripts/cli-setup.mjs +22 -6
- package/plugins/lisa-devin/skills/lisa-session-manager/scripts/cli-usage.mjs +22 -12
- package/plugins/lisa-devin/skills/lisa-session-manager/scripts/cli-workers.mjs +21 -0
- package/plugins/lisa-devin/skills/lisa-session-manager/scripts/human-answer.mjs +76 -21
- package/plugins/lisa-devin/skills/lisa-session-manager/scripts/ledger.mjs +6 -2
- package/plugins/lisa-devin/skills/lisa-session-manager/scripts/token-migrate.mjs +15 -3
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-devin/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-devin/.devin-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-devin/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-devin/.devin-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-devin/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-devin/.devin-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-devin/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-devin/.devin-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-devin/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-devin/.devin-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-devin/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-devin/.devin-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-devin/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-devin/.devin-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-devin/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-devin/.devin-plugin/plugin.json +1 -1
- package/plugins/src/base/hooks/parity-safety-net.sh +36 -21
- package/plugins/src/base/skills/lisa-parity-code-review/SKILL.md +3 -2
- package/plugins/src/base/skills/lisa-parity-code-simplifier/SKILL.md +1 -1
- package/plugins/src/base/skills/lisa-parity-coderabbit/SKILL.md +1 -1
- package/plugins/src/base/skills/lisa-session-manager/SKILL.md +25 -6
- package/plugins/src/base/skills/lisa-session-manager/scripts/cli-human.mjs +37 -10
- package/plugins/src/base/skills/lisa-session-manager/scripts/cli-setup.mjs +22 -6
- package/plugins/src/base/skills/lisa-session-manager/scripts/cli-usage.mjs +22 -12
- package/plugins/src/base/skills/lisa-session-manager/scripts/cli-workers.mjs +21 -0
- package/plugins/src/base/skills/lisa-session-manager/scripts/human-answer.mjs +76 -21
- package/plugins/src/base/skills/lisa-session-manager/scripts/ledger.mjs +6 -2
- package/plugins/src/base/skills/lisa-session-manager/scripts/token-migrate.mjs +15 -3
- package/typescript/copy-overwrite/scripts/check-skipped-required-checks.mjs +62 -4
|
@@ -16,7 +16,10 @@
|
|
|
16
16
|
* answer, and an ordinary reply such as `[x] option 1` is not a machine tag
|
|
17
17
|
* (#4561). Only then is the author matched, on stable ids only: a GitHub
|
|
18
18
|
* login or numeric id, a Jira accountId, a Linear user id, never a display
|
|
19
|
-
* name or email, which anyone can set.
|
|
19
|
+
* name or email, which anyone can set. An all-digit allowed entry is a
|
|
20
|
+
* numeric id and matches only the `id` or `accountId` field, never a login,
|
|
21
|
+
* which its owner chooses and may make all digits (#4571). The text is
|
|
22
|
+
* carried as data.
|
|
20
23
|
*
|
|
21
24
|
* The question the answer must follow is the comment whose id the manager
|
|
22
25
|
* recorded last; with no recorded id, the latest comment that STARTS with
|
|
@@ -46,6 +49,17 @@ const COMMENT_ID_KEYS = ["id", "node_id"];
|
|
|
46
49
|
/** The author fields that are stable ids, never settable display text. */
|
|
47
50
|
const STABLE_ID_KEYS = ["login", "id", "accountId"];
|
|
48
51
|
|
|
52
|
+
/**
|
|
53
|
+
* The author fields an all-digit allowed entry may match. A GitHub login can
|
|
54
|
+
* be all digits and is chosen by its owner, so a numeric operator id must
|
|
55
|
+
* never be matched against a login: user `583231` would otherwise pass as the
|
|
56
|
+
* operator whose numeric id is 583231 (CodySwannGT/lisa#4571).
|
|
57
|
+
*/
|
|
58
|
+
const NUMERIC_ID_KEYS = ["id", "accountId"];
|
|
59
|
+
|
|
60
|
+
/** An allowed entry that is a numeric account id, never a login. */
|
|
61
|
+
const ALL_DIGITS = /^\d+$/;
|
|
62
|
+
|
|
49
63
|
/**
|
|
50
64
|
* The string or number values of `keys` on `value`, as strings.
|
|
51
65
|
* @param {unknown} value - Object to read
|
|
@@ -126,37 +140,78 @@ function questionIndex(rows, managerCommentIds, authorOf) {
|
|
|
126
140
|
* the manager's latest question comment ({@link questionIndex}) that is not
|
|
127
141
|
* a machine's ({@link isMachineAuthoredComment}), has text of its own
|
|
128
142
|
* outside quoted lines (a comment made only of `>` lines quotes someone
|
|
129
|
-
* else and answers nothing),
|
|
143
|
+
* else and answers nothing), whose author's stable id is allowed, and that
|
|
144
|
+
* `refuses` does not refuse. Comments are taken in the order given
|
|
145
|
+
* (chronological).
|
|
146
|
+
*
|
|
147
|
+
* A refused reply (one that looks like it holds a credential) is skipped and
|
|
148
|
+
* reported in `refused`, never returned: returning it made every later cycle
|
|
149
|
+
* pick the same reply and fail on it again, so a corrected reply posted after
|
|
150
|
+
* it was never read (CodySwannGT/lisa#4571).
|
|
130
151
|
* @param {{comments: unknown, allowed: readonly string[],
|
|
131
|
-
* managerCommentIds?: readonly (string|number)[]
|
|
132
|
-
*
|
|
152
|
+
* managerCommentIds?: readonly (string|number)[],
|
|
153
|
+
* refuses?: (text: string) => string | null | undefined}} input - The
|
|
154
|
+
* item's comments, the answerer ids, the ids the manager posted, and why a
|
|
155
|
+
* reply's text may not be queued (null when it may)
|
|
133
156
|
* @returns {{answer: {text: string, author: string, at: string|null} | null,
|
|
134
|
-
* reason: string
|
|
157
|
+
* reason: string, refused?: {id: string|null, author: string, why: string}[]}}
|
|
158
|
+
* The answer, or null with why there is none, and (only when any were)
|
|
159
|
+
* the replies refused
|
|
135
160
|
*/
|
|
136
|
-
export function pickItemAnswer({
|
|
161
|
+
export function pickItemAnswer({
|
|
162
|
+
comments,
|
|
163
|
+
allowed,
|
|
164
|
+
managerCommentIds = [],
|
|
165
|
+
refuses = () => null,
|
|
166
|
+
}) {
|
|
137
167
|
if (!Array.isArray(comments)) {
|
|
138
168
|
throw new Error('stdin must be JSON {"comments":[...]}');
|
|
139
169
|
}
|
|
140
170
|
const rows = comments.filter(row => row && typeof row === "object");
|
|
141
|
-
const allow =
|
|
142
|
-
const
|
|
143
|
-
|
|
171
|
+
const allow = allowed.map(String);
|
|
172
|
+
const numeric = new Set(allow.filter(id => ALL_DIGITS.test(id)));
|
|
173
|
+
const named = new Set(allow.filter(id => !ALL_DIGITS.test(id)));
|
|
174
|
+
const authorOf = row => {
|
|
175
|
+
const author = row.author ?? row.user;
|
|
176
|
+
return (
|
|
177
|
+
idsOf(author, NUMERIC_ID_KEYS).find(id => numeric.has(id)) ??
|
|
178
|
+
idsOf(author, STABLE_ID_KEYS).find(id => named.has(id))
|
|
179
|
+
);
|
|
180
|
+
};
|
|
144
181
|
const asked = questionIndex(rows, managerCommentIds, authorOf);
|
|
182
|
+
const refused = [];
|
|
145
183
|
if (asked === -1) return { answer: null, reason: "no-question-comment" };
|
|
146
|
-
const
|
|
184
|
+
const withRefused = result =>
|
|
185
|
+
refused.length > 0 ? { ...result, refused } : result;
|
|
186
|
+
const replies = rows
|
|
147
187
|
.slice(asked + 1)
|
|
148
188
|
.filter(row => !isMachineAuthoredComment(row, { managerCommentIds }))
|
|
149
|
-
.
|
|
189
|
+
.filter(
|
|
150
190
|
row => authorOf(row) !== undefined && unquoted(bodyOf(row)).trim() !== ""
|
|
151
191
|
);
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
192
|
+
for (const reply of replies) {
|
|
193
|
+
const text = bodyOf(reply).trim();
|
|
194
|
+
const why = refuses(text);
|
|
195
|
+
if (why) {
|
|
196
|
+
refused.push({
|
|
197
|
+
id: idsOf(reply, COMMENT_ID_KEYS)[0] ?? null,
|
|
198
|
+
author: authorOf(reply),
|
|
199
|
+
why,
|
|
200
|
+
});
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
const at = reply.createdAt ?? reply.created ?? reply.created_at ?? null;
|
|
204
|
+
return withRefused({
|
|
205
|
+
answer: {
|
|
206
|
+
text,
|
|
207
|
+
author: authorOf(reply),
|
|
208
|
+
at: typeof at === "string" ? at : null,
|
|
209
|
+
},
|
|
210
|
+
reason: "answered",
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
return withRefused({
|
|
214
|
+
answer: null,
|
|
215
|
+
reason: refused.length > 0 ? "credential-refused" : "no-answer-yet",
|
|
216
|
+
});
|
|
162
217
|
}
|
|
@@ -297,7 +297,9 @@ export function recordSpawn(
|
|
|
297
297
|
startedAt: at,
|
|
298
298
|
lastProgressAt: at,
|
|
299
299
|
logsHash: null,
|
|
300
|
-
|
|
300
|
+
// Kept, never reset: only clearPendingQuestion clears a question, once
|
|
301
|
+
// its answer is delivered and the marker removed (lisa#4571).
|
|
302
|
+
pendingQuestion: previous.pendingQuestion ?? null,
|
|
301
303
|
reason: null,
|
|
302
304
|
finishedAt: null,
|
|
303
305
|
attempts,
|
|
@@ -354,7 +356,9 @@ export function recordFinish(projectRoot, { ref, status, reason }, seams) {
|
|
|
354
356
|
status,
|
|
355
357
|
reason,
|
|
356
358
|
finishedAt: at,
|
|
357
|
-
|
|
359
|
+
// Kept: a question recorded before the finish still holds its answer
|
|
360
|
+
// and the tracker marker the manager must remove (lisa#4571).
|
|
361
|
+
pendingQuestion: previous.pendingQuestion ?? null,
|
|
358
362
|
history: [
|
|
359
363
|
...(previous.history ?? []),
|
|
360
364
|
{ event: "finish", at, status, reason },
|
|
@@ -62,8 +62,9 @@ export function digest(value) {
|
|
|
62
62
|
* of anything else in it (values are never kept or reported).
|
|
63
63
|
* @param {string} stateDir - Resolved state dir
|
|
64
64
|
* @param {Pick<import("node:fs"), "existsSync" | "readFileSync">} fs - Seam
|
|
65
|
-
* @returns {{ path: string, token: string | undefined,
|
|
66
|
-
* null} The file, or null when absent
|
|
65
|
+
* @returns {{ path: string, token: string | undefined, tokenLines: number,
|
|
66
|
+
* others: string[] } | null} The file, or null when absent; `token` is the
|
|
67
|
+
* first `TELEGRAM_BOT_TOKEN` line's value and `tokenLines` counts them all
|
|
67
68
|
*/
|
|
68
69
|
export function readEnvFile(stateDir, fs) {
|
|
69
70
|
const file = path.join(stateDir, TOKEN_FILE);
|
|
@@ -74,10 +75,12 @@ export function readEnvFile(stateDir, fs) {
|
|
|
74
75
|
.map((line, at) => ({ line: line.trim(), at }))
|
|
75
76
|
.filter(({ line }) => line !== "" && !line.startsWith("#"))
|
|
76
77
|
.map(({ line, at }) => ({ match: ENV_LINE.exec(line), at }));
|
|
77
|
-
const
|
|
78
|
+
const tokenLines = entries.filter(({ match }) => match?.[1] === TOKEN_VAR);
|
|
79
|
+
const token = tokenLines[0]?.match;
|
|
78
80
|
return {
|
|
79
81
|
path: file,
|
|
80
82
|
token: token ? token[2].replace(/^(['"])(.*)\1$/, "$2") : undefined,
|
|
83
|
+
tokenLines: tokenLines.length,
|
|
81
84
|
others: entries
|
|
82
85
|
.filter(({ match }) => match?.[1] !== TOKEN_VAR)
|
|
83
86
|
.map(({ match, at }) => match?.[1] ?? `an unrecognised line ${at + 1}`),
|
|
@@ -109,6 +112,15 @@ export function removable(file) {
|
|
|
109
112
|
*/
|
|
110
113
|
export function assertMigratable(file) {
|
|
111
114
|
if (file === null) return;
|
|
115
|
+
// Only the first line's token is verified, and the whole file is deleted
|
|
116
|
+
// after, so a later line would be lost unverified (CodySwannGT/lisa#4571).
|
|
117
|
+
if ((file.tokenLines ?? 0) > 1) {
|
|
118
|
+
throw setupError(
|
|
119
|
+
`${file.path} has ${file.tokenLines} ${TOKEN_VAR} lines, so which ` +
|
|
120
|
+
`token is meant is ambiguous. Keep exactly one, then run store-token ` +
|
|
121
|
+
`again. Nothing was changed.`
|
|
122
|
+
);
|
|
123
|
+
}
|
|
112
124
|
if (file.others.length > 0) {
|
|
113
125
|
throw setupError(
|
|
114
126
|
`${file.path} holds ${file.others.join(", ")} besides ${TOKEN_VAR}, so ` +
|
|
@@ -2862,6 +2862,50 @@ export function readReviewSubstitutes(
|
|
|
2862
2862
|
return substitutesAtHead(comments, names, headSha, canWrite);
|
|
2863
2863
|
}
|
|
2864
2864
|
|
|
2865
|
+
/**
|
|
2866
|
+
* Review states that are a VERDICT — the ones that set or clear an objection.
|
|
2867
|
+
* `COMMENTED` and `PENDING` are absent on purpose: neither withdraws one.
|
|
2868
|
+
*/
|
|
2869
|
+
const DECISIVE_REVIEW_STATES = new Set([
|
|
2870
|
+
REVIEW_OBJECTION_STATE,
|
|
2871
|
+
"APPROVED",
|
|
2872
|
+
"DISMISSED",
|
|
2873
|
+
]);
|
|
2874
|
+
|
|
2875
|
+
/**
|
|
2876
|
+
* Reduces reviews to each author's latest decisive review.
|
|
2877
|
+
*
|
|
2878
|
+
* Later wins by `submittedAt` when both reviews carry a parseable one; when
|
|
2879
|
+
* either does not, the later array position wins, which is the reviews
|
|
2880
|
+
* endpoint's chronological order.
|
|
2881
|
+
* @param {ReadonlyArray<{state?: string, author?: string, submittedAt?: string}>} reviews - Reviews, oldest first
|
|
2882
|
+
* @returns {Map<string|symbol, {state?: string, author?: string, submittedAt?: string}>} Latest decisive review per author; each author-less review keys alone
|
|
2883
|
+
*/
|
|
2884
|
+
function latestDecisiveByAuthor(reviews) {
|
|
2885
|
+
const latest = new Map();
|
|
2886
|
+
for (const review of reviews) {
|
|
2887
|
+
const state = String(review?.state ?? "").toUpperCase();
|
|
2888
|
+
if (!DECISIVE_REVIEW_STATES.has(state)) continue;
|
|
2889
|
+
// An author-less review (a deleted account) is never reduced against
|
|
2890
|
+
// another: two of them are not the same reviewer, so a missing login can
|
|
2891
|
+
// never let one's approval withdraw the other's objection.
|
|
2892
|
+
const author =
|
|
2893
|
+
typeof review?.author === "string" && review.author !== ""
|
|
2894
|
+
? review.author
|
|
2895
|
+
: Symbol("unknown-author");
|
|
2896
|
+
const prior = latest.get(author);
|
|
2897
|
+
const priorAt = Date.parse(prior?.submittedAt ?? "");
|
|
2898
|
+
const thisAt = Date.parse(review?.submittedAt ?? "");
|
|
2899
|
+
const earlier =
|
|
2900
|
+
prior !== undefined &&
|
|
2901
|
+
Number.isFinite(priorAt) &&
|
|
2902
|
+
Number.isFinite(thisAt) &&
|
|
2903
|
+
thisAt < priorAt;
|
|
2904
|
+
if (!earlier) latest.set(author, review);
|
|
2905
|
+
}
|
|
2906
|
+
return latest;
|
|
2907
|
+
}
|
|
2908
|
+
|
|
2865
2909
|
/**
|
|
2866
2910
|
* Whether an objection STANDS at this head, from the review objects.
|
|
2867
2911
|
*
|
|
@@ -2880,12 +2924,26 @@ export function readReviewSubstitutes(
|
|
|
2880
2924
|
* those were at the merged head. An unfiltered read would have reddened the
|
|
2881
2925
|
* other two for objections their authors had already addressed.
|
|
2882
2926
|
*
|
|
2927
|
+
* ## Each reviewer's LATEST verdict at head, not any verdict at head
|
|
2928
|
+
*
|
|
2929
|
+
* A reviewer can object and then approve the SAME commit — a bot running a
|
|
2930
|
+
* request-changes workflow submits `CHANGES_REQUESTED`, the threads get
|
|
2931
|
+
* resolved, and it submits `APPROVED` against the unchanged head a minute
|
|
2932
|
+
* later. GitHub's own `reviewDecision` clears on that approval; counting every
|
|
2933
|
+
* `CHANGES_REQUESTED` at head kept the gate red on an objection its author had
|
|
2934
|
+
* withdrawn (CodySwannGT/lisa#4571). So the head-filtered reviews are reduced
|
|
2935
|
+
* to each author's latest DECISIVE review — `APPROVED`, `CHANGES_REQUESTED` or
|
|
2936
|
+
* `DISMISSED` — ordered by `submittedAt`, with the endpoint's chronological
|
|
2937
|
+
* order breaking ties and standing in when a timestamp is missing. A
|
|
2938
|
+
* `COMMENTED` review does not withdraw an objection, for the same reason it
|
|
2939
|
+
* does not clear GitHub's `reviewDecision`: commenting is not a verdict.
|
|
2940
|
+
*
|
|
2883
2941
|
* ## Reported, never inferred
|
|
2884
2942
|
*
|
|
2885
2943
|
* This says only what the review objects say. It does NOT consult the status
|
|
2886
2944
|
* description, and nothing about a waiver, a rate limit or an entitlement
|
|
2887
2945
|
* reaches it — that is the point of having a second input at all.
|
|
2888
|
-
* @param {ReadonlyArray<{state?: string, commitSha?: string, author?: string}>} reviews - Review objects
|
|
2946
|
+
* @param {ReadonlyArray<{state?: string, commitSha?: string, author?: string, submittedAt?: string}>} reviews - Review objects, in the endpoint's chronological order
|
|
2889
2947
|
* @param {string|undefined} headSha - The head commit under inspection
|
|
2890
2948
|
* @returns {{objected: boolean, reviewers: string[], staleDropped: number}} What stands at head
|
|
2891
2949
|
*/
|
|
@@ -2895,7 +2953,7 @@ export function readObjectionAtHead(reviews, headSha) {
|
|
|
2895
2953
|
headSha === undefined || headSha === ""
|
|
2896
2954
|
? []
|
|
2897
2955
|
: all.filter(review => review?.commitSha === headSha);
|
|
2898
|
-
const objections = atHead.filter(
|
|
2956
|
+
const objections = [...latestDecisiveByAuthor(atHead).values()].filter(
|
|
2899
2957
|
review =>
|
|
2900
2958
|
String(review?.state ?? "").toUpperCase() === REVIEW_OBJECTION_STATE
|
|
2901
2959
|
);
|
|
@@ -2917,7 +2975,7 @@ export function readObjectionAtHead(reviews, headSha) {
|
|
|
2917
2975
|
* carries `commit_id`, which is what makes the staleness filter possible.
|
|
2918
2976
|
* @param {string|number} pr - The pull request
|
|
2919
2977
|
* @param {string} [repo] - `OWNER/NAME`; defaults to the current repository
|
|
2920
|
-
* @returns {Array<{state: string, commitSha: string, author: string}>} Review objects
|
|
2978
|
+
* @returns {Array<{state: string, commitSha: string, author: string, submittedAt: string}>} Review objects
|
|
2921
2979
|
* @throws {Error} When the slug cannot be resolved or `gh` cannot answer
|
|
2922
2980
|
*/
|
|
2923
2981
|
export function fetchPullRequestReviews(pr, repo) {
|
|
@@ -2929,7 +2987,7 @@ export function fetchPullRequestReviews(pr, repo) {
|
|
|
2929
2987
|
}
|
|
2930
2988
|
return ghApiPaginatedArray(
|
|
2931
2989
|
`repos/${slug}/pulls/${pr}/reviews?per_page=100`,
|
|
2932
|
-
"[.[] | {state: .state, commitSha: .commit_id, author: .user.login}]"
|
|
2990
|
+
"[.[] | {state: .state, commitSha: .commit_id, author: .user.login, submittedAt: .submitted_at}]"
|
|
2933
2991
|
);
|
|
2934
2992
|
}
|
|
2935
2993
|
|