@akagilnc/pi-workflow-roles 0.1.2033 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akagilnc/pi-workflow-roles",
3
- "version": "0.1.2033",
3
+ "version": "0.1.2041",
4
4
  "description": "Soul-bound workflow roles for Pi",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -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
- * Sole global-flag grammar for parseArgv.
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 token = argv[index];
96
- if (token === undefined) return undefined;
97
- if (token === "--help" || token === "-h") {
98
- return { flag: "help", consume: 1 };
99
- }
100
- if (token === "--model") {
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 (token.startsWith("--model=")) {
116
+ if (taken.def.id === "model") {
108
117
  return {
109
118
  flag: "model",
110
- consume: 1,
111
- value: token.slice("--model=".length),
119
+ consume: consumed as 1 | 2,
120
+ value: taken.value,
112
121
  };
113
122
  }
114
- if (token === "--thinking") {
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: token.slice("--thinking=".length),
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: takePublicGlobalFlag above.
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
- "Global options: --model provider/model --thinking level",
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>` still derives from the same typed registry.
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 caps = listHelpCapabilities();
437
- const match = caps.find((cap) => cap.name === topic);
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
- if (match.kind === "support") {
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());