@floh-solutions/pharos-cli 0.4.0 → 0.12.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/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +381 -7
- package/dist/cli.js.map +1 -1
- package/dist/commands/attach.d.ts +30 -0
- package/dist/commands/attach.d.ts.map +1 -0
- package/dist/commands/attach.js +55 -0
- package/dist/commands/attach.js.map +1 -0
- package/dist/commands/create.d.ts +35 -0
- package/dist/commands/create.d.ts.map +1 -0
- package/dist/commands/create.js +82 -0
- package/dist/commands/create.js.map +1 -0
- package/dist/commands/detach.d.ts +34 -0
- package/dist/commands/detach.d.ts.map +1 -0
- package/dist/commands/detach.js +117 -0
- package/dist/commands/detach.js.map +1 -0
- package/dist/commands/discover.d.ts +66 -0
- package/dist/commands/discover.d.ts.map +1 -0
- package/dist/commands/discover.js +128 -0
- package/dist/commands/discover.js.map +1 -0
- package/dist/commands/hooks.d.ts +24 -0
- package/dist/commands/hooks.d.ts.map +1 -0
- package/dist/commands/hooks.js +184 -0
- package/dist/commands/hooks.js.map +1 -0
- package/dist/commands/image.d.ts +44 -0
- package/dist/commands/image.d.ts.map +1 -0
- package/dist/commands/image.js +114 -0
- package/dist/commands/image.js.map +1 -0
- package/dist/commands/import.d.ts +51 -0
- package/dist/commands/import.d.ts.map +1 -0
- package/dist/commands/import.js +190 -0
- package/dist/commands/import.js.map +1 -0
- package/dist/commands/link.d.ts +40 -0
- package/dist/commands/link.d.ts.map +1 -0
- package/dist/commands/link.js +150 -0
- package/dist/commands/link.js.map +1 -0
- package/dist/commands/people.d.ts +39 -0
- package/dist/commands/people.d.ts.map +1 -0
- package/dist/commands/people.js +64 -0
- package/dist/commands/people.js.map +1 -0
- package/dist/commands/query.d.ts +43 -0
- package/dist/commands/query.d.ts.map +1 -0
- package/dist/commands/query.js +202 -0
- package/dist/commands/query.js.map +1 -0
- package/dist/commands/recycle.d.ts +10 -0
- package/dist/commands/recycle.d.ts.map +1 -0
- package/dist/commands/recycle.js +118 -0
- package/dist/commands/recycle.js.map +1 -0
- package/dist/commands/update.d.ts +52 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +181 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/commands/wiki.d.ts.map +1 -1
- package/dist/commands/wiki.js +133 -2
- package/dist/commands/wiki.js.map +1 -1
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +32 -2
- package/dist/output.js.map +1 -1
- package/dist/wiql-build.d.ts +92 -0
- package/dist/wiql-build.d.ts.map +1 -0
- package/dist/wiql-build.js +147 -0
- package/dist/wiql-build.js.map +1 -0
- package/package.json +3 -3
- package/skill/SKILL.md +315 -65
package/skill/SKILL.md
CHANGED
|
@@ -1,62 +1,318 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: pharos
|
|
3
|
-
description: "Use
|
|
3
|
+
description: "Use for AZURE DEVOPS work specifically — when the user says Azure DevOps, ADO or dev.azure.com, or names an ADO work item by number (\"pick up 4821\", \"what's on 210\"). Covers reading or updating an ADO work item, ticket, bug, story or epic; the ADO board, backlog, sprint or iteration; what is assigned to you in Azure DevOps; reading or writing an ADO project wiki page and its comments; linking a plan to an epic; attaching a file to a work item or taking one off; putting an inline image into a description, a comment, a wiki page or a wiki comment; @mentioning somebody so they are actually notified; importing Markdown, Word or PDF files as wiki pages; and whether a failed Azure DevOps call is worth retrying. NOT for other trackers — the Argus board, GitHub issues, Jira, Linear — where \"task\", \"todo\" and \"board\" mean something else entirely."
|
|
4
4
|
license: Proprietary
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Working Azure DevOps with `pharos`
|
|
8
8
|
|
|
9
|
-
`pharos` is a CLI that gives you
|
|
10
|
-
|
|
11
|
-
`pharos --help` for the complete verb list; this skill is the part that is not
|
|
12
|
-
in the help text.
|
|
9
|
+
`pharos` is a CLI that gives you Azure DevOps from a shell, authenticated by an
|
|
10
|
+
environment variable rather than a browser login.
|
|
13
11
|
|
|
14
|
-
|
|
12
|
+
**Everything it can do is listed below. Do not run `pharos --help` to find
|
|
13
|
+
out** — that costs several calls and this section is the same information.
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
whoami who ADO_PAT belongs to. @Me resolves to this
|
|
17
|
+
types what --state and --type will ACCEPT, per type
|
|
18
|
+
query WHICH work items — assigned to you, in a sprint,
|
|
19
|
+
of a type, still open. Hydrated items, not ids.
|
|
20
|
+
task <id> one work item, whole: fields, comments,
|
|
21
|
+
attachments, relations WITH titles, and the
|
|
22
|
+
content + discussion of every linked wiki page
|
|
23
|
+
create <type> --title "…" one work item. --parent goes in the SAME patch
|
|
24
|
+
update <id> change a field: --state --priority --assignee
|
|
25
|
+
--title, or --field Name=value for anything else
|
|
26
|
+
link <id> --parent <id> relate two items. Also --child --related
|
|
27
|
+
unlink <id> --parent <id> --predecessor --successor --duplicate
|
|
28
|
+
attach <id> <file> put a FILE on a work item (Attachments list)
|
|
29
|
+
detach <id> <url-or-guid> take one off. --yes
|
|
30
|
+
download <id-or-url> read one back. --out <path> or it writes nothing
|
|
31
|
+
image <file> upload a PICTURE for use inside text. Prints the
|
|
32
|
+
markdown to paste. NOT the same as attach
|
|
33
|
+
people [query] who can be @mentioned, with the @<guid> form
|
|
34
|
+
delete <id> --yes → Recycle Bin (no permanent delete, on purpose)
|
|
35
|
+
restore <id> bring one back
|
|
36
|
+
deleted what is in the Recycle Bin, with names
|
|
37
|
+
wiki list | tree | read <path> | write <path> | delete <path>
|
|
38
|
+
wiki move <path> <new path> move a page; sub-pages come with it
|
|
39
|
+
wiki rename <path> <new name> the same call, leaf only. BOTH need --yes
|
|
40
|
+
wiki duplicate <path> [to] a verb Azure DevOps lacks. "<path> - Copy N"
|
|
41
|
+
wiki image <file> a picture for a PAGE or page comment. Different
|
|
42
|
+
endpoint from `image`, and the name is unique-d
|
|
43
|
+
wiki import <file...> .md .txt .docx .pdf .rtf .html -> pages
|
|
44
|
+
--under <path> --as <name>
|
|
45
|
+
comment list | add | edit | delete <target> is a work item id OR a wiki path
|
|
46
|
+
comment react | unreact | reactors like dislike heart hooray smile confused
|
|
47
|
+
hooks list | check | create | repoint | delete service hooks for realtime
|
|
48
|
+
plan <file> an implementation plan → a work item tree
|
|
49
|
+
setup org, project, token → keychain + shell profile
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Text input: `--text` / `--file` / `--stdin`. Global: `--pretty` for a human,
|
|
53
|
+
`--yes` for destructive verbs, `--dry-run` to preview.
|
|
54
|
+
|
|
55
|
+
**`ADO_ORG`, `ADO_PROJECT` and `ADO_PAT` are already in the environment** after
|
|
56
|
+
setup. Do not check them before working; a missing one announces itself as
|
|
57
|
+
`"kind": "config"` on exit 2, which is the only time it matters.
|
|
58
|
+
|
|
59
|
+
## Finding the work: `pharos query`
|
|
60
|
+
|
|
61
|
+
**Do not reach for `curl` and the WIQL endpoint.** This skill used to hand you a
|
|
62
|
+
recipe for exactly that, because there was no query verb. There is one now, and
|
|
63
|
+
it does the part the recipe could not: WIQL returns **ids only**, so the recipe
|
|
64
|
+
gave you sixteen bare numbers and a call per item to make them mean anything.
|
|
15
65
|
|
|
16
66
|
```bash
|
|
17
|
-
pharos
|
|
67
|
+
pharos query --mine # assigned to you, still open
|
|
68
|
+
pharos query --sprint "Sprint 1" # --sprint current for @currentIteration
|
|
69
|
+
pharos query --type Epic --state Doing # both repeatable
|
|
70
|
+
pharos query --assignee "ada@contoso.com"
|
|
71
|
+
pharos query --tag api --all # --all includes finished work
|
|
72
|
+
pharos query --wiql "SELECT [System.Id] FROM WorkItems WHERE …" # escape hatch
|
|
18
73
|
```
|
|
19
74
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
75
|
+
Flags AND together. Output is hydrated items — id, type, title, state, assignee,
|
|
76
|
+
iteration, tags, priority, changed — so `--mine` is one command, not a query
|
|
77
|
+
followed by a fetch per result. Follow up with `pharos task <id>` only for the
|
|
78
|
+
few you are actually going to work on.
|
|
23
79
|
|
|
24
|
-
**
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
80
|
+
**Read the `openness` field before you report a count.** "12 open" is
|
|
81
|
+
meaningless until you know what was counted as finished, and Azure DevOps lets a
|
|
82
|
+
process template rename every state. `query` reads the project's own state
|
|
83
|
+
categories and tells you which states it treated as terminal; if it could not
|
|
84
|
+
read them it says so and falls back to guessing, and that is your cue to pass
|
|
85
|
+
`--state` explicitly. `assignedTo` names who `@Me` actually resolved to — a
|
|
86
|
+
shared or service token makes "assigned to me" quietly mean somebody else.
|
|
28
87
|
|
|
29
|
-
|
|
88
|
+
Do NOT write `[System.State] NOT IN GROUP 'Completed'` if you reach for `--wiql`.
|
|
89
|
+
It parses, returns 200, and matches **everything** — `IN GROUP` covers work item
|
|
90
|
+
TYPE categories only, and an unknown group resolves to the empty set with no
|
|
91
|
+
error. Measured, 2026-08-05.
|
|
30
92
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
93
|
+
## Changing a work item
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
pharos update 225 --state Doing # move the state when the work moves
|
|
97
|
+
pharos update 225 --priority 1 --assignee "ada@contoso.com"
|
|
98
|
+
pharos update 225 --field Microsoft.VSTS.Scheduling.RemainingWork=3
|
|
99
|
+
pharos attach 225 ./bestsellers.xlsx --comment "The numbers"
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
`update` **reads the item and applies the change under a `test` op on `/rev`**,
|
|
103
|
+
so somebody who wrote between your read and your write gets you a `conflict`
|
|
104
|
+
rather than losing their edit. Setting a value it already has writes nothing and
|
|
105
|
+
says so — a pointless PATCH still bumps `System.Rev` and invalidates every other
|
|
106
|
+
cached revision on the item. `--dry-run` shows the before → after and writes
|
|
107
|
+
nothing. There is no `--yes`: a field edit is an ordinary edit and Azure DevOps
|
|
108
|
+
keeps every revision.
|
|
109
|
+
|
|
110
|
+
`attach` uploads the bytes and then links them as an `AttachedFile` relation —
|
|
111
|
+
two calls, one command. Attachments are **immutable**: attaching the same file
|
|
112
|
+
twice makes two of them, and there is no replace and no versioning. `detach`
|
|
113
|
+
takes one off; it needs `--yes`, finds the attachment by identity rather than by
|
|
114
|
+
position, and leaves the bytes in Azure DevOps so re-attaching the url puts it
|
|
115
|
+
back.
|
|
116
|
+
|
|
117
|
+
`create` puts `--parent` in the same patch as the fields, so a child is never
|
|
118
|
+
briefly an orphan, and it takes the same flag names as `update`.
|
|
119
|
+
|
|
120
|
+
**`unlink` refuses rather than guessing, and that is worth knowing before you
|
|
121
|
+
see it.** Azure DevOps removes a relation by its POSITION in the array, so an
|
|
122
|
+
index from a stale read cuts a different link and the request still succeeds.
|
|
123
|
+
`unlink` finds the relation by identity and removes it under a `test` op on the
|
|
124
|
+
revision it read. If the relation is not there you get exit 3 — that means
|
|
125
|
+
nothing happened, not that the call failed.
|
|
34
126
|
|
|
35
|
-
|
|
127
|
+
`delete` moves to the **Recycle Bin** and needs `--yes`; the refusal quotes the
|
|
128
|
+
title first. `restore <id>` brings it back and needs no flag.
|
|
36
129
|
|
|
37
|
-
|
|
130
|
+
**There is no permanent delete here, deliberately.** It is the only irreversible
|
|
131
|
+
verb Azure DevOps has, and `--yes` is a flag you have learned to pass. The web
|
|
132
|
+
UI owns it. If somebody genuinely needs to purge, send them there rather than
|
|
133
|
+
looking for a flag.
|
|
38
134
|
|
|
39
|
-
|
|
40
|
-
|---|---|
|
|
41
|
-
| **Use `pharos`** | anything touching a **wiki page's comments** (the MCP has no tool for these at all), deleting a comment, reactions, deleting a wiki page, and `pharos task <id>` for gathering context |
|
|
42
|
-
| **Either works** | reading and querying work items, creating them, updating fields and state, adding a comment, reading a wiki page |
|
|
43
|
-
| **The MCP may be better** | code search, repository and pull request work, anything outside work items and wikis |
|
|
135
|
+
## Pictures in text, and files beside it
|
|
44
136
|
|
|
45
|
-
|
|
46
|
-
|
|
137
|
+
**These are two different things and picking the wrong one is the mistake worth
|
|
138
|
+
avoiding.**
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
pharos attach 225 ./bestsellers.xlsx # a FILE, in the Attachments list
|
|
142
|
+
pharos image ./screenshot.png # a PICTURE, to put inside the text
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
An **attachment** creates an `AttachedFile` relation and appears in the work
|
|
146
|
+
item's Attachments list. An **inline image** creates no relation at all —
|
|
147
|
+
measured on #333, two screenshots pasted into a description gave `relations: 0`
|
|
148
|
+
and `attachments: []`. It is an `` in the field text and nothing else,
|
|
149
|
+
which is why the Attachments list is right to show nothing for it.
|
|
150
|
+
|
|
151
|
+
So `image` prints a markdown line and leaves the writing to you:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
pharos image ./chart.png --pretty
|
|
155
|
+
# 
|
|
156
|
+
|
|
157
|
+
pharos update 225 --field System.Description="$(cat <<'EOF'
|
|
158
|
+
Revenue is up. See the chart:
|
|
159
|
+
|
|
160
|
+

|
|
161
|
+
EOF
|
|
162
|
+
)"
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
**A wiki picture is a different endpoint and needs `wiki image`.** A wiki is a
|
|
166
|
+
git repository, so an attachment there is a FILE and its NAME is its identity:
|
|
167
|
+
upload a second `image.png` and it lands on the first, and every page pointing
|
|
168
|
+
at `/.attachments/image.png` silently changes picture. `wiki image` makes the
|
|
169
|
+
name unique before sending and prints a repo-relative link:
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
pharos wiki image ./diagram.png --pretty
|
|
173
|
+
# 
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Use that path, **not** an absolute url — a page linking to a `wit/attachments`
|
|
177
|
+
url renders for anyone with a session and breaks for everybody else. The same
|
|
178
|
+
markdown works in a page and in a page comment.
|
|
179
|
+
|
|
180
|
+
## Mentioning somebody
|
|
181
|
+
|
|
182
|
+
**A mention is `@<guid>` and nothing else notifies.** `@Ada Lovelace` written
|
|
183
|
+
into a comment is plain text: it reads like a mention to every human who sees
|
|
184
|
+
it, links to nobody, and sends no notification. Nothing errors, so this fails
|
|
185
|
+
silently and stays failed.
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
pharos people # everyone the board knows, with the form
|
|
189
|
+
pharos people ada # filter by name or email
|
|
190
|
+
pharos comment add 225 --text "Ready for review @<0f45a818-878d-6d7a-ba8c-1f5568a89ed4>"
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
The names come from the board's own work items — everyone assigned, creating or
|
|
194
|
+
changing anything — rather than from an identity endpoint, because those live on
|
|
195
|
+
another host and want scopes a work-scoped PAT does not have. So somebody who
|
|
196
|
+
has never touched an item here will not be listed; the guid out of any Azure
|
|
197
|
+
DevOps url works just as well.
|
|
198
|
+
|
|
199
|
+
## Turning documents into wiki pages
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
pharos wiki import ./notes.md ./spec.docx --under "/Guides"
|
|
203
|
+
pharos wiki import ./report.pdf --as "Q3 Report"
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
`.md` and `.txt` are copied **verbatim** — they are already the target format,
|
|
207
|
+
and anything done to them would be reformatting a document somebody wrote
|
|
208
|
+
deliberately. `.docx`, `.pdf`, `.rtf` and `.html` are converted by
|
|
209
|
+
`pharos-convert`, which is the **same converter the macOS app uses**, so both
|
|
210
|
+
produce the same page from the same file. If it is not on PATH, markdown and
|
|
211
|
+
text still import and everything else is refused by name with the reason.
|
|
212
|
+
|
|
213
|
+
Three rules, because each of them is a way to lose work:
|
|
214
|
+
|
|
215
|
+
- **A name collision SKIPS and says so.** A page write with an empty version is
|
|
216
|
+
a *create*, so writing over an existing page is silent data loss.
|
|
217
|
+
- **Names are settled against the batch as well as the wiki**, so importing
|
|
218
|
+
`Notes.docx` beside `Notes.pdf` gives two pages rather than one written twice.
|
|
219
|
+
A name you give with `--as` is never renamed — it is an instruction, so it is
|
|
220
|
+
allowed to collide and skip.
|
|
221
|
+
- **An empty document is refused.** A scanned PDF carries no extractable text at
|
|
222
|
+
all and PDFKit returns an empty string with no error; an empty page would look
|
|
223
|
+
like a successful import until somebody opened it.
|
|
224
|
+
|
|
225
|
+
Exit 3 when **nothing** landed. A partial batch exits 0 and names what skipped —
|
|
226
|
+
retrying it blindly would collide with the pages it just made.
|
|
227
|
+
|
|
228
|
+
## Do not guess a state name — ask
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
pharos types # every type, its states, and which mean "finished"
|
|
232
|
+
pharos types --type Task
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
**`update` now refuses a state the item's type does not have, before writing,
|
|
236
|
+
with the valid ones attached** — so you rarely need to run this first. Run it
|
|
237
|
+
when you want to see the shape, or when composing a `--wiql` filter.
|
|
238
|
+
|
|
239
|
+
**States belong to a TYPE, not to the project.** A state that exists elsewhere
|
|
240
|
+
is still not one this item can take: `In Progress` is real on a Test Suite and
|
|
241
|
+
invalid on a Task, and checking the project as a whole is the same guess one
|
|
242
|
+
layer down. The categories are shown as well as the names because "which
|
|
243
|
+
states exist" and "which mean finished" are different questions — `Inactive` is
|
|
244
|
+
finished on a Test Plan and appears in nobody's hard-coded Done/Closed/Removed
|
|
245
|
+
list.
|
|
246
|
+
|
|
247
|
+
`pharos whoami` is the other one worth reaching for early: it names the identity
|
|
248
|
+
behind `ADO_PAT`, which is who `@Me` resolves to and who every write is
|
|
249
|
+
attributed to. A shared or service token quietly makes "assigned to me" mean
|
|
250
|
+
somebody else. It is org-scoped, so it still answers when the project is
|
|
251
|
+
misconfigured — which is exactly when you need it.
|
|
252
|
+
|
|
253
|
+
## What `pharos` does NOT do — read this before you go looking
|
|
254
|
+
|
|
255
|
+
- **Free-text and code search.** Nothing here covers it. For work items,
|
|
256
|
+
`pharos query --wiql "… WHERE [System.Title] CONTAINS 'thing'"` gets close;
|
|
257
|
+
for code there is no substitute short of the REST API.
|
|
258
|
+
- **Iterations, areas, capacity, backlogs, teams.**
|
|
259
|
+
- Pull requests, builds, pipelines.
|
|
260
|
+
|
|
261
|
+
**There is no Azure DevOps MCP server here any more, and that is deliberate.**
|
|
262
|
+
It authenticated through the Azure CLI, so it opened a browser mid-task — which
|
|
263
|
+
makes a headless session stop and wait for a human who is not watching.
|
|
264
|
+
|
|
265
|
+
For a **read** this tool does not offer, the REST API is fine and costs nothing
|
|
266
|
+
to get wrong. For a **write** it does not offer, say the gap out loud rather than
|
|
267
|
+
routing around it: several things here exist in no other Azure DevOps tool at
|
|
268
|
+
all — wiki page comments and reactions, attachment upload AND removal, inline
|
|
269
|
+
images for a work item field or a wiki page, mentioning somebody in a form that
|
|
270
|
+
actually notifies, importing a Word document or a PDF as a page, editing or
|
|
271
|
+
deleting a work item comment, service hooks, and applying a change under a
|
|
272
|
+
`test` op on `/rev` so a teammate who wrote first cannot be silently
|
|
273
|
+
overwritten. Those are what the
|
|
274
|
+
guards are, and they are the reason to come back here rather than hand-roll.
|
|
275
|
+
|
|
276
|
+
## Start every task with one command
|
|
277
|
+
|
|
278
|
+
```bash
|
|
279
|
+
pharos task <id>
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
One call: the item, its comments, its attachments, its relations **with their
|
|
283
|
+
titles**, and the full content of every linked wiki page **plus the discussion
|
|
284
|
+
on those pages**.
|
|
285
|
+
|
|
286
|
+
**Do not assemble this yourself.** By hand it is five or six lookups, and the
|
|
287
|
+
plan a colleague wrote is usually on a linked wiki page rather than in the
|
|
288
|
+
description. A task worked without it is a task worked without the plan.
|
|
289
|
+
|
|
290
|
+
Anything that could not be fetched appears in `problems[]`. **If that array is
|
|
291
|
+
not empty, say so before acting** — a context with an invisible hole in it gets
|
|
292
|
+
reasoned from confidently.
|
|
47
293
|
|
|
48
294
|
## Reading the outcome
|
|
49
295
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
296
|
+
Success is JSON on stdout; failure is JSON on stderr with a non-zero exit. An
|
|
297
|
+
empty array with exit 0 is a query that matched nothing — a different fact from
|
|
298
|
+
a failure.
|
|
299
|
+
|
|
300
|
+
**Read `advice` when it is there.** Azure DevOps answers a licence problem with
|
|
301
|
+
`TF401289: The current user does not have permission to create tag definitions`,
|
|
302
|
+
which is accurate and tells you nothing to do. Where the code is recognised the
|
|
303
|
+
error carries an `advice` field saying what it actually means here — including
|
|
304
|
+
the cases that read as one thing and are another: a failed tag on a create means
|
|
305
|
+
**the work item exists and only the tag is missing**, and a refused delete is
|
|
306
|
+
usually the account's ACCESS LEVEL rather than any permission, because a
|
|
307
|
+
Stakeholder cannot delete however the permissions are set. `credentialIsFine:
|
|
308
|
+
true` means stop re-checking the token.
|
|
53
309
|
|
|
54
310
|
| exit | meaning | what to do |
|
|
55
311
|
|---|---|---|
|
|
56
312
|
| `0` | it worked | carry on |
|
|
57
313
|
| `1` | the call failed | check `kind`; retry only if it is `rateLimit` (wait `retryAfterMs`) or transient |
|
|
58
314
|
| `2` | called wrong, or not configured | **never retry unchanged.** Fix the call, or the setup |
|
|
59
|
-
| `3` | a guard here refused | re-run with `--yes
|
|
315
|
+
| `3` | a guard here refused | re-run with `--yes` — after deciding it is right |
|
|
60
316
|
|
|
61
317
|
`"kind": "conflict"` means somebody wrote first. Your work is still valid:
|
|
62
318
|
re-read, re-apply. It carries both revisions — and note that **posting a comment
|
|
@@ -71,49 +327,51 @@ pharos wiki delete /Plans/Old --yes # done
|
|
|
71
327
|
```
|
|
72
328
|
|
|
73
329
|
**Exit 3 with `"kind": "refused"` means nothing happened.** Do not report the
|
|
74
|
-
work as done. The refusal carries a preview
|
|
75
|
-
|
|
76
|
-
re-running with the flag.
|
|
330
|
+
work as done. The refusal carries a preview — check it is what you intended
|
|
331
|
+
before adding `--yes`, rather than reflexively re-running with the flag.
|
|
77
332
|
|
|
78
|
-
Replacing a wiki page needs `--yes`; creating one does not. The refusal
|
|
79
|
-
|
|
80
|
-
|
|
333
|
+
Replacing a wiki page needs `--yes`; creating one does not. The refusal says how
|
|
334
|
+
many bytes are at stake, which is how you notice you are about to overwrite
|
|
335
|
+
somebody's page instead of writing a new one.
|
|
81
336
|
|
|
82
|
-
|
|
83
|
-
refused.
|
|
337
|
+
## Never hand-roll a WRITE
|
|
84
338
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
Every known Azure DevOps failure of this kind is handled inside the tool: the
|
|
339
|
+
Every known Azure DevOps trap on the write path is handled inside the tool: the
|
|
88
340
|
lost-update on wiki writes, the deleted-comment field that lies, the reaction
|
|
89
341
|
call that needs an empty body, artifact links that must carry a project GUID,
|
|
90
342
|
relation removal that would otherwise take the wrong link.
|
|
91
343
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
344
|
+
So a **failing** `pharos` command means the request was genuinely wrong or the
|
|
345
|
+
API genuinely refused. Read the error; do not reach for `curl`.
|
|
346
|
+
|
|
347
|
+
This is a rule about writes, not about reads. A read `pharos` does not offer —
|
|
348
|
+
reach for `query --wiql`, or the REST API — is fine, costs nothing to get wrong,
|
|
349
|
+
and is better than refusing to answer. A write it does not offer is a gap worth
|
|
350
|
+
reporting, not routing around: the guards are the reason the tool exists.
|
|
351
|
+
|
|
352
|
+
**Moving or renaming a page changes what it IS.** A wiki page has no id — the
|
|
353
|
+
path is its identity — so anything pointing at the old path stops resolving,
|
|
354
|
+
including artifact links from work items, and nothing reports it. That is why
|
|
355
|
+
both verbs need `--yes` while `write` does not, and why the refusal names what
|
|
356
|
+
it is about to break. Sub-pages move with their parent.
|
|
96
357
|
|
|
97
358
|
## Three things no tool can fix
|
|
98
359
|
|
|
99
360
|
1. **A wiki is a git repository.** Two writes to the same wiki at the same
|
|
100
361
|
moment are two pushes racing for one HEAD. Write pages one at a time.
|
|
101
362
|
2. **There is no wiki event in service hooks.** You cannot subscribe to "a wiki
|
|
102
|
-
page changed" — only to `git.push` on the wiki's repository.
|
|
103
|
-
react to wiki edits, that is the only route.
|
|
363
|
+
page changed" — only to `git.push` on the wiki's repository.
|
|
104
364
|
3. **Wiki ancestors are not created for you.** Writing `/A/B` when `/A` does not
|
|
105
365
|
exist is refused. Build a page tree top-down, one write per level.
|
|
106
366
|
|
|
107
367
|
## The loop
|
|
108
368
|
|
|
109
|
-
Working a task end to end, in the order that keeps the board honest:
|
|
110
|
-
|
|
111
369
|
1. `pharos task <id>` — read everything, including the linked plan.
|
|
112
370
|
2. If a plan is needed, write it: `pharos wiki write /Plans/<name> --stdin`,
|
|
113
371
|
then link it to the epic so the next person finds it the same way you did.
|
|
114
372
|
3. Break it down — create the child items.
|
|
115
373
|
4. `pharos comment add <id> --file notes.md` — decisions belong on the item,
|
|
116
|
-
not only in a chat log
|
|
374
|
+
not only in a chat log nobody else can read.
|
|
117
375
|
5. Move the state when the work moves, not at the end.
|
|
118
376
|
|
|
119
377
|
**Every person uses their own token.** Board attribution is per-person, so
|
|
@@ -122,17 +380,9 @@ sharing one.
|
|
|
122
380
|
|
|
123
381
|
## When it is not set up
|
|
124
382
|
|
|
125
|
-
`"kind": "config"` on exit 2 means the environment is missing. Run
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
It asks for the organisation, project and token, stores the token in the OS
|
|
132
|
-
keychain rather than a file, writes the right shell profile, and verifies both
|
|
133
|
-
permission scopes — Work Items and Wiki are separate in Azure DevOps, and a
|
|
134
|
-
token missing the second one works until the first wiki write.
|
|
135
|
-
|
|
136
|
-
Variables live in the environment (`ADO_ORG`, `ADO_PROJECT`, `ADO_PAT`) and
|
|
137
|
-
**never in the tool's own config**, which is what lets the same command run in
|
|
138
|
-
CI and unattended. After setup, a **new** shell is needed.
|
|
383
|
+
`"kind": "config"` on exit 2 means the environment is missing. Run `pharos
|
|
384
|
+
setup`: it stores the token in the OS keychain rather than a file, writes the
|
|
385
|
+
profile that **scripted** shells read, and verifies both permission scopes —
|
|
386
|
+
Work Items and Wiki are separate in Azure DevOps, and a token missing the second
|
|
387
|
+
works fine until the first wiki write days later. A **new** shell is needed
|
|
388
|
+
afterwards.
|