@floh-solutions/pharos-cli 0.1.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.
@@ -0,0 +1,87 @@
1
+ /**
2
+ * The output contract.
3
+ *
4
+ * **The consumer is a program**, so the shape of what comes out is part of the
5
+ * interface and not a presentation detail. Two rules, and everything else
6
+ * follows from them:
7
+ *
8
+ * 1. **Success goes to stdout as JSON. Failure goes to stderr as JSON, with a
9
+ * non-zero exit.** An agent must be able to tell "no results" from "the
10
+ * call failed" without reading prose, and text output makes that a guess.
11
+ * An empty list on stdout and exit 0 is a *successful* query that matched
12
+ * nothing; that is a different fact from a 403.
13
+ * 2. **The exit code says what KIND of wrong it went**, because the three
14
+ * kinds want three different responses and an agent that cannot tell them
15
+ * apart retries the one thing that will never work.
16
+ */
17
+ /**
18
+ * | code | meaning | what an agent should do |
19
+ * |---|---|---|
20
+ * | 0 | it worked | carry on |
21
+ * | 1 | the call failed | possibly retry — check `kind` and `retryAfterMs` |
22
+ * | 2 | you called it wrong, or it is not configured | **never** retry unchanged |
23
+ * | 3 | refused by a guard here | re-run with `--yes`, or raise `--max-writes` |
24
+ */
25
+ export declare const EXIT_OK = 0;
26
+ export declare const EXIT_FAILED = 1;
27
+ export declare const EXIT_USAGE = 2;
28
+ export declare const EXIT_REFUSED = 3;
29
+ export type ExitCode = 0 | 1 | 2 | 3;
30
+ export type ErrorKind = "usage" | "config" | "auth" | "refused" | "conflict" | "notFound" | "rateLimit" | "network" | "ado" | "internal";
31
+ export interface Io {
32
+ stdout: (line: string) => void;
33
+ stderr: (line: string) => void;
34
+ }
35
+ /**
36
+ * An error this CLI raised itself, as opposed to one Azure DevOps returned.
37
+ *
38
+ * `detail` is merged into the emitted error object, so a refusal can carry the
39
+ * numbers that explain it (the budget and what was spent) rather than only
40
+ * prose about them.
41
+ */
42
+ export declare class CliError extends Error {
43
+ readonly kind: ErrorKind;
44
+ readonly exitCode: ExitCode;
45
+ readonly detail: Record<string, unknown>;
46
+ /**
47
+ * Read by `ado-core`'s transport when this is thrown from inside an injected
48
+ * `fetch` — the write budget does exactly that. It means "a decision, not a
49
+ * connection failure": rethrow me as I am and do not replay the request.
50
+ * Without it a refusal comes back as `kind: "network"` with exit 1, and an
51
+ * agent retries a cap that will never lift.
52
+ */
53
+ readonly noRetry = true;
54
+ constructor(message: string, kind: ErrorKind, exitCode: ExitCode, detail?: Record<string, unknown>);
55
+ }
56
+ /** Called wrong. Exit 2 — an agent must change the call, not repeat it. */
57
+ export declare function usageError(message: string, detail?: Record<string, unknown>): CliError;
58
+ /**
59
+ * A guard here said no. Exit 3, distinct from every other failure **because it
60
+ * is the only one the caller can clear by asking again more explicitly** — and
61
+ * an agent that cannot see that difference either gives up on work it was
62
+ * allowed to do, or hammers a 403 forever.
63
+ */
64
+ export declare function refusal(message: string, detail?: Record<string, unknown>): CliError;
65
+ /** Success. One JSON object, indented — a transcript gets read by people too. */
66
+ export declare function emit(io: Io, value: unknown): ExitCode;
67
+ /** Human output. Only ever reached via `--pretty`. */
68
+ export declare function emitText(io: Io, text: string): ExitCode;
69
+ /**
70
+ * Turn anything thrown into the structured error an agent can act on, and
71
+ * return the exit code that goes with it.
72
+ *
73
+ * The mapping is the useful part. Azure DevOps failures arrive as a wall of
74
+ * status codes that all mean "no"; these are the distinctions that change what
75
+ * to do next:
76
+ *
77
+ * - **conflict** — somebody else wrote first. The work is still valid; re-read
78
+ * and re-apply. Carries both revisions so a caller can diff rather than
79
+ * guess, and says out loud that a bare comment bumps `System.Rev`, which is
80
+ * why a rev mismatch is not proof of an edit collision.
81
+ * - **auth** — exit 2, not 1. A bad PAT is a configuration fault and retrying
82
+ * it is pure noise.
83
+ * - **rateLimit** — carries `retryAfterMs`, the only case where waiting and
84
+ * repeating the identical call is correct.
85
+ */
86
+ export declare function reportError(io: Io, error: unknown): ExitCode;
87
+ //# sourceMappingURL=output.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output.d.ts","sourceRoot":"","sources":["../src/output.ts"],"names":[],"mappings":"AAUA;;;;;;;;;;;;;;;GAeG;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,OAAO,IAAI,CAAC;AACzB,eAAO,MAAM,WAAW,IAAI,CAAC;AAC7B,eAAO,MAAM,UAAU,IAAI,CAAC;AAC5B,eAAO,MAAM,YAAY,IAAI,CAAC;AAE9B,MAAM,MAAM,QAAQ,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAErC,MAAM,MAAM,SAAS,GACjB,OAAO,GACP,QAAQ,GACR,MAAM,GACN,SAAS,GACT,UAAU,GACV,UAAU,GACV,WAAW,GACX,SAAS,GACT,KAAK,GACL,UAAU,CAAC;AAEf,MAAM,WAAW,EAAE;IACjB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAChC;AAED;;;;;;GAMG;AACH,qBAAa,QAAS,SAAQ,KAAK;IACjC,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAEzC;;;;;;OAMG;IACH,QAAQ,CAAC,OAAO,QAAQ;gBAGtB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,SAAS,EACf,QAAQ,EAAE,QAAQ,EAClB,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM;CAQvC;AAED,2EAA2E;AAC3E,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAAG,QAAQ,CAE1F;AAED;;;;;GAKG;AACH,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAAG,QAAQ,CAEvF;AAED,iFAAiF;AACjF,wBAAgB,IAAI,CAAC,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,GAAG,QAAQ,CAGrD;AAED,sDAAsD;AACtD,wBAAgB,QAAQ,CAAC,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,QAAQ,CAGvD;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,GAAG,QAAQ,CAI5D"}
package/dist/output.js ADDED
@@ -0,0 +1,194 @@
1
+ import { AdoAuthError, AdoConcurrencyError, AdoConfigError, AdoError, AdoNetworkError, AdoNotFoundError, AdoRateLimitError, } from "@floh-solutions/ado-core";
2
+ /**
3
+ * The output contract.
4
+ *
5
+ * **The consumer is a program**, so the shape of what comes out is part of the
6
+ * interface and not a presentation detail. Two rules, and everything else
7
+ * follows from them:
8
+ *
9
+ * 1. **Success goes to stdout as JSON. Failure goes to stderr as JSON, with a
10
+ * non-zero exit.** An agent must be able to tell "no results" from "the
11
+ * call failed" without reading prose, and text output makes that a guess.
12
+ * An empty list on stdout and exit 0 is a *successful* query that matched
13
+ * nothing; that is a different fact from a 403.
14
+ * 2. **The exit code says what KIND of wrong it went**, because the three
15
+ * kinds want three different responses and an agent that cannot tell them
16
+ * apart retries the one thing that will never work.
17
+ */
18
+ /**
19
+ * | code | meaning | what an agent should do |
20
+ * |---|---|---|
21
+ * | 0 | it worked | carry on |
22
+ * | 1 | the call failed | possibly retry — check `kind` and `retryAfterMs` |
23
+ * | 2 | you called it wrong, or it is not configured | **never** retry unchanged |
24
+ * | 3 | refused by a guard here | re-run with `--yes`, or raise `--max-writes` |
25
+ */
26
+ export const EXIT_OK = 0;
27
+ export const EXIT_FAILED = 1;
28
+ export const EXIT_USAGE = 2;
29
+ export const EXIT_REFUSED = 3;
30
+ /**
31
+ * An error this CLI raised itself, as opposed to one Azure DevOps returned.
32
+ *
33
+ * `detail` is merged into the emitted error object, so a refusal can carry the
34
+ * numbers that explain it (the budget and what was spent) rather than only
35
+ * prose about them.
36
+ */
37
+ export class CliError extends Error {
38
+ kind;
39
+ exitCode;
40
+ detail;
41
+ /**
42
+ * Read by `ado-core`'s transport when this is thrown from inside an injected
43
+ * `fetch` — the write budget does exactly that. It means "a decision, not a
44
+ * connection failure": rethrow me as I am and do not replay the request.
45
+ * Without it a refusal comes back as `kind: "network"` with exit 1, and an
46
+ * agent retries a cap that will never lift.
47
+ */
48
+ noRetry = true;
49
+ constructor(message, kind, exitCode, detail = {}) {
50
+ super(message);
51
+ this.name = "CliError";
52
+ this.kind = kind;
53
+ this.exitCode = exitCode;
54
+ this.detail = detail;
55
+ }
56
+ }
57
+ /** Called wrong. Exit 2 — an agent must change the call, not repeat it. */
58
+ export function usageError(message, detail = {}) {
59
+ return new CliError(message, "usage", EXIT_USAGE, detail);
60
+ }
61
+ /**
62
+ * A guard here said no. Exit 3, distinct from every other failure **because it
63
+ * is the only one the caller can clear by asking again more explicitly** — and
64
+ * an agent that cannot see that difference either gives up on work it was
65
+ * allowed to do, or hammers a 403 forever.
66
+ */
67
+ export function refusal(message, detail = {}) {
68
+ return new CliError(message, "refused", EXIT_REFUSED, detail);
69
+ }
70
+ /** Success. One JSON object, indented — a transcript gets read by people too. */
71
+ export function emit(io, value) {
72
+ io.stdout(JSON.stringify(value, undefined, 2));
73
+ return EXIT_OK;
74
+ }
75
+ /** Human output. Only ever reached via `--pretty`. */
76
+ export function emitText(io, text) {
77
+ io.stdout(text);
78
+ return EXIT_OK;
79
+ }
80
+ /**
81
+ * Turn anything thrown into the structured error an agent can act on, and
82
+ * return the exit code that goes with it.
83
+ *
84
+ * The mapping is the useful part. Azure DevOps failures arrive as a wall of
85
+ * status codes that all mean "no"; these are the distinctions that change what
86
+ * to do next:
87
+ *
88
+ * - **conflict** — somebody else wrote first. The work is still valid; re-read
89
+ * and re-apply. Carries both revisions so a caller can diff rather than
90
+ * guess, and says out loud that a bare comment bumps `System.Rev`, which is
91
+ * why a rev mismatch is not proof of an edit collision.
92
+ * - **auth** — exit 2, not 1. A bad PAT is a configuration fault and retrying
93
+ * it is pure noise.
94
+ * - **rateLimit** — carries `retryAfterMs`, the only case where waiting and
95
+ * repeating the identical call is correct.
96
+ */
97
+ export function reportError(io, error) {
98
+ const { body, code } = describe(error);
99
+ io.stderr(JSON.stringify({ error: body }, undefined, 2));
100
+ return code;
101
+ }
102
+ function describe(error) {
103
+ if (error instanceof CliError) {
104
+ return {
105
+ body: { kind: error.kind, message: error.message, ...error.detail },
106
+ code: error.exitCode,
107
+ };
108
+ }
109
+ if (error instanceof AdoConcurrencyError) {
110
+ return {
111
+ body: {
112
+ kind: "conflict",
113
+ message: error.message,
114
+ status: error.status,
115
+ workItemId: error.workItemId,
116
+ expectedRev: error.expectedRev,
117
+ actualRev: error.actualRev,
118
+ hint: "Re-read the item and re-apply your change. Diff the fields you actually changed first "
119
+ + "— a comment alone bumps System.Rev, so this is not necessarily an edit collision.",
120
+ },
121
+ code: EXIT_FAILED,
122
+ };
123
+ }
124
+ if (error instanceof AdoRateLimitError) {
125
+ return {
126
+ body: {
127
+ kind: "rateLimit",
128
+ message: error.message,
129
+ status: error.status,
130
+ retryAfterMs: error.retryAfterMs,
131
+ hint: "Wait retryAfterMs and repeat the identical call.",
132
+ },
133
+ code: EXIT_FAILED,
134
+ };
135
+ }
136
+ // A bad or expired PAT is a CONFIGURATION fault, so it exits 2 with the rest
137
+ // of the "you cannot fix this by trying again" family.
138
+ if (error instanceof AdoAuthError) {
139
+ return {
140
+ body: {
141
+ kind: "auth",
142
+ message: error.message,
143
+ status: error.status,
144
+ activityId: error.activityId,
145
+ hint: "Check ADO_PAT: expired, revoked, or missing a scope. Wiki writes need vso.wiki_write "
146
+ + "and work item writes need vso.work_write; they are separate.",
147
+ },
148
+ code: EXIT_USAGE,
149
+ };
150
+ }
151
+ if (error instanceof AdoNotFoundError) {
152
+ return {
153
+ body: adoBody("notFound", error),
154
+ code: EXIT_FAILED,
155
+ };
156
+ }
157
+ if (error instanceof AdoError) {
158
+ return { body: adoBody("ado", error), code: EXIT_FAILED };
159
+ }
160
+ if (error instanceof AdoNetworkError) {
161
+ return {
162
+ body: {
163
+ kind: "network",
164
+ message: error.message,
165
+ method: error.method,
166
+ url: error.url,
167
+ },
168
+ code: EXIT_FAILED,
169
+ };
170
+ }
171
+ if (error instanceof AdoConfigError) {
172
+ return { body: { kind: "config", message: error.message }, code: EXIT_USAGE };
173
+ }
174
+ return {
175
+ body: {
176
+ kind: "internal",
177
+ message: error instanceof Error ? error.message : String(error),
178
+ },
179
+ code: EXIT_FAILED,
180
+ };
181
+ }
182
+ function adoBody(kind, error) {
183
+ return {
184
+ kind,
185
+ message: error.message,
186
+ status: error.status,
187
+ method: error.method,
188
+ url: error.url,
189
+ typeKey: error.typeKey,
190
+ // Quote this at Microsoft support. It is the only handle they accept.
191
+ activityId: error.activityId,
192
+ };
193
+ }
194
+ //# sourceMappingURL=output.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output.js","sourceRoot":"","sources":["../src/output.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,77 @@
1
+ import { AdoClient, WikiApi } from "@floh-solutions/ado-core";
2
+ import { WriteBudget } from "./budget.js";
3
+ import { type ExitCode, type Io } from "./output.js";
4
+ /**
5
+ * One invocation's world: the client, the budget it spends against, the wiki it
6
+ * resolved, and whether it is allowed to actually write.
7
+ */
8
+ export interface SessionOptions {
9
+ env: NodeJS.ProcessEnv;
10
+ organization?: string | undefined;
11
+ project?: string | undefined;
12
+ /** Wiki name or id. Resolved lazily — most commands never need it. */
13
+ wiki?: string | undefined;
14
+ budget: WriteBudget;
15
+ /** `--yes` was passed and `--dry-run` was not. */
16
+ apply: boolean;
17
+ /** `--dry-run` was passed: a preview is the requested OUTPUT, not a refusal. */
18
+ previewOnly: boolean;
19
+ pretty: boolean;
20
+ /** Injected by tests; production passes nothing and gets a real client. */
21
+ client?: AdoClient | undefined;
22
+ }
23
+ export declare class Session {
24
+ #private;
25
+ readonly client: AdoClient;
26
+ readonly wiki: WikiApi;
27
+ readonly budget: WriteBudget;
28
+ readonly apply: boolean;
29
+ readonly previewOnly: boolean;
30
+ readonly pretty: boolean;
31
+ constructor(options: SessionOptions);
32
+ /**
33
+ * The wiki to act on, resolved once.
34
+ *
35
+ * **Ambiguity is refused, not guessed.** A project usually has exactly one
36
+ * wiki and then there is nothing to decide. When it has several, silently
37
+ * picking the first means a plan lands in the wrong one and nothing says so —
38
+ * so the tie is broken only where the answer is unambiguous (a single project
39
+ * wiki among code wikis, which is the normal mixed case), and otherwise the
40
+ * names come back with the refusal so the caller can name one.
41
+ */
42
+ wikiIdentifier(): Promise<string>;
43
+ /** The project GUID and wiki id an artifact link must be built from. */
44
+ wikiForLinking(): Promise<{
45
+ name: string;
46
+ wikiId: string;
47
+ projectId: string;
48
+ }>;
49
+ }
50
+ /** What a destructive verb is about to do, described before it does it. */
51
+ export interface DestructiveAction {
52
+ /** Imperative, for the message: "delete wiki page /Plans/Foo". */
53
+ what: string;
54
+ /** The structured version, emitted either way so a preview is machine-readable. */
55
+ would: Record<string, unknown>;
56
+ }
57
+ /**
58
+ * The gate in front of anything that destroys or overwrites.
59
+ *
60
+ * Three outcomes, and the difference between the first two is the whole point:
61
+ *
62
+ * | invocation | result | exit |
63
+ * |---|---|---|
64
+ * | (no flag) | **refused**, with the preview attached | 3 |
65
+ * | `--dry-run` | preview on stdout — you asked for exactly this | 0 |
66
+ * | `--yes` | it happens | 0 |
67
+ *
68
+ * A bare invocation exits **non-zero**, and that is deliberate. Exiting 0 with
69
+ * `"applied": false` reads as success to anything that checks only the status,
70
+ * and "an agent that quietly does less looks identical to one that succeeded"
71
+ * is how a half-finished job gets reported as done (`docs/pharos-cli-plan.md`
72
+ * §2.5). `--dry-run` exits 0 because there the preview IS the deliverable.
73
+ *
74
+ * @returns `undefined` to proceed with the write, or the exit code to return.
75
+ */
76
+ export declare function approve(io: Io, session: Session, action: DestructiveAction): ExitCode | undefined;
77
+ //# sourceMappingURL=session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,OAAO,EAAgB,MAAM,0BAA0B,CAAC;AAE5E,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAsC,KAAK,QAAQ,EAAE,KAAK,EAAE,EAAE,MAAM,aAAa,CAAC;AAEzF;;;GAGG;AAEH,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,sEAAsE;IACtE,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,MAAM,EAAE,WAAW,CAAC;IACpB,kDAAkD;IAClD,KAAK,EAAE,OAAO,CAAC;IACf,gFAAgF;IAChF,WAAW,EAAE,OAAO,CAAC;IACrB,MAAM,EAAE,OAAO,CAAC;IAChB,2EAA2E;IAC3E,MAAM,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;CAChC;AAED,qBAAa,OAAO;;IAClB,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;gBAKb,OAAO,EAAE,cAAc;IAkBnC;;;;;;;;;OASG;IACG,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC;IAIvC,wEAAwE;IAClE,cAAc,IAAI,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;CAiDrF;AAED,2EAA2E;AAC3E,MAAM,WAAW,iBAAiB;IAChC,kEAAkE;IAClE,IAAI,EAAE,MAAM,CAAC;IACb,mFAAmF;IACnF,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,OAAO,CAAC,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,iBAAiB,GAAG,QAAQ,GAAG,SAAS,CAYjG"}
@@ -0,0 +1,114 @@
1
+ import { AdoClient, WikiApi } from "@floh-solutions/ado-core";
2
+ import { WriteBudget } from "./budget.js";
3
+ import { EXIT_OK, emit, refusal, usageError } from "./output.js";
4
+ export class Session {
5
+ client;
6
+ wiki;
7
+ budget;
8
+ apply;
9
+ previewOnly;
10
+ pretty;
11
+ #wikiHint;
12
+ #resolvedWiki;
13
+ constructor(options) {
14
+ this.budget = options.budget;
15
+ this.apply = options.apply;
16
+ this.previewOnly = options.previewOnly;
17
+ this.pretty = options.pretty;
18
+ this.#wikiHint = options.wiki;
19
+ this.client =
20
+ options.client
21
+ ?? AdoClient.fromEnv(options.env, {
22
+ ...(options.organization === undefined ? {} : { organization: options.organization }),
23
+ ...(options.project === undefined ? {} : { project: options.project }),
24
+ // Every write goes through here, whoever issued it.
25
+ fetch: options.budget.wrap((input, init) => globalThis.fetch(input, init)),
26
+ });
27
+ this.wiki = new WikiApi(this.client.http);
28
+ }
29
+ /**
30
+ * The wiki to act on, resolved once.
31
+ *
32
+ * **Ambiguity is refused, not guessed.** A project usually has exactly one
33
+ * wiki and then there is nothing to decide. When it has several, silently
34
+ * picking the first means a plan lands in the wrong one and nothing says so —
35
+ * so the tie is broken only where the answer is unambiguous (a single project
36
+ * wiki among code wikis, which is the normal mixed case), and otherwise the
37
+ * names come back with the refusal so the caller can name one.
38
+ */
39
+ async wikiIdentifier() {
40
+ return (await this.#resolveWiki()).name;
41
+ }
42
+ /** The project GUID and wiki id an artifact link must be built from. */
43
+ async wikiForLinking() {
44
+ const wiki = await this.#resolveWiki();
45
+ if (wiki.projectId === undefined || wiki.projectId === "") {
46
+ throw usageError(`The wiki "${wiki.name}" did not report a projectId, so an artifact link cannot be built. `
47
+ + "A link built with the project NAME is stored by Azure DevOps and resolves for nobody, "
48
+ + "so this refuses rather than writing one.");
49
+ }
50
+ return { name: wiki.name, wikiId: wiki.id, projectId: wiki.projectId };
51
+ }
52
+ async #resolveWiki() {
53
+ if (this.#resolvedWiki !== undefined)
54
+ return this.#resolvedWiki;
55
+ const wikis = await this.wiki.listWikis();
56
+ if (wikis.length === 0) {
57
+ throw usageError("This project has no wiki.");
58
+ }
59
+ if (this.#wikiHint !== undefined && this.#wikiHint !== "") {
60
+ const hint = this.#wikiHint;
61
+ const found = wikis.find((candidate) => candidate.name === hint || candidate.id === hint);
62
+ if (found === undefined) {
63
+ throw usageError(`No wiki called "${hint}" in this project.`, {
64
+ wikis: wikis.map((candidate) => candidate.name),
65
+ });
66
+ }
67
+ this.#resolvedWiki = found;
68
+ return found;
69
+ }
70
+ if (wikis.length === 1) {
71
+ this.#resolvedWiki = wikis[0];
72
+ return this.#resolvedWiki;
73
+ }
74
+ const projectWikis = wikis.filter((candidate) => candidate.type === "projectWiki");
75
+ if (projectWikis.length === 1) {
76
+ this.#resolvedWiki = projectWikis[0];
77
+ return this.#resolvedWiki;
78
+ }
79
+ throw usageError(`This project has ${wikis.length} wikis and no unambiguous default — pass --wiki <name>.`, { wikis: wikis.map((candidate) => candidate.name) });
80
+ }
81
+ }
82
+ /**
83
+ * The gate in front of anything that destroys or overwrites.
84
+ *
85
+ * Three outcomes, and the difference between the first two is the whole point:
86
+ *
87
+ * | invocation | result | exit |
88
+ * |---|---|---|
89
+ * | (no flag) | **refused**, with the preview attached | 3 |
90
+ * | `--dry-run` | preview on stdout — you asked for exactly this | 0 |
91
+ * | `--yes` | it happens | 0 |
92
+ *
93
+ * A bare invocation exits **non-zero**, and that is deliberate. Exiting 0 with
94
+ * `"applied": false` reads as success to anything that checks only the status,
95
+ * and "an agent that quietly does less looks identical to one that succeeded"
96
+ * is how a half-finished job gets reported as done (`docs/pharos-cli-plan.md`
97
+ * §2.5). `--dry-run` exits 0 because there the preview IS the deliverable.
98
+ *
99
+ * @returns `undefined` to proceed with the write, or the exit code to return.
100
+ */
101
+ export function approve(io, session, action) {
102
+ if (session.previewOnly) {
103
+ emit(io, { applied: false, dryRun: true, ...action.would });
104
+ return EXIT_OK;
105
+ }
106
+ if (!session.apply) {
107
+ throw refusal(`Refusing to ${action.what} without --yes. Nothing was changed.`, {
108
+ ...action.would,
109
+ hint: "Re-run with --yes to apply it, or --dry-run to see this without the refusal.",
110
+ });
111
+ }
112
+ return undefined;
113
+ }
114
+ //# sourceMappingURL=session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * What a comment verb is addressing: a work item, or a wiki page.
3
+ *
4
+ * The two comment surfaces are different APIs with different quirks, but from
5
+ * where an agent stands they are one idea — "the discussion about this thing" —
6
+ * so `pharos comment` takes either and the target says which.
7
+ *
8
+ * **The rule is that a wiki page is an absolute path, and that is not a
9
+ * convention invented here.** The Azure DevOps wiki API answers 404 for a page
10
+ * path without its leading slash; the MCP server's `add_artifact_link` strips
11
+ * it, which is exactly how links get written that resolve for nobody. So the
12
+ * spelling that disambiguates is also the only spelling that works, and a
13
+ * target that omits it is rejected with the reason rather than quietly fixed —
14
+ * somebody passing `Plans/Foo` has a bug somewhere upstream worth finding.
15
+ */
16
+ export type Target = {
17
+ kind: "workItem";
18
+ id: number;
19
+ } | {
20
+ kind: "wikiPage";
21
+ path: string;
22
+ };
23
+ export declare function parseTarget(raw: string): Target;
24
+ export declare function describeTarget(target: Target): string;
25
+ //# sourceMappingURL=target.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"target.d.ts","sourceRoot":"","sources":["../src/target.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,MAAM,GACd;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAChC;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvC,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAsB/C;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAErD"}
package/dist/target.js ADDED
@@ -0,0 +1,23 @@
1
+ import { usageError } from "./output.js";
2
+ export function parseTarget(raw) {
3
+ const value = raw.trim();
4
+ if (/^\d+$/.test(value)) {
5
+ const id = Number(value);
6
+ if (id <= 0)
7
+ throw usageError(`"${raw}" is not a work item id.`, { target: raw });
8
+ return { kind: "workItem", id };
9
+ }
10
+ if (value.startsWith("/")) {
11
+ if (value === "/") {
12
+ throw usageError("The wiki root has no comments — name a page.", { target: raw });
13
+ }
14
+ return { kind: "wikiPage", path: value };
15
+ }
16
+ throw usageError(`Cannot tell what "${raw}" is. A work item is its number (210); a wiki page is an absolute `
17
+ + `path (/Plans/Foo). The leading slash is required by the wiki API itself, not by this tool `
18
+ + `— it answers 404 without one.`, { target: raw });
19
+ }
20
+ export function describeTarget(target) {
21
+ return target.kind === "workItem" ? `#${target.id}` : target.path;
22
+ }
23
+ //# sourceMappingURL=target.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"target.js","sourceRoot":"","sources":["../src/target.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAqBzC,MAAM,UAAU,WAAW,CAAC,GAAW;IACrC,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IAEzB,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACxB,MAAM,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;QACzB,IAAI,EAAE,IAAI,CAAC;YAAE,MAAM,UAAU,CAAC,IAAI,GAAG,0BAA0B,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;QAClF,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC;IAClC,CAAC;IAED,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QAC1B,IAAI,KAAK,KAAK,GAAG,EAAE,CAAC;YAClB,MAAM,UAAU,CAAC,8CAA8C,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;QACpF,CAAC;QACD,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;IAC3C,CAAC;IAED,MAAM,UAAU,CACd,qBAAqB,GAAG,oEAAoE;UACxF,4FAA4F;UAC5F,+BAA+B,EACnC,EAAE,MAAM,EAAE,GAAG,EAAE,CAChB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,MAAc;IAC3C,OAAO,MAAM,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC;AACpE,CAAC"}
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@floh-solutions/pharos-cli",
3
+ "version": "0.1.0",
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
+ "license": "UNLICENSED",
6
+ "author": "FLOH Solutions",
7
+ "type": "module",
8
+ "main": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ }
15
+ },
16
+ "bin": {
17
+ "pharos": "./dist/bin.js"
18
+ },
19
+ "files": [
20
+ "dist"
21
+ ],
22
+ "engines": {
23
+ "node": ">=22"
24
+ },
25
+ "scripts": {
26
+ "build": "tsc -p tsconfig.build.json",
27
+ "typecheck": "tsc -p tsconfig.json --noEmit",
28
+ "test": "vitest run",
29
+ "test:watch": "vitest",
30
+ "test:live": "ADO_LIVE=1 vitest run",
31
+ "start": "node dist/bin.js"
32
+ },
33
+ "dependencies": {
34
+ "@floh-solutions/ado-core": "workspace:*",
35
+ "@floh-solutions/plan-to-board": "workspace:*"
36
+ },
37
+ "devDependencies": {
38
+ "@types/node": "^22.10.2",
39
+ "typescript": "^5.7.2",
40
+ "vitest": "^3.0.5"
41
+ }
42
+ }