@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.
Files changed (53) hide show
  1. package/README.md +97 -10
  2. package/dist/adopt-type.d.ts +58 -0
  3. package/dist/adopt-type.d.ts.map +1 -0
  4. package/dist/adopt-type.js +61 -0
  5. package/dist/adopt-type.js.map +1 -0
  6. package/dist/bridge.d.ts +156 -0
  7. package/dist/bridge.d.ts.map +1 -0
  8. package/dist/bridge.js +157 -0
  9. package/dist/bridge.js.map +1 -0
  10. package/dist/cli.d.ts +1 -1
  11. package/dist/cli.d.ts.map +1 -1
  12. package/dist/cli.js +176 -31
  13. package/dist/cli.js.map +1 -1
  14. package/dist/commands/bridge.d.ts +30 -0
  15. package/dist/commands/bridge.d.ts.map +1 -0
  16. package/dist/commands/bridge.js +255 -0
  17. package/dist/commands/bridge.js.map +1 -0
  18. package/dist/commands/comment-github.d.ts +138 -0
  19. package/dist/commands/comment-github.d.ts.map +1 -0
  20. package/dist/commands/comment-github.js +427 -0
  21. package/dist/commands/comment-github.js.map +1 -0
  22. package/dist/commands/comment.d.ts.map +1 -1
  23. package/dist/commands/comment.js +114 -19
  24. package/dist/commands/comment.js.map +1 -1
  25. package/dist/commands/doctor.d.ts +169 -0
  26. package/dist/commands/doctor.d.ts.map +1 -1
  27. package/dist/commands/doctor.js +241 -44
  28. package/dist/commands/doctor.js.map +1 -1
  29. package/dist/commands/issue.d.ts +133 -0
  30. package/dist/commands/issue.d.ts.map +1 -1
  31. package/dist/commands/issue.js +292 -110
  32. package/dist/commands/issue.js.map +1 -1
  33. package/dist/commands/setup.d.ts +45 -0
  34. package/dist/commands/setup.d.ts.map +1 -1
  35. package/dist/commands/setup.js +74 -0
  36. package/dist/commands/setup.js.map +1 -1
  37. package/dist/half-link.d.ts +14 -4
  38. package/dist/half-link.d.ts.map +1 -1
  39. package/dist/half-link.js +14 -4
  40. package/dist/half-link.js.map +1 -1
  41. package/dist/output.d.ts.map +1 -1
  42. package/dist/output.js +123 -1
  43. package/dist/output.js.map +1 -1
  44. package/dist/session.d.ts +51 -2
  45. package/dist/session.d.ts.map +1 -1
  46. package/dist/session.js +62 -11
  47. package/dist/session.js.map +1 -1
  48. package/dist/target.d.ts +40 -4
  49. package/dist/target.d.ts.map +1 -1
  50. package/dist/target.js +14 -3
  51. package/dist/target.js.map +1 -1
  52. package/package.json +4 -4
  53. 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;AAoCtD,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,CAoCnB"}
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"}
@@ -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 either surface.
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 two surfaces behave differently in ways that would bite a caller writing
14
- * this by hand, and each difference is handled on its own side rather than
15
- * assumed away:
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
- * Both are `swallowed`/`filtered`/`pinned` inside `ado-core`; the table is here
25
- * because the temptation when reading this file is to unify the two branches.
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("`pharos comment <list|add|edit|delete|react|unreact> <target> …`.");
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) or a wiki page `
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
- return react(io, session, target, commentId(rest[0]), reactionType(rest[1]), true);
58
- case "unreact":
59
- return react(io, session, target, commentId(rest[0]), reactionType(rest[1]), false);
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 list, add, edit, delete, react, unreact or reactors.`);
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(", ")}.`);