@floh-solutions/pharos-cli 0.12.0 → 0.14.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.
@@ -1,5 +1,14 @@
1
1
  import { taskContext } from "@floh-solutions/ado-core";
2
2
  import { emit, emitText, usageError } from "../output.js";
3
+ /**
4
+ * How many child fetches are in flight at once.
5
+ *
6
+ * Six rather than unbounded: an epic with forty children would otherwise open
7
+ * forty connections and invite the rate limiter, and `taskContext` is itself
8
+ * several requests per item. Rather than a number tuned to nothing, this is the
9
+ * smallest value that keeps a typical epic (eight children) to two rounds.
10
+ */
11
+ const CHILD_CONCURRENCY = 6;
3
12
  export async function runTask(io, session, positionals, options) {
4
13
  const raw = positionals[0];
5
14
  if (raw === undefined)
@@ -7,12 +16,95 @@ export async function runTask(io, session, positionals, options) {
7
16
  if (!/^\d+$/.test(raw)) {
8
17
  throw usageError(`"${raw}" is not a work item id.`, { given: raw });
9
18
  }
10
- const context = await taskContext(session.client, Number(raw), {
19
+ const fetch = async (id) => taskContext(session.client, id, {
11
20
  includeWikiContent: options.wikiContent,
12
21
  includeWikiComments: options.wikiComments,
13
22
  includeRelatedTitles: options.relatedTitles,
14
23
  });
15
- return session.pretty ? emitText(io, render(context)) : emit(io, context);
24
+ const tree = await withChildren(await fetch(Number(raw)), options.depth, fetch);
25
+ const shaped = options.compact ? compact(tree) : tree;
26
+ return session.pretty ? emitText(io, render(shaped)) : emit(io, shaped);
27
+ }
28
+ /**
29
+ * Fetches the hierarchy under an item, breadth-first and bounded.
30
+ *
31
+ * **A child that cannot be read becomes a `problems[]` entry, not a failure.**
32
+ * One unreadable item out of forty must not cost the other thirty-nine — a
33
+ * partial tree that says what is missing is far more useful than an error, and
34
+ * it is the same rule `taskContext` already applies to a wiki page it cannot
35
+ * reach.
36
+ */
37
+ async function withChildren(context, depth, fetch) {
38
+ if (depth <= 0)
39
+ return context;
40
+ const ids = context.related
41
+ .filter((item) => item.rel === "System.LinkTypes.Hierarchy-Forward")
42
+ .map((item) => item.id);
43
+ if (ids.length === 0)
44
+ return context;
45
+ const children = [];
46
+ const problems = [];
47
+ for (let start = 0; start < ids.length; start += CHILD_CONCURRENCY) {
48
+ const batch = ids.slice(start, start + CHILD_CONCURRENCY);
49
+ const settled = await Promise.all(batch.map(async (id) => {
50
+ try {
51
+ return await withChildren(await fetch(id), depth - 1, fetch);
52
+ }
53
+ catch (error) {
54
+ problems.push(`child #${id} could not be read: ${error instanceof Error ? error.message : String(error)}`);
55
+ return undefined;
56
+ }
57
+ }));
58
+ for (const child of settled)
59
+ if (child !== undefined)
60
+ children.push(child);
61
+ }
62
+ return {
63
+ ...context,
64
+ children,
65
+ problems: problems.length === 0 ? context.problems : [...context.problems, ...problems],
66
+ };
67
+ }
68
+ /**
69
+ * The same context with the parts no agent reads taken out.
70
+ *
71
+ * **Measured, on work item #40: 2,466 of 10,367 bytes — 24% — were identity
72
+ * blobs.** `System.CreatedBy` and its three siblings each carry `url`,
73
+ * `_links.avatar.href`, `imageUrl`, `descriptor` and a GUID, and what gets used
74
+ * is `displayName`. Over the nine calls an epic used to cost, that is some nine
75
+ * kilobytes of avatar URLs.
76
+ *
77
+ * Two rules, and deliberately only two — this trims noise, it does not decide
78
+ * which FIELDS matter:
79
+ *
80
+ * - an object with a `displayName` collapses to that string. The `--pretty`
81
+ * renderer has always done this (see ``identity``); the JSON path simply
82
+ * never did.
83
+ * - `WEF_…` keys go. They are the board extension's own per-team markers —
84
+ * `WEF_<guid>_Kanban.Column` — and they are already in `System.BoardColumn`.
85
+ *
86
+ * Nothing else is dropped. A field bag that quietly loses fields would be worse
87
+ * than a large one, because the loss is invisible at the point it matters.
88
+ */
89
+ function compact(context) {
90
+ const fields = {};
91
+ for (const [name, value] of Object.entries(context.fields)) {
92
+ if (name.startsWith("WEF_"))
93
+ continue;
94
+ if (typeof value === "object" && value !== null) {
95
+ const display = value.displayName;
96
+ if (typeof display === "string") {
97
+ fields[name] = display;
98
+ continue;
99
+ }
100
+ }
101
+ fields[name] = value;
102
+ }
103
+ return {
104
+ ...context,
105
+ fields,
106
+ ...(context.children === undefined ? {} : { children: context.children.map(compact) }),
107
+ };
16
108
  }
17
109
  /**
18
110
  * The human rendering.
@@ -22,7 +114,7 @@ export async function runTask(io, session, positionals, options) {
22
114
  * LAST and never omitted — a context with a silent hole in it is worse than no
23
115
  * context, because it is reasoned from confidently.
24
116
  */
25
- function render(context) {
117
+ function render(context, indent = "") {
26
118
  const field = (name) => {
27
119
  const value = context.fields[name];
28
120
  return typeof value === "string" && value.trim() !== "" ? value : undefined;
@@ -94,7 +186,17 @@ function render(context) {
94
186
  for (const problem of context.problems)
95
187
  lines.push(` - ${problem}`);
96
188
  }
97
- return lines.join("\n");
189
+ // The children LAST and set off by a rule, so a long epic still reads as one
190
+ // item followed by its parts rather than as an undifferentiated wall.
191
+ if (context.children !== undefined && context.children.length > 0) {
192
+ lines.push("", `${"=".repeat(60)}`, `## Children of #${context.id} (${context.children.length})`);
193
+ for (const child of context.children) {
194
+ lines.push("", "-".repeat(60), render(child, `${indent} `));
195
+ }
196
+ }
197
+ return indent === ""
198
+ ? lines.join("\n")
199
+ : lines.map((line) => (line === "" ? line : `${indent}${line}`)).join("\n");
98
200
  }
99
201
  /** `System.LinkTypes.Hierarchy-Forward` reads as `Child`. */
100
202
  function shortRel(rel) {
@@ -1 +1 @@
1
- {"version":3,"file":"task.js","sourceRoot":"","sources":["../../src/commands/task.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"task.js","sourceRoot":"","sources":["../../src/commands/task.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@floh-solutions/pharos-cli",
3
- "version": "0.12.0",
3
+ "version": "0.14.0",
4
4
  "description": "Azure DevOps from a headless shell, for an agent: the whole context of a task in one call, and the wiki/comment verbs Microsoft's MCP server does not ship.",
5
5
  "license": "UNLICENSED",
6
6
  "author": "FLOH Solutions",
@@ -24,8 +24,8 @@
24
24
  "node": ">=22"
25
25
  },
26
26
  "dependencies": {
27
- "@floh-solutions/plan-to-board": "0.1.1",
28
- "@floh-solutions/ado-core": "0.7.0"
27
+ "@floh-solutions/ado-core": "0.7.0",
28
+ "@floh-solutions/plan-to-board": "0.1.1"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@types/node": "^22.10.2",
package/skill/SKILL.md CHANGED
@@ -20,11 +20,21 @@ query WHICH work items — assigned to you, in a sprint
20
20
  task <id> one work item, whole: fields, comments,
21
21
  attachments, relations WITH titles, and the
22
22
  content + discussion of every linked wiki page
23
+ --children an EPIC and everything under it, in
24
+ ONE call. --depth <n> for deeper (max 5)
25
+ --compact flatten identities, drop board keys
23
26
  create <type> --title "…" one work item. --parent goes in the SAME patch
24
27
  update <id> change a field: --state --priority --assignee
25
28
  --title, or --field Name=value for anything else
26
29
  link <id> --parent <id> relate two items. Also --child --related
27
30
  unlink <id> --parent <id> --predecessor --successor --duplicate
31
+ link <id> --wiki-page <path> link a WIKI PAGE to a work item. This is what
32
+ makes a plan findable by `task`
33
+ history <id> what CHANGED, field by field, who and when
34
+ iterations | areas the sprints with their dates, and the areas
35
+ links every relation type this ORG has, and which
36
+ --flag reaches it (six of ~eighteen)
37
+ fields [--type T] what a field will ACCEPT — allowed values
28
38
  attach <id> <file> put a FILE on a work item (Attachments list)
29
39
  detach <id> <url-or-guid> take one off. --yes
30
40
  download <id-or-url> read one back. --out <path> or it writes nothing
@@ -250,12 +260,68 @@ attributed to. A shared or service token quietly makes "assigned to me" mean
250
260
  somebody else. It is org-scoped, so it still answers when the project is
251
261
  misconfigured — which is exactly when you need it.
252
262
 
263
+ ## Ask, do not guess — the five discovery verbs
264
+
265
+ Every one of these replaced a guess, and a guess that silently succeeds against
266
+ the wrong value is worse than one that fails:
267
+
268
+ ```bash
269
+ pharos history 39 # what changed on it, field by field, who and when
270
+ pharos iterations # the sprints, with start/finish dates
271
+ pharos areas # the area tree
272
+ pharos links # every relation type, and which --flag reaches it
273
+ pharos fields --type Task --constrained # what Priority and Activity accept
274
+ ```
275
+
276
+ **`iterations` prints TWO paths and only one of them works as a field value.**
277
+ `path` is the classification node — `\Tibata\Iteration\Sprint 1`. `fieldPath`
278
+ is what `System.IterationPath` and `--sprint` take — `Tibata\Sprint 1`, with no
279
+ `Iteration` segment. Handing the node path to the field is a 400 that reads as
280
+ though the sprint does not exist. A sprint with `startDate: null` is normal —
281
+ most orgs never set them.
282
+
283
+ **`history` reads `/updates`, which is the diff.** `/revisions` is snapshots you
284
+ would have to diff yourself. Bookkeeping fields that change on every revision
285
+ (`System.Rev`, the dates, the watermark) are filtered out unless you pass
286
+ `--all`; a revision that changed only those is dropped entirely, because it is
287
+ not a change anybody made.
288
+
289
+ **`links` exists because `link` names six kinds and an org has about eighteen.**
290
+ `Affects`, `TestedBy`, the `Remote.*` family and `Duplicate-Reverse` have no
291
+ flag. The output marks which ones do, so "does this link type exist" and "can I
292
+ make it from here" are one answer.
293
+
294
+ **`fields` needs `--type`.** Allowed values belong to the TYPE, not the project
295
+ — `Activity` is on `Task` and on neither `Epic` nor `Issue` in the Basic
296
+ process, so there is no project-wide answer to "which fields are there".
297
+
298
+ ## Linking a wiki page to a work item
299
+
300
+ **This is what makes a plan findable.** `pharos task` reads linked wiki pages
301
+ and their discussion — that is the whole point of it — and the link is what puts
302
+ them there:
303
+
304
+ ```bash
305
+ pharos wiki write "/Plans/Sprint 3" --stdin < plan.md
306
+ pharos link 39 --wiki-page "/Plans/Sprint 3"
307
+ pharos unlink 39 --wiki-page "/Plans/Sprint 3" # by identity, guarded
308
+ ```
309
+
310
+ `--wiki-page`, **not** `--wiki`: `--wiki <name>` is the global flag naming which
311
+ wiki to work in, and using it here means "the wiki called /Plans/Sprint 3".
312
+
313
+ A wiki artifact link is `vstfs:///Wiki/WikiPage/<project>%2F<wiki>%2F<path>` —
314
+ the page's PATH is its identity, with no id anywhere. So **moving or renaming a
315
+ page silently breaks every link to it**, which is why `wiki move` and `wiki
316
+ rename` need `--yes` and `wiki write` does not.
317
+
253
318
  ## What `pharos` does NOT do — read this before you go looking
254
319
 
255
320
  - **Free-text and code search.** Nothing here covers it. For work items,
256
321
  `pharos query --wiql "… WHERE [System.Title] CONTAINS 'thing'"` gets close;
257
322
  for code there is no substitute short of the REST API.
258
- - **Iterations, areas, capacity, backlogs, teams.**
323
+ - **Capacity, backlogs, teams.** Iterations and areas ARE covered — see
324
+ `iterations` / `areas` above — but team capacity and backlog ordering are not.
259
325
  - Pull requests, builds, pipelines.
260
326
 
261
327
  **There is no Azure DevOps MCP server here any more, and that is deliberate.**
@@ -276,7 +342,8 @@ guards are, and they are the reason to come back here rather than hand-roll.
276
342
  ## Start every task with one command
277
343
 
278
344
  ```bash
279
- pharos task <id>
345
+ pharos task <id> # a leaf
346
+ pharos task <id> --children --compact # an EPIC and everything under it
280
347
  ```
281
348
 
282
349
  One call: the item, its comments, its attachments, its relations **with their
@@ -291,6 +358,78 @@ Anything that could not be fetched appears in `problems[]`. **If that array is
291
358
  not empty, say so before acting** — a context with an invisible hole in it gets
292
359
  reasoned from confidently.
293
360
 
361
+ ### What it actually returns
362
+
363
+ **Read this before writing a parser.** The shapes below are the CLI's, not the
364
+ Azure DevOps API's, and they differ in exactly the places you would guess wrong:
365
+
366
+ ```jsonc
367
+ {
368
+ "id": 40,
369
+ "fields": { // the raw ADO field bag
370
+ "System.Title": "…",
371
+ "System.State": "To Do",
372
+ "System.Description": "plain text, real newlines — NOT html",
373
+ "System.AssignedTo": { "displayName": "…", "uniqueName": "…" }
374
+ },
375
+ "comments": [
376
+ { "id": 1741895,
377
+ "text": "I have updated the description",
378
+ "createdBy": "André Kwakernaat", // a STRING, already flattened
379
+ "createdDate": "2026-08-05T10:15:43.167Z" }
380
+ ],
381
+ "attachments": [],
382
+ "related": [ // titles and states ALREADY resolved
383
+ { "id": 46, "rel": "System.LinkTypes.Hierarchy-Forward",
384
+ "title": "Skill: …", "state": "To Do" }
385
+ ],
386
+ "wikiPages": [],
387
+ "problems": [],
388
+ "children": [] // only with --children/--depth
389
+ }
390
+ ```
391
+
392
+ Three things that trip up a parser written against the REST API:
393
+
394
+ - **`comments[].createdBy` is a string**, not an identity object. There is no
395
+ `.displayName` on it.
396
+ - **Description and comment text are plain text**, already converted. Do not
397
+ strip tags; there are none.
398
+ - **A field is ABSENT when unset, not empty.** `fields["System.Description"]` is
399
+ simply missing on an item nobody has written one for — which is a *finding*
400
+ worth reporting, not a crash. Measured: seven of eight children of one epic.
401
+
402
+ `--pretty` renders all of the above as readable markdown and drops the identity
403
+ noise. It is not only "for a human" — for reading a single item it is usually
404
+ the better format for you too.
405
+
406
+ ### Starting from an EPIC
407
+
408
+ An epic is not a big task, and the loop below is written for a leaf. Handed a
409
+ parent, the first question is **which children are actually specified**:
410
+
411
+ ```bash
412
+ pharos task 39 --children --compact
413
+ ```
414
+
415
+ One call instead of one per child — measured on a real epic, nine calls and
416
+ ~42 KB became one call and ~22 KB. Then, **before proposing any work**, report
417
+ the split:
418
+
419
+ > #40 has a complete 5,694-character spec. #41–#47 have no description at all.
420
+ > Seven of eight are placeholders.
421
+
422
+ That is the single most useful thing to say about a parent item, and it is the
423
+ thing an agent is most likely to skip — the titles read like a plan, so an
424
+ implementation gets inferred from them and nobody notices it was invented.
425
+
426
+ `--depth <n>` walks further (max 5). A child that cannot be read becomes a
427
+ `problems[]` entry rather than failing the whole call, so a partial tree still
428
+ tells you what is missing.
429
+
430
+ `--compact` flattens identities to display names and drops the `WEF_…` board
431
+ extension keys. Measured at ~24% of an item's bytes and nothing reads them.
432
+
294
433
  ## Reading the outcome
295
434
 
296
435
  Success is JSON on stdout; failure is JSON on stderr with a non-zero exit. An