@akagilnc/pi-workflow-roles 0.1.2026 → 0.1.2041
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/README.md +88 -1
- package/README.zh-CN.md +88 -1
- package/dist/compliance-transport.js +24 -19
- package/dist/evidence-child-executor.js +4 -0
- package/dist/package-owned-tool-idle.js +66 -4
- package/dist/public-cli/main.js +1055 -443
- package/package.json +1 -1
- package/src/compliance-transport.ts +24 -19
- package/src/evidence-child-executor.ts +4 -0
- package/src/package-owned-tool-idle.ts +64 -7
- package/src/public-cli/cli.ts +95 -53
- package/src/public-cli/invocation.ts +295 -404
- package/src/public-cli/option-definitions.ts +1173 -0
package/package.json
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
} from "./evidence-child-executor.ts";
|
|
8
8
|
import { createAuditorDossierTool } from "./auditor-dossier-tool.ts";
|
|
9
9
|
import type { DossierObservation } from "./dossier-resolution.ts";
|
|
10
|
+
import { withPackageOwnedToolIdleSuspended } from "./package-owned-tool-idle.ts";
|
|
10
11
|
import type { NoReceiptLifecycleFacts } from "./receipt-delivery-policy.ts";
|
|
11
12
|
|
|
12
13
|
export type ComplianceCompletion = AuditorCompletion;
|
|
@@ -116,24 +117,28 @@ export type RunComplianceAuditOptions = {
|
|
|
116
117
|
};
|
|
117
118
|
|
|
118
119
|
export async function runComplianceAudit(options: RunComplianceAuditOptions): Promise<ComplianceDecision> {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
120
|
+
// #339: only the real compliance-audit await leaves the outer package-owned
|
|
121
|
+
// idle owner. Pre/post-audit work stays under the single outer backstop.
|
|
122
|
+
return withPackageOwnedToolIdleSuspended(async () => {
|
|
123
|
+
const prompt = options.serializedInput ?? AUDITOR_DOSSIER_PROMPT;
|
|
124
|
+
const receipt = await executeAuditorChild({
|
|
125
|
+
tool: options.tool,
|
|
126
|
+
dossierTool: createAuditorDossierTool(options.runDirectory),
|
|
127
|
+
systemPrompt: options.systemPrompt,
|
|
128
|
+
prompt,
|
|
129
|
+
roleLabel: options.roleLabel,
|
|
130
|
+
context: options.context,
|
|
131
|
+
retainResponse: (response) => retainComplianceResponse(options.context, response),
|
|
132
|
+
...(options.runCompletion === undefined ? {} : { runCompletion: options.runCompletion }),
|
|
133
|
+
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
134
|
+
});
|
|
135
|
+
if (receipt.noReceiptLifecycle !== undefined) {
|
|
136
|
+
return {
|
|
137
|
+
status: "no-receipt",
|
|
138
|
+
...receipt.noReceiptLifecycle,
|
|
139
|
+
...(receipt.response.usage === undefined ? {} : { usage: receipt.response.usage }),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
return readComplianceCandidate(receipt.decision, receipt.response.usage);
|
|
130
143
|
});
|
|
131
|
-
if (receipt.noReceiptLifecycle !== undefined) {
|
|
132
|
-
return {
|
|
133
|
-
status: "no-receipt",
|
|
134
|
-
...receipt.noReceiptLifecycle,
|
|
135
|
-
...(receipt.response.usage === undefined ? {} : { usage: receipt.response.usage }),
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
return readComplianceCandidate(receipt.decision, receipt.response.usage);
|
|
139
144
|
}
|
|
@@ -381,6 +381,10 @@ export async function createInheritedRuntime(options: InheritedRuntimeOptions):
|
|
|
381
381
|
};
|
|
382
382
|
|
|
383
383
|
runtime.registerNativeProvider(provider);
|
|
384
|
+
// registerNativeProvider fires a background refresh; await it so child
|
|
385
|
+
// AgentSession.prompt sees configured auth without racing void refresh
|
|
386
|
+
// (mock timers / slow CI otherwise stall before the compliance stream).
|
|
387
|
+
await runtime.refresh({ allowNetwork: false });
|
|
384
388
|
return state;
|
|
385
389
|
}
|
|
386
390
|
|
|
@@ -5,12 +5,18 @@
|
|
|
5
5
|
* Real producing onUpdate resets; final resolve/reject clears; timeout throws so
|
|
6
6
|
* Pi settles the current call as an LLM-visible isError tool result. No retry,
|
|
7
7
|
* role failure, process termination, signal abort, config, or Pi built-in coverage.
|
|
8
|
+
*
|
|
9
|
+
* #339: do not name-exempt whole terminating tools. Outer idle stays armed for
|
|
10
|
+
* pre/post-audit work. Only the real compliance-audit await suspends this single
|
|
11
|
+
* layer (ADR 0059 owns that interval); resume re-arms the same outer backstop.
|
|
8
12
|
*/
|
|
13
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
9
14
|
import type { ExtensionAPI, ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
10
15
|
|
|
11
16
|
import {
|
|
12
17
|
DEFAULT_STREAM_IDLE_TIMEOUT_MS,
|
|
13
18
|
createStreamIdleGuard,
|
|
19
|
+
type StreamIdleGuard,
|
|
14
20
|
} from "./stream-idle-guard.ts";
|
|
15
21
|
import { isProducingToolUpdate } from "./tool-execution-observation.ts";
|
|
16
22
|
|
|
@@ -19,6 +25,17 @@ export const PACKAGE_OWNED_TOOL_IDLE_TIMEOUT_CODE = "AK_PACKAGE_OWNED_TOOL_IDLE_
|
|
|
19
25
|
|
|
20
26
|
const WRAPPED = Symbol.for("ak.packageOwnedToolIdleWrapped");
|
|
21
27
|
|
|
28
|
+
type PackageOwnedToolIdleSuspension = {
|
|
29
|
+
suspend(): void;
|
|
30
|
+
resume(): void;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Active outer package-owned execute idle, if any. Nested tool executes install
|
|
35
|
+
* their own store; compliance audit only suspends the store visible at await time.
|
|
36
|
+
*/
|
|
37
|
+
const packageOwnedToolIdleScope = new AsyncLocalStorage<PackageOwnedToolIdleSuspension>();
|
|
38
|
+
|
|
22
39
|
export class PackageOwnedToolIdleTimeoutError extends Error {
|
|
23
40
|
readonly code = PACKAGE_OWNED_TOOL_IDLE_TIMEOUT_CODE;
|
|
24
41
|
readonly idleTimeoutMs = PACKAGE_OWNED_TOOL_IDLE_TIMEOUT_MS;
|
|
@@ -52,6 +69,24 @@ function isPackageOwnedToolActivityUpdate(partialResult: unknown): boolean {
|
|
|
52
69
|
return true;
|
|
53
70
|
}
|
|
54
71
|
|
|
72
|
+
/**
|
|
73
|
+
* #339: suspend the active package-owned execute idle for one real compliance
|
|
74
|
+
* audit await. Single-layer only — production has one runComplianceAudit seam.
|
|
75
|
+
* No-op outside a wrapped package-owned execute. No second timeout or retry.
|
|
76
|
+
*/
|
|
77
|
+
export async function withPackageOwnedToolIdleSuspended<T>(
|
|
78
|
+
run: () => Promise<T>,
|
|
79
|
+
): Promise<T> {
|
|
80
|
+
const scope = packageOwnedToolIdleScope.getStore();
|
|
81
|
+
if (scope === undefined) return run();
|
|
82
|
+
scope.suspend();
|
|
83
|
+
try {
|
|
84
|
+
return await run();
|
|
85
|
+
} finally {
|
|
86
|
+
scope.resume();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
55
90
|
/**
|
|
56
91
|
* Single shared execute wrapper for package-owned tool definitions.
|
|
57
92
|
* Idempotent: wrapping twice returns the same protected definition.
|
|
@@ -72,7 +107,8 @@ export function wrapPackageOwnedToolDefinition<T extends PackageOwnedToolLike>(t
|
|
|
72
107
|
const onUpdate = args[3] as ((partialResult: unknown) => void) | undefined;
|
|
73
108
|
return new Promise((resolve, reject) => {
|
|
74
109
|
let settled = false;
|
|
75
|
-
|
|
110
|
+
let suspended = false;
|
|
111
|
+
let idle: StreamIdleGuard = createStreamIdleGuard({
|
|
76
112
|
idleTimeoutMs: PACKAGE_OWNED_TOOL_IDLE_TIMEOUT_MS,
|
|
77
113
|
});
|
|
78
114
|
|
|
@@ -88,6 +124,25 @@ export function wrapPackageOwnedToolDefinition<T extends PackageOwnedToolLike>(t
|
|
|
88
124
|
};
|
|
89
125
|
idle.signal.addEventListener("abort", onIdle, { once: true });
|
|
90
126
|
|
|
127
|
+
const suspension: PackageOwnedToolIdleSuspension = {
|
|
128
|
+
suspend(): void {
|
|
129
|
+
if (settled || suspended) return;
|
|
130
|
+
suspended = true;
|
|
131
|
+
// ADR 0059 owns the audit interval — drop this layer until audit returns.
|
|
132
|
+
idle.signal.removeEventListener("abort", onIdle);
|
|
133
|
+
idle.dispose();
|
|
134
|
+
},
|
|
135
|
+
resume(): void {
|
|
136
|
+
if (settled || !suspended) return;
|
|
137
|
+
suspended = false;
|
|
138
|
+
// Fresh single-layer silence window for post-audit work (e.g. cleanup).
|
|
139
|
+
idle = createStreamIdleGuard({
|
|
140
|
+
idleTimeoutMs: PACKAGE_OWNED_TOOL_IDLE_TIMEOUT_MS,
|
|
141
|
+
});
|
|
142
|
+
idle.signal.addEventListener("abort", onIdle, { once: true });
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
|
|
91
146
|
const guardedOnUpdate = onUpdate === undefined
|
|
92
147
|
? undefined
|
|
93
148
|
: (partialResult: unknown) => {
|
|
@@ -101,12 +156,14 @@ export function wrapPackageOwnedToolDefinition<T extends PackageOwnedToolLike>(t
|
|
|
101
156
|
callArgs[2] = signal;
|
|
102
157
|
callArgs[3] = guardedOnUpdate;
|
|
103
158
|
|
|
104
|
-
void
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
159
|
+
void packageOwnedToolIdleScope.run(suspension, async () => {
|
|
160
|
+
try {
|
|
161
|
+
const result = await originalExecute(...callArgs);
|
|
162
|
+
settle(() => resolve(result));
|
|
163
|
+
} catch (error) {
|
|
164
|
+
settle(() => reject(error));
|
|
165
|
+
}
|
|
166
|
+
});
|
|
110
167
|
});
|
|
111
168
|
};
|
|
112
169
|
(wrappedExecute as { [WRAPPED]?: boolean })[WRAPPED] = true;
|
package/src/public-cli/cli.ts
CHANGED
|
@@ -34,6 +34,14 @@ import {
|
|
|
34
34
|
parseReviewerArgv,
|
|
35
35
|
parseTaishiArgv,
|
|
36
36
|
} from "./invocation.ts";
|
|
37
|
+
import {
|
|
38
|
+
createTypedOptionConsumer,
|
|
39
|
+
optionsForOwner,
|
|
40
|
+
projectOwnerOptions,
|
|
41
|
+
renderOwnerOptionHelpLines,
|
|
42
|
+
type PublicOptionDefinition,
|
|
43
|
+
type TypedOptionConsumer,
|
|
44
|
+
} from "./option-definitions.ts";
|
|
37
45
|
import { runPublicCoder, runPublicCoderResume } from "./coder-run.ts";
|
|
38
46
|
import { runPublicCollector } from "./collector-run.ts";
|
|
39
47
|
import { runPublicDoctor } from "./doctor-run.ts";
|
|
@@ -64,21 +72,25 @@ export { CliUsageError } from "./cli-errors.ts";
|
|
|
64
72
|
export type { CliIo } from "./cli-io.ts";
|
|
65
73
|
|
|
66
74
|
/**
|
|
67
|
-
* Sole production map: public role command → argv parser.
|
|
68
|
-
* cli handlers consume this table; no parallel set.
|
|
75
|
+
* Sole production map: public role command → argv parser + option definitions.
|
|
76
|
+
* cli handlers and help consume this table; no parallel spelling set (#342).
|
|
69
77
|
*/
|
|
70
78
|
export const PUBLIC_ROLE_ARGV = {
|
|
71
|
-
judge: { parse: parseJudgeArgv },
|
|
72
|
-
coder: { parse: parseCoderArgv },
|
|
73
|
-
fixer: { parse: parseFixerArgv },
|
|
74
|
-
collector: { parse: parseCollectorArgv },
|
|
75
|
-
doctor: { parse: parseDoctorArgv },
|
|
76
|
-
merger: { parse: parseMergerArgv },
|
|
77
|
-
reviewer: { parse: parseReviewerArgv },
|
|
79
|
+
judge: { parse: parseJudgeArgv, options: optionsForOwner("judge") },
|
|
80
|
+
coder: { parse: parseCoderArgv, options: optionsForOwner("coder") },
|
|
81
|
+
fixer: { parse: parseFixerArgv, options: optionsForOwner("fixer") },
|
|
82
|
+
collector: { parse: parseCollectorArgv, options: optionsForOwner("collector") },
|
|
83
|
+
doctor: { parse: parseDoctorArgv, options: optionsForOwner("doctor") },
|
|
84
|
+
merger: { parse: parseMergerArgv, options: optionsForOwner("merger") },
|
|
85
|
+
reviewer: { parse: parseReviewerArgv, options: optionsForOwner("reviewer") },
|
|
78
86
|
/** Deterministic analysis seat (#336) — argv parse only; no LLM admission. */
|
|
79
|
-
taishi: { parse: parseTaishiArgv },
|
|
87
|
+
taishi: { parse: parseTaishiArgv, options: optionsForOwner("taishi") },
|
|
80
88
|
} as const;
|
|
81
89
|
|
|
90
|
+
/** Global public options — same typed table as role rows (#342). */
|
|
91
|
+
export const PUBLIC_GLOBAL_OPTIONS: readonly PublicOptionDefinition[] =
|
|
92
|
+
optionsForOwner("global");
|
|
93
|
+
|
|
82
94
|
type TakenPublicGlobalFlag =
|
|
83
95
|
| { flag: "help"; consume: 1 }
|
|
84
96
|
| { flag: "model"; consume: 1 | 2; value: string | undefined }
|
|
@@ -86,43 +98,33 @@ type TakenPublicGlobalFlag =
|
|
|
86
98
|
|
|
87
99
|
/**
|
|
88
100
|
* If `argv[index]` is a public global flag, describe its span and payload.
|
|
89
|
-
*
|
|
101
|
+
* Spellings + repeatable come solely from PUBLIC_OPTION_TABLE.global via the
|
|
102
|
+
* shared typed consumer (#342).
|
|
90
103
|
*/
|
|
91
104
|
function takePublicGlobalFlag(
|
|
92
105
|
argv: readonly string[],
|
|
93
106
|
index: number,
|
|
107
|
+
options: TypedOptionConsumer,
|
|
94
108
|
): TakenPublicGlobalFlag | undefined {
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
if (
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const value = argv[index + 1];
|
|
102
|
-
if (value === undefined) {
|
|
103
|
-
return { flag: "model", consume: 1, value: undefined };
|
|
104
|
-
}
|
|
105
|
-
return { flag: "model", consume: 2, value };
|
|
109
|
+
const tokens = argv.slice(index);
|
|
110
|
+
const taken = options.takeDashed(tokens as string[]);
|
|
111
|
+
if (taken === undefined) return undefined;
|
|
112
|
+
const consumed = argv.length - index - tokens.length;
|
|
113
|
+
if (taken.def.id === "help") {
|
|
114
|
+
return { flag: "help", consume: consumed as 1 };
|
|
106
115
|
}
|
|
107
|
-
if (
|
|
116
|
+
if (taken.def.id === "model") {
|
|
108
117
|
return {
|
|
109
118
|
flag: "model",
|
|
110
|
-
consume: 1,
|
|
111
|
-
value:
|
|
119
|
+
consume: consumed as 1 | 2,
|
|
120
|
+
value: taken.value,
|
|
112
121
|
};
|
|
113
122
|
}
|
|
114
|
-
if (
|
|
115
|
-
const raw = argv[index + 1];
|
|
116
|
-
if (raw === undefined) {
|
|
117
|
-
return { flag: "thinking", consume: 1, raw: undefined };
|
|
118
|
-
}
|
|
119
|
-
return { flag: "thinking", consume: 2, raw };
|
|
120
|
-
}
|
|
121
|
-
if (token.startsWith("--thinking=")) {
|
|
123
|
+
if (taken.def.id === "thinking") {
|
|
122
124
|
return {
|
|
123
125
|
flag: "thinking",
|
|
124
|
-
consume: 1,
|
|
125
|
-
raw:
|
|
126
|
+
consume: consumed as 1 | 2,
|
|
127
|
+
raw: taken.value,
|
|
126
128
|
};
|
|
127
129
|
}
|
|
128
130
|
return undefined;
|
|
@@ -231,17 +233,18 @@ function parseArgv(argv: readonly string[]): ParsedGlobal {
|
|
|
231
233
|
let thinking: PublicThinkingLevel | undefined;
|
|
232
234
|
let help = false;
|
|
233
235
|
const positional: string[] = [];
|
|
236
|
+
const globalOptions = createTypedOptionConsumer(PUBLIC_GLOBAL_OPTIONS);
|
|
234
237
|
|
|
235
238
|
// Global flags may appear before or after the subcommand
|
|
236
239
|
// (`ak-role --model x roles` and `ak-role roles --model x`).
|
|
237
|
-
// Grammar authority:
|
|
240
|
+
// Grammar authority: shared typed consumer over PUBLIC_OPTION_TABLE.global.
|
|
238
241
|
while (args.length > 0) {
|
|
239
242
|
if (args[0] === "--") {
|
|
240
243
|
args.shift();
|
|
241
244
|
positional.push(...args);
|
|
242
245
|
break;
|
|
243
246
|
}
|
|
244
|
-
const taken = takePublicGlobalFlag(args, 0);
|
|
247
|
+
const taken = takePublicGlobalFlag(args, 0, globalOptions);
|
|
245
248
|
if (taken !== undefined) {
|
|
246
249
|
if (taken.flag === "help") {
|
|
247
250
|
help = true;
|
|
@@ -292,9 +295,31 @@ export function helpDocument() {
|
|
|
292
295
|
executable: "ak-role",
|
|
293
296
|
capabilities: listHelpCapabilities(),
|
|
294
297
|
internalEntrypoint: INTERNAL_ROLE_ENTRYPOINT_RELATIVE,
|
|
298
|
+
/** #342 structured global options from the sole option table. */
|
|
299
|
+
globalOptions: projectOwnerOptions("global"),
|
|
295
300
|
};
|
|
296
301
|
}
|
|
297
302
|
|
|
303
|
+
/** Structured help facts for one public command/role (#342). */
|
|
304
|
+
export function helpDocumentForCommand(command: string) {
|
|
305
|
+
if (command === "global") {
|
|
306
|
+
return {
|
|
307
|
+
command: "global" as const,
|
|
308
|
+
kind: "global" as const,
|
|
309
|
+
options: projectOwnerOptions("global"),
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
if (command in PUBLIC_ROLE_ARGV) {
|
|
313
|
+
const owner = command as keyof typeof PUBLIC_ROLE_ARGV;
|
|
314
|
+
return {
|
|
315
|
+
command: owner,
|
|
316
|
+
kind: owner === "taishi" ? ("deterministic" as const) : ("role" as const),
|
|
317
|
+
options: projectOwnerOptions(owner),
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
return undefined;
|
|
321
|
+
}
|
|
322
|
+
|
|
298
323
|
function renderHelp(): string {
|
|
299
324
|
const doc = helpDocument();
|
|
300
325
|
const lines: string[] = [
|
|
@@ -324,15 +349,43 @@ function renderHelp(): string {
|
|
|
324
349
|
lines.push(` ${cap.name}`);
|
|
325
350
|
}
|
|
326
351
|
}
|
|
352
|
+
lines.push("", "Global options:");
|
|
353
|
+
lines.push(...renderOwnerOptionHelpLines("global"));
|
|
327
354
|
lines.push(
|
|
328
355
|
"",
|
|
329
|
-
"
|
|
356
|
+
"Role options: ak-role help <command>",
|
|
330
357
|
"Persistent config: ak-role config set <seat> <provider/model:thinking>",
|
|
331
358
|
"Effective seats: ak-role roles",
|
|
332
359
|
);
|
|
333
360
|
return `${lines.join("\n")}\n`;
|
|
334
361
|
}
|
|
335
362
|
|
|
363
|
+
function renderCommandHelp(command: string): string | undefined {
|
|
364
|
+
const caps = listHelpCapabilities();
|
|
365
|
+
const match = caps.find((cap) => cap.name === command);
|
|
366
|
+
if (match === undefined) return undefined;
|
|
367
|
+
const lines: string[] = [];
|
|
368
|
+
if (match.kind === "support") {
|
|
369
|
+
lines.push(`command\t${match.name}\tkind\tsupport`);
|
|
370
|
+
} else if (match.kind === "deterministic") {
|
|
371
|
+
lines.push(`command\t${match.name}\tkind\tdeterministic`);
|
|
372
|
+
} else {
|
|
373
|
+
lines.push(
|
|
374
|
+
`command\t${match.name}\tkind\trole\tphases\t${match.phases
|
|
375
|
+
.map((p) => (p === null ? "none" : p))
|
|
376
|
+
.join(",")}\tdefault\t${match.defaultPhase ?? "none"}`,
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
if (command in PUBLIC_ROLE_ARGV) {
|
|
380
|
+
lines.push(
|
|
381
|
+
...renderOwnerOptionHelpLines(
|
|
382
|
+
command as keyof typeof PUBLIC_ROLE_ARGV,
|
|
383
|
+
),
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
return `${lines.join("\n")}\n`;
|
|
387
|
+
}
|
|
388
|
+
|
|
336
389
|
function renderRoles(seats: readonly EffectiveSeat[]): string {
|
|
337
390
|
const lines: string[] = ["seat\tkind\tsource\tmodel"];
|
|
338
391
|
for (const seat of seats) {
|
|
@@ -430,25 +483,14 @@ export async function runAkRole(
|
|
|
430
483
|
parsed.command === undefined ||
|
|
431
484
|
parsed.command === "help"
|
|
432
485
|
) {
|
|
433
|
-
// Layered help: `help <topic>`
|
|
486
|
+
// Layered help: `help <topic>` derives from the typed registry + option table (#342).
|
|
434
487
|
if (parsed.command === "help" && parsed.args[0] !== undefined) {
|
|
435
488
|
const topic = parsed.args[0];
|
|
436
|
-
const
|
|
437
|
-
|
|
438
|
-
if (match === undefined) {
|
|
489
|
+
const rendered = renderCommandHelp(topic);
|
|
490
|
+
if (rendered === undefined) {
|
|
439
491
|
throw new CliUsageError(`unknown help topic: ${topic}`);
|
|
440
492
|
}
|
|
441
|
-
|
|
442
|
-
io.stdout(`command\t${match.name}\tkind\tsupport\n`);
|
|
443
|
-
} else if (match.kind === "deterministic") {
|
|
444
|
-
io.stdout(`command\t${match.name}\tkind\tdeterministic\n`);
|
|
445
|
-
} else {
|
|
446
|
-
io.stdout(
|
|
447
|
-
`command\t${match.name}\tkind\trole\tphases\t${match.phases
|
|
448
|
-
.map((p) => (p === null ? "none" : p))
|
|
449
|
-
.join(",")}\tdefault\t${match.defaultPhase ?? "none"}\n`,
|
|
450
|
-
);
|
|
451
|
-
}
|
|
493
|
+
io.stdout(rendered);
|
|
452
494
|
return { exitCode: 0 };
|
|
453
495
|
}
|
|
454
496
|
io.stdout(renderHelp());
|