@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
package/README.md CHANGED
@@ -116,16 +116,18 @@ capability that must be impossible has to be withheld at the token.
116
116
  task <id> the whole context, in one call
117
117
  wiki list | tree | read | write | delete
118
118
  comment list | add | edit | delete | react | unreact | reactors
119
+ comment hide | unhide | pin | unpin GitHub only
119
120
  issue adopt | link | say | trail | drift | backfill | close
120
121
  a GitHub issue and a work item, joined —
121
122
  and kept honest
122
123
  plan <file> an implementation plan becomes a work item tree
123
124
  ```
124
125
 
125
- `comment` takes either kind of target: a work item is its number (`210`), a wiki
126
- page is an **absolute path** (`/Plans/Foo`). The leading slash is required by
127
- the wiki API itself it answers 404 without oneso the spelling that tells
128
- them apart is also the only spelling that works.
126
+ `comment` takes any of three targets: a work item is its number (`210`), a wiki
127
+ page is an **absolute path** (`/Plans/Foo`), a GitHub issue is
128
+ `owner/name#45`. The leading slash is required by the wiki API itself it
129
+ answers 404 without one — so the spelling that tells them apart is also the only
130
+ spelling that works, and the three grammars cannot collide.
129
131
 
130
132
  Long text does not belong on a command line:
131
133
 
@@ -134,6 +136,54 @@ pharos comment add 210 --file review.md
134
136
  pharos wiki write /Plans/Sprint-4 --stdin < plan.md
135
137
  ```
136
138
 
139
+ ### `comment` on a GitHub issue — the surface `gh` half has and half has not
140
+
141
+ ```bash
142
+ pharos comment list contoso/widgets#45 # ids, state, and what you MAY do
143
+ pharos comment edit contoso/widgets#45 900 --file fixed.md
144
+ pharos comment delete contoso/widgets#45 900 --yes
145
+ pharos comment react contoso/widgets#45 900 thumbs-up
146
+ pharos comment hide contoso/widgets#45 900 off-topic
147
+ pharos comment pin contoso/widgets#45 900
148
+ ```
149
+
150
+ This is the one place `pharos` reaches past the join, and it is two arguments
151
+ rather than one. **Editing or deleting a comment can destroy the link record**,
152
+ which nothing outside this repo knows to protect. And **hide and pin have no
153
+ `gh` verb at all** — they are GraphQL mutations with no REST route, so they pass
154
+ the same "nothing else can do this" test the `issue` verbs pass. Posting a *new*
155
+ comment is still `gh issue comment` or `pharos issue say`, and `comment add` on
156
+ a GitHub issue is refused with both of those named.
157
+
158
+ **`list` is the load-bearing verb**, and it is not one the macOS app needed: the
159
+ app has a window, an agent has this. Before it there was no way to learn a
160
+ GitHub comment id from this CLI at all, so nothing else here was addressable. It
161
+ prints both ids per comment — the REST number that edit, delete and reactions
162
+ address, and the node id that hide and pin take, because neither is derivable
163
+ from the other — plus a `may` object read from GitHub's own `viewerCan…` fields.
164
+ For a window those grey out a menu item; for an agent they turn a write that
165
+ would 403 into a fact known beforehand.
166
+
167
+ **An edit cannot double or strip the `pharos:v1` trailer.** Send prose with no
168
+ trailer and the existing one is restored, with `markerPreserved: true` saying
169
+ so; send two, or one naming a different work item, or one on a comment that
170
+ never had one, and the edit is refused with the reason and the repair. The rule
171
+ lives in `gh-core`, not here, because it is a rule about the link record rather
172
+ than about this command — and the macOS app edits through the same package.
173
+
174
+ **Deleting the comment that carries the trailer is allowed and never silent.**
175
+ The `--yes` refusal names the work item about to lose its GitHub end and hands
176
+ over the `pharos issue link` that puts it back; `--dry-run` prints the same
177
+ preview and exits 0. Both read the comment first, because a preview that could
178
+ not say which of the two deletes this is would be a preview of the wrong thing.
179
+
180
+ Reactions are **not one vocabulary across the two platforms**. Azure DevOps
181
+ takes `like dislike heart hooray smile confused`; GitHub takes `thumbs-up
182
+ thumbs-down laugh hooray confused heart rocket eyes`. Passing one platform's
183
+ name to the other is refused rather than translated. The CLI spelling is words
184
+ because GitHub's own names for the first two are `+1` and `-1`, and `-1` as a
185
+ positional is read as a flag by every argument parser including this one.
186
+
137
187
  ### `issue` — the GitHub edge, and only the edge
138
188
 
139
189
  ```bash
@@ -144,6 +194,7 @@ pharos issue trail 4821 # …or trail contoso/widgets#45
144
194
  pharos issue drift # where the two platforms disagree
145
195
  pharos issue backfill contoso/widgets --limit 25 --yes
146
196
  pharos issue close 4821 --text "Shipped in 1.4.0."
197
+ pharos issue edit contoso/widgets#45 --title "Crash on resize" --yes
147
198
  ```
148
199
 
149
200
  Azure DevOps already has a GitHub integration and **the link it makes carries
@@ -152,12 +203,38 @@ the transition only ever fires from a commit or a PR merge, never from closing
152
203
  an issue. That is the gap, and it is the whole pitch — not "there is no
153
204
  integration".
154
205
 
155
- **Seven verbs, because `gh` already exists.** Listing, viewing and commenting on
206
+ **Eight verbs, because `gh` already exists.** Listing, viewing and commenting on
156
207
  an issue is `gh`'s job and it is better at it — closing one *end* is `gh issue
157
208
  close`, and closing the *pair* is `pharos issue close`; a second GitHub CLI
158
209
  would be the drifting duplicate this repo has been bitten by three times.
159
210
  Pharos owns what `gh` cannot see: the join.
160
211
 
212
+ **`edit` is the one verb that overlaps `gh` head on, and it earns that by
213
+ refusing.** `gh issue edit` writes the same fields and writes them well; what it
214
+ cannot do is notice that somebody changed the thing you are overwriting. It is
215
+ last-write-wins — if a colleague renamed the issue while you were composing,
216
+ their rename is gone and neither of you is told. Fine for a human at a terminal,
217
+ wrong for an agent that reads, thinks for a minute, and writes into a world that
218
+ moved. Want an unguarded edit? `gh issue edit` is right there.
219
+
220
+ The guard is a **value compare**, because GitHub offers nothing better:
221
+ `PATCH /repos/{o}/{r}/issues/{n}` answers **400** to `If-Match` (measured), and
222
+ `updated_at` and the ETag both move when somebody merely *comments*, so a
223
+ timestamp guard would refuse a good edit for a change that touched nothing. So
224
+ `edit` re-reads immediately before sending and refuses only if the text it is
225
+ about to overwrite moved. Pass `--if-title` / `--if-body` / `--if-body-file` to
226
+ guard against what **you** saw rather than against this command's own read; the
227
+ output says which, as `fields.guard.window`. It narrows the race to one round
228
+ trip and does not close it, and it says so.
229
+
230
+ Two things GitHub does quietly, which `edit` reports:
231
+ `POST …/labels` **creates** a repository label that does not exist (so an unknown
232
+ one is refused with near misses — `--create-label` is how you mean it), and
233
+ `POST …/assignees` **ignores** a login it will not assign while still answering
234
+ 201 (so read `assignees.ignored`). Labels and assignees go through their own
235
+ add/remove endpoints, never the whole-issue PATCH, which carries them as whole
236
+ arrays and would overwrite every label on every write.
237
+
161
238
  `adopt` creates the work item **and** writes both ends of the link:
162
239
 
163
240
  | | where | |
@@ -196,11 +273,12 @@ asks **this machine** — an adoption is written to `~/.config/pharos/half-links
196
273
  between the create and the comment, and removed when the comment lands — and
197
274
  then **the board**, narrowing on `[System.HyperLinkCount]` and matching the
198
275
  relation URLs client-side, which is the only way round Azure DevOps having no
199
- query over them. The first sees a work item created seconds ago that the query
200
- index has not caught up with yet; the second sees a half-link left on another
201
- machine. Every run reports `boardCheck`, because "the board says nobody has
202
- adopted this" and "the board could not be asked" are different facts and only
203
- one of them means the check happened.
276
+ query over them. The board is the stronger of the two it sees a half-link
277
+ left on another machine and the local record covers what it cannot: a board
278
+ query that is refused, a half-link older than that scan's cap, and a refusal
279
+ that costs no round trip. Every run reports `boardCheck`, because "the board
280
+ says nobody has adopted this" and "the board could not be asked" are different
281
+ facts and only one of them means the check happened.
204
282
 
205
283
  `trail` is read-only and answers from **either** end — the output shape does not
206
284
  depend on which you passed. Read `evidence`: `["hyperlink","marker"]` is a
@@ -430,6 +508,15 @@ name, so the wrong one answers **200** for a different repository. That is what
430
508
  — without it every `pharos issue` verb is refused by name with the reason, and
431
509
  the Azure DevOps verbs are untouched.
432
510
 
511
+ **And it runs in the other direction too: a verb whose target is a GitHub issue
512
+ needs no `ADO_PAT`.** `pharos comment list contoso/widgets#45` reads over `gh`'s
513
+ credential and touches Azure DevOps at no point, so it works on a machine that
514
+ has never had a PAT. The Azure DevOps configuration is resolved when something
515
+ actually needs Azure DevOps, and not before — which means the `kind: "config"`
516
+ error naming `ADO_PAT` now arrives only from a verb that would have used it.
517
+ `pharos issue` is the exception and honestly so: it writes to both platforms in
518
+ one invocation, so it needs both credentials.
519
+
433
520
  **`half-links.json` sits beside it, and it is the only thing this CLI remembers
434
521
  between runs.** An adoption is written there between the work item create and
435
522
  the GitHub comment and removed when the comment lands, so what survives is
@@ -0,0 +1,58 @@
1
+ import type { GitHubIssue } from "@floh-solutions/gh-core";
2
+ import { type CategorySource } from "./bridge.js";
3
+ import type { Session } from "./session.js";
4
+ /**
5
+ * Which work item type an adopted issue becomes — **resolved, never named**
6
+ * (#872).
7
+ *
8
+ * ## The bug this replaces
9
+ *
10
+ * `issue.ts` carried `const DEFAULT_WORK_ITEM_TYPE = "Issue"` and used it in two
11
+ * places. That is correct on *this* project by accident: Basic's requirement
12
+ * type happens to be called `Issue`. On **Agile** the word means an
13
+ * *impediment* — a blocker, tracked off the backlog — and the requirement type
14
+ * is `User Story`. So every GitHub issue adopted onto an Agile board was filed
15
+ * as a blocker, silently, and the CLI shipped that way in 0.25.0.
16
+ *
17
+ * A type name is a property of the project's **process template**. It can never
18
+ * be written down here.
19
+ *
20
+ * ## How it is decided
21
+ *
22
+ * 1. `--type` wins outright. Somebody naming a type has looked at their own
23
+ * board, and this must not argue with them.
24
+ * 2. Otherwise the issue's **labels** choose a category — a saved mapping by
25
+ * label id first, then the name defaults for a repository nobody has
26
+ * configured. See `bridge.ts`.
27
+ * 3. The category is resolved against **this project's** own answer, from
28
+ * `wit/workitemtypecategories`. The lookups are ordered because they
29
+ * degrade: a feature request asks for `FeatureCategory` and settles for
30
+ * `RequirementCategory`, which is all Basic has.
31
+ * 4. If none of that resolves — an unreachable board, a project with no
32
+ * requirement category at all — it falls back to the old constant **and says
33
+ * so**, because refusing to adopt over a naming question would be worse than
34
+ * adopting into a type somebody can change.
35
+ *
36
+ * Every step is reported, because "this became a Bug because you mapped it" and
37
+ * "this became a Bug because nothing matched" are different facts and only one
38
+ * of them is a decision.
39
+ */
40
+ export interface ResolvedType {
41
+ /** What to create. */
42
+ type: string;
43
+ /** How it was arrived at, for the report. */
44
+ source: CategorySource | "flag" | "fallback";
45
+ /** The category that was asked for, when one was. */
46
+ category?: string;
47
+ /** The label that decided it, when one did. */
48
+ label?: string;
49
+ /** Set when the project could not answer and the constant was used. */
50
+ note?: string;
51
+ }
52
+ /** The last-resort name. Basic's requirement type, and the historical default. */
53
+ export declare const FALLBACK_WORK_ITEM_TYPE = "Issue";
54
+ export declare function resolveWorkItemType(session: Session, issue: Pick<GitHubIssue, "repo" | "labels">, options?: {
55
+ type?: string | undefined;
56
+ env?: NodeJS.ProcessEnv;
57
+ }): Promise<ResolvedType>;
58
+ //# sourceMappingURL=adopt-type.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adopt-type.d.ts","sourceRoot":"","sources":["../src/adopt-type.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAE3D,OAAO,EAKL,KAAK,cAAc,EACpB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAE5C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,MAAM,WAAW,YAAY;IAC3B,sBAAsB;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,6CAA6C;IAC7C,MAAM,EAAE,cAAc,GAAG,MAAM,GAAG,UAAU,CAAC;IAC7C,qDAAqD;IACrD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,+CAA+C;IAC/C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uEAAuE;IACvE,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,kFAAkF;AAClF,eAAO,MAAM,uBAAuB,UAAU,CAAC;AAE/C,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,IAAI,CAAC,WAAW,EAAE,MAAM,GAAG,QAAQ,CAAC,EAC3C,OAAO,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAA;CAAO,GACnE,OAAO,CAAC,YAAY,CAAC,CAkDvB"}
@@ -0,0 +1,61 @@
1
+ import { WorkItemTypesApi, typeForCategory } from "@floh-solutions/ado-core";
2
+ import { categoryFor, defaultBridgePath, parseBridgeConfig, repoConfig, } from "./bridge.js";
3
+ /** The last-resort name. Basic's requirement type, and the historical default. */
4
+ export const FALLBACK_WORK_ITEM_TYPE = "Issue";
5
+ export async function resolveWorkItemType(session, issue, options = {}) {
6
+ if (options.type !== undefined && options.type !== "") {
7
+ return { type: options.type, source: "flag" };
8
+ }
9
+ const env = options.env ?? process.env;
10
+ const organization = session.client.organization;
11
+ const project = session.client.project ?? "";
12
+ const config = await readBridge(env);
13
+ const repo = repoConfig(config, organization, project, issue.repo);
14
+ const choice = categoryFor(issue.labels.map((label) => ({ id: label.id ?? -1, name: label.name })), repo);
15
+ let catalog;
16
+ try {
17
+ catalog = await new WorkItemTypesApi(session.client.http).categories();
18
+ }
19
+ catch (error) {
20
+ // The board could not be asked. Adopting into the historical default is
21
+ // better than refusing, and naming the reason is what stops it reading as a
22
+ // deliberate choice.
23
+ return {
24
+ type: FALLBACK_WORK_ITEM_TYPE,
25
+ source: "fallback",
26
+ note: `the project's work item types could not be read (${error.message}), so the `
27
+ + `default "${FALLBACK_WORK_ITEM_TYPE}" was used — pass --type to be explicit`,
28
+ };
29
+ }
30
+ const resolved = typeForCategory(catalog, ...choice.categories);
31
+ if (resolved === undefined) {
32
+ return {
33
+ type: FALLBACK_WORK_ITEM_TYPE,
34
+ source: "fallback",
35
+ ...(choice.label === undefined ? {} : { label: choice.label }),
36
+ note: `this project has none of ${choice.categories.join(", ")}, so the default `
37
+ + `"${FALLBACK_WORK_ITEM_TYPE}" was used — pass --type to be explicit`,
38
+ };
39
+ }
40
+ return {
41
+ type: resolved,
42
+ source: choice.source,
43
+ ...(choice.categories[0] === undefined ? {} : { category: choice.categories[0] }),
44
+ ...(choice.label === undefined ? {} : { label: choice.label }),
45
+ };
46
+ }
47
+ /** The mapping, or an empty one. A missing or malformed file is not an error. */
48
+ async function readBridge(env) {
49
+ const path = defaultBridgePath(env);
50
+ try {
51
+ const { readFile } = await import("node:fs/promises");
52
+ return parseBridgeConfig(await readFile(path, "utf8"), path);
53
+ }
54
+ catch {
55
+ // No file yet is the ordinary state, and a malformed one must not take down
56
+ // `adopt` — it degrades to the name defaults, which is what an
57
+ // unconfigured repository gets anyway.
58
+ return { version: 1, scopes: {} };
59
+ }
60
+ }
61
+ //# sourceMappingURL=adopt-type.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adopt-type.js","sourceRoot":"","sources":["../src/adopt-type.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,156 @@
1
+ /**
2
+ * `~/.config/pharos/bridge.json` — **what an issue becomes when somebody adopts
3
+ * it**, and the one file the CLI and the app both read (#861).
4
+ *
5
+ * ## Why it is a file rather than app state
6
+ *
7
+ * The same rule `pharos-convert` and `doctor` follow, and the one AGENTS.md
8
+ * keeps restating: if the app owns the mapping and the CLI guesses, then
9
+ * `pharos issue adopt` in a headless session files to a different work item type
10
+ * than the app does — and **the one that drifts is the one nobody is watching**.
11
+ * So the CLI owns the file, the CLI has verbs over it, and the app's settings
12
+ * page is a GUI over those verbs rather than a second implementation.
13
+ *
14
+ * ## Why it is keyed by scope on day one
15
+ *
16
+ * The UI only ever shows one organisation and project today. The key is there
17
+ * anyway because this is the single artefact multi-scope would otherwise force a
18
+ * rewrite of — see `docs/pharos-multi-scope-plan.md`. It costs one nesting level
19
+ * now and saves throwing the file away later.
20
+ *
21
+ * ## Why labels are keyed by ID
22
+ *
23
+ * **Probed on a live repository:** creating a label, renaming it and reading it
24
+ * back leaves `id` and `node_id` unchanged. So an id-keyed mapping survives a
25
+ * rename, which is the thing people actually do to labels.
26
+ *
27
+ * Two consequences worth stating rather than discovering:
28
+ *
29
+ * - **Ids are per repository.** `bug` is `11710323621` on one repo and
30
+ * `1593484620` on another. A mapping is therefore per repo by construction,
31
+ * not a global preference.
32
+ * - **Delete-and-recreate yields a NEW id**, so a mapping whose id no longer
33
+ * resolves goes visibly *broken* rather than silently re-binding to a
34
+ * different label that happens to share a name. That is the correct
35
+ * behaviour: the old label is gone, and what replaced it is somebody else's
36
+ * decision to confirm.
37
+ *
38
+ * `name` is stored beside the id for display and for diagnosis. It is never what
39
+ * a lookup matches on.
40
+ */
41
+ export interface BridgeConfig {
42
+ version: 1;
43
+ scopes: Record<string, ScopeConfig>;
44
+ }
45
+ export interface ScopeConfig {
46
+ repos: Record<string, RepoBridgeConfig>;
47
+ digest: DigestPolicy;
48
+ }
49
+ export interface RepoBridgeConfig {
50
+ /** Label **id** (as a string key) → what adopting an issue with it produces. */
51
+ labels: Record<string, LabelMapping>;
52
+ /** Where an issue with no mapped label goes. */
53
+ defaultCategory: string;
54
+ }
55
+ export interface LabelMapping {
56
+ /** Display only, and deliberately not what anything matches on. */
57
+ name: string;
58
+ /** An Azure DevOps work item type CATEGORY, never a type name. */
59
+ category: string;
60
+ }
61
+ /**
62
+ * When a digest is written back to the work item.
63
+ *
64
+ * **Two events, not every comment.** Adopt and close are decisions; a comment is
65
+ * chatter. Mirroring chatter puts noise on a client's board and re-opens the
66
+ * echo problem the hub already paid for — while never writing turns the work
67
+ * item into a dead link, which is worse. See the wave spec.
68
+ */
69
+ export interface DigestPolicy {
70
+ onAdopt: boolean;
71
+ onClose: boolean;
72
+ }
73
+ export declare const DEFAULT_DIGEST: DigestPolicy;
74
+ /** Azure DevOps' category reference names, as used here. */
75
+ export declare const CATEGORY: {
76
+ readonly bug: "Microsoft.BugCategory";
77
+ readonly requirement: "Microsoft.RequirementCategory";
78
+ readonly feature: "Microsoft.FeatureCategory";
79
+ readonly epic: "Microsoft.EpicCategory";
80
+ readonly task: "Microsoft.TaskCategory";
81
+ };
82
+ /**
83
+ * What a repository nobody has configured does.
84
+ *
85
+ * **Matched by NAME, and that is the one place a name is allowed to matter.** A
86
+ * repo you have never opened the settings for should still adopt sensibly, and
87
+ * the only thing available before somebody maps anything is what the labels are
88
+ * called. The moment a mapping is saved it pins to the id and renames stop
89
+ * mattering — *id for bindings you made, name for defaults you did not*.
90
+ *
91
+ * Ordered, because the lookups degrade: a feature request wants
92
+ * `FeatureCategory` and settles for `RequirementCategory`, which is all Basic
93
+ * has.
94
+ */
95
+ export declare const NAME_DEFAULTS: readonly {
96
+ readonly match: readonly string[];
97
+ readonly categories: readonly string[];
98
+ }[];
99
+ /** Where the file lives. `PHAROS_BRIDGE_FILE` overrides it, for tests. */
100
+ export declare function defaultBridgePath(env?: NodeJS.ProcessEnv): string;
101
+ /** `org/project`, lowercased — the scope key. */
102
+ export declare function scopeKey(org: string, project: string): string;
103
+ export declare function emptyBridgeConfig(): BridgeConfig;
104
+ /**
105
+ * Read a config, or refuse with a message naming the file.
106
+ *
107
+ * **A file it cannot parse is an answer, not a crash** — the same rule
108
+ * `doctor`'s repos.json handling follows. A malformed mapping means adoption
109
+ * falls back to the name defaults and says so, rather than taking the CLI down
110
+ * on an unrelated verb.
111
+ */
112
+ export declare function parseBridgeConfig(text: string, source?: string): BridgeConfig;
113
+ export declare function formatBridgeConfig(config: BridgeConfig): string;
114
+ /** This repository's mapping in this scope, or the empty one. */
115
+ export declare function repoConfig(config: BridgeConfig, org: string, project: string, repo: string): RepoBridgeConfig;
116
+ export declare function digestPolicy(config: BridgeConfig, org: string, project: string): DigestPolicy;
117
+ /** One label as GitHub reports it. */
118
+ export interface IssueLabel {
119
+ id: number;
120
+ name: string;
121
+ }
122
+ /** How a category was arrived at — reported, because a silent default misleads. */
123
+ export type CategorySource = "mapping" | "name-default" | "repo-default";
124
+ export interface CategoryChoice {
125
+ /** Ordered: try each in turn, because a project may not have the first. */
126
+ categories: readonly string[];
127
+ source: CategorySource;
128
+ /** Which label decided it, when one did. */
129
+ label?: string;
130
+ }
131
+ /**
132
+ * Which category an issue's labels ask for.
133
+ *
134
+ * **Saved mappings win, by id.** Then the name defaults, for a repository nobody
135
+ * has configured. Then the repository's own default.
136
+ *
137
+ * The `source` comes back with it on purpose: "this went to a requirement
138
+ * because you mapped `bug`" and "this went to a requirement because nothing
139
+ * matched" are different facts, and a picker that showed the same preselection
140
+ * for both would be hiding the second one.
141
+ */
142
+ export declare function categoryFor(labels: readonly IssueLabel[], repo: RepoBridgeConfig): CategoryChoice;
143
+ /**
144
+ * A saved mapping whose label no longer exists in the repository.
145
+ *
146
+ * Reported rather than pruned. The mapping is a decision somebody made, and a
147
+ * label that has gone is usually a rename they did not realise breaks it — or a
148
+ * delete-and-recreate, which is a genuinely different label. Either way the
149
+ * answer is to show it, not to guess.
150
+ */
151
+ export declare function brokenMappings(repo: RepoBridgeConfig, live: readonly IssueLabel[]): {
152
+ id: string;
153
+ name: string;
154
+ category: string;
155
+ }[];
156
+ //# sourceMappingURL=bridge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,CAAC,CAAC;IACX,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;CACrC;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IACxC,MAAM,EAAE,YAAY,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,gFAAgF;IAChF,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IACrC,gDAAgD;IAChD,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,YAAY;IAC3B,mEAAmE;IACnE,IAAI,EAAE,MAAM,CAAC;IACb,kEAAkE;IAClE,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,eAAO,MAAM,cAAc,EAAE,YAA+C,CAAC;AAE7E,4DAA4D;AAC5D,eAAO,MAAM,QAAQ;;;;;;CAMX,CAAC;AAEX;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,aAAa,EAAE,SAAS;IAAE,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAAC,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,EAMjH,CAAC;AAEF,0EAA0E;AAC1E,wBAAgB,iBAAiB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAK9E;AAED,iDAAiD;AACjD,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED,wBAAgB,iBAAiB,IAAI,YAAY,CAEhD;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,SAAgB,GAAG,YAAY,CA+CpF;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAE/D;AAED,iEAAiE;AACjE,wBAAgB,UAAU,CACxB,MAAM,EAAE,YAAY,EACpB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,GACX,gBAAgB,CAQlB;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,YAAY,CAE7F;AAED,sCAAsC;AACtC,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;CACd;AAED,mFAAmF;AACnF,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,cAAc,GAAG,cAAc,CAAC;AAEzE,MAAM,WAAW,cAAc;IAC7B,2EAA2E;IAC3E,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B,MAAM,EAAE,cAAc,CAAC;IACvB,4CAA4C;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CACzB,MAAM,EAAE,SAAS,UAAU,EAAE,EAC7B,IAAI,EAAE,gBAAgB,GACrB,cAAc,CAiBhB;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,gBAAgB,EACtB,IAAI,EAAE,SAAS,UAAU,EAAE,GAC1B;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,EAAE,CAKlD"}
package/dist/bridge.js ADDED
@@ -0,0 +1,157 @@
1
+ import { homedir } from "node:os";
2
+ import { join } from "node:path";
3
+ export const DEFAULT_DIGEST = { onAdopt: true, onClose: true };
4
+ /** Azure DevOps' category reference names, as used here. */
5
+ export const CATEGORY = {
6
+ bug: "Microsoft.BugCategory",
7
+ requirement: "Microsoft.RequirementCategory",
8
+ feature: "Microsoft.FeatureCategory",
9
+ epic: "Microsoft.EpicCategory",
10
+ task: "Microsoft.TaskCategory",
11
+ };
12
+ /**
13
+ * What a repository nobody has configured does.
14
+ *
15
+ * **Matched by NAME, and that is the one place a name is allowed to matter.** A
16
+ * repo you have never opened the settings for should still adopt sensibly, and
17
+ * the only thing available before somebody maps anything is what the labels are
18
+ * called. The moment a mapping is saved it pins to the id and renames stop
19
+ * mattering — *id for bindings you made, name for defaults you did not*.
20
+ *
21
+ * Ordered, because the lookups degrade: a feature request wants
22
+ * `FeatureCategory` and settles for `RequirementCategory`, which is all Basic
23
+ * has.
24
+ */
25
+ export const NAME_DEFAULTS = [
26
+ { match: ["bug", "defect", "regression"], categories: [CATEGORY.bug] },
27
+ {
28
+ match: ["enhancement", "feature", "feature request"],
29
+ categories: [CATEGORY.feature, CATEGORY.requirement],
30
+ },
31
+ ];
32
+ /** Where the file lives. `PHAROS_BRIDGE_FILE` overrides it, for tests. */
33
+ export function defaultBridgePath(env = process.env) {
34
+ const override = env.PHAROS_BRIDGE_FILE;
35
+ if (override !== undefined && override !== "")
36
+ return override;
37
+ const base = env.XDG_CONFIG_HOME ?? join(homedir(), ".config");
38
+ return join(base, "pharos", "bridge.json");
39
+ }
40
+ /** `org/project`, lowercased — the scope key. */
41
+ export function scopeKey(org, project) {
42
+ return `${org.trim().toLowerCase()}/${project.trim().toLowerCase()}`;
43
+ }
44
+ export function emptyBridgeConfig() {
45
+ return { version: 1, scopes: {} };
46
+ }
47
+ /**
48
+ * Read a config, or refuse with a message naming the file.
49
+ *
50
+ * **A file it cannot parse is an answer, not a crash** — the same rule
51
+ * `doctor`'s repos.json handling follows. A malformed mapping means adoption
52
+ * falls back to the name defaults and says so, rather than taking the CLI down
53
+ * on an unrelated verb.
54
+ */
55
+ export function parseBridgeConfig(text, source = "bridge.json") {
56
+ let raw;
57
+ try {
58
+ raw = JSON.parse(text);
59
+ }
60
+ catch (error) {
61
+ throw new Error(`${source} is not valid JSON: ${error.message}`);
62
+ }
63
+ if (typeof raw !== "object" || raw === null)
64
+ throw new Error(`${source} is not an object.`);
65
+ const record = raw;
66
+ if (record.version !== 1) {
67
+ throw new Error(`${source} has version ${String(record.version)}; this build understands 1.`);
68
+ }
69
+ const scopes = {};
70
+ const rawScopes = (record.scopes ?? {});
71
+ for (const [key, value] of Object.entries(rawScopes)) {
72
+ const scope = (value ?? {});
73
+ const repos = {};
74
+ for (const [repo, rawRepo] of Object.entries((scope.repos ?? {}))) {
75
+ const entry = (rawRepo ?? {});
76
+ const labels = {};
77
+ for (const [id, rawLabel] of Object.entries((entry.labels ?? {}))) {
78
+ const label = (rawLabel ?? {});
79
+ if (typeof label.category !== "string")
80
+ continue;
81
+ labels[id] = {
82
+ name: typeof label.name === "string" ? label.name : "",
83
+ category: label.category,
84
+ };
85
+ }
86
+ repos[repo.toLowerCase()] = {
87
+ labels,
88
+ defaultCategory: typeof entry.defaultCategory === "string" ? entry.defaultCategory : CATEGORY.requirement,
89
+ };
90
+ }
91
+ const digest = (scope.digest ?? {});
92
+ scopes[key.toLowerCase()] = {
93
+ repos,
94
+ digest: {
95
+ onAdopt: digest.onAdopt !== false,
96
+ onClose: digest.onClose !== false,
97
+ },
98
+ };
99
+ }
100
+ return { version: 1, scopes };
101
+ }
102
+ export function formatBridgeConfig(config) {
103
+ return `${JSON.stringify(config, null, 2)}\n`;
104
+ }
105
+ /** This repository's mapping in this scope, or the empty one. */
106
+ export function repoConfig(config, org, project, repo) {
107
+ const scope = config.scopes[scopeKey(org, project)];
108
+ return (scope?.repos[repo.trim().toLowerCase()] ?? {
109
+ labels: {},
110
+ defaultCategory: CATEGORY.requirement,
111
+ });
112
+ }
113
+ export function digestPolicy(config, org, project) {
114
+ return config.scopes[scopeKey(org, project)]?.digest ?? DEFAULT_DIGEST;
115
+ }
116
+ /**
117
+ * Which category an issue's labels ask for.
118
+ *
119
+ * **Saved mappings win, by id.** Then the name defaults, for a repository nobody
120
+ * has configured. Then the repository's own default.
121
+ *
122
+ * The `source` comes back with it on purpose: "this went to a requirement
123
+ * because you mapped `bug`" and "this went to a requirement because nothing
124
+ * matched" are different facts, and a picker that showed the same preselection
125
+ * for both would be hiding the second one.
126
+ */
127
+ export function categoryFor(labels, repo) {
128
+ for (const label of labels) {
129
+ const mapped = repo.labels[String(label.id)];
130
+ if (mapped !== undefined) {
131
+ return { categories: [mapped.category], source: "mapping", label: label.name };
132
+ }
133
+ }
134
+ for (const label of labels) {
135
+ const lowered = label.name.trim().toLowerCase();
136
+ const rule = NAME_DEFAULTS.find((candidate) => candidate.match.includes(lowered));
137
+ if (rule !== undefined) {
138
+ return { categories: rule.categories, source: "name-default", label: label.name };
139
+ }
140
+ }
141
+ return { categories: [repo.defaultCategory], source: "repo-default" };
142
+ }
143
+ /**
144
+ * A saved mapping whose label no longer exists in the repository.
145
+ *
146
+ * Reported rather than pruned. The mapping is a decision somebody made, and a
147
+ * label that has gone is usually a rename they did not realise breaks it — or a
148
+ * delete-and-recreate, which is a genuinely different label. Either way the
149
+ * answer is to show it, not to guess.
150
+ */
151
+ export function brokenMappings(repo, live) {
152
+ const known = new Set(live.map((label) => String(label.id)));
153
+ return Object.entries(repo.labels)
154
+ .filter(([id]) => !known.has(id))
155
+ .map(([id, mapping]) => ({ id, name: mapping.name, category: mapping.category }));
156
+ }
157
+ //# sourceMappingURL=bridge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge.js","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"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"}
package/dist/cli.d.ts CHANGED
@@ -12,7 +12,7 @@ import { type ExitCode } from "./output.js";
12
12
  * expensive kind of wrong. Anything added to the switch belongs here, and the
13
13
  * test below fails if it does not.
14
14
  */
15
- export declare const COMMANDS: readonly ["setup", "whoami", "query", "task", "types", "history", "iterations", "areas", "links", "fields", "create", "update", "link", "unlink", "delete", "restore", "deleted", "attach", "detach", "download", "image", "people", "wiki", "comment", "hooks", "plan", "doctor", "issue"];
15
+ export declare const COMMANDS: readonly ["setup", "whoami", "query", "task", "types", "history", "iterations", "areas", "links", "fields", "create", "update", "link", "unlink", "delete", "restore", "deleted", "attach", "detach", "download", "image", "people", "wiki", "comment", "hooks", "plan", "doctor", "issue", "bridge"];
16
16
  export interface RunOptions {
17
17
  argv: string[];
18
18
  cwd: string;
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AA8B5D,OAAO,EAKL,KAAK,QAAQ,EAEd,MAAM,aAAa,CAAC;AAyBrB;;;;;;;;;;GAUG;AACH,eAAO,MAAM,QAAQ,6RAMX,CAAC;AA0cX,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IACvB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,gDAAgD;IAChD,SAAS,CAAC,EAAE,CAAC,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,SAAS,CAAC;IAChD,+EAA+E;IAC/E,MAAM,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;IAC/B,uDAAuD;IACvD,MAAM,CAAC,EAAE,YAAY,GAAG,SAAS,CAAC;CACnC;AAED,wBAAsB,GAAG,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,QAAQ,CAAC,CAOhE"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AA+B5D,OAAO,EAKL,KAAK,QAAQ,EAEd,MAAM,aAAa,CAAC;AAyBrB;;;;;;;;;;GAUG;AACH,eAAO,MAAM,QAAQ,uSAMX,CAAC;AA8gBX,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IACvB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,gDAAgD;IAChD,SAAS,CAAC,EAAE,CAAC,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,SAAS,CAAC;IAChD,+EAA+E;IAC/E,MAAM,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;IAC/B,uDAAuD;IACvD,MAAM,CAAC,EAAE,YAAY,GAAG,SAAS,CAAC;CACnC;AAED,wBAAsB,GAAG,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,QAAQ,CAAC,CAOhE"}