@floh-solutions/pharos-cli 0.24.0 → 0.26.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 +97 -10
- package/dist/adopt-type.d.ts +58 -0
- package/dist/adopt-type.d.ts.map +1 -0
- package/dist/adopt-type.js +61 -0
- package/dist/adopt-type.js.map +1 -0
- package/dist/bridge.d.ts +156 -0
- package/dist/bridge.d.ts.map +1 -0
- package/dist/bridge.js +157 -0
- package/dist/bridge.js.map +1 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +176 -31
- package/dist/cli.js.map +1 -1
- package/dist/commands/bridge.d.ts +30 -0
- package/dist/commands/bridge.d.ts.map +1 -0
- package/dist/commands/bridge.js +255 -0
- package/dist/commands/bridge.js.map +1 -0
- 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 +169 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +241 -44
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/issue.d.ts +133 -0
- package/dist/commands/issue.d.ts.map +1 -1
- package/dist/commands/issue.js +292 -110
- package/dist/commands/issue.js.map +1 -1
- package/dist/commands/setup.d.ts +45 -0
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +74 -0
- package/dist/commands/setup.js.map +1 -1
- package/dist/half-link.d.ts +14 -4
- package/dist/half-link.d.ts.map +1 -1
- package/dist/half-link.js +14 -4
- package/dist/half-link.js.map +1 -1
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +123 -1
- package/dist/output.js.map +1 -1
- package/dist/session.d.ts +51 -2
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +62 -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 +4 -4
- package/skill/SKILL.md +167 -7
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
import { GITHUB_HIDE_REASONS, GITHUB_REACTIONS, GitHubNotFoundError, MarkerGuardError, findMarker, formatIssueCoordinate, isPharosAuthored, parseHideReason, parseReaction, } from "@floh-solutions/gh-core";
|
|
2
|
+
import { emit, refusal, usageError } from "../output.js";
|
|
3
|
+
import { approve } from "../session.js";
|
|
4
|
+
import { describeTarget } from "../target.js";
|
|
5
|
+
// MARK: - list
|
|
6
|
+
/**
|
|
7
|
+
* Every comment on an issue, with the state and the permissions REST cannot
|
|
8
|
+
* report.
|
|
9
|
+
*
|
|
10
|
+
* **This verb is why the rest of them are usable at all.** Before it, nothing
|
|
11
|
+
* in this CLI could tell an agent a GitHub comment id: `pharos issue say`
|
|
12
|
+
* returns the id of the comment it just posted and that was the entire surface.
|
|
13
|
+
* The app never needed a list because it has a window. An agent has this.
|
|
14
|
+
*
|
|
15
|
+
* The `viewerCan…` flags are the part worth having. For a window they grey out
|
|
16
|
+
* a menu item; for an agent they turn a write that would 403 into a fact known
|
|
17
|
+
* before the write, which is the difference between a failed run and a run that
|
|
18
|
+
* reports what it may do.
|
|
19
|
+
*/
|
|
20
|
+
export async function githubList(io, session, issue) {
|
|
21
|
+
const { github, ref } = await resolve(session, issue);
|
|
22
|
+
const sweep = await github.comments.details(ref);
|
|
23
|
+
if (sweep.issueMissing) {
|
|
24
|
+
// GraphQL answers a null `repository` with NO errors entry for an issue
|
|
25
|
+
// that was deleted, transferred or is not visible to this account. Without
|
|
26
|
+
// this the caller cannot tell that from a discussion with no comments.
|
|
27
|
+
throw refusal(`${formatIssueCoordinate(issue)} could not be read as ${ref.account}. GitHub answered with `
|
|
28
|
+
+ "no issue rather than an error, which is what it does for an issue that was deleted or "
|
|
29
|
+
+ "transferred — or that this account cannot see.", { issue, account: ref.account });
|
|
30
|
+
}
|
|
31
|
+
return emit(io, {
|
|
32
|
+
target: formatIssueCoordinate(issue),
|
|
33
|
+
account: ref.account,
|
|
34
|
+
truncated: sweep.truncated,
|
|
35
|
+
comments: sweep.comments.map((comment) => ({
|
|
36
|
+
id: comment.id,
|
|
37
|
+
// Printed beside the number precisely so `hide` and `pin` need no lookup.
|
|
38
|
+
nodeId: comment.nodeId,
|
|
39
|
+
author: comment.author ?? null,
|
|
40
|
+
body: comment.body,
|
|
41
|
+
createdAt: comment.createdAt,
|
|
42
|
+
// Not `createdAt !== updatedAt`: a reaction moves updatedAt, so that
|
|
43
|
+
// comparison labels a comment edited because somebody gave it a 👍.
|
|
44
|
+
lastEditedAt: comment.lastEditedAt ?? null,
|
|
45
|
+
url: comment.htmlUrl ?? null,
|
|
46
|
+
hidden: comment.isMinimized,
|
|
47
|
+
hiddenReason: comment.minimizedReason ?? null,
|
|
48
|
+
pinned: comment.isPinned,
|
|
49
|
+
/** True when this comment carries the link record. */
|
|
50
|
+
pharosAuthored: isPharosAuthored(comment.body),
|
|
51
|
+
workItemId: findMarker(comment.body)?.adoId ?? null,
|
|
52
|
+
reactions: comment.reactions.map((group) => ({
|
|
53
|
+
type: group.reaction.cli,
|
|
54
|
+
emoji: group.reaction.emoji,
|
|
55
|
+
count: group.count,
|
|
56
|
+
mine: group.viewerHasReacted,
|
|
57
|
+
})),
|
|
58
|
+
may: {
|
|
59
|
+
edit: comment.viewerCanUpdate,
|
|
60
|
+
delete: comment.viewerCanDelete,
|
|
61
|
+
hide: comment.viewerCanMinimize,
|
|
62
|
+
unhide: comment.viewerCanUnminimize,
|
|
63
|
+
pin: comment.viewerCanPin,
|
|
64
|
+
unpin: comment.viewerCanUnpin,
|
|
65
|
+
react: comment.viewerCanReact,
|
|
66
|
+
},
|
|
67
|
+
})),
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
// MARK: - add, which is refused
|
|
71
|
+
/**
|
|
72
|
+
* There is no `pharos comment add` on a GitHub issue, and there should not be.
|
|
73
|
+
*
|
|
74
|
+
* Plan §8 — do not rebuild `gh`. Two verbs already cover posting, and they
|
|
75
|
+
* cover different jobs: `gh issue comment` writes to GitHub, and
|
|
76
|
+
* `pharos issue say` writes the same message to GitHub *and* a summary to the
|
|
77
|
+
* work item, which is the whole reason the edge exists. A third door here would
|
|
78
|
+
* post to an adopted issue without the board ever hearing, which is exactly the
|
|
79
|
+
* asymmetry `say` was built to prevent — and it would do it under a verb whose
|
|
80
|
+
* Azure DevOps spelling *does* reach the board.
|
|
81
|
+
*
|
|
82
|
+
* Refused rather than silently absent: an agent that tries this has a real
|
|
83
|
+
* intention, and the useful answer names which of the two it wanted.
|
|
84
|
+
*/
|
|
85
|
+
export function githubRefuseAdd(issue) {
|
|
86
|
+
const coordinate = formatIssueCoordinate(issue);
|
|
87
|
+
throw usageError(`\`pharos comment add\` does not post to a GitHub issue. Two verbs already do, and which you `
|
|
88
|
+
+ `want depends on whether the board should hear about it:\n`
|
|
89
|
+
+ ` pharos issue say ${coordinate} "…" the reporter AND the linked work item\n`
|
|
90
|
+
+ ` gh issue comment ${issue.number} -R ${issue.repo} … GitHub only\n`
|
|
91
|
+
+ "This CLI owns the edge, not the nodes — and a comment posted here that the work item "
|
|
92
|
+
+ "never heard about is the asymmetry `say` exists to prevent.", { issue, use: ["pharos issue say", "gh issue comment"] });
|
|
93
|
+
}
|
|
94
|
+
// MARK: - edit
|
|
95
|
+
/**
|
|
96
|
+
* Edit a comment, behind `gh-core`'s marker guard.
|
|
97
|
+
*
|
|
98
|
+
* The guard is in the package rather than here on purpose — it is a rule about
|
|
99
|
+
* the link record, not about this command, and the macOS app writes through the
|
|
100
|
+
* same rule when it edits. What this arm adds is the reporting: a restored
|
|
101
|
+
* trailer is stated in the output rather than happening quietly.
|
|
102
|
+
*/
|
|
103
|
+
export async function githubEdit(io, session, issue, handle, text) {
|
|
104
|
+
const { github, ref } = await resolve(session, issue);
|
|
105
|
+
const id = await restId(session, ref, handle);
|
|
106
|
+
try {
|
|
107
|
+
const result = await github.comments.edit(ref, id, text);
|
|
108
|
+
return emit(io, {
|
|
109
|
+
applied: true,
|
|
110
|
+
target: formatIssueCoordinate(issue),
|
|
111
|
+
commentId: result.comment.id,
|
|
112
|
+
url: result.comment.htmlUrl ?? null,
|
|
113
|
+
/**
|
|
114
|
+
* The caller stripped the `pharos:v1` trailer and it was put back. Said
|
|
115
|
+
* out loud because the alternative — restoring it in silence — makes a
|
|
116
|
+
* body that differs from what was handed over, and an agent comparing
|
|
117
|
+
* what it sent with what is there would find a difference it cannot
|
|
118
|
+
* explain.
|
|
119
|
+
*/
|
|
120
|
+
markerPreserved: result.markerPreserved,
|
|
121
|
+
workItemId: result.marker === undefined ? null : (findMarker(result.marker)?.adoId ?? null),
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
if (error instanceof MarkerGuardError) {
|
|
126
|
+
// Reported as a refusal (exit 3), not a failure: nothing went wrong, the
|
|
127
|
+
// edit was declined for a reason the caller can act on.
|
|
128
|
+
throw refusal(error.message, {
|
|
129
|
+
issue,
|
|
130
|
+
commentId: id,
|
|
131
|
+
reason: error.reason,
|
|
132
|
+
trailers: error.detail,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
throw error;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// MARK: - delete
|
|
139
|
+
/**
|
|
140
|
+
* Delete a comment — and name what that costs before doing it.
|
|
141
|
+
*
|
|
142
|
+
* `approve()` already gates every destructive verb behind `--yes`. What is
|
|
143
|
+
* added here is that the preview knows whether this particular comment is the
|
|
144
|
+
* one carrying the `pharos:v1` trailer, because deleting **that** comment
|
|
145
|
+
* destroys the GitHub half of the link: `linksFromComments` reads it, so after
|
|
146
|
+
* this the pair is one-sided and `pharos issue drift` reports it — correctly,
|
|
147
|
+
* and after the fact.
|
|
148
|
+
*
|
|
149
|
+
* So the comment is read *before* the gate, including under `--dry-run`. A
|
|
150
|
+
* preview that could not say which of the two deletes this is would be a
|
|
151
|
+
* preview of the wrong thing.
|
|
152
|
+
*
|
|
153
|
+
* It is not refused outright. `--yes` is the caller's decision and there are
|
|
154
|
+
* good reasons to remove a link comment — a mis-adoption, a wrong repository.
|
|
155
|
+
* The repair is named instead.
|
|
156
|
+
*/
|
|
157
|
+
export async function githubRemove(io, session, issue, handle) {
|
|
158
|
+
const { github, ref } = await resolve(session, issue);
|
|
159
|
+
const id = await restId(session, ref, handle);
|
|
160
|
+
let existing;
|
|
161
|
+
try {
|
|
162
|
+
existing = await github.comments.get(ref, id);
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
if (!(error instanceof GitHubNotFoundError))
|
|
166
|
+
throw error;
|
|
167
|
+
/**
|
|
168
|
+
* **Already gone, and that is success.**
|
|
169
|
+
*
|
|
170
|
+
* This read exists so the preview can say whether the comment carries the
|
|
171
|
+
* link — but adding it would otherwise have broken the one property
|
|
172
|
+
* `CommentsApi.remove` is careful to provide: a delete whose reply was lost
|
|
173
|
+
* and is retried gets a 404, and that 404 means the caller got what it
|
|
174
|
+
* asked for. Without this arm the retry failed on the *read* before ever
|
|
175
|
+
* reaching the idempotent DELETE, which is the exact "sends somebody
|
|
176
|
+
* looking for a comment that is already gone" failure the rule is about.
|
|
177
|
+
* Found by running it against real GitHub rather than by a test (#827).
|
|
178
|
+
*
|
|
179
|
+
* The `--yes` gate is skipped deliberately: there is nothing left to
|
|
180
|
+
* destroy, and demanding confirmation for a no-op is how a safe retry
|
|
181
|
+
* becomes a manual step.
|
|
182
|
+
*/
|
|
183
|
+
return emit(io, {
|
|
184
|
+
applied: true,
|
|
185
|
+
action: "deleted",
|
|
186
|
+
target: formatIssueCoordinate(issue),
|
|
187
|
+
commentId: id,
|
|
188
|
+
alreadyGone: true,
|
|
189
|
+
// Not knowable now — the comment is gone and so is its body.
|
|
190
|
+
carriesLinkMarker: null,
|
|
191
|
+
workItemId: null,
|
|
192
|
+
// The ambiguity stated rather than hidden: this is the same status code
|
|
193
|
+
// GitHub gives for a comment that exists and is not visible to this
|
|
194
|
+
// account, so "already gone" is the likely reading and not the only one.
|
|
195
|
+
note: `Nothing to delete: comment ${String(id)} is not there. On GitHub a 404 is also how `
|
|
196
|
+
+ `"not visible to this account" is spelled, so if you did not expect this, check that `
|
|
197
|
+
+ `${ref.account} is the right account for ${issue.repo} in repos.json.`,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
const marker = findMarker(existing.body);
|
|
201
|
+
const carriesLink = isPharosAuthored(existing.body);
|
|
202
|
+
const stop = approve(io, session, {
|
|
203
|
+
what: carriesLink
|
|
204
|
+
? `delete comment ${id} on ${formatIssueCoordinate(issue)} — THE COMMENT CARRYING THE LINK`
|
|
205
|
+
: `delete comment ${id} on ${formatIssueCoordinate(issue)}`,
|
|
206
|
+
would: {
|
|
207
|
+
action: "deleteComment",
|
|
208
|
+
target: formatIssueCoordinate(issue),
|
|
209
|
+
commentId: id,
|
|
210
|
+
author: existing.author ?? null,
|
|
211
|
+
/**
|
|
212
|
+
* The consequence, structured, so a caller reading JSON sees it too.
|
|
213
|
+
* "A caveat in a reply is a gap somebody closes the todo over."
|
|
214
|
+
*/
|
|
215
|
+
carriesLinkMarker: carriesLink,
|
|
216
|
+
workItemId: marker?.adoId ?? null,
|
|
217
|
+
...(carriesLink
|
|
218
|
+
? {
|
|
219
|
+
consequence: "This is the comment that records the link to Azure DevOps. Deleting it leaves the "
|
|
220
|
+
+ "pair one-sided: `pharos issue trail` will stop resolving it from the GitHub end "
|
|
221
|
+
+ "and `pharos issue drift` will report it.",
|
|
222
|
+
repair: marker === undefined
|
|
223
|
+
? `pharos issue link ${formatIssueCoordinate(issue)} <work-item-id> --yes`
|
|
224
|
+
: `pharos issue link ${formatIssueCoordinate(issue)} ${marker.adoId} --yes`,
|
|
225
|
+
}
|
|
226
|
+
: {}),
|
|
227
|
+
},
|
|
228
|
+
// The one destructive verb in this CLI that writes to GitHub and to nothing
|
|
229
|
+
// else, so the gate must not resolve an Azure DevOps credential on its way
|
|
230
|
+
// past. Everything above this line already ran without one (#839).
|
|
231
|
+
platform: "github",
|
|
232
|
+
});
|
|
233
|
+
if (stop !== undefined)
|
|
234
|
+
return stop;
|
|
235
|
+
const { existed } = await github.comments.remove(ref, id);
|
|
236
|
+
return emit(io, {
|
|
237
|
+
applied: true,
|
|
238
|
+
action: "deleted",
|
|
239
|
+
target: formatIssueCoordinate(issue),
|
|
240
|
+
commentId: id,
|
|
241
|
+
// A 404 on delete is success — a retry after a lost reply gets one, and it
|
|
242
|
+
// means the caller got what it asked for. Reported, never failed over.
|
|
243
|
+
alreadyGone: !existed,
|
|
244
|
+
carriesLinkMarker: carriesLink,
|
|
245
|
+
workItemId: marker?.adoId ?? null,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
// MARK: - reactions
|
|
249
|
+
export async function githubReact(io, session, issue, handle, rawReaction, engaged) {
|
|
250
|
+
const reaction = reactionArgument(rawReaction);
|
|
251
|
+
const { github, ref } = await resolve(session, issue);
|
|
252
|
+
const id = await restId(session, ref, handle);
|
|
253
|
+
const { changed } = await github.comments.setReaction(ref, id, reaction, engaged);
|
|
254
|
+
return emit(io, {
|
|
255
|
+
applied: true,
|
|
256
|
+
target: formatIssueCoordinate(issue),
|
|
257
|
+
commentId: id,
|
|
258
|
+
type: reaction.cli,
|
|
259
|
+
emoji: reaction.emoji,
|
|
260
|
+
engaged,
|
|
261
|
+
/**
|
|
262
|
+
* False when the end state was already true — un-reacting something you
|
|
263
|
+
* never reacted to. A desired end state rather than a toggle is what makes
|
|
264
|
+
* the verb safe to repeat, and this is how a repeat reports itself.
|
|
265
|
+
*/
|
|
266
|
+
changed,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
/** A count is not a name — "three people reacted" needs this second call. */
|
|
270
|
+
export async function githubReactors(io, session, issue, handle, rawReaction) {
|
|
271
|
+
const reaction = reactionArgument(rawReaction);
|
|
272
|
+
const { github, ref } = await resolve(session, issue);
|
|
273
|
+
const id = await restId(session, ref, handle);
|
|
274
|
+
const { owner, name } = splitRepo(ref.repo);
|
|
275
|
+
const rows = await github.http.request({
|
|
276
|
+
account: ref.account,
|
|
277
|
+
method: "GET",
|
|
278
|
+
path: `repos/${owner}/${name}/issues/comments/${id}/reactions`,
|
|
279
|
+
query: { content: reaction.rest, per_page: 100 },
|
|
280
|
+
idempotent: true,
|
|
281
|
+
});
|
|
282
|
+
const logins = (Array.isArray(rows) ? rows : [])
|
|
283
|
+
.map((row) => row.user?.login)
|
|
284
|
+
.filter((login) => typeof login === "string");
|
|
285
|
+
return emit(io, {
|
|
286
|
+
target: formatIssueCoordinate(issue),
|
|
287
|
+
commentId: id,
|
|
288
|
+
type: reaction.cli,
|
|
289
|
+
emoji: reaction.emoji,
|
|
290
|
+
count: logins.length,
|
|
291
|
+
logins,
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
// MARK: - hide and pin
|
|
295
|
+
/**
|
|
296
|
+
* Hide or unhide, over GraphQL. There is no REST route — `POST …/minimize`
|
|
297
|
+
* answers 404.
|
|
298
|
+
*
|
|
299
|
+
* The reason is read back and reported as GitHub spells it, which is **not**
|
|
300
|
+
* how it was sent: `OFF_TOPIC` goes out and `off-topic` comes back. The CLI
|
|
301
|
+
* takes the display spelling for exactly that reason — ask for `off-topic`,
|
|
302
|
+
* read `off-topic`, and nothing has to know about the asymmetry.
|
|
303
|
+
*/
|
|
304
|
+
export async function githubHide(io, session, issue, handle, reason) {
|
|
305
|
+
const { github, ref } = await resolve(session, issue);
|
|
306
|
+
const nodeId = await nodeIdOf(session, ref, handle);
|
|
307
|
+
const state = await github.comments.setHidden(ref, nodeId, reason);
|
|
308
|
+
return emit(io, {
|
|
309
|
+
applied: true,
|
|
310
|
+
target: formatIssueCoordinate(issue),
|
|
311
|
+
nodeId,
|
|
312
|
+
hidden: state.isMinimized,
|
|
313
|
+
// GitHub's own words, from the mutation's own payload — not an echo of what
|
|
314
|
+
// was asked for. If those two ever disagree, this is the one that is true.
|
|
315
|
+
hiddenReason: state.minimizedReason ?? null,
|
|
316
|
+
...(reason === undefined ? {} : { requested: reason.display, sent: reason.wire }),
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Pin or unpin, over GraphQL. No REST route exists at all.
|
|
321
|
+
*
|
|
322
|
+
* The input field is `issueCommentId`, not `subjectId` as minimize takes —
|
|
323
|
+
* handled in `gh-core`, noted here because the two mutations sit beside each
|
|
324
|
+
* other in GitHub's schema and the wrong one is an HTTP 200 that does nothing.
|
|
325
|
+
*/
|
|
326
|
+
export async function githubPin(io, session, issue, handle, pinned) {
|
|
327
|
+
const { github, ref } = await resolve(session, issue);
|
|
328
|
+
const nodeId = await nodeIdOf(session, ref, handle);
|
|
329
|
+
const now = await github.comments.setPinned(ref, nodeId, pinned);
|
|
330
|
+
return emit(io, {
|
|
331
|
+
applied: true,
|
|
332
|
+
target: formatIssueCoordinate(issue),
|
|
333
|
+
nodeId,
|
|
334
|
+
pinned: now,
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
// MARK: - arguments
|
|
338
|
+
/**
|
|
339
|
+
* Azure DevOps has no hide and no pin — not "not implemented here", **no such
|
|
340
|
+
* concept in the API**.
|
|
341
|
+
*
|
|
342
|
+
* Said plainly rather than reported as an unknown verb, because those are
|
|
343
|
+
* different facts: an unknown verb reads as a gap this CLI could close, and
|
|
344
|
+
* somebody would then go looking for the endpoint.
|
|
345
|
+
*
|
|
346
|
+
* **Returns the error rather than throwing it**, so the call site reads
|
|
347
|
+
* `if (target.kind !== "githubIssue") throw noSuchThingOnAzureDevOps(…)` and the
|
|
348
|
+
* compiler narrows the target on the next line. A function that threw would
|
|
349
|
+
* leave every GitHub-only arm needing a second refusal for a case that cannot
|
|
350
|
+
* happen — which is how an impossible branch acquires code nobody tests.
|
|
351
|
+
*/
|
|
352
|
+
export function noSuchThingOnAzureDevOps(target, verb) {
|
|
353
|
+
return usageError(`\`comment ${verb}\` is a GitHub thing, and ${describeTarget(target)} is on Azure DevOps, `
|
|
354
|
+
+ "which has neither hidden nor pinned comments — there is no field to set and no endpoint "
|
|
355
|
+
+ "to call. Name a GitHub issue (contoso/widgets#45).", { verb, target: describeTarget(target) });
|
|
356
|
+
}
|
|
357
|
+
export function hideReasonArgument(raw) {
|
|
358
|
+
const reasons = GITHUB_HIDE_REASONS.map((reason) => reason.display);
|
|
359
|
+
if (raw === undefined) {
|
|
360
|
+
throw usageError(`Hide it as what? \`pharos comment hide <owner/name#n> <comment> <reason>\` — one of: `
|
|
361
|
+
+ `${reasons.join(", ")}. GitHub requires a classifier and shows it beside the hidden `
|
|
362
|
+
+ "comment, so there is no neutral default to pick.", { reasons });
|
|
363
|
+
}
|
|
364
|
+
const reason = parseHideReason(raw);
|
|
365
|
+
if (reason === undefined) {
|
|
366
|
+
throw usageError(`"${raw}" is not a hide reason. One of: ${reasons.join(", ")}.`, { reasons });
|
|
367
|
+
}
|
|
368
|
+
return reason;
|
|
369
|
+
}
|
|
370
|
+
function reactionArgument(raw) {
|
|
371
|
+
const names = GITHUB_REACTIONS.map((reaction) => reaction.cli);
|
|
372
|
+
if (raw === undefined) {
|
|
373
|
+
throw usageError(`Which reaction? One of: ${names.join(", ")}.`, { reactions: names });
|
|
374
|
+
}
|
|
375
|
+
const reaction = parseReaction(raw);
|
|
376
|
+
if (reaction === undefined) {
|
|
377
|
+
throw usageError(`"${raw}" is not a GitHub reaction. One of: ${names.join(", ")}. Azure DevOps's names `
|
|
378
|
+
+ "(like, dislike, smile) are a different vocabulary and are not accepted here.", { reactions: names });
|
|
379
|
+
}
|
|
380
|
+
return reaction;
|
|
381
|
+
}
|
|
382
|
+
// MARK: - resolving
|
|
383
|
+
async function resolve(session, issue) {
|
|
384
|
+
const github = await session.github();
|
|
385
|
+
// Throws a GitHubConfigError naming the repo and repos.json when unbound —
|
|
386
|
+
// reported as kind "config", exit 2, because no retry can fix it.
|
|
387
|
+
const binding = github.binding(issue.repo);
|
|
388
|
+
return { github, ref: { ...binding, number: issue.number } };
|
|
389
|
+
}
|
|
390
|
+
/** The REST id, looked up from the node id only when that is what was given. */
|
|
391
|
+
async function restId(session, ref, handle) {
|
|
392
|
+
if (handle.kind === "rest")
|
|
393
|
+
return handle.id;
|
|
394
|
+
// The node id addresses nothing over REST, and there is no single-node REST
|
|
395
|
+
// route to resolve it — so this is the discussion read, and it is the one
|
|
396
|
+
// direction of the lookup that costs a page walk. Passing the number instead
|
|
397
|
+
// (which `list` prints beside the node id) avoids it entirely.
|
|
398
|
+
const github = await session.github();
|
|
399
|
+
const sweep = await github.comments.details(ref);
|
|
400
|
+
const found = sweep.comments.find((comment) => comment.nodeId === handle.nodeId);
|
|
401
|
+
if (found === undefined) {
|
|
402
|
+
throw usageError(`No comment with node id ${handle.nodeId} on ${formatIssueCoordinate(ref)}. `
|
|
403
|
+
+ "`pharos comment list` prints both ids for every comment.", { nodeId: handle.nodeId });
|
|
404
|
+
}
|
|
405
|
+
return found.id;
|
|
406
|
+
}
|
|
407
|
+
/** The node id, looked up from the REST id only when that is what was given. */
|
|
408
|
+
async function nodeIdOf(session, ref, handle) {
|
|
409
|
+
if (handle.kind === "node")
|
|
410
|
+
return handle.nodeId;
|
|
411
|
+
// One cheap REST read — the single-comment route carries `node_id`. This is
|
|
412
|
+
// the direction that does NOT need a page walk, which is why `hide` given a
|
|
413
|
+
// number costs two requests and given a node id costs one.
|
|
414
|
+
const github = await session.github();
|
|
415
|
+
const comment = await github.comments.get(ref, handle.id);
|
|
416
|
+
const nodeId = comment.nodeId;
|
|
417
|
+
if (nodeId === undefined || nodeId === "") {
|
|
418
|
+
throw usageError(`Comment ${handle.id} on ${formatIssueCoordinate(ref)} reported no node id, so it cannot be `
|
|
419
|
+
+ "hidden or pinned — both are GraphQL mutations and the node id is what they address.", { commentId: handle.id });
|
|
420
|
+
}
|
|
421
|
+
return nodeId;
|
|
422
|
+
}
|
|
423
|
+
function splitRepo(repo) {
|
|
424
|
+
const slash = repo.indexOf("/");
|
|
425
|
+
return { owner: repo.slice(0, slash), name: repo.slice(slash + 1) };
|
|
426
|
+
}
|
|
427
|
+
//# sourceMappingURL=comment-github.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"comment-github.js","sourceRoot":"","sources":["../../src/commands/comment-github.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"comment.d.ts","sourceRoot":"","sources":["../../src/commands/comment.ts"],"names":[],"mappings":"AAEA,OAAO,EAAoB,KAAK,QAAQ,EAAE,KAAK,EAAE,EAAE,MAAM,cAAc,CAAC;AACxE,OAAO,EAAW,KAAK,OAAO,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"comment.d.ts","sourceRoot":"","sources":["../../src/commands/comment.ts"],"names":[],"mappings":"AAEA,OAAO,EAAoB,KAAK,QAAQ,EAAE,KAAK,EAAE,EAAE,MAAM,cAAc,CAAC;AACxE,OAAO,EAAW,KAAK,OAAO,EAAE,MAAM,eAAe,CAAC;AAuFtD,wBAAsB,UAAU,CAC9B,EAAE,EAAE,EAAE,EACN,OAAO,EAAE,OAAO,EAChB,WAAW,EAAE,SAAS,MAAM,EAAE,EAC9B,KAAK,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,GACnC,OAAO,CAAC,QAAQ,CAAC,CAoEnB"}
|
package/dist/commands/comment.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { emit, usageError } from "../output.js";
|
|
2
2
|
import { approve } from "../session.js";
|
|
3
3
|
import { describeTarget, parseTarget } from "../target.js";
|
|
4
|
+
import { githubHide, githubList, githubPin, githubReact, githubReactors, githubRefuseAdd, githubRemove, githubEdit, hideReasonArgument, noSuchThingOnAzureDevOps, } from "./comment-github.js";
|
|
4
5
|
/**
|
|
5
|
-
* `pharos comment …` — the discussion, on
|
|
6
|
+
* `pharos comment …` — the discussion, on any of its three surfaces.
|
|
6
7
|
*
|
|
7
8
|
* **Wiki comments exist in no other tool.** The Azure DevOps MCP server ships
|
|
8
9
|
* none of these verbs for a wiki page — not list, not add, not edit, not
|
|
@@ -10,19 +11,43 @@ import { describeTarget, parseTarget } from "../target.js";
|
|
|
10
11
|
* comments it has add and update only, so delete and reactions are also
|
|
11
12
|
* reachable from nowhere else.
|
|
12
13
|
*
|
|
13
|
-
* The
|
|
14
|
-
*
|
|
15
|
-
*
|
|
14
|
+
* The surfaces behave differently in ways that would bite a caller writing this
|
|
15
|
+
* by hand, and each difference is handled on its own side rather than assumed
|
|
16
|
+
* away:
|
|
16
17
|
*
|
|
17
|
-
* | | work item | wiki page |
|
|
18
|
-
*
|
|
19
|
-
* | keyed on | the work item id | the page's NUMERIC id, not its path |
|
|
20
|
-
* | `includeDeleted` | honoured | ignored — deleted comments come back regardless |
|
|
21
|
-
* | second delete | 404 | 500 |
|
|
22
|
-
* | reactions api-version | `7.2-preview.1` | `7.1-preview.1` |
|
|
18
|
+
* | | work item | wiki page | GitHub issue |
|
|
19
|
+
* |---|---|---|---|
|
|
20
|
+
* | keyed on | the work item id | the page's NUMERIC id, not its path | the issue coordinate |
|
|
21
|
+
* | `includeDeleted` | honoured | ignored — deleted comments come back regardless | n/a |
|
|
22
|
+
* | second delete | 404 | 500 | 404, and it means success |
|
|
23
|
+
* | reactions api-version | `7.2-preview.1` | `7.1-preview.1` | n/a |
|
|
24
|
+
* | reaction vocabulary | like/dislike/heart/hooray/smile/confused | same | eight, and `+1` is one of them |
|
|
25
|
+
* | hide, pin | no such concept | no such concept | GraphQL only — no REST route exists |
|
|
23
26
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
27
|
+
* The first two are `swallowed`/`filtered`/`pinned` inside `ado-core`; the
|
|
28
|
+
* GitHub arm lives in `comment-github.ts` over `gh-core`. The table is here
|
|
29
|
+
* because the temptation when reading this file is to unify the branches.
|
|
30
|
+
*
|
|
31
|
+
* ## Why GitHub is a third target rather than `pharos issue comment`
|
|
32
|
+
*
|
|
33
|
+
* Because `Target` already existed. This file has always dispatched on "which
|
|
34
|
+
* kind of thing is this comment attached to", and a GitHub issue is a third
|
|
35
|
+
* answer to a question that was already being asked. The alternative spells
|
|
36
|
+
* `edit`, `delete`, `react` and `unreact` a second time under another noun,
|
|
37
|
+
* over another engine — the drifting-second-implementation shape `AGENTS.md`
|
|
38
|
+
* names three separate times, and the one it says nobody looks at.
|
|
39
|
+
*
|
|
40
|
+
* ## `add` is REFUSED on a GitHub issue, and that refusal is the design
|
|
41
|
+
*
|
|
42
|
+
* Plan §8: do not rebuild `gh`. Posting a plain comment is `gh issue comment`,
|
|
43
|
+
* and posting one that reaches the reporter *and* the board is
|
|
44
|
+
* `pharos issue say`. A `pharos comment add` on a GitHub issue would be a third
|
|
45
|
+
* thing — a comment on an adopted issue that the work item never hears about —
|
|
46
|
+
* which is precisely the asymmetry `say` exists to prevent. So it is refused
|
|
47
|
+
* with both alternatives named. See {@link githubRefuseAdd}.
|
|
48
|
+
*
|
|
49
|
+
* Everything else here acts on a comment that already exists, which is work
|
|
50
|
+
* neither `gh` nor `say` does at all.
|
|
26
51
|
*/
|
|
27
52
|
const REACTIONS = [
|
|
28
53
|
"like",
|
|
@@ -32,35 +57,81 @@ const REACTIONS = [
|
|
|
32
57
|
"smile",
|
|
33
58
|
"confused",
|
|
34
59
|
];
|
|
60
|
+
const VERBS = [
|
|
61
|
+
"list",
|
|
62
|
+
"add",
|
|
63
|
+
"edit",
|
|
64
|
+
"delete",
|
|
65
|
+
"react",
|
|
66
|
+
"unreact",
|
|
67
|
+
"reactors",
|
|
68
|
+
"hide",
|
|
69
|
+
"unhide",
|
|
70
|
+
"pin",
|
|
71
|
+
"unpin",
|
|
72
|
+
];
|
|
35
73
|
export async function runComment(io, session, positionals, input) {
|
|
36
74
|
const verb = positionals[0];
|
|
37
75
|
if (verb === undefined) {
|
|
38
|
-
throw usageError(
|
|
76
|
+
throw usageError(`\`pharos comment <${VERBS.join("|")}> <target> …\`.`, { verbs: VERBS });
|
|
39
77
|
}
|
|
40
78
|
const rawTarget = positionals[1];
|
|
41
79
|
if (rawTarget === undefined) {
|
|
42
|
-
throw usageError(`Which thing? \`pharos comment ${verb} <target>\` — a work item id (210)
|
|
43
|
-
+ "path (/Plans/Foo).");
|
|
80
|
+
throw usageError(`Which thing? \`pharos comment ${verb} <target>\` — a work item id (210), a wiki page `
|
|
81
|
+
+ "path (/Plans/Foo), or a GitHub issue (contoso/widgets#45).");
|
|
44
82
|
}
|
|
45
83
|
const target = parseTarget(rawTarget);
|
|
46
84
|
const rest = positionals.slice(2);
|
|
47
85
|
switch (verb) {
|
|
48
86
|
case "list":
|
|
87
|
+
if (target.kind === "githubIssue")
|
|
88
|
+
return githubList(io, session, target.issue);
|
|
49
89
|
return list(io, session, target);
|
|
50
90
|
case "add":
|
|
91
|
+
if (target.kind === "githubIssue")
|
|
92
|
+
return githubRefuseAdd(target.issue);
|
|
51
93
|
return add(io, session, target, textFrom(rest[0], input.text));
|
|
52
94
|
case "edit":
|
|
95
|
+
if (target.kind === "githubIssue") {
|
|
96
|
+
return githubEdit(io, session, target.issue, handle(rest[0]), textFrom(rest[1], input.text));
|
|
97
|
+
}
|
|
53
98
|
return edit(io, session, target, commentId(rest[0]), textFrom(rest[1], input.text));
|
|
54
99
|
case "delete":
|
|
100
|
+
if (target.kind === "githubIssue")
|
|
101
|
+
return githubRemove(io, session, target.issue, handle(rest[0]));
|
|
55
102
|
return remove(io, session, target, commentId(rest[0]));
|
|
56
103
|
case "react":
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
104
|
+
case "unreact": {
|
|
105
|
+
const on = verb === "react";
|
|
106
|
+
if (target.kind === "githubIssue") {
|
|
107
|
+
return githubReact(io, session, target.issue, handle(rest[0]), rest[1], on);
|
|
108
|
+
}
|
|
109
|
+
return react(io, session, target, commentId(rest[0]), reactionType(rest[1]), on);
|
|
110
|
+
}
|
|
60
111
|
case "reactors":
|
|
112
|
+
if (target.kind === "githubIssue") {
|
|
113
|
+
return githubReactors(io, session, target.issue, handle(rest[0]), rest[1]);
|
|
114
|
+
}
|
|
61
115
|
return reactors(io, session, target, commentId(rest[0]), reactionType(rest[1]));
|
|
116
|
+
// Hide and pin exist on GitHub and nowhere else — Azure DevOps has no such
|
|
117
|
+
// field and no such endpoint, so the refusal says that rather than letting
|
|
118
|
+
// it read as a verb this CLI has not got round to.
|
|
119
|
+
case "hide":
|
|
120
|
+
case "unhide": {
|
|
121
|
+
if (target.kind !== "githubIssue")
|
|
122
|
+
throw noSuchThingOnAzureDevOps(target, verb);
|
|
123
|
+
return githubHide(io, session, target.issue, handle(rest[0]), verb === "hide" ? hideReasonArgument(rest[1]) : undefined);
|
|
124
|
+
}
|
|
125
|
+
case "pin":
|
|
126
|
+
case "unpin": {
|
|
127
|
+
if (target.kind !== "githubIssue")
|
|
128
|
+
throw noSuchThingOnAzureDevOps(target, verb);
|
|
129
|
+
return githubPin(io, session, target.issue, handle(rest[0]), verb === "pin");
|
|
130
|
+
}
|
|
62
131
|
default:
|
|
63
|
-
throw usageError(`Unknown comment verb "${verb}". Try
|
|
132
|
+
throw usageError(`Unknown comment verb "${verb}". Try ${VERBS.join(", ")}.`, {
|
|
133
|
+
verbs: VERBS,
|
|
134
|
+
});
|
|
64
135
|
}
|
|
65
136
|
}
|
|
66
137
|
async function list(io, session, target) {
|
|
@@ -206,6 +277,30 @@ function commentId(raw) {
|
|
|
206
277
|
}
|
|
207
278
|
return Number(raw);
|
|
208
279
|
}
|
|
280
|
+
/**
|
|
281
|
+
* A GitHub comment, named by **either** of its two ids.
|
|
282
|
+
*
|
|
283
|
+
* GitHub gives a comment a numeric REST id and a string node id, and neither is
|
|
284
|
+
* derivable from the other: edit, delete and reactions address the number,
|
|
285
|
+
* while hide and pin take the node. `pharos comment list` prints both, so the
|
|
286
|
+
* normal agent flow — list, then act — already holds whichever one is needed.
|
|
287
|
+
*
|
|
288
|
+
* Taking either here means that flow never needs a lookup: pass the number and
|
|
289
|
+
* a verb that wants the node resolves it with one extra read; pass the node
|
|
290
|
+
* back and nothing is spent. Anything not all-digits is treated as a node id
|
|
291
|
+
* (they are opaque strings, currently `IC_…`, and this does not hard-code that
|
|
292
|
+
* prefix — GitHub has changed node id formats before).
|
|
293
|
+
*/
|
|
294
|
+
function handle(raw) {
|
|
295
|
+
if (raw === undefined || raw.trim() === "") {
|
|
296
|
+
throw usageError("Which comment? Expected a comment id — the number or the node id, both of which "
|
|
297
|
+
+ "`pharos comment list <owner/name#n>` prints.");
|
|
298
|
+
}
|
|
299
|
+
const value = raw.trim();
|
|
300
|
+
return /^\d+$/.test(value)
|
|
301
|
+
? { kind: "rest", id: Number(value) }
|
|
302
|
+
: { kind: "node", nodeId: value };
|
|
303
|
+
}
|
|
209
304
|
function reactionType(raw) {
|
|
210
305
|
if (raw === undefined) {
|
|
211
306
|
throw usageError(`Which reaction? One of: ${REACTIONS.join(", ")}.`);
|