@floh-solutions/pharos-cli 0.23.0 → 0.25.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/README.md +409 -5
- package/dist/capabilities.d.ts.map +1 -1
- package/dist/capabilities.js +23 -0
- package/dist/capabilities.js.map +1 -1
- package/dist/cli.d.ts +4 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +362 -36
- package/dist/cli.js.map +1 -1
- package/dist/commands/comment-github.d.ts +138 -0
- package/dist/commands/comment-github.d.ts.map +1 -0
- package/dist/commands/comment-github.js +427 -0
- package/dist/commands/comment-github.js.map +1 -0
- package/dist/commands/comment.d.ts.map +1 -1
- package/dist/commands/comment.js +114 -19
- package/dist/commands/comment.js.map +1 -1
- package/dist/commands/doctor.d.ts +71 -2
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +414 -8
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/issue.d.ts +75 -0
- package/dist/commands/issue.d.ts.map +1 -0
- package/dist/commands/issue.js +2466 -0
- package/dist/commands/issue.js.map +1 -0
- package/dist/commands/setup.d.ts +6 -0
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +114 -0
- package/dist/commands/setup.js.map +1 -1
- package/dist/half-link.d.ts +157 -0
- package/dist/half-link.d.ts.map +1 -0
- package/dist/half-link.js +257 -0
- package/dist/half-link.js.map +1 -0
- package/dist/issue-scan.d.ts +383 -0
- package/dist/issue-scan.d.ts.map +1 -0
- package/dist/issue-scan.js +455 -0
- package/dist/issue-scan.js.map +1 -0
- package/dist/output.d.ts +18 -1
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +293 -0
- package/dist/output.js.map +1 -1
- package/dist/session.d.ts +101 -2
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +132 -11
- package/dist/session.js.map +1 -1
- package/dist/target.d.ts +40 -4
- package/dist/target.d.ts.map +1 -1
- package/dist/target.js +14 -3
- package/dist/target.js.map +1 -1
- package/package.json +3 -2
- package/skill/SKILL.md +422 -11
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
import { normalizeRepo } from "@floh-solutions/gh-core";
|
|
2
|
+
import { FALLBACK_TERMINAL_STATES } from "./wiql-build.js";
|
|
3
|
+
/**
|
|
4
|
+
* The join behind `pharos issue drift` and `pharos issue backfill`, with no I/O
|
|
5
|
+
* in it.
|
|
6
|
+
*
|
|
7
|
+
* **Separate from the command for the same reason `wiql-build.ts` is** — this is
|
|
8
|
+
* where the bugs are. Every interesting question here is a comparison between
|
|
9
|
+
* two records that came from two different systems, and comparisons are exactly
|
|
10
|
+
* what can be proved without a network: which spelling of a repository counts as
|
|
11
|
+
* the same repository, what "still open" means on a board that renamed its
|
|
12
|
+
* states, whether one piece of evidence on each of two layers is one link or
|
|
13
|
+
* two. The command file around this reads, calls, and prints.
|
|
14
|
+
*
|
|
15
|
+
* ## The one idea both verbs are built on
|
|
16
|
+
*
|
|
17
|
+
* **The Azure DevOps side is the enumerable one, and that is what makes a
|
|
18
|
+
* 400-issue repository affordable.**
|
|
19
|
+
*
|
|
20
|
+
* GitHub cannot answer "which of my issues has Pharos adopted" — the answer
|
|
21
|
+
* lives in a comment on each issue, so asking costs one request per issue and a
|
|
22
|
+
* backfill would spend four hundred requests deciding what to do before doing
|
|
23
|
+
* anything. Azure DevOps *can*: a work item carries its `Hyperlink` relations on
|
|
24
|
+
* its own row, so one WIQL plus one `workitemsbatch` returns every link on the
|
|
25
|
+
* board at once.
|
|
26
|
+
*
|
|
27
|
+
* That index is correct as an answer to "what is already adopted" because
|
|
28
|
+
* `adopt` writes the Azure DevOps end **first** (`issue.ts` ▸ the write order).
|
|
29
|
+
* The half-state it can leave is a hyperlink with no comment, never a comment
|
|
30
|
+
* with no hyperlink — so an issue missing from this index was either never
|
|
31
|
+
* adopted, or adopted by something that died before its first write. Both mean
|
|
32
|
+
* the same thing to a backfill.
|
|
33
|
+
*
|
|
34
|
+
* The mirror-image state is still possible if somebody deletes the relation by
|
|
35
|
+
* hand, and it is why {@link backfillCandidates} produces *candidates* rather
|
|
36
|
+
* than a work list: the per-issue marker read that would catch it happens at
|
|
37
|
+
* adoption time, on the handful being written, instead of on all four hundred.
|
|
38
|
+
*
|
|
39
|
+
* ## Two traps peers already paid for
|
|
40
|
+
*
|
|
41
|
+
* 1. **Repositories are compared on {@link normalizeRepo}, never with `===`.**
|
|
42
|
+
* GitHub resolves a repository case-insensitively and echoes back whatever
|
|
43
|
+
* spelling it was created with, so `Contoso/Widgets` and `contoso/widgets`
|
|
44
|
+
* are one repository and two strings. The app track reported a perfectly good
|
|
45
|
+
* link as half-written over exactly this (#787).
|
|
46
|
+
* 2. **Evidence is counted nowhere.** `["marker","mention"]` is two pieces of
|
|
47
|
+
* evidence and one side — both are GitHub's — so a link with no `Hyperlink`
|
|
48
|
+
* at all passes a length check (#782 found this in its own code). Whether
|
|
49
|
+
* both platforms carry a link is `gh-core`'s `isTwoSided`, and whether the
|
|
50
|
+
* GitHub half was even *looked at* is {@link IssueSide.markers} being present
|
|
51
|
+
* rather than empty.
|
|
52
|
+
*/
|
|
53
|
+
// MARK: - which work items to look at
|
|
54
|
+
/**
|
|
55
|
+
* The statement `drift` runs when nobody passed `--wiql`.
|
|
56
|
+
*
|
|
57
|
+
* **BOTH `System.HyperLinkCount` AND `System.ExternalLinkCount`, and the reason
|
|
58
|
+
* this reads as belt-and-braces is that it once was neither.**
|
|
59
|
+
*
|
|
60
|
+
* This narrowed on `ExternalLinkCount` alone, on the stated grounds that it was
|
|
61
|
+
* the *broader* filter and that a false negative here is far worse than a false
|
|
62
|
+
* positive. The asymmetry argument is right. The premise was wrong, and it made
|
|
63
|
+
* the command silently useless: **the two counters are disjoint, not nested.**
|
|
64
|
+
* Azure DevOps counts each relation kind separately —
|
|
65
|
+
* `RelatedLinkCount` (work item ↔ work item), `HyperLinkCount` (a plain
|
|
66
|
+
* `Hyperlink`), `ExternalLinkCount` (an `ArtifactLink`: a commit, PR or build),
|
|
67
|
+
* `AttachedFileCount`.
|
|
68
|
+
*
|
|
69
|
+
* `adopt` writes a **`Hyperlink`** — it must, because minting the native
|
|
70
|
+
* `vstfs:///GitHub/Issue/…` artifact URI needs a connection id that is behind a
|
|
71
|
+
* 401 on our scopes (§2). So every pair this tool creates increments
|
|
72
|
+
* `HyperLinkCount` and leaves `ExternalLinkCount` at zero, and the old default
|
|
73
|
+
* could **never** see a link Pharos had made. It answered "no drift" forever,
|
|
74
|
+
* exit 0, which is the failure mode a drift detector must not have.
|
|
75
|
+
*
|
|
76
|
+
* Measured on the live board (#801, re-verified): an adopted pair read
|
|
77
|
+
* `HyperLinkCount 1, ExternalLinkCount 0`, and the two work items that DID
|
|
78
|
+
* match `ExternalLinkCount > 0` each carried an `ArtifactLink` and no
|
|
79
|
+
* `Hyperlink`.
|
|
80
|
+
*
|
|
81
|
+
* Both are kept rather than swapping one for the other, because a client whose
|
|
82
|
+
* org *does* have the Azure Boards GitHub connection gets a native artifact
|
|
83
|
+
* link in addition to ours (§2) — narrowing to hyperlinks alone would then miss
|
|
84
|
+
* exactly the boards that are best integrated.
|
|
85
|
+
*
|
|
86
|
+
* There is no way to ask WIQL about the *contents* of a relation — it queries
|
|
87
|
+
* fields, and a `Hyperlink`'s URL is not one. So this narrows to work items that
|
|
88
|
+
* have external links at all and the URLs are parsed afterwards, in
|
|
89
|
+
* {@link linksOnWorkItems}.
|
|
90
|
+
*
|
|
91
|
+
* `[System.TeamProject] = @project` is not redundant with the project in the
|
|
92
|
+
* URL: WIQL happily returns items from every project the token can read, and a
|
|
93
|
+
* drift report that reached across an organisation would be answering a question
|
|
94
|
+
* nobody asked. Same argument `buildWiql` already makes.
|
|
95
|
+
*/
|
|
96
|
+
export function driftWiql() {
|
|
97
|
+
return ("SELECT [System.Id] FROM WorkItems"
|
|
98
|
+
+ " WHERE [System.TeamProject] = @project"
|
|
99
|
+
+ " AND ([System.HyperLinkCount] > 0 OR [System.ExternalLinkCount] > 0)"
|
|
100
|
+
+ " ORDER BY [System.ChangedDate] DESC");
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* The statement `adopt` runs **before creating anything**, to find a work item
|
|
104
|
+
* that already links the issue in front of it.
|
|
105
|
+
*
|
|
106
|
+
* ## Why this is not `driftWiql`
|
|
107
|
+
*
|
|
108
|
+
* Two narrower choices, and both are the point.
|
|
109
|
+
*
|
|
110
|
+
* **`HyperLinkCount` alone.** `drift` asks "is any link here broken", so it
|
|
111
|
+
* takes both counters — a client whose organisation *does* have the Azure
|
|
112
|
+
* Boards connection gets a native `ArtifactLink` in addition to ours, and
|
|
113
|
+
* missing those would miss the best-integrated boards. This asks a different
|
|
114
|
+
* question: *did Pharos already adopt this issue*, and Pharos writes a
|
|
115
|
+
* `Hyperlink`, always, because the connection id an artifact link needs is
|
|
116
|
+
* behind a 401 on our scopes (§2). So `ExternalLinkCount` here would add every
|
|
117
|
+
* work item that ever had a commit, a PR or a build attached — which on a real
|
|
118
|
+
* board is most of them — to a query that runs on the way to a single
|
|
119
|
+
* adoption. Measured on the live board (#801): the adopted pair answered
|
|
120
|
+
* `HyperLinkCount 1, ExternalLinkCount 0`, and `[System.HyperLinkCount] > 0`
|
|
121
|
+
* returned it while `[System.ExternalLinkCount] > 0` returned two other items
|
|
122
|
+
* and not it.
|
|
123
|
+
*
|
|
124
|
+
* **Capped, newest first.** `drift` is a deliberate sweep and reads the whole
|
|
125
|
+
* board; this is a guard on a one-issue verb and must not turn it into one. The
|
|
126
|
+
* state it exists to catch is a work item written minutes ago, so ordering by
|
|
127
|
+
* `ChangedDate` descending and stopping at {@link ADOPT_GUARD_LIMIT} keeps the
|
|
128
|
+
* cost flat while looking exactly where the answer is. A board with more
|
|
129
|
+
* hyperlinked work items than that is not a wrong answer — it is a partial one,
|
|
130
|
+
* and `adopt` says so rather than reporting a clean check.
|
|
131
|
+
*
|
|
132
|
+
* There is still no way to ask WIQL about the *contents* of a relation, so the
|
|
133
|
+
* URLs are matched afterwards, client-side, by {@link claimsOn}. That is the
|
|
134
|
+
* same narrow-then-filter shape `drift` uses and the same reason.
|
|
135
|
+
*/
|
|
136
|
+
export function adoptGuardWiql() {
|
|
137
|
+
return ("SELECT [System.Id] FROM WorkItems"
|
|
138
|
+
+ " WHERE [System.TeamProject] = @project"
|
|
139
|
+
+ " AND [System.HyperLinkCount] > 0"
|
|
140
|
+
+ " ORDER BY [System.ChangedDate] DESC");
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* How many hyperlinked work items `adopt`'s guard looks at. Two
|
|
144
|
+
* `workitemsbatch` calls at the 200-per-request chunk, on top of the WIQL.
|
|
145
|
+
*/
|
|
146
|
+
export const ADOPT_GUARD_LIMIT = 400;
|
|
147
|
+
/**
|
|
148
|
+
* Every GitHub issue link on a batch of work items, flattened.
|
|
149
|
+
*
|
|
150
|
+
* `hyperlinks` is a function rather than a field because reading relations off a
|
|
151
|
+
* `WorkItem` is the command's business — this file is not allowed to care what
|
|
152
|
+
* shape Azure DevOps returns.
|
|
153
|
+
*/
|
|
154
|
+
export function linksOnWorkItems(items, describe, hyperlinks) {
|
|
155
|
+
const out = [];
|
|
156
|
+
for (const item of items) {
|
|
157
|
+
const workItem = describe(item);
|
|
158
|
+
for (const link of hyperlinks(item)) {
|
|
159
|
+
out.push({
|
|
160
|
+
workItem,
|
|
161
|
+
repo: normalizeRepo(link.repo),
|
|
162
|
+
spelling: link.repo,
|
|
163
|
+
issueNumber: link.issueNumber,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return out;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* The links that name one particular issue — "who on this board already claims
|
|
171
|
+
* it".
|
|
172
|
+
*
|
|
173
|
+
* Compared on {@link normalizeRepo}, never with `===`: GitHub resolves a
|
|
174
|
+
* repository case-insensitively and echoes back whichever spelling it was
|
|
175
|
+
* created with, so a `Hyperlink` written as `Contoso/Widgets` and a coordinate
|
|
176
|
+
* typed as `contoso/widgets` are one issue and two strings. #787 reported a
|
|
177
|
+
* perfectly good link as half-written over exactly this, and here the same slip
|
|
178
|
+
* would be worse: it would report *no* claim, and a second work item is what
|
|
179
|
+
* follows from that.
|
|
180
|
+
*/
|
|
181
|
+
export function claimsOn(links, coordinate) {
|
|
182
|
+
const repo = normalizeRepo(coordinate.repo);
|
|
183
|
+
return links.filter((link) => link.repo === repo && link.issueNumber === coordinate.number);
|
|
184
|
+
}
|
|
185
|
+
/** The repositories a set of links touches, lower-cased and de-duplicated. */
|
|
186
|
+
export function reposOf(links) {
|
|
187
|
+
return [...new Set(links.map((link) => link.repo))].sort();
|
|
188
|
+
}
|
|
189
|
+
/** The catalogue as this file needs it, or the stock names with a warning attached. */
|
|
190
|
+
export function finishedStates(catalog, failure) {
|
|
191
|
+
if (catalog === undefined) {
|
|
192
|
+
return {
|
|
193
|
+
source: "fallback",
|
|
194
|
+
terminal: FALLBACK_TERMINAL_STATES,
|
|
195
|
+
byType: new Map(),
|
|
196
|
+
ambiguous: [],
|
|
197
|
+
note: "COULD NOT read this project's state categories"
|
|
198
|
+
+ (failure === undefined ? "" : ` (${failure})`)
|
|
199
|
+
+ ", so these are Azure DevOps' stock names. A project that renames its states will have "
|
|
200
|
+
+ "work items misclassified here — treat a state drift row as a question rather than a "
|
|
201
|
+
+ "finding until this reads.",
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
return {
|
|
205
|
+
source: "categories",
|
|
206
|
+
terminal: catalog.terminal,
|
|
207
|
+
byType: new Map([...catalog.byType].map(([type, states]) => [type, states.terminal])),
|
|
208
|
+
ambiguous: catalog.ambiguous,
|
|
209
|
+
note: "Read from this project's own state categories, so it is right whatever the process "
|
|
210
|
+
+ "template calls things."
|
|
211
|
+
+ (catalog.ambiguous.length === 0
|
|
212
|
+
? ""
|
|
213
|
+
: ` ${catalog.ambiguous.join(", ")} mean different things on different work item types, `
|
|
214
|
+
+ "so each work item was judged against its own."),
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Is this work item finished?
|
|
219
|
+
*
|
|
220
|
+
* **Its own type's list first**, because that is the whole reason `StateCatalog`
|
|
221
|
+
* carries `byType`: `Resolved` can be terminal on a Bug and open on a User
|
|
222
|
+
* Story, and one flat list gets one of them wrong. The flat list is the fallback
|
|
223
|
+
* for a type the catalogue does not know — a work item type added since, or a
|
|
224
|
+
* batch that came back with no `System.WorkItemType` at all.
|
|
225
|
+
*
|
|
226
|
+
* **An unknown state counts as open**, matching `stateCatalog`'s own rule and
|
|
227
|
+
* for its reason: the two mistakes are not equal. Calling a finished work item
|
|
228
|
+
* open puts a row in front of somebody who can dismiss it; calling an open one
|
|
229
|
+
* finished hides drift, which is the one thing this command exists to surface.
|
|
230
|
+
*/
|
|
231
|
+
export function isFinished(states, type, state) {
|
|
232
|
+
if (state === null || state === "")
|
|
233
|
+
return false;
|
|
234
|
+
const perType = type === null ? undefined : states.byType.get(type);
|
|
235
|
+
if (perType !== undefined)
|
|
236
|
+
return perType.includes(state);
|
|
237
|
+
return states.terminal.includes(state);
|
|
238
|
+
}
|
|
239
|
+
export function terminalStateFor(states, type) {
|
|
240
|
+
// The catalogue could not be read at all. `drift` degrades to the stock names
|
|
241
|
+
// because a misclassified ROW is a question somebody can dismiss; here the
|
|
242
|
+
// same guess would be a WRITE, so it refuses instead.
|
|
243
|
+
if (states.source === "fallback")
|
|
244
|
+
return { kind: "unknown" };
|
|
245
|
+
const perType = type === null ? undefined : states.byType.get(type);
|
|
246
|
+
const candidates = perType ?? states.terminal;
|
|
247
|
+
if (candidates.length === 0)
|
|
248
|
+
return { kind: "none" };
|
|
249
|
+
if (candidates.length === 1)
|
|
250
|
+
return { kind: "one", state: candidates[0] };
|
|
251
|
+
return { kind: "several", states: [...candidates].sort() };
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Judge one link.
|
|
255
|
+
*
|
|
256
|
+
* **Order matters, and it is worst-first.** A pair whose issue cannot be read
|
|
257
|
+
* has no state to compare and no marker to look for, so reporting it as three
|
|
258
|
+
* problems would be reporting one problem three times — and the three rows would
|
|
259
|
+
* not even agree, because two of them would be inferred from an absence. Each
|
|
260
|
+
* link produces at most one row.
|
|
261
|
+
*/
|
|
262
|
+
export function judgeLink(link, side, states) {
|
|
263
|
+
const coordinate = `${link.spelling}#${link.issueNumber}`;
|
|
264
|
+
const base = { workItem: link.workItem, coordinate, issue: null };
|
|
265
|
+
if (side.missing === true) {
|
|
266
|
+
return {
|
|
267
|
+
healthy: false,
|
|
268
|
+
drift: {
|
|
269
|
+
...base,
|
|
270
|
+
kind: "missing-issue",
|
|
271
|
+
evidence: ["hyperlink"],
|
|
272
|
+
detail: `Work item #${link.workItem.id} links ${coordinate}, and GitHub says there is no such `
|
|
273
|
+
+ "issue. It was deleted, transferred to another repository, or the link was written "
|
|
274
|
+
+ "with the wrong number.",
|
|
275
|
+
...(side.problem === undefined ? {} : { problem: side.problem }),
|
|
276
|
+
},
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
if (side.issue === undefined) {
|
|
280
|
+
return {
|
|
281
|
+
healthy: false,
|
|
282
|
+
drift: {
|
|
283
|
+
...base,
|
|
284
|
+
kind: "unreadable",
|
|
285
|
+
evidence: ["hyperlink"],
|
|
286
|
+
problem: side.problem ?? "the issue could not be read",
|
|
287
|
+
detail: `Work item #${link.workItem.id} links ${coordinate} and that repository could not be `
|
|
288
|
+
+ "read from this machine, so nothing about this pair was checked. This is usually a "
|
|
289
|
+
+ "repository nobody here is bound to — somebody else adopted it, configured "
|
|
290
|
+
+ "differently — which is information rather than a fault.",
|
|
291
|
+
},
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
const issue = { ...side.issue, coordinate };
|
|
295
|
+
const evidence = ["hyperlink"];
|
|
296
|
+
let marked = false;
|
|
297
|
+
if (side.markers !== undefined) {
|
|
298
|
+
// Only markers naming THIS work item count. An issue carrying a marker for
|
|
299
|
+
// some other work item is not this link's other half, and taking it as one
|
|
300
|
+
// would report a genuinely broken pair as complete.
|
|
301
|
+
for (const found of side.markers) {
|
|
302
|
+
if (found.workItemId !== link.workItem.id)
|
|
303
|
+
continue;
|
|
304
|
+
for (const piece of found.evidence) {
|
|
305
|
+
if (piece === "hyperlink")
|
|
306
|
+
continue;
|
|
307
|
+
if (!evidence.includes(piece))
|
|
308
|
+
evidence.push(piece);
|
|
309
|
+
marked = true;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
evidence.sort();
|
|
314
|
+
// Checked before the state comparison because a half-written link is a fact
|
|
315
|
+
// about the LINK, and the state comparison is a fact about the work — a pair
|
|
316
|
+
// that is both gets reported as the one somebody has to fix first.
|
|
317
|
+
if (side.markers !== undefined && !marked) {
|
|
318
|
+
return {
|
|
319
|
+
healthy: false,
|
|
320
|
+
drift: {
|
|
321
|
+
...base,
|
|
322
|
+
kind: "one-sided",
|
|
323
|
+
missing: "marker",
|
|
324
|
+
issue,
|
|
325
|
+
evidence,
|
|
326
|
+
detail: `Work item #${link.workItem.id} links ${coordinate} and nothing on the issue says so. `
|
|
327
|
+
+ "The reporter cannot see that anybody picked this up, and no sweep starting from "
|
|
328
|
+
+ "GitHub will find the work item. This is what a failed `adopt` leaves behind.",
|
|
329
|
+
recover: `pharos issue link ${coordinate} ${link.workItem.id}`,
|
|
330
|
+
},
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
const finished = isFinished(states, link.workItem.type, link.workItem.state);
|
|
334
|
+
const closed = side.issue.state === "closed";
|
|
335
|
+
if (closed && !finished) {
|
|
336
|
+
return {
|
|
337
|
+
healthy: false,
|
|
338
|
+
drift: {
|
|
339
|
+
...base,
|
|
340
|
+
kind: "state",
|
|
341
|
+
direction: "issue-closed",
|
|
342
|
+
issue,
|
|
343
|
+
evidence,
|
|
344
|
+
detail: `${coordinate} is closed${side.issue.stateReason === null ? "" : ` (${side.issue.stateReason})`}`
|
|
345
|
+
+ ` and work item #${link.workItem.id} is ${describeState(link.workItem.state)}. Either `
|
|
346
|
+
+ "the board has not caught up, or it was closed on GitHub without the work being done.",
|
|
347
|
+
},
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
if (!closed && finished) {
|
|
351
|
+
return {
|
|
352
|
+
healthy: false,
|
|
353
|
+
drift: {
|
|
354
|
+
...base,
|
|
355
|
+
kind: "state",
|
|
356
|
+
direction: "work-item-finished",
|
|
357
|
+
issue,
|
|
358
|
+
evidence,
|
|
359
|
+
detail: `Work item #${link.workItem.id} is ${describeState(link.workItem.state)} and `
|
|
360
|
+
+ `${coordinate} is still open. The reporter has not been told it is done — this is the `
|
|
361
|
+
+ "one that is rude on a public repository.",
|
|
362
|
+
},
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
return { healthy: true };
|
|
366
|
+
}
|
|
367
|
+
function describeState(state) {
|
|
368
|
+
return state === null || state === "" ? "in an unnamed state" : `in "${state}"`;
|
|
369
|
+
}
|
|
370
|
+
/** The counts a drift report leads with, so nobody has to tally the rows. */
|
|
371
|
+
export function tallyDrift(rows) {
|
|
372
|
+
const counts = {
|
|
373
|
+
state: 0,
|
|
374
|
+
"missing-issue": 0,
|
|
375
|
+
"one-sided": 0,
|
|
376
|
+
unreadable: 0,
|
|
377
|
+
};
|
|
378
|
+
for (const row of rows)
|
|
379
|
+
counts[row.kind] += 1;
|
|
380
|
+
return counts;
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Which issues in a repository the board has never heard of.
|
|
384
|
+
*
|
|
385
|
+
* `linked` is the set of issue numbers already reached by a `Hyperlink` — the
|
|
386
|
+
* index this file's header argues for, and the reason this costs two Azure
|
|
387
|
+
* DevOps calls instead of one GitHub call per issue.
|
|
388
|
+
*
|
|
389
|
+
* **Ordered by issue number ascending**, which is a decision rather than an
|
|
390
|
+
* accident. The sweep arrives in `updated_at` order, so taking the first `n` of
|
|
391
|
+
* *that* would adopt a different set every run as issues get touched — and a
|
|
392
|
+
* `--limit` that means something different each time is a `--limit` nobody can
|
|
393
|
+
* resume against. Oldest issue first is stable, obvious, and makes a second run
|
|
394
|
+
* carry on where the first stopped.
|
|
395
|
+
*/
|
|
396
|
+
export function backfillCandidates(issues, linked) {
|
|
397
|
+
return issues
|
|
398
|
+
.filter((issue) => !linked.has(issue.number))
|
|
399
|
+
.sort((left, right) => left.number - right.number);
|
|
400
|
+
}
|
|
401
|
+
/** The issue numbers already linked from the board, for one repository. */
|
|
402
|
+
export function linkedNumbersIn(links, repo) {
|
|
403
|
+
const wanted = normalizeRepo(repo);
|
|
404
|
+
return new Set(links.filter((link) => link.repo === wanted).map((link) => link.issueNumber));
|
|
405
|
+
}
|
|
406
|
+
/** How many writes adopting `count` issues costs. One create, one comment, each. */
|
|
407
|
+
export const WRITES_PER_ADOPTION = 2;
|
|
408
|
+
// MARK: - running the reads
|
|
409
|
+
/**
|
|
410
|
+
* Run `work` over `items`, at most `limit` at a time, keeping the input order.
|
|
411
|
+
*
|
|
412
|
+
* `drift` reads two endpoints per link and a board with four hundred of them is
|
|
413
|
+
* the case this milestone exists for, so doing them one at a time is minutes of
|
|
414
|
+
* waiting. `Promise.all` over all of them is the other extreme and it is worse:
|
|
415
|
+
* GitHub's secondary rate limit is explicitly about concurrency rather than
|
|
416
|
+
* volume, and tripping it turns a diagnostic into an hour's ban on a token
|
|
417
|
+
* somebody else is also using.
|
|
418
|
+
*
|
|
419
|
+
* Rejections are **not** caught here. A caller that wants a failed read to
|
|
420
|
+
* become a row rather than an exception says so in `work` — which `drift` does,
|
|
421
|
+
* because one unreadable repository must not kill a sweep (#782).
|
|
422
|
+
*/
|
|
423
|
+
export async function inBatches(items, limit, work) {
|
|
424
|
+
const results = new Array(items.length);
|
|
425
|
+
let next = 0;
|
|
426
|
+
const runner = async () => {
|
|
427
|
+
for (;;) {
|
|
428
|
+
const index = next++;
|
|
429
|
+
if (index >= items.length)
|
|
430
|
+
return;
|
|
431
|
+
results[index] = await work(items[index], index);
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
await Promise.all(Array.from({ length: Math.max(1, Math.min(limit, items.length)) }, runner));
|
|
435
|
+
return results;
|
|
436
|
+
}
|
|
437
|
+
/** How many issue reads `drift` has in flight at once. */
|
|
438
|
+
export const READ_CONCURRENCY = 6;
|
|
439
|
+
/**
|
|
440
|
+
* A WIQL failure that names the field this file introduced.
|
|
441
|
+
*
|
|
442
|
+
* `System.ExternalLinkCount` is queryable on every process template Azure DevOps
|
|
443
|
+
* ships, and this has not been run against a customised one. If a project
|
|
444
|
+
* somehow does not expose it the statement fails to parse, which reads as "drift
|
|
445
|
+
* is broken" — so the error says which clause is suspect and hands over the
|
|
446
|
+
* escape hatch `pharos query` already established.
|
|
447
|
+
*/
|
|
448
|
+
export function looksLikeWiqlRejection(message) {
|
|
449
|
+
const text = message.toLowerCase();
|
|
450
|
+
return (text.includes("externallinkcount")
|
|
451
|
+
|| text.includes("wiql")
|
|
452
|
+
|| text.includes("does not exist")
|
|
453
|
+
|| text.includes("is not recognized"));
|
|
454
|
+
}
|
|
455
|
+
//# sourceMappingURL=issue-scan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"issue-scan.js","sourceRoot":"","sources":["../src/issue-scan.ts"],"names":[],"mappings":"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"}
|
package/dist/output.d.ts
CHANGED
|
@@ -27,7 +27,9 @@ export declare const EXIT_FAILED = 1;
|
|
|
27
27
|
export declare const EXIT_USAGE = 2;
|
|
28
28
|
export declare const EXIT_REFUSED = 3;
|
|
29
29
|
export type ExitCode = 0 | 1 | 2 | 3;
|
|
30
|
-
export type ErrorKind = "usage" | "config" | "auth" | "refused" | "conflict" | "notFound" | "rateLimit" | "network" | "ado"
|
|
30
|
+
export type ErrorKind = "usage" | "config" | "auth" | "refused" | "conflict" | "notFound" | "rateLimit" | "network" | "ado"
|
|
31
|
+
/** GitHub said no, and none of the kinds above describe it more usefully. */
|
|
32
|
+
| "github" | "internal";
|
|
31
33
|
export interface Io {
|
|
32
34
|
stdout: (line: string) => void;
|
|
33
35
|
stderr: (line: string) => void;
|
|
@@ -84,4 +86,19 @@ export declare function emitText(io: Io, text: string): ExitCode;
|
|
|
84
86
|
* repeating the identical call is correct.
|
|
85
87
|
*/
|
|
86
88
|
export declare function reportError(io: Io, error: unknown): ExitCode;
|
|
89
|
+
/**
|
|
90
|
+
* The same classification `reportError` uses, without emitting anything.
|
|
91
|
+
*
|
|
92
|
+
* For the one shape that needs it: a verb that has **already written one half of
|
|
93
|
+
* something** when the second half fails. `pharos issue adopt` creates the work
|
|
94
|
+
* item and then comments on the issue, so a failure at the second step must
|
|
95
|
+
* report the first step's result rather than only the error — otherwise the work
|
|
96
|
+
* item exists and nothing anywhere names it. Re-classifying by hand there would
|
|
97
|
+
* be a second, drifting copy of this table.
|
|
98
|
+
*/
|
|
99
|
+
export declare function failureOf(error: unknown): {
|
|
100
|
+
kind: ErrorKind;
|
|
101
|
+
code: ExitCode;
|
|
102
|
+
body: Record<string, unknown>;
|
|
103
|
+
};
|
|
87
104
|
//# sourceMappingURL=output.d.ts.map
|
package/dist/output.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"output.d.ts","sourceRoot":"","sources":["../src/output.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"output.d.ts","sourceRoot":"","sources":["../src/output.ts"],"names":[],"mappings":"AA6BA;;;;;;;;;;;;;;;GAeG;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,OAAO,IAAI,CAAC;AACzB,eAAO,MAAM,WAAW,IAAI,CAAC;AAC7B,eAAO,MAAM,UAAU,IAAI,CAAC;AAC5B,eAAO,MAAM,YAAY,IAAI,CAAC;AAE9B,MAAM,MAAM,QAAQ,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAErC,MAAM,MAAM,SAAS,GACjB,OAAO,GACP,QAAQ,GACR,MAAM,GACN,SAAS,GACT,UAAU,GACV,UAAU,GACV,WAAW,GACX,SAAS,GACT,KAAK;AACP,6EAA6E;GAC3E,QAAQ,GACR,UAAU,CAAC;AAEf,MAAM,WAAW,EAAE;IACjB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAChC;AAED;;;;;;GAMG;AACH,qBAAa,QAAS,SAAQ,KAAK;IACjC,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAEzC;;;;;;OAMG;IACH,QAAQ,CAAC,OAAO,QAAQ;gBAGtB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,SAAS,EACf,QAAQ,EAAE,QAAQ,EAClB,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM;CAQvC;AAED,2EAA2E;AAC3E,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAAG,QAAQ,CAE1F;AAED;;;;;GAKG;AACH,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAAG,QAAQ,CAEvF;AAED,iFAAiF;AACjF,wBAAgB,IAAI,CAAC,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,GAAG,QAAQ,CAGrD;AAED,sDAAsD;AACtD,wBAAgB,QAAQ,CAAC,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,QAAQ,CAGvD;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,GAAG,QAAQ,CAI5D;AAED;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG;IACzC,IAAI,EAAE,SAAS,CAAC;IAChB,IAAI,EAAE,QAAQ,CAAC;IACf,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B,CAGA"}
|