@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.
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +133 -14
- package/dist/cli.js.map +1 -1
- package/dist/commands/catalog.d.ts +72 -0
- package/dist/commands/catalog.d.ts.map +1 -0
- package/dist/commands/catalog.js +283 -0
- package/dist/commands/catalog.js.map +1 -0
- package/dist/commands/link.d.ts +2 -0
- package/dist/commands/link.d.ts.map +1 -1
- package/dist/commands/link.js +141 -2
- package/dist/commands/link.js.map +1 -1
- package/dist/commands/task.d.ts +13 -0
- package/dist/commands/task.d.ts.map +1 -1
- package/dist/commands/task.js +106 -4
- package/dist/commands/task.js.map +1 -1
- package/package.json +3 -3
- package/skill/SKILL.md +141 -2
package/dist/commands/task.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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":"AAAA,OAAO,EAAE,WAAW,EAAoB,MAAM,0BAA0B,CAAC;AAEzE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAA0B,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"task.js","sourceRoot":"","sources":["../../src/commands/task.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAoB,MAAM,0BAA0B,CAAC;AAEzE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAA0B,MAAM,cAAc,CAAC;AAwClF;;;;;;;GAOG;AACH,MAAM,iBAAiB,GAAG,CAAC,CAAC;AAE5B,MAAM,CAAC,KAAK,UAAU,OAAO,CAC3B,EAAM,EACN,OAAgB,EAChB,WAA8B,EAC9B,OAAoB;IAEpB,MAAM,GAAG,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC;IAC3B,IAAI,GAAG,KAAK,SAAS;QAAE,MAAM,UAAU,CAAC,sCAAsC,CAAC,CAAC;IAChF,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACvB,MAAM,UAAU,CAAC,IAAI,GAAG,0BAA0B,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;IACtE,CAAC;IAED,MAAM,KAAK,GAAG,KAAK,EAAE,EAAU,EAAwB,EAAE,CACvD,WAAW,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,EAAE;QAC9B,kBAAkB,EAAE,OAAO,CAAC,WAAW;QACvC,mBAAmB,EAAE,OAAO,CAAC,YAAY;QACzC,oBAAoB,EAAE,OAAO,CAAC,aAAa;KAC5C,CAAC,CAAC;IAEL,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,MAAM,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IAChF,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAEtD,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;AAC1E,CAAC;AAED;;;;;;;;GAQG;AACH,KAAK,UAAU,YAAY,CACzB,OAAoB,EACpB,KAAa,EACb,KAA2C;IAE3C,IAAI,KAAK,IAAI,CAAC;QAAE,OAAO,OAAO,CAAC;IAE/B,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO;SACxB,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,KAAK,oCAAoC,CAAC;SACnE,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC1B,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,OAAO,CAAC;IAErC,MAAM,QAAQ,GAAe,EAAE,CAAC;IAChC,MAAM,QAAQ,GAAa,EAAE,CAAC;IAE9B,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,GAAG,CAAC,MAAM,EAAE,KAAK,IAAI,iBAAiB,EAAE,CAAC;QACnE,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,GAAG,iBAAiB,CAAC,CAAC;QAC1D,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAC/B,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE;YACrB,IAAI,CAAC;gBACH,OAAO,MAAM,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC,EAAE,KAAK,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC;YAC/D,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,QAAQ,CAAC,IAAI,CACX,UAAU,EAAE,uBAAuB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAC5F,CAAC;gBACF,OAAO,SAAS,CAAC;YACnB,CAAC;QACH,CAAC,CAAC,CACH,CAAC;QACF,KAAK,MAAM,KAAK,IAAI,OAAO;YAAE,IAAI,KAAK,KAAK,SAAS;gBAAE,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC7E,CAAC;IAED,OAAO;QACL,GAAG,OAAO;QACV,QAAQ;QACR,QAAQ,EAAE,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,QAAQ,EAAE,GAAG,QAAQ,CAAC;KACxF,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,SAAS,OAAO,CAAqB,OAAU;IAC7C,MAAM,MAAM,GAA4B,EAAE,CAAC;IAC3C,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAC3D,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC;YAAE,SAAS;QACtC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YAChD,MAAM,OAAO,GAAI,KAAmC,CAAC,WAAW,CAAC;YACjE,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;gBAChC,MAAM,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC;gBACvB,SAAS;YACX,CAAC;QACH,CAAC;QACD,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;IACvB,CAAC;IACD,OAAO;QACL,GAAG,OAAO;QACV,MAAM;QACN,GAAG,CAAC,OAAO,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;KACvF,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,MAAM,CAAC,OAAiB,EAAE,MAAM,GAAG,EAAE;IAC5C,MAAM,KAAK,GAAG,CAAC,IAAY,EAAsB,EAAE;QACjD,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACnC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;IAC9E,CAAC,CAAC;IACF,MAAM,QAAQ,GAAG,CAAC,IAAY,EAAsB,EAAE;QACpD,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,OAAO,KAAK,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAC;QAC5C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YAChD,MAAM,OAAO,GAAI,KAAmC,CAAC,WAAW,CAAC;YACjE,IAAI,OAAO,OAAO,KAAK,QAAQ;gBAAE,OAAO,OAAO,CAAC;QAClD,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC,CAAC;IAEF,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,IAAI,GAAG,KAAK,CAAC,qBAAqB,CAAC,IAAI,WAAW,CAAC;IACzD,MAAM,KAAK,GAAG,KAAK,CAAC,cAAc,CAAC,IAAI,GAAG,CAAC;IAC3C,KAAK,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,EAAE,MAAM,IAAI,MAAM,KAAK,MAAM,KAAK,CAAC,cAAc,CAAC,IAAI,YAAY,EAAE,CAAC,CAAC;IAE7F,MAAM,IAAI,GAAG;QACX,CAAC,UAAU,EAAE,QAAQ,CAAC,mBAAmB,CAAC,CAAC;QAC3C,CAAC,MAAM,EAAE,KAAK,CAAC,iBAAiB,CAAC,CAAC;QAClC,CAAC,WAAW,EAAE,KAAK,CAAC,sBAAsB,CAAC,CAAC;QAC5C,CAAC,MAAM,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;KAC/B,CAAC,MAAM,CAAC,CAAC,IAAI,EAA4B,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC;IACpE,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACpB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,KAAK,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;IACzE,CAAC;IAED,MAAM,WAAW,GAAG,KAAK,CAAC,oBAAoB,CAAC,CAAC;IAChD,IAAI,WAAW,KAAK,SAAS;QAAE,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,gBAAgB,EAAE,EAAE,EAAE,WAAW,CAAC,CAAC;IAEjF,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,gBAAgB,OAAO,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;IAC3D,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;IAC1D,KAAK,MAAM,OAAO,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;QACvC,KAAK,CAAC,IAAI,CACR,EAAE,EACF,KAAK,OAAO,CAAC,SAAS,IAAI,SAAS,GAAG,OAAO,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,OAAO,CAAC,WAAW,EAAE,OAAO,OAAO,CAAC,EAAE,EAAE,EAC7H,OAAO,CAAC,IAAI,CACb,CAAC;IACJ,CAAC;IAED,IAAI,OAAO,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC/B,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,eAAe,OAAO,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;QACzD,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;YACnC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,yBAAyB,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACvF,KAAK,CAAC,IAAI,CAAC,KAAK,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,GAAG,MAAM,EAAE,CAAC,CAAC;QAC3H,CAAC;IACH,CAAC;IAED,IAAI,OAAO,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACnC,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,mBAAmB,OAAO,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC;QACjE,KAAK,MAAM,UAAU,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;YAC7C,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,UAAU,CAAC,IAAI,SAAS,CAAC;YAChF,KAAK,CAAC,IAAI,CAAC,KAAK,UAAU,CAAC,IAAI,GAAG,IAAI,EAAE,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;QACrC,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,YAAY,IAAI,CAAC,IAAI,MAAM,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC;QAC5D,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;YAC/B,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,sBAAsB,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;YACrD,SAAS;QACX,CAAC;QACD,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS;YAAE,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QAC7D,IAAI,IAAI,CAAC,QAAQ,KAAK,SAAS,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC5D,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,sBAAsB,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;YAC9D,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACpC,KAAK,CAAC,IAAI,CACR,EAAE,EACF,KAAK,OAAO,CAAC,SAAS,EAAE,WAAW,IAAI,SAAS,OAAO,OAAO,CAAC,EAAE,EAAE,EACnE,OAAO,CAAC,IAAI,CACb,CAAC;YACJ,CAAC;QACH,CAAC;IACH,CAAC;IAED,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAChC,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,0CAA0C,CAAC,CAAC;QAC3D,KAAK,MAAM,OAAO,IAAI,OAAO,CAAC,QAAQ;YAAE,KAAK,CAAC,IAAI,CAAC,OAAO,OAAO,EAAE,CAAC,CAAC;IACvE,CAAC;IAED,6EAA6E;IAC7E,sEAAsE;IACtE,IAAI,OAAO,CAAC,QAAQ,KAAK,SAAS,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAClE,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,EAAE,mBAAmB,OAAO,CAAC,EAAE,KAAK,OAAO,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;QAClG,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;YACrC,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,KAAK,EAAE,GAAG,MAAM,IAAI,CAAC,CAAC,CAAC;QAC/D,CAAC;IACH,CAAC;IAED,OAAO,MAAM,KAAK,EAAE;QAClB,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC;QAClB,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,MAAM,GAAG,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAChF,CAAC;AAED,6DAA6D;AAC7D,SAAS,QAAQ,CAAC,GAAW;IAC3B,MAAM,KAAK,GAA2B;QACpC,oCAAoC,EAAE,OAAO;QAC7C,oCAAoC,EAAE,QAAQ;QAC9C,qCAAqC,EAAE,WAAW;QAClD,qCAAqC,EAAE,aAAa;QACpD,0BAA0B,EAAE,SAAS;QACrC,oCAAoC,EAAE,WAAW;QACjD,oCAAoC,EAAE,eAAe;KACtD,CAAC;IACF,OAAO,KAAK,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC;AAC3B,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@floh-solutions/pharos-cli",
|
|
3
|
-
"version": "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/
|
|
28
|
-
"@floh-solutions/
|
|
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
|
-
- **
|
|
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
|