@danypops/papyrus 0.42.0 → 0.42.2

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.
Files changed (73) hide show
  1. package/package.json +2 -2
  2. package/src/adapters/sqlite-artifact-scope-store.ts +18 -9
  3. package/src/adapters/sqlite-artifact-store.ts +7 -5
  4. package/src/adapters/sqlite-discussion-round-store.ts +26 -17
  5. package/src/adapters/sqlite-gate-runner.ts +1 -1
  6. package/src/adapters/sqlite-graph-projection-store.ts +14 -10
  7. package/src/adapters/sqlite-log-store.ts +36 -17
  8. package/src/adapters/sqlite-note-event-store.ts +20 -16
  9. package/src/adapters/sqlite-session-identity-store.ts +13 -7
  10. package/src/adapters/sqlite-task-event-store.ts +29 -21
  11. package/src/adapters/sqlite-task-focus-store.ts +36 -10
  12. package/src/adapters/sqlite-task-lease-store.ts +12 -6
  13. package/src/adapters/sqlite-task-scope-store.ts +25 -14
  14. package/src/artifact-relationship-view.ts +3 -3
  15. package/src/artifact-subtree.ts +4 -2
  16. package/src/authority-registry.ts +2 -1
  17. package/src/cli.ts +785 -179
  18. package/src/client.ts +46 -20
  19. package/src/constants.ts +15 -4
  20. package/src/daemon-state.ts +4 -12
  21. package/src/daemon.ts +31 -9
  22. package/src/db.ts +119 -98
  23. package/src/discussion-service.ts +109 -44
  24. package/src/domain/artifact-event.ts +17 -4
  25. package/src/domain/artifact.ts +3 -1
  26. package/src/domain/blueprint-definition.ts +26 -31
  27. package/src/domain/checklist.ts +20 -17
  28. package/src/domain/discussion.ts +37 -18
  29. package/src/domain/gate.ts +7 -7
  30. package/src/domain/log-entry.ts +1 -1
  31. package/src/domain/note-event.ts +20 -7
  32. package/src/domain/task-event.ts +17 -7
  33. package/src/domain-services.ts +241 -110
  34. package/src/graph-projection-service.ts +34 -8
  35. package/src/id-migration.ts +17 -4
  36. package/src/index.ts +16 -11
  37. package/src/log-service.ts +6 -5
  38. package/src/log.ts +19 -0
  39. package/src/modules/discuss.ts +63 -28
  40. package/src/modules/docs.ts +74 -17
  41. package/src/modules/graph-projection.ts +20 -9
  42. package/src/modules/logs.ts +33 -21
  43. package/src/modules/notes.ts +66 -28
  44. package/src/modules/playbooks.ts +88 -29
  45. package/src/modules/rules.ts +57 -15
  46. package/src/modules/session-identity.ts +6 -2
  47. package/src/modules/tasks.ts +142 -67
  48. package/src/note-service.ts +11 -7
  49. package/src/ops.ts +131 -68
  50. package/src/playbook-definition.ts +56 -17
  51. package/src/playbook-execution.ts +13 -3
  52. package/src/ports/note-event-store.ts +6 -4
  53. package/src/ports/task-event-store.ts +9 -6
  54. package/src/ports/task-focus-store.ts +17 -4
  55. package/src/ports/task-lease-store.ts +9 -4
  56. package/src/ports/task-scope-store.ts +3 -1
  57. package/src/service.ts +143 -89
  58. package/src/session-identity-service.ts +10 -2
  59. package/src/task-context.ts +28 -16
  60. package/src/task-execution.ts +4 -12
  61. package/src/task-graph-view.ts +12 -12
  62. package/src/task-relationship-view.ts +1 -3
  63. package/src/task-service.ts +167 -72
  64. package/src/vehicle/artifact-trash-vehicle.ts +25 -13
  65. package/src/vehicle/artifact-vehicle-shared.ts +28 -7
  66. package/src/vehicle/docs-vehicle.ts +48 -16
  67. package/src/vehicle/notes-vehicle.ts +24 -6
  68. package/src/vehicle/papyrus-vehicle.ts +14 -3
  69. package/src/vehicle/playbooks-vehicle.ts +87 -18
  70. package/src/vehicle/rules-vehicle.ts +58 -21
  71. package/src/vehicle/tasks-vehicle.ts +366 -54
  72. package/src/version.ts +1 -1
  73. package/src/workflow-execution.ts +71 -52
package/src/cli.ts CHANGED
@@ -6,11 +6,11 @@ import { join } from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
7
  import { createNodeServiceInstallDeps, generateSystemdUnit, installUserService, type ServiceSpec } from "@danypops/vehicle-server/service";
8
8
  import { connectPapyrusClient, type PapyrusClient } from "./client.ts";
9
- import { DAEMON_UNIT_NAME, TASK_EXECUTION_MAX_NODES, dbPath } from "./constants.ts";
9
+ import { DAEMON_UNIT_NAME, dbPath, TASK_EXECUTION_MAX_NODES } from "./constants.ts";
10
10
  import { serveMain } from "./daemon.ts";
11
11
  import { openDb } from "./db.ts";
12
- import { applyIdMigration, mirrorDatabase, planIdMigration, verifyIdMigration, type IdMigrationPlan } from "./id-migration.ts";
13
12
  import type { GateResult } from "./domain/gate.ts";
13
+ import { applyIdMigration, type IdMigrationPlan, mirrorDatabase, planIdMigration, verifyIdMigration } from "./id-migration.ts";
14
14
  import type { TaskExecutionPlan } from "./task-execution.ts";
15
15
  import type { TaskBlockage, TaskCompletion } from "./task-service.ts";
16
16
 
@@ -52,7 +52,7 @@ export function renderSystemdUnit(options: SystemdUnitOptions): string {
52
52
  }
53
53
 
54
54
  function unitPath(): string {
55
- const configHome = process.env["XDG_CONFIG_HOME"] ?? join(homedir(), ".config");
55
+ const configHome = process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config");
56
56
  return join(configHome, "systemd", "user", DAEMON_UNIT_NAME);
57
57
  }
58
58
 
@@ -199,7 +199,15 @@ function usage(): never {
199
199
  type TaskCliClient = Pick<PapyrusClient, "call">;
200
200
  type MigrationResult = { from: number; to: number; applied: string[] };
201
201
  type CliArtifact = { id: string; title: string; status: string; body?: string };
202
- type CliTaskLease = { taskId: string; owner: string; token: string; claimedAt: string; leaseExpiresAt: string; heartbeatAt?: string; note?: string };
202
+ type CliTaskLease = {
203
+ taskId: string;
204
+ owner: string;
205
+ token: string;
206
+ claimedAt: string;
207
+ leaseExpiresAt: string;
208
+ heartbeatAt?: string;
209
+ note?: string;
210
+ };
203
211
  type CliCompletion = Omit<TaskCompletion, "artifact" | "blocked"> & {
204
212
  artifact: CliArtifact;
205
213
  blocked: Array<Omit<TaskBlockage, "artifact"> & { artifact: CliArtifact }>;
@@ -288,7 +296,11 @@ export function runIdMigrationCli(args: string[]): string {
288
296
  const mirrorPath = flag("out");
289
297
  if (!mirrorPath) throw new Error("migrate-ids mirror requires --out <path>");
290
298
  const sourceDb = openDb(source);
291
- try { mirrorDatabase(sourceDb, mirrorPath); } finally { sourceDb.close(); }
299
+ try {
300
+ mirrorDatabase(sourceDb, mirrorPath);
301
+ } finally {
302
+ sourceDb.close();
303
+ }
292
304
 
293
305
  const mirror = openDb(mirrorPath);
294
306
  let plan: IdMigrationPlan;
@@ -318,9 +330,14 @@ export function runIdMigrationCli(args: string[]): string {
318
330
  const plan = readIdMap(flag("idmap") ?? `${mirrorPath}.idmap.json`);
319
331
  const mirror = openDb(mirrorPath);
320
332
  let result: ReturnType<typeof verifyIdMigration>;
321
- try { result = verifyIdMigration(mirror, plan); } finally { mirror.close(); }
333
+ try {
334
+ result = verifyIdMigration(mirror, plan);
335
+ } finally {
336
+ mirror.close();
337
+ }
322
338
  if (json) return JSON.stringify(result);
323
- if (result.ok) return `Validation passed: ${plan.idMap.size} artifact id(s) correctly migrated.\nNext: papyrus migrate-ids promote --mirror ${mirrorPath}`;
339
+ if (result.ok)
340
+ return `Validation passed: ${plan.idMap.size} artifact id(s) correctly migrated.\nNext: papyrus migrate-ids promote --mirror ${mirrorPath}`;
324
341
  return `Validation FAILED:\n${result.problems.map((problem) => ` - ${problem}`).join("\n")}\nDo not promote this mirror.`;
325
342
  }
326
343
 
@@ -330,14 +347,22 @@ export function runIdMigrationCli(args: string[]): string {
330
347
  const target = flag("db") ?? dbPath();
331
348
  const force = rest.includes("--force");
332
349
  if (isDaemonActive() && !force) {
333
- throw new Error(`refusing to promote while ${DAEMON_UNIT_NAME} is active -- stop it first (papyrus service stop), or pass --force if you have already verified it is safe`);
350
+ throw new Error(
351
+ `refusing to promote while ${DAEMON_UNIT_NAME} is active -- stop it first (papyrus service stop), or pass --force if you have already verified it is safe`,
352
+ );
334
353
  }
335
354
  const plan = readIdMap(flag("idmap") ?? `${mirrorPath}.idmap.json`);
336
355
  const mirror = openDb(mirrorPath);
337
356
  let result: ReturnType<typeof verifyIdMigration>;
338
- try { result = verifyIdMigration(mirror, plan); } finally { mirror.close(); }
357
+ try {
358
+ result = verifyIdMigration(mirror, plan);
359
+ } finally {
360
+ mirror.close();
361
+ }
339
362
  if (!result.ok) {
340
- throw new Error(`refusing to promote: mirror failed validation (${result.problems.length} problem(s)) -- run migrate-ids validate for details`);
363
+ throw new Error(
364
+ `refusing to promote: mirror failed validation (${result.problems.length} problem(s)) -- run migrate-ids validate for details`,
365
+ );
341
366
  }
342
367
  let backupPath: string | undefined;
343
368
  if (existsSync(target)) {
@@ -377,7 +402,10 @@ export async function runGraphCli(args: string[], client: TaskCliClient): Promis
377
402
  const argument = args[index]!;
378
403
  if (argument === "--json") continue;
379
404
  const flags: Record<string, string> = {
380
- "--id": "id", "--actor": "actor", "--session-id": "session_id", "--since": "since",
405
+ "--id": "id",
406
+ "--actor": "actor",
407
+ "--session-id": "session_id",
408
+ "--since": "since",
381
409
  "--direction": "direction",
382
410
  };
383
411
  if (argument in flags) {
@@ -415,13 +443,20 @@ export async function runGraphCli(args: string[], client: TaskCliClient): Promis
415
443
  }
416
444
  if (action === "unlink") {
417
445
  if (positional.length !== 4) throw new Error("graph unlink requires <from> <relation> <to>");
418
- const result = await client.call<Record<string, unknown>, { removed: boolean }>("graph.unlink", { from: first, relation: second, to: third });
446
+ const result = await client.call<Record<string, unknown>, { removed: boolean }>("graph.unlink", {
447
+ from: first,
448
+ relation: second,
449
+ to: third,
450
+ });
419
451
  if (json) return JSON.stringify(result);
420
452
  return result.removed ? `Unlinked ${first} --${second}--> ${third}` : `No such relationship: ${first} --${second}--> ${third}`;
421
453
  }
422
454
  if (action === "tree") {
423
455
  if (positional.length !== 2) throw new Error("graph tree requires exactly one artifact id");
424
- const artifact = await client.call<Record<string, unknown>, CliArtifact & { edges?: Array<{ from: string; relation: string; to: string }> }>("graph.tree", { id: first, depth, max_nodes: maxNodes });
456
+ const artifact = await client.call<
457
+ Record<string, unknown>,
458
+ CliArtifact & { edges?: Array<{ from: string; relation: string; to: string }> }
459
+ >("graph.tree", { id: first, depth, max_nodes: maxNodes });
425
460
  if (json) return JSON.stringify(artifact);
426
461
  const edges = artifact.edges ?? [];
427
462
  return edges.length === 0
@@ -437,11 +472,13 @@ export async function runGraphCli(args: string[], client: TaskCliClient): Promis
437
472
  const page = await client.call<Record<string, unknown>, import("./domain/artifact-event.ts").ArtifactEventPage>("graph.history", input);
438
473
  if (json) return JSON.stringify(page);
439
474
  if (page.events.length === 0) return "No recorded events.";
440
- return page.events.map((event) => {
441
- const transition = event.fromStatus || event.toStatus ? ` ${event.fromStatus ?? "\u2205"} \u2192 ${event.toStatus ?? "\u2205"}` : "";
442
- const relation = event.relation ? ` ${event.relation} \u2192 ${event.relatedId}` : "";
443
- return `${event.occurredAt} ${event.artifactId} ${event.type}${transition}${relation} \u00b7 ${event.actor}/${event.source}${event.sessionId ? ` \u00b7 ${event.sessionId}` : ""}`;
444
- }).join("\n");
475
+ return page.events
476
+ .map((event) => {
477
+ const transition = event.fromStatus || event.toStatus ? ` ${event.fromStatus ?? "\u2205"} \u2192 ${event.toStatus ?? "\u2205"}` : "";
478
+ const relation = event.relation ? ` ${event.relation} \u2192 ${event.relatedId}` : "";
479
+ return `${event.occurredAt} ${event.artifactId} ${event.type}${transition}${relation} \u00b7 ${event.actor}/${event.source}${event.sessionId ? ` \u00b7 ${event.sessionId}` : ""}`;
480
+ })
481
+ .join("\n");
445
482
  }
446
483
 
447
484
  export async function runDocsCli(args: string[], client: TaskCliClient): Promise<string> {
@@ -460,15 +497,49 @@ export async function runDocsCli(args: string[], client: TaskCliClient): Promise
460
497
  for (let index = 0; index < args.length; index++) {
461
498
  const argument = args[index]!;
462
499
  if (argument === "--json") continue;
463
- if (argument === "--title") { title = args[++index]; if (title === undefined) throw new Error("--title requires a value"); continue; }
464
- if (argument === "--body") { body = args[++index]; if (body === undefined) throw new Error("--body requires a value"); continue; }
465
- if (argument === "--subtype") { subtype = args[++index]; if (!subtype) throw new Error("--subtype requires a value"); continue; }
466
- if (argument === "--labels-json") { labels = parseJsonStringArrayFlag(args[++index], "--labels-json"); continue; }
467
- if (argument === "--extra-json") { extra = parseJsonObjectFlag(args[++index], "--extra-json"); continue; }
468
- if (argument === "--template-id") { templateId = args[++index]; if (!templateId) throw new Error("--template-id requires a value"); continue; }
469
- if (argument === "--status") { status = args[++index]; if (!status) throw new Error("--status requires a value"); continue; }
470
- if (argument === "--text") { text = args[++index]; if (text === undefined) throw new Error("--text requires a value"); continue; }
471
- if (argument === "--project-root") { projectRoot = args[++index]; if (!projectRoot) throw new Error("--project-root requires a value"); continue; }
500
+ if (argument === "--title") {
501
+ title = args[++index];
502
+ if (title === undefined) throw new Error("--title requires a value");
503
+ continue;
504
+ }
505
+ if (argument === "--body") {
506
+ body = args[++index];
507
+ if (body === undefined) throw new Error("--body requires a value");
508
+ continue;
509
+ }
510
+ if (argument === "--subtype") {
511
+ subtype = args[++index];
512
+ if (!subtype) throw new Error("--subtype requires a value");
513
+ continue;
514
+ }
515
+ if (argument === "--labels-json") {
516
+ labels = parseJsonStringArrayFlag(args[++index], "--labels-json");
517
+ continue;
518
+ }
519
+ if (argument === "--extra-json") {
520
+ extra = parseJsonObjectFlag(args[++index], "--extra-json");
521
+ continue;
522
+ }
523
+ if (argument === "--template-id") {
524
+ templateId = args[++index];
525
+ if (!templateId) throw new Error("--template-id requires a value");
526
+ continue;
527
+ }
528
+ if (argument === "--status") {
529
+ status = args[++index];
530
+ if (!status) throw new Error("--status requires a value");
531
+ continue;
532
+ }
533
+ if (argument === "--text") {
534
+ text = args[++index];
535
+ if (text === undefined) throw new Error("--text requires a value");
536
+ continue;
537
+ }
538
+ if (argument === "--project-root") {
539
+ projectRoot = args[++index];
540
+ if (!projectRoot) throw new Error("--project-root requires a value");
541
+ continue;
542
+ }
472
543
  if (argument === "--limit") {
473
544
  const value = args[++index];
474
545
  if (!value || Number.isNaN(Number(value))) throw new Error("--limit requires a numeric value");
@@ -485,14 +556,27 @@ export async function runDocsCli(args: string[], client: TaskCliClient): Promise
485
556
  case "create": {
486
557
  if (id) throw new Error("docs create accepts no positional arguments");
487
558
  if (!title) throw new Error("docs create requires --title");
488
- const artifact = await client.call<Record<string, unknown>, CliArtifact>("docs.create", { title, body, subtype, labels, extra, template_id: templateId, project_root: projectRoot });
559
+ const artifact = await client.call<Record<string, unknown>, CliArtifact>("docs.create", {
560
+ title,
561
+ body,
562
+ subtype,
563
+ labels,
564
+ extra,
565
+ template_id: templateId,
566
+ project_root: projectRoot,
567
+ });
489
568
  result = artifact;
490
569
  human = `Created document: ${artifactLabel(artifact)}`;
491
570
  break;
492
571
  }
493
572
  case "list": {
494
573
  if (id) throw new Error("docs list accepts no positional arguments");
495
- const rows = await client.call<Record<string, unknown>, CliArtifact[]>("docs.list", { status, text, limit, project_root: projectRoot });
574
+ const rows = await client.call<Record<string, unknown>, CliArtifact[]>("docs.list", {
575
+ status,
576
+ text,
577
+ limit,
578
+ project_root: projectRoot,
579
+ });
496
580
  result = rows;
497
581
  human = rows.length === 0 ? "No documents found." : rows.map((row) => artifactLabel(row)).join("\n");
498
582
  break;
@@ -529,7 +613,8 @@ export async function runDocsCli(args: string[], client: TaskCliClient): Promise
529
613
  }
530
614
  case "update": {
531
615
  if (!id || second) throw new Error("docs update requires exactly one document id");
532
- if (title === undefined && body === undefined && labels === undefined) throw new Error("docs update requires --title, --body, or --labels-json");
616
+ if (title === undefined && body === undefined && labels === undefined)
617
+ throw new Error("docs update requires --title, --body, or --labels-json");
533
618
  const artifact = await client.call<Record<string, unknown>, CliArtifact>("docs.update", { id, title, body, labels });
534
619
  result = artifact;
535
620
  human = `${artifactLabel(artifact)}`;
@@ -558,16 +643,54 @@ export async function runRulesCli(args: string[], client: TaskCliClient, project
558
643
  for (let index = 0; index < args.length; index++) {
559
644
  const argument = args[index]!;
560
645
  if (argument === "--json") continue;
561
- if (argument === "--title") { title = args[++index]; if (title === undefined) throw new Error("--title requires a value"); continue; }
562
- if (argument === "--body") { body = args[++index]; if (body === undefined) throw new Error("--body requires a value"); continue; }
563
- if (argument === "--condition") { condition = args[++index]; if (condition === undefined) throw new Error("--condition requires a value"); continue; }
564
- if (argument === "--rule-action") { ruleAction = args[++index]; if (ruleAction === undefined) throw new Error("--rule-action requires a value"); continue; }
565
- if (argument === "--severity") { severity = args[++index]; if (!severity) throw new Error("--severity requires a value"); continue; }
566
- if (argument === "--labels-json") { labels = parseJsonStringArrayFlag(args[++index], "--labels-json"); continue; }
567
- if (argument === "--extra-json") { extra = parseJsonObjectFlag(args[++index], "--extra-json"); continue; }
568
- if (argument === "--status") { status = args[++index]; if (!status) throw new Error("--status requires a value"); continue; }
569
- if (argument === "--text") { text = args[++index]; if (text === undefined) throw new Error("--text requires a value"); continue; }
570
- if (argument === "--project-root") { ruleProjectRoot = args[++index]; if (!ruleProjectRoot) throw new Error("--project-root requires a value"); continue; }
646
+ if (argument === "--title") {
647
+ title = args[++index];
648
+ if (title === undefined) throw new Error("--title requires a value");
649
+ continue;
650
+ }
651
+ if (argument === "--body") {
652
+ body = args[++index];
653
+ if (body === undefined) throw new Error("--body requires a value");
654
+ continue;
655
+ }
656
+ if (argument === "--condition") {
657
+ condition = args[++index];
658
+ if (condition === undefined) throw new Error("--condition requires a value");
659
+ continue;
660
+ }
661
+ if (argument === "--rule-action") {
662
+ ruleAction = args[++index];
663
+ if (ruleAction === undefined) throw new Error("--rule-action requires a value");
664
+ continue;
665
+ }
666
+ if (argument === "--severity") {
667
+ severity = args[++index];
668
+ if (!severity) throw new Error("--severity requires a value");
669
+ continue;
670
+ }
671
+ if (argument === "--labels-json") {
672
+ labels = parseJsonStringArrayFlag(args[++index], "--labels-json");
673
+ continue;
674
+ }
675
+ if (argument === "--extra-json") {
676
+ extra = parseJsonObjectFlag(args[++index], "--extra-json");
677
+ continue;
678
+ }
679
+ if (argument === "--status") {
680
+ status = args[++index];
681
+ if (!status) throw new Error("--status requires a value");
682
+ continue;
683
+ }
684
+ if (argument === "--text") {
685
+ text = args[++index];
686
+ if (text === undefined) throw new Error("--text requires a value");
687
+ continue;
688
+ }
689
+ if (argument === "--project-root") {
690
+ ruleProjectRoot = args[++index];
691
+ if (!ruleProjectRoot) throw new Error("--project-root requires a value");
692
+ continue;
693
+ }
571
694
  if (argument === "--limit") {
572
695
  const value = args[++index];
573
696
  if (!value || Number.isNaN(Number(value))) throw new Error("--limit requires a numeric value");
@@ -584,14 +707,28 @@ export async function runRulesCli(args: string[], client: TaskCliClient, project
584
707
  case "create": {
585
708
  if (id) throw new Error("rules create accepts no positional arguments");
586
709
  if (!title) throw new Error("rules create requires --title");
587
- const artifact = await client.call<Record<string, unknown>, CliArtifact>("rules.create", { title, body, condition, rule_action: ruleAction, severity, labels, extra, project_root: ruleProjectRoot });
710
+ const artifact = await client.call<Record<string, unknown>, CliArtifact>("rules.create", {
711
+ title,
712
+ body,
713
+ condition,
714
+ rule_action: ruleAction,
715
+ severity,
716
+ labels,
717
+ extra,
718
+ project_root: ruleProjectRoot,
719
+ });
588
720
  result = artifact;
589
721
  human = `Created rule: ${artifactLabel(artifact)}`;
590
722
  break;
591
723
  }
592
724
  case "list": {
593
725
  if (id) throw new Error("rules list accepts no positional arguments");
594
- const rows = await client.call<Record<string, unknown>, CliArtifact[]>("rules.list", { status, text, limit, project_root: ruleProjectRoot });
726
+ const rows = await client.call<Record<string, unknown>, CliArtifact[]>("rules.list", {
727
+ status,
728
+ text,
729
+ limit,
730
+ project_root: ruleProjectRoot,
731
+ });
595
732
  result = rows;
596
733
  human = rows.length === 0 ? "No rules found." : rows.map((row) => artifactLabel(row)).join("\n");
597
734
  break;
@@ -641,7 +778,8 @@ export async function runRulesCli(args: string[], client: TaskCliClient, project
641
778
  }
642
779
  case "update": {
643
780
  if (!id || second) throw new Error("rules update requires exactly one rule id");
644
- if (title === undefined && body === undefined && labels === undefined) throw new Error("rules update requires --title, --body, or --labels-json");
781
+ if (title === undefined && body === undefined && labels === undefined)
782
+ throw new Error("rules update requires --title, --body, or --labels-json");
645
783
  const artifact = await client.call<Record<string, unknown>, CliArtifact>("rules.update", { id, title, body, labels });
646
784
  result = artifact;
647
785
  human = `${artifactLabel(artifact)}`;
@@ -672,18 +810,61 @@ export async function runPlaybooksCli(args: string[], client: TaskCliClient): Pr
672
810
  for (let index = 0; index < args.length; index++) {
673
811
  const argument = args[index]!;
674
812
  if (argument === "--json") continue;
675
- if (argument === "--title") { title = args[++index]; if (title === undefined) throw new Error("--title requires a value"); continue; }
676
- if (argument === "--body") { body = args[++index]; if (body === undefined) throw new Error("--body requires a value"); continue; }
677
- if (argument === "--trigger") { trigger = args[++index]; if (trigger === undefined) throw new Error("--trigger requires a value"); continue; }
678
- if (argument === "--steps-json") { steps = parseJsonAnyFlag(args[++index], "--steps-json"); continue; }
679
- if (argument === "--tools-json") { tools = parseJsonStringArrayFlag(args[++index], "--tools-json"); continue; }
680
- if (argument === "--labels-json") { labels = parseJsonStringArrayFlag(args[++index], "--labels-json"); continue; }
681
- if (argument === "--extra-json") { extra = parseJsonObjectFlag(args[++index], "--extra-json"); continue; }
682
- if (argument === "--arguments-json") { playbookArguments = parseJsonAnyFlag(args[++index], "--arguments-json"); continue; }
683
- if (argument === "--run-id") { runId = args[++index]; if (!runId) throw new Error("--run-id requires a value"); continue; }
684
- if (argument === "--status") { status = args[++index]; if (!status) throw new Error("--status requires a value"); continue; }
685
- if (argument === "--text") { text = args[++index]; if (text === undefined) throw new Error("--text requires a value"); continue; }
686
- if (argument === "--project-root") { playbookProjectRoot = args[++index]; if (!playbookProjectRoot) throw new Error("--project-root requires a value"); continue; }
813
+ if (argument === "--title") {
814
+ title = args[++index];
815
+ if (title === undefined) throw new Error("--title requires a value");
816
+ continue;
817
+ }
818
+ if (argument === "--body") {
819
+ body = args[++index];
820
+ if (body === undefined) throw new Error("--body requires a value");
821
+ continue;
822
+ }
823
+ if (argument === "--trigger") {
824
+ trigger = args[++index];
825
+ if (trigger === undefined) throw new Error("--trigger requires a value");
826
+ continue;
827
+ }
828
+ if (argument === "--steps-json") {
829
+ steps = parseJsonAnyFlag(args[++index], "--steps-json");
830
+ continue;
831
+ }
832
+ if (argument === "--tools-json") {
833
+ tools = parseJsonStringArrayFlag(args[++index], "--tools-json");
834
+ continue;
835
+ }
836
+ if (argument === "--labels-json") {
837
+ labels = parseJsonStringArrayFlag(args[++index], "--labels-json");
838
+ continue;
839
+ }
840
+ if (argument === "--extra-json") {
841
+ extra = parseJsonObjectFlag(args[++index], "--extra-json");
842
+ continue;
843
+ }
844
+ if (argument === "--arguments-json") {
845
+ playbookArguments = parseJsonAnyFlag(args[++index], "--arguments-json");
846
+ continue;
847
+ }
848
+ if (argument === "--run-id") {
849
+ runId = args[++index];
850
+ if (!runId) throw new Error("--run-id requires a value");
851
+ continue;
852
+ }
853
+ if (argument === "--status") {
854
+ status = args[++index];
855
+ if (!status) throw new Error("--status requires a value");
856
+ continue;
857
+ }
858
+ if (argument === "--text") {
859
+ text = args[++index];
860
+ if (text === undefined) throw new Error("--text requires a value");
861
+ continue;
862
+ }
863
+ if (argument === "--project-root") {
864
+ playbookProjectRoot = args[++index];
865
+ if (!playbookProjectRoot) throw new Error("--project-root requires a value");
866
+ continue;
867
+ }
687
868
  if (argument === "--limit") {
688
869
  const value = args[++index];
689
870
  if (!value || Number.isNaN(Number(value))) throw new Error("--limit requires a numeric value");
@@ -700,14 +881,29 @@ export async function runPlaybooksCli(args: string[], client: TaskCliClient): Pr
700
881
  case "create": {
701
882
  if (id) throw new Error("playbooks create accepts no positional arguments");
702
883
  if (!title) throw new Error("playbooks create requires --title");
703
- const artifact = await client.call<Record<string, unknown>, CliArtifact>("playbooks.create", { title, body, trigger, steps, tools, labels, extra, arguments: playbookArguments, project_root: playbookProjectRoot });
884
+ const artifact = await client.call<Record<string, unknown>, CliArtifact>("playbooks.create", {
885
+ title,
886
+ body,
887
+ trigger,
888
+ steps,
889
+ tools,
890
+ labels,
891
+ extra,
892
+ arguments: playbookArguments,
893
+ project_root: playbookProjectRoot,
894
+ });
704
895
  result = artifact;
705
896
  human = `Created playbook: ${artifactLabel(artifact)}`;
706
897
  break;
707
898
  }
708
899
  case "list": {
709
900
  if (id) throw new Error("playbooks list accepts no positional arguments");
710
- const rows = await client.call<Record<string, unknown>, CliArtifact[]>("playbooks.list", { status, text, limit, project_root: playbookProjectRoot });
901
+ const rows = await client.call<Record<string, unknown>, CliArtifact[]>("playbooks.list", {
902
+ status,
903
+ text,
904
+ limit,
905
+ project_root: playbookProjectRoot,
906
+ });
711
907
  result = rows;
712
908
  human = rows.length === 0 ? "No playbooks found." : rows.map((row) => artifactLabel(row)).join("\n");
713
909
  break;
@@ -728,7 +924,10 @@ export async function runPlaybooksCli(args: string[], client: TaskCliClient): Pr
728
924
  }
729
925
  case "invoke": {
730
926
  if (!id || second) throw new Error("playbooks invoke requires exactly one playbook id");
731
- const invocation = await client.call<Record<string, unknown>, { entryTaskId: string; missingArguments?: string[] }>("playbooks.invoke", { id, arguments: playbookArguments, run_id: runId, project_root: playbookProjectRoot });
927
+ const invocation = await client.call<Record<string, unknown>, { entryTaskId: string; missingArguments?: string[] }>(
928
+ "playbooks.invoke",
929
+ { id, arguments: playbookArguments, run_id: runId, project_root: playbookProjectRoot },
930
+ );
732
931
  result = invocation;
733
932
  human = invocation.missingArguments
734
933
  ? `Missing required argument(s): ${invocation.missingArguments.join(", ")}.`
@@ -744,7 +943,7 @@ export async function runPlaybooksCli(args: string[], client: TaskCliClient): Pr
744
943
  break;
745
944
  }
746
945
  case "assign-project": {
747
- if (!id || second === undefined && positional.length > 2) throw new Error("playbooks assign-project requires <id> [project-root]");
946
+ if (!id || (second === undefined && positional.length > 2)) throw new Error("playbooks assign-project requires <id> [project-root]");
748
947
  const artifact = await client.call<Record<string, unknown>, CliArtifact>("playbooks.assign_project", { id, project_root: second });
749
948
  result = artifact;
750
949
  human = second ? `Assigned ${id} to ${second}` : `Unscoped ${id}`;
@@ -752,7 +951,8 @@ export async function runPlaybooksCli(args: string[], client: TaskCliClient): Pr
752
951
  }
753
952
  case "update": {
754
953
  if (!id || second) throw new Error("playbooks update requires exactly one playbook id");
755
- if (title === undefined && body === undefined && labels === undefined) throw new Error("playbooks update requires --title, --body, or --labels-json");
954
+ if (title === undefined && body === undefined && labels === undefined)
955
+ throw new Error("playbooks update requires --title, --body, or --labels-json");
756
956
  const artifact = await client.call<Record<string, unknown>, CliArtifact>("playbooks.update", { id, title, body, labels });
757
957
  result = artifact;
758
958
  human = `${artifactLabel(artifact)}`;
@@ -787,7 +987,9 @@ export async function runPlaybooksCli(args: string[], client: TaskCliClient): Pr
787
987
  break;
788
988
  }
789
989
  default:
790
- throw new Error("playbooks action must be create, list, show, invoke, preview, enable, disable, assign-project, update, contain, uncontain, depend, or undepend");
990
+ throw new Error(
991
+ "playbooks action must be create, list, show, invoke, preview, enable, disable, assign-project, update, contain, uncontain, depend, or undepend",
992
+ );
791
993
  }
792
994
  return json ? JSON.stringify(result) : human;
793
995
  }
@@ -811,16 +1013,54 @@ export async function runArtifactCli(args: string[], client: TaskCliClient, proj
811
1013
  for (let index = 0; index < args.length; index++) {
812
1014
  const argument = args[index]!;
813
1015
  if (argument === "--json") continue;
814
- if (argument === "--kind") { kind = args[++index]; if (!kind) throw new Error("--kind requires a value"); continue; }
815
- if (argument === "--title") { title = args[++index]; if (title === undefined) throw new Error("--title requires a value"); continue; }
816
- if (argument === "--body") { body = args[++index]; if (body === undefined) throw new Error("--body requires a value"); continue; }
817
- if (argument === "--status") { status = args[++index]; if (!status) throw new Error("--status requires a value"); continue; }
818
- if (argument === "--subtype") { subtype = args[++index]; if (!subtype) throw new Error("--subtype requires a value"); continue; }
819
- if (argument === "--labels-json") { labels = parseJsonStringArrayFlag(args[++index], "--labels-json"); continue; }
820
- if (argument === "--extra-json") { extra = parseJsonObjectFlag(args[++index], "--extra-json"); continue; }
821
- if (argument === "--template-id") { templateId = args[++index]; if (!templateId) throw new Error("--template-id requires a value"); continue; }
822
- if (argument === "--reason") { reason = args[++index]; if (reason === undefined) throw new Error("--reason requires a value"); continue; }
823
- if (argument === "--text") { text = args[++index]; if (text === undefined) throw new Error("--text requires a value"); continue; }
1016
+ if (argument === "--kind") {
1017
+ kind = args[++index];
1018
+ if (!kind) throw new Error("--kind requires a value");
1019
+ continue;
1020
+ }
1021
+ if (argument === "--title") {
1022
+ title = args[++index];
1023
+ if (title === undefined) throw new Error("--title requires a value");
1024
+ continue;
1025
+ }
1026
+ if (argument === "--body") {
1027
+ body = args[++index];
1028
+ if (body === undefined) throw new Error("--body requires a value");
1029
+ continue;
1030
+ }
1031
+ if (argument === "--status") {
1032
+ status = args[++index];
1033
+ if (!status) throw new Error("--status requires a value");
1034
+ continue;
1035
+ }
1036
+ if (argument === "--subtype") {
1037
+ subtype = args[++index];
1038
+ if (!subtype) throw new Error("--subtype requires a value");
1039
+ continue;
1040
+ }
1041
+ if (argument === "--labels-json") {
1042
+ labels = parseJsonStringArrayFlag(args[++index], "--labels-json");
1043
+ continue;
1044
+ }
1045
+ if (argument === "--extra-json") {
1046
+ extra = parseJsonObjectFlag(args[++index], "--extra-json");
1047
+ continue;
1048
+ }
1049
+ if (argument === "--template-id") {
1050
+ templateId = args[++index];
1051
+ if (!templateId) throw new Error("--template-id requires a value");
1052
+ continue;
1053
+ }
1054
+ if (argument === "--reason") {
1055
+ reason = args[++index];
1056
+ if (reason === undefined) throw new Error("--reason requires a value");
1057
+ continue;
1058
+ }
1059
+ if (argument === "--text") {
1060
+ text = args[++index];
1061
+ if (text === undefined) throw new Error("--text requires a value");
1062
+ continue;
1063
+ }
824
1064
  if (argument === "--limit") {
825
1065
  const value = args[++index];
826
1066
  if (!value || Number.isNaN(Number(value))) throw new Error("--limit requires a numeric value");
@@ -850,7 +1090,14 @@ export async function runArtifactCli(args: string[], client: TaskCliClient, proj
850
1090
  if (id) throw new Error("artifact create accepts no positional arguments");
851
1091
  if (!kind && !templateId) throw new Error("artifact create requires --kind (or --template-id)");
852
1092
  const artifact = await client.call<Record<string, unknown>, CliArtifact>("artifact.create", {
853
- kind, title, body, status, subtype, labels, extra, template_id: templateId,
1093
+ kind,
1094
+ title,
1095
+ body,
1096
+ status,
1097
+ subtype,
1098
+ labels,
1099
+ extra,
1100
+ template_id: templateId,
854
1101
  ...(kind === "task" ? { project_root: projectRoot } : {}),
855
1102
  });
856
1103
  result = artifact;
@@ -873,14 +1120,20 @@ export async function runArtifactCli(args: string[], client: TaskCliClient, proj
873
1120
  }
874
1121
  case "remove": {
875
1122
  if (!id) throw new Error("artifact remove requires exactly one artifact id");
876
- const record = await client.call<Record<string, unknown>, { artifactId: string; trashedAt: string; purgeAfter: string; reason?: string }>("artifact.remove", { id, reason });
1123
+ const record = await client.call<
1124
+ Record<string, unknown>,
1125
+ { artifactId: string; trashedAt: string; purgeAfter: string; reason?: string }
1126
+ >("artifact.remove", { id, reason });
877
1127
  result = record;
878
1128
  human = `Trashed ${record.artifactId}: eligible for purge at ${record.purgeAfter}`;
879
1129
  break;
880
1130
  }
881
1131
  case "remove-subtree": {
882
1132
  if (!id) throw new Error("artifact remove-subtree requires exactly one artifact id");
883
- const outcome = await client.call<Record<string, unknown>, { removed: string[]; skipped: string[] }>("artifact.remove_subtree", { id, reason });
1133
+ const outcome = await client.call<Record<string, unknown>, { removed: string[]; skipped: string[] }>("artifact.remove_subtree", {
1134
+ id,
1135
+ reason,
1136
+ });
884
1137
  result = outcome;
885
1138
  human = `Trashed ${outcome.removed.length} artifact(s)${outcome.skipped.length > 0 ? `, skipped ${outcome.skipped.length} already-trashed` : ""}.`;
886
1139
  break;
@@ -894,16 +1147,25 @@ export async function runArtifactCli(args: string[], client: TaskCliClient, proj
894
1147
  }
895
1148
  case "trash-status": {
896
1149
  if (!id) throw new Error("artifact trash-status requires exactly one artifact id");
897
- const record = await client.call<Record<string, unknown>, { artifactId: string; trashedAt: string; purgeAfter: string; reason?: string } | null>("artifact.trash_status", { id });
1150
+ const record = await client.call<
1151
+ Record<string, unknown>,
1152
+ { artifactId: string; trashedAt: string; purgeAfter: string; reason?: string } | null
1153
+ >("artifact.trash_status", { id });
898
1154
  result = record;
899
- human = record ? `${record.artifactId}: trashed at ${record.trashedAt}, purge eligible at ${record.purgeAfter}` : `${id} is not trashed`;
1155
+ human = record
1156
+ ? `${record.artifactId}: trashed at ${record.trashedAt}, purge eligible at ${record.purgeAfter}`
1157
+ : `${id} is not trashed`;
900
1158
  break;
901
1159
  }
902
1160
  case "trash-list": {
903
1161
  if (id) throw new Error("artifact trash-list accepts no positional arguments");
904
- const rows = await client.call<Record<string, unknown>, Array<{ artifactId: string; trashedAt: string; purgeAfter: string; reason?: string }>>("artifact.trash_list", {});
1162
+ const rows = await client.call<
1163
+ Record<string, unknown>,
1164
+ Array<{ artifactId: string; trashedAt: string; purgeAfter: string; reason?: string }>
1165
+ >("artifact.trash_list", {});
905
1166
  result = rows;
906
- human = rows.length === 0 ? "Trash is empty." : rows.map((row) => `${row.artifactId}: purge eligible at ${row.purgeAfter}`).join("\n");
1167
+ human =
1168
+ rows.length === 0 ? "Trash is empty." : rows.map((row) => `${row.artifactId}: purge eligible at ${row.purgeAfter}`).join("\n");
907
1169
  break;
908
1170
  }
909
1171
  default:
@@ -931,8 +1193,15 @@ export async function runGraphProjectionCli(args: string[], client: TaskCliClien
931
1193
  for (let index = 0; index < args.length; index++) {
932
1194
  const argument = args[index]!;
933
1195
  if (argument === "--json") continue;
934
- if (argument === "--batch-json") { batch = parseJsonObjectFlag(args[++index], "--batch-json"); continue; }
935
- if (argument === "--producer-id") { producerId = args[++index]; if (!producerId) throw new Error("--producer-id requires a value"); continue; }
1196
+ if (argument === "--batch-json") {
1197
+ batch = parseJsonObjectFlag(args[++index], "--batch-json");
1198
+ continue;
1199
+ }
1200
+ if (argument === "--producer-id") {
1201
+ producerId = args[++index];
1202
+ if (!producerId) throw new Error("--producer-id requires a value");
1203
+ continue;
1204
+ }
936
1205
  positional.push(argument);
937
1206
  }
938
1207
  const [action] = positional;
@@ -967,9 +1236,27 @@ export async function runLogCli(args: string[], client: TaskCliClient, projectRo
967
1236
  for (let index = 0; index < args.length; index++) {
968
1237
  const argument = args[index]!;
969
1238
  if (argument === "--json") continue;
970
- if (argument === "--global") { global = true; continue; }
971
- if (argument === "--fields-json") { fields = parseJsonObjectFlag(args[++index], "--fields-json"); continue; }
972
- if (["--source", "--source-label", "--level", "--message", "--operation-id", "--session-id", "--occurred-at", "--since", "--limit"].includes(argument)) {
1239
+ if (argument === "--global") {
1240
+ global = true;
1241
+ continue;
1242
+ }
1243
+ if (argument === "--fields-json") {
1244
+ fields = parseJsonObjectFlag(args[++index], "--fields-json");
1245
+ continue;
1246
+ }
1247
+ if (
1248
+ [
1249
+ "--source",
1250
+ "--source-label",
1251
+ "--level",
1252
+ "--message",
1253
+ "--operation-id",
1254
+ "--session-id",
1255
+ "--occurred-at",
1256
+ "--since",
1257
+ "--limit",
1258
+ ].includes(argument)
1259
+ ) {
973
1260
  const value = args[++index];
974
1261
  if (value === undefined) throw new Error(`${argument} requires a value`);
975
1262
  if (argument === "--source") source = value;
@@ -996,17 +1283,25 @@ export async function runLogCli(args: string[], client: TaskCliClient, projectRo
996
1283
  if (!message) throw new Error("log append requires --message");
997
1284
  if (!operationId) throw new Error("log append requires --operation-id");
998
1285
  const result = await client.call("logs.append", {
999
- source_id: source, ...(sourceLabel ? { source_label: sourceLabel } : {}),
1286
+ source_id: source,
1287
+ ...(sourceLabel ? { source_label: sourceLabel } : {}),
1000
1288
  ...(global ? {} : { project_root: projectRoot }),
1001
- level, message, operation_id: operationId,
1002
- ...(fields ? { fields } : {}), ...(sessionId ? { session_id: sessionId } : {}), ...(occurredAt ? { occurred_at: occurredAt } : {}),
1289
+ level,
1290
+ message,
1291
+ operation_id: operationId,
1292
+ ...(fields ? { fields } : {}),
1293
+ ...(sessionId ? { session_id: sessionId } : {}),
1294
+ ...(occurredAt ? { occurred_at: occurredAt } : {}),
1003
1295
  });
1004
1296
  return json ? JSON.stringify(result) : JSON.stringify(result, null, 2);
1005
1297
  }
1006
1298
  if (action === "query") {
1007
1299
  if (!source) throw new Error("log query requires --source");
1008
1300
  const result = await client.call<Record<string, unknown>, { entries: unknown[]; truncated: boolean }>("logs.query", {
1009
- source_id: source, ...(since ? { since } : {}), ...(level ? { level } : {}), ...(limit === undefined ? {} : { limit }),
1301
+ source_id: source,
1302
+ ...(since ? { since } : {}),
1303
+ ...(level ? { level } : {}),
1304
+ ...(limit === undefined ? {} : { limit }),
1010
1305
  });
1011
1306
  if (json) return JSON.stringify(result);
1012
1307
  const lines = result.entries.map((entry) => JSON.stringify(entry));
@@ -1023,20 +1318,31 @@ export async function runSessionIdentityCli(args: string[], client: TaskCliClien
1023
1318
  for (let index = 0; index < args.length; index++) {
1024
1319
  const argument = args[index]!;
1025
1320
  if (argument === "--json") continue;
1026
- if (argument === "--session-id") { sessionId = args[++index]; continue; }
1027
- if (argument === "--session-secret") { secret = args[++index]; continue; }
1321
+ if (argument === "--session-id") {
1322
+ sessionId = args[++index];
1323
+ continue;
1324
+ }
1325
+ if (argument === "--session-secret") {
1326
+ secret = args[++index];
1327
+ continue;
1328
+ }
1028
1329
  if (argument.startsWith("--")) throw new Error(`unknown session option ${argument}`);
1029
1330
  positional.push(argument);
1030
1331
  }
1031
1332
  const [action] = positional;
1032
1333
  if (action === "register") {
1033
1334
  if (!sessionId) throw new Error("session register requires --session-id");
1034
- const result = await client.call<Record<string, unknown>, { sessionId: string; secret: string }>("session.register", { session_id: sessionId });
1335
+ const result = await client.call<Record<string, unknown>, { sessionId: string; secret: string }>("session.register", {
1336
+ session_id: sessionId,
1337
+ });
1035
1338
  return json ? JSON.stringify(result) : JSON.stringify(result, null, 2);
1036
1339
  }
1037
1340
  if (action === "release") {
1038
1341
  if (!sessionId) throw new Error("session release requires --session-id");
1039
- const result = await client.call<Record<string, unknown>, { released: boolean }>("session.release", { session_id: sessionId, ...(secret ? { session_secret: secret } : {}) });
1342
+ const result = await client.call<Record<string, unknown>, { released: boolean }>("session.release", {
1343
+ session_id: sessionId,
1344
+ ...(secret ? { session_secret: secret } : {}),
1345
+ });
1040
1346
  return json ? JSON.stringify(result) : JSON.stringify(result, null, 2);
1041
1347
  }
1042
1348
  throw new Error("session action must be register or release");
@@ -1064,20 +1370,71 @@ export async function runDiscussCli(args: string[], client: TaskCliClient): Prom
1064
1370
  for (let index = 0; index < args.length; index++) {
1065
1371
  const argument = args[index]!;
1066
1372
  if (argument === "--json") continue;
1067
- if (argument === "--title") { title = args[++index]; if (!title) throw new Error("--title requires a value"); continue; }
1068
- if (argument === "--actor") { actor = args[++index]; if (!actor) throw new Error("--actor requires a value"); continue; }
1069
- if (argument === "--content") { content = args[++index]; if (content === undefined) throw new Error("--content requires a value"); continue; }
1070
- if (argument === "--body") { body = args[++index]; if (body === undefined) throw new Error("--body requires a value"); continue; }
1071
- if (argument === "--labels-json") { labels = parseJsonStringArrayFlag(args[++index], "--labels-json"); continue; }
1072
- if (argument === "--blocks-json") { blocksTaskIds = parseJsonStringArrayFlag(args[++index], "--blocks-json"); continue; }
1073
- if (argument === "--task-id") { taskId = args[++index]; if (!taskId) throw new Error("--task-id requires a value"); continue; }
1074
- if (argument === "--options-json") { options = parseJsonStringArrayFlag(args[++index], "--options-json"); continue; }
1075
- if (argument === "--options-mode") { optionsMode = args[++index]; if (!optionsMode) throw new Error("--options-mode requires a value"); continue; }
1076
- if (argument === "--option-descriptions-json") { optionDescriptions = parseJsonStringArrayFlag(args[++index], "--option-descriptions-json"); continue; }
1077
- if (argument === "--selected-json") { selected = parseJsonStringArrayFlag(args[++index], "--selected-json"); continue; }
1078
- if (argument === "--reason") { reason = args[++index]; if (reason === undefined) throw new Error("--reason requires a value"); continue; }
1079
- if (argument === "--settlement") { settlement = args[++index]; if (!settlement) throw new Error("--settlement requires a value"); continue; }
1080
- if (argument === "--state") { state = args[++index]; if (!state) throw new Error("--state requires a value"); continue; }
1373
+ if (argument === "--title") {
1374
+ title = args[++index];
1375
+ if (!title) throw new Error("--title requires a value");
1376
+ continue;
1377
+ }
1378
+ if (argument === "--actor") {
1379
+ actor = args[++index];
1380
+ if (!actor) throw new Error("--actor requires a value");
1381
+ continue;
1382
+ }
1383
+ if (argument === "--content") {
1384
+ content = args[++index];
1385
+ if (content === undefined) throw new Error("--content requires a value");
1386
+ continue;
1387
+ }
1388
+ if (argument === "--body") {
1389
+ body = args[++index];
1390
+ if (body === undefined) throw new Error("--body requires a value");
1391
+ continue;
1392
+ }
1393
+ if (argument === "--labels-json") {
1394
+ labels = parseJsonStringArrayFlag(args[++index], "--labels-json");
1395
+ continue;
1396
+ }
1397
+ if (argument === "--blocks-json") {
1398
+ blocksTaskIds = parseJsonStringArrayFlag(args[++index], "--blocks-json");
1399
+ continue;
1400
+ }
1401
+ if (argument === "--task-id") {
1402
+ taskId = args[++index];
1403
+ if (!taskId) throw new Error("--task-id requires a value");
1404
+ continue;
1405
+ }
1406
+ if (argument === "--options-json") {
1407
+ options = parseJsonStringArrayFlag(args[++index], "--options-json");
1408
+ continue;
1409
+ }
1410
+ if (argument === "--options-mode") {
1411
+ optionsMode = args[++index];
1412
+ if (!optionsMode) throw new Error("--options-mode requires a value");
1413
+ continue;
1414
+ }
1415
+ if (argument === "--option-descriptions-json") {
1416
+ optionDescriptions = parseJsonStringArrayFlag(args[++index], "--option-descriptions-json");
1417
+ continue;
1418
+ }
1419
+ if (argument === "--selected-json") {
1420
+ selected = parseJsonStringArrayFlag(args[++index], "--selected-json");
1421
+ continue;
1422
+ }
1423
+ if (argument === "--reason") {
1424
+ reason = args[++index];
1425
+ if (reason === undefined) throw new Error("--reason requires a value");
1426
+ continue;
1427
+ }
1428
+ if (argument === "--settlement") {
1429
+ settlement = args[++index];
1430
+ if (!settlement) throw new Error("--settlement requires a value");
1431
+ continue;
1432
+ }
1433
+ if (argument === "--state") {
1434
+ state = args[++index];
1435
+ if (!state) throw new Error("--state requires a value");
1436
+ continue;
1437
+ }
1081
1438
  if (argument === "--after-round") {
1082
1439
  const value = args[++index];
1083
1440
  if (!value || Number.isNaN(Number(value))) throw new Error("--after-round requires a numeric value");
@@ -1097,12 +1454,30 @@ export async function runDiscussCli(args: string[], client: TaskCliClient): Prom
1097
1454
  switch (action) {
1098
1455
  case "open": {
1099
1456
  if (id) throw new Error("discuss open accepts no positional arguments");
1100
- const result = await client.call<Record<string, unknown>, unknown>("discuss.open", { title, actor, content, body, labels, blocks_task_ids: blocksTaskIds, options, options_mode: optionsMode, option_descriptions: optionDescriptions });
1457
+ const result = await client.call<Record<string, unknown>, unknown>("discuss.open", {
1458
+ title,
1459
+ actor,
1460
+ content,
1461
+ body,
1462
+ labels,
1463
+ blocks_task_ids: blocksTaskIds,
1464
+ options,
1465
+ options_mode: optionsMode,
1466
+ option_descriptions: optionDescriptions,
1467
+ });
1101
1468
  return json ? JSON.stringify(result) : JSON.stringify(result, null, 2);
1102
1469
  }
1103
1470
  case "reply": {
1104
1471
  if (!id) throw new Error("discuss reply requires exactly one discussion id");
1105
- const result = await client.call<Record<string, unknown>, unknown>("discuss.reply", { id, actor, content, selected, options, options_mode: optionsMode, option_descriptions: optionDescriptions });
1472
+ const result = await client.call<Record<string, unknown>, unknown>("discuss.reply", {
1473
+ id,
1474
+ actor,
1475
+ content,
1476
+ selected,
1477
+ options,
1478
+ options_mode: optionsMode,
1479
+ option_descriptions: optionDescriptions,
1480
+ });
1106
1481
  return json ? JSON.stringify(result) : JSON.stringify(result, null, 2);
1107
1482
  }
1108
1483
  case "defer": {
@@ -1182,34 +1557,77 @@ export async function runNoteCli(args: string[], client: TaskCliClient, projectR
1182
1557
  let human: string;
1183
1558
  if (action === "capture") {
1184
1559
  if (!id || target) throw new Error("notes capture requires exactly one request argument");
1185
- result = await client.call("notes.capture", { body: id, ...(title ? { title } : {}), project_root: projectRoot, actor: "human", source: "cli" }) as CliArtifact;
1560
+ result = (await client.call("notes.capture", {
1561
+ body: id,
1562
+ ...(title ? { title } : {}),
1563
+ project_root: projectRoot,
1564
+ actor: "human",
1565
+ source: "cli",
1566
+ })) as CliArtifact;
1186
1567
  human = `Captured: ${artifactLabel(result)}`;
1187
1568
  } else if (action === "list") {
1188
1569
  if (id) throw new Error("notes list accepts no positional arguments");
1189
- result = await client.call("notes.list", { project_root: projectRoot, ...(status ? { status } : {}), ...(text ? { text } : {}), ...(limit === undefined ? {} : { limit }) }) as CliArtifact[];
1570
+ result = (await client.call("notes.list", {
1571
+ project_root: projectRoot,
1572
+ ...(status ? { status } : {}),
1573
+ ...(text ? { text } : {}),
1574
+ ...(limit === undefined ? {} : { limit }),
1575
+ })) as CliArtifact[];
1190
1576
  human = result.length > 0 ? result.map((note) => `[${note.status}] ${artifactLabel(note)}`).join("\n") : "No open notes.";
1191
1577
  } else if (action === "show") {
1192
1578
  if (!id || target) throw new Error("notes show requires exactly one note id");
1193
- result = await client.call("notes.show", { id, project_root: projectRoot }) as CliArtifact;
1579
+ result = (await client.call("notes.show", { id, project_root: projectRoot })) as CliArtifact;
1194
1580
  human = `${artifactLabel(result)}\n\n${result.body ?? ""}`.trimEnd();
1195
1581
  } else if (action === "history") {
1196
1582
  if (!id || target) throw new Error("notes history requires exactly one note id");
1197
- const page = await client.call<Record<string, unknown>, import("./domain/note-event.ts").NoteHistoryPage>("notes.history", { id, project_root: projectRoot, direction: "desc", ...(limit === undefined ? {} : { limit }) });
1583
+ const page = await client.call<Record<string, unknown>, import("./domain/note-event.ts").NoteHistoryPage>("notes.history", {
1584
+ id,
1585
+ project_root: projectRoot,
1586
+ direction: "desc",
1587
+ ...(limit === undefined ? {} : { limit }),
1588
+ });
1198
1589
  result = page;
1199
- human = page.events.length === 0
1200
- ? `No recorded history for ${id}.`
1201
- : [...page.events].reverse().map((event) => `${event.occurredAt} ${event.type} · ${event.actor}/${event.source}${event.relatedId ? ` · ${event.relatedId}` : ""}${event.disposition ? ` · ${event.disposition}` : ""}${event.reason ? ` · ${event.reason}` : ""}`).join("\n");
1590
+ human =
1591
+ page.events.length === 0
1592
+ ? `No recorded history for ${id}.`
1593
+ : [...page.events]
1594
+ .reverse()
1595
+ .map(
1596
+ (event) =>
1597
+ `${event.occurredAt} ${event.type} · ${event.actor}/${event.source}${event.relatedId ? ` · ${event.relatedId}` : ""}${event.disposition ? ` · ${event.disposition}` : ""}${event.reason ? ` · ${event.reason}` : ""}`,
1598
+ )
1599
+ .join("\n");
1202
1600
  } else if (action === "consume") {
1203
1601
  if (!id || target) throw new Error("notes consume requires exactly one note id");
1204
- result = await client.call("notes.consume", { id, project_root: projectRoot, actor: "agent", source: "cli", ...(reason ? { reason } : {}) }) as CliArtifact;
1602
+ result = (await client.call("notes.consume", {
1603
+ id,
1604
+ project_root: projectRoot,
1605
+ actor: "agent",
1606
+ source: "cli",
1607
+ ...(reason ? { reason } : {}),
1608
+ })) as CliArtifact;
1205
1609
  human = `Consumed: ${artifactLabel(result)}`;
1206
1610
  } else if (action === "promote") {
1207
1611
  if (!id || !target || positional.length !== 3) throw new Error("notes promote requires a note id and target artifact id");
1208
- result = await client.call("notes.promote", { id, target_id: target, project_root: projectRoot, actor: "agent", source: "cli", ...(reason ? { reason } : {}) }) as CliArtifact;
1612
+ result = (await client.call("notes.promote", {
1613
+ id,
1614
+ target_id: target,
1615
+ project_root: projectRoot,
1616
+ actor: "agent",
1617
+ source: "cli",
1618
+ ...(reason ? { reason } : {}),
1619
+ })) as CliArtifact;
1209
1620
  human = `Promoted: ${artifactLabel(result)} → ${target}`;
1210
1621
  } else if (action === "archive") {
1211
1622
  if (!id || !target || positional.length !== 3) throw new Error("notes archive requires a note id and disposition");
1212
- result = await client.call("notes.archive", { id, disposition: target, project_root: projectRoot, actor: "human", source: "cli", ...(reason ? { reason } : {}) }) as CliArtifact;
1623
+ result = (await client.call("notes.archive", {
1624
+ id,
1625
+ disposition: target,
1626
+ project_root: projectRoot,
1627
+ actor: "human",
1628
+ source: "cli",
1629
+ ...(reason ? { reason } : {}),
1630
+ })) as CliArtifact;
1213
1631
  human = `Archived: ${artifactLabel(result)} · ${target}`;
1214
1632
  } else {
1215
1633
  throw new Error("notes action must be capture, list, show, history, consume, promote, or archive");
@@ -1257,12 +1675,22 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
1257
1675
  if (!sessionSecret) throw new Error("--session-secret requires a value");
1258
1676
  continue;
1259
1677
  }
1260
- if (argument === "--title" || argument === "--body" || argument === "--labels-json" || argument === "--status" || argument === "--reason") {
1678
+ if (
1679
+ argument === "--title" ||
1680
+ argument === "--body" ||
1681
+ argument === "--labels-json" ||
1682
+ argument === "--status" ||
1683
+ argument === "--reason"
1684
+ ) {
1261
1685
  const value = args[++index];
1262
1686
  if (value === undefined) throw new Error(`${argument} requires a value`);
1263
- if (argument === "--title") { updateInput.title = value; title = value; }
1264
- else if (argument === "--body") { updateInput.body = value; body = value; }
1265
- else if (argument === "--reason") reason = value;
1687
+ if (argument === "--title") {
1688
+ updateInput.title = value;
1689
+ title = value;
1690
+ } else if (argument === "--body") {
1691
+ updateInput.body = value;
1692
+ body = value;
1693
+ } else if (argument === "--reason") reason = value;
1266
1694
  else if (argument === "--status") {
1267
1695
  status = value;
1268
1696
  if (value === "todo") updateInput.status = value;
@@ -1272,7 +1700,10 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
1272
1700
  }
1273
1701
  continue;
1274
1702
  }
1275
- if (argument === "--extra-json") { extra = parseJsonObjectFlag(args[++index]!, "--extra-json"); continue; }
1703
+ if (argument === "--extra-json") {
1704
+ extra = parseJsonObjectFlag(args[++index]!, "--extra-json");
1705
+ continue;
1706
+ }
1276
1707
  if (argument === "--gates-json") {
1277
1708
  const value = args[++index];
1278
1709
  if (!value) throw new Error("--gates-json requires a value");
@@ -1281,11 +1712,29 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
1281
1712
  gates = parsed;
1282
1713
  continue;
1283
1714
  }
1284
- if (argument === "--checklist-json") { checklist = parseJsonObjectFlag(args[++index]!, "--checklist-json"); continue; }
1285
- if (argument === "--template-id") { templateId = args[++index]; if (!templateId) throw new Error("--template-id requires a value"); continue; }
1286
- if (argument === "--parent-id") { parentId = args[++index]; if (!parentId) throw new Error("--parent-id requires a value"); continue; }
1287
- if (argument === "--depends-on-json") { dependsOn = parseJsonStringArrayFlag(args[++index]!, "--depends-on-json"); continue; }
1288
- if (argument === "--text") { text = args[++index]; if (text === undefined) throw new Error("--text requires a value"); continue; }
1715
+ if (argument === "--checklist-json") {
1716
+ checklist = parseJsonObjectFlag(args[++index]!, "--checklist-json");
1717
+ continue;
1718
+ }
1719
+ if (argument === "--template-id") {
1720
+ templateId = args[++index];
1721
+ if (!templateId) throw new Error("--template-id requires a value");
1722
+ continue;
1723
+ }
1724
+ if (argument === "--parent-id") {
1725
+ parentId = args[++index];
1726
+ if (!parentId) throw new Error("--parent-id requires a value");
1727
+ continue;
1728
+ }
1729
+ if (argument === "--depends-on-json") {
1730
+ dependsOn = parseJsonStringArrayFlag(args[++index]!, "--depends-on-json");
1731
+ continue;
1732
+ }
1733
+ if (argument === "--text") {
1734
+ text = args[++index];
1735
+ if (text === undefined) throw new Error("--text requires a value");
1736
+ continue;
1737
+ }
1289
1738
  if (argument === "--limit") {
1290
1739
  const value = args[++index];
1291
1740
  if (!value || Number.isNaN(Number(value))) throw new Error("--limit requires a numeric value");
@@ -1298,10 +1747,26 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
1298
1747
  listScope = value;
1299
1748
  continue;
1300
1749
  }
1301
- if (argument === "--root-task-id") { rootTaskId = args[++index]; if (!rootTaskId) throw new Error("--root-task-id requires a value"); continue; }
1302
- if (argument === "--owner") { owner = args[++index]; if (!owner) throw new Error("--owner requires a value"); continue; }
1303
- if (argument === "--token") { token = args[++index]; if (!token) throw new Error("--token requires a value"); continue; }
1304
- if (argument === "--note") { note = args[++index]; if (note === undefined) throw new Error("--note requires a value"); continue; }
1750
+ if (argument === "--root-task-id") {
1751
+ rootTaskId = args[++index];
1752
+ if (!rootTaskId) throw new Error("--root-task-id requires a value");
1753
+ continue;
1754
+ }
1755
+ if (argument === "--owner") {
1756
+ owner = args[++index];
1757
+ if (!owner) throw new Error("--owner requires a value");
1758
+ continue;
1759
+ }
1760
+ if (argument === "--token") {
1761
+ token = args[++index];
1762
+ if (!token) throw new Error("--token requires a value");
1763
+ continue;
1764
+ }
1765
+ if (argument === "--note") {
1766
+ note = args[++index];
1767
+ if (note === undefined) throw new Error("--note requires a value");
1768
+ continue;
1769
+ }
1305
1770
  if (argument === "--ttl-ms") {
1306
1771
  const value = args[++index];
1307
1772
  if (!value || Number.isNaN(Number(value))) throw new Error("--ttl-ms requires a numeric value");
@@ -1314,13 +1779,17 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
1314
1779
  cursor = Number(value);
1315
1780
  continue;
1316
1781
  }
1317
- if (argument === "--event-types-json") { eventTypes = parseJsonStringArrayFlag(args[++index]!, "--event-types-json"); continue; }
1782
+ if (argument === "--event-types-json") {
1783
+ eventTypes = parseJsonStringArrayFlag(args[++index]!, "--event-types-json");
1784
+ continue;
1785
+ }
1318
1786
  if (argument.startsWith("--")) throw new Error(`unknown tasks option ${argument}`);
1319
1787
  positional.push(argument);
1320
1788
  }
1321
1789
  const [action, id, dependencyId] = positional;
1322
1790
  const reasonSupportedActions = new Set(["update", "depend", "undepend", "contain", "uncontain"]);
1323
- if (reason !== undefined && !reasonSupportedActions.has(action ?? "")) throw new Error("--reason is only supported by tasks update, depend, undepend, contain, and uncontain");
1791
+ if (reason !== undefined && !reasonSupportedActions.has(action ?? ""))
1792
+ throw new Error("--reason is only supported by tasks update, depend, undepend, contain, and uncontain");
1324
1793
  const sessionScope = sessionId ? { session_id: sessionId } : {};
1325
1794
  // Only meaningful alongside a registered session_id (see session.register); required by the
1326
1795
  // daemon only for the specific Focus-mutating operations below (focus/pause/unpause/clear_focus)
@@ -1331,14 +1800,20 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
1331
1800
  switch (action) {
1332
1801
  case "active": {
1333
1802
  if (id) throw new Error("tasks active accepts no positional arguments");
1334
- const active = await client.call<Record<string, unknown>, CliArtifact | null>("tasks.active", { project_root: projectRoot, ...sessionScope });
1803
+ const active = await client.call<Record<string, unknown>, CliArtifact | null>("tasks.active", {
1804
+ project_root: projectRoot,
1805
+ ...sessionScope,
1806
+ });
1335
1807
  result = active;
1336
1808
  human = active ? `Active: ${artifactLabel(active)}` : "No active task.";
1337
1809
  break;
1338
1810
  }
1339
1811
  case "focused": {
1340
1812
  if (id) throw new Error("tasks focused accepts no positional arguments");
1341
- const focus = await client.call<Record<string, unknown>, { artifact: CliArtifact; status: "active" | "paused"; updatedAt: string } | null>("tasks.focused", { project_root: projectRoot, ...sessionScope });
1813
+ const focus = await client.call<
1814
+ Record<string, unknown>,
1815
+ { artifact: CliArtifact; status: "active" | "paused"; updatedAt: string } | null
1816
+ >("tasks.focused", { project_root: projectRoot, ...sessionScope });
1342
1817
  result = focus;
1343
1818
  human = focus ? `Focused (${focus.status}): ${artifactLabel(focus.artifact)}` : "No focused task.";
1344
1819
  break;
@@ -1347,14 +1822,24 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
1347
1822
  case "unpause": {
1348
1823
  if (id) throw new Error(`tasks ${action} accepts no positional arguments`);
1349
1824
  const operation = action === "pause" ? "tasks.pause" : "tasks.unpause";
1350
- const focus = await client.call<Record<string, unknown>, { artifact: CliArtifact; status: string }>(operation, { actor: "user", source: "cli", ...sessionScope, ...sessionSecretField });
1825
+ const focus = await client.call<Record<string, unknown>, { artifact: CliArtifact; status: string }>(operation, {
1826
+ actor: "user",
1827
+ source: "cli",
1828
+ ...sessionScope,
1829
+ ...sessionSecretField,
1830
+ });
1351
1831
  result = focus;
1352
1832
  human = `Focused (${focus.status}): ${artifactLabel(focus.artifact)}`;
1353
1833
  break;
1354
1834
  }
1355
1835
  case "clear-focus": {
1356
1836
  if (id) throw new Error("tasks clear-focus accepts no positional arguments");
1357
- const cleared = await client.call<Record<string, unknown>, { cleared: boolean }>("tasks.clear_focus", { actor: "user", source: "cli", ...sessionScope, ...sessionSecretField });
1837
+ const cleared = await client.call<Record<string, unknown>, { cleared: boolean }>("tasks.clear_focus", {
1838
+ actor: "user",
1839
+ source: "cli",
1840
+ ...sessionScope,
1841
+ ...sessionSecretField,
1842
+ });
1358
1843
  result = cleared;
1359
1844
  human = cleared.cleared ? "Task focus cleared." : "No focused task.";
1360
1845
  break;
@@ -1406,20 +1891,36 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
1406
1891
  }
1407
1892
  case "event-feed": {
1408
1893
  if (id) throw new Error("tasks event-feed accepts no positional arguments");
1409
- const page = await client.call<Record<string, unknown>, import("./domain/task-event.ts").TaskEventFeedPage>("tasks.event_feed", { cursor, limit, event_types: eventTypes });
1894
+ const page = await client.call<Record<string, unknown>, import("./domain/task-event.ts").TaskEventFeedPage>("tasks.event_feed", {
1895
+ cursor,
1896
+ limit,
1897
+ event_types: eventTypes,
1898
+ });
1410
1899
  result = page;
1411
- human = page.events.length === 0
1412
- ? "No events."
1413
- : page.events.map((event) => `${event.id} ${event.occurredAt} ${event.taskId} ${event.type}`).join("\n");
1900
+ human =
1901
+ page.events.length === 0
1902
+ ? "No events."
1903
+ : page.events.map((event) => `${event.id} ${event.occurredAt} ${event.taskId} ${event.type}`).join("\n");
1414
1904
  break;
1415
1905
  }
1416
1906
  case "create": {
1417
1907
  if (id) throw new Error("tasks create accepts no positional arguments");
1418
1908
  if (!title) throw new Error("tasks create requires --title");
1419
1909
  const artifact = await client.call<Record<string, unknown>, CliArtifact>("tasks.create", {
1420
- title, body, status, labels, extra, gates, checklist,
1421
- template_id: templateId, parent_id: parentId, depends_on: dependsOn,
1422
- project_root: projectRoot, actor: "user", source: "cli", ...sessionScope,
1910
+ title,
1911
+ body,
1912
+ status,
1913
+ labels,
1914
+ extra,
1915
+ gates,
1916
+ checklist,
1917
+ template_id: templateId,
1918
+ parent_id: parentId,
1919
+ depends_on: dependsOn,
1920
+ project_root: projectRoot,
1921
+ actor: "user",
1922
+ source: "cli",
1923
+ ...sessionScope,
1423
1924
  });
1424
1925
  result = artifact;
1425
1926
  human = `Created task: ${artifactLabel(artifact)}`;
@@ -1428,7 +1929,14 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
1428
1929
  case "list": {
1429
1930
  if (id) throw new Error("tasks list accepts no positional arguments");
1430
1931
  const rows = await client.call<Record<string, unknown>, CliArtifact[]>("tasks.list", {
1431
- status, text, limit, labels, project_root: projectRoot, scope: listScope, root_task_id: rootTaskId, ...sessionScope,
1932
+ status,
1933
+ text,
1934
+ limit,
1935
+ labels,
1936
+ project_root: projectRoot,
1937
+ scope: listScope,
1938
+ root_task_id: rootTaskId,
1939
+ ...sessionScope,
1432
1940
  });
1433
1941
  result = rows;
1434
1942
  human = rows.length === 0 ? "No tasks found." : rows.map((row) => artifactLabel(row)).join("\n");
@@ -1445,9 +1953,10 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
1445
1953
  if (!id || dependencyId) throw new Error("tasks run-gates requires exactly one task id");
1446
1954
  const results = await client.call<Record<string, unknown>, GateResult[]>("tasks.run_gates", { id, actor: "user", source: "cli" });
1447
1955
  result = results;
1448
- human = results.length === 0
1449
- ? "No gates configured."
1450
- : results.map((gate) => `${gate.passed ? "✓" : "✗"} ${gate.gate.type}: ${gate.gate.target} — ${gate.output}`).join("\n");
1956
+ human =
1957
+ results.length === 0
1958
+ ? "No gates configured."
1959
+ : results.map((gate) => `${gate.passed ? "✓" : "✗"} ${gate.gate.type}: ${gate.gate.target} — ${gate.output}`).join("\n");
1451
1960
  break;
1452
1961
  }
1453
1962
  case "set-checklist": {
@@ -1469,7 +1978,10 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
1469
1978
  case "context": {
1470
1979
  if (id) throw new Error("tasks context accepts no positional arguments");
1471
1980
  const summary = await client.call<Record<string, unknown>, string | null>("tasks.context", {
1472
- project_root: projectRoot, scope: listScope, root_task_id: rootTaskId, ...sessionScope,
1981
+ project_root: projectRoot,
1982
+ scope: listScope,
1983
+ root_task_id: rootTaskId,
1984
+ ...sessionScope,
1473
1985
  });
1474
1986
  result = summary;
1475
1987
  human = summary ?? "No open tasks.";
@@ -1478,7 +1990,12 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
1478
1990
  case "contain": {
1479
1991
  if (!id || !dependencyId || positional.length !== 3) throw new Error("tasks contain requires a parent id and child id");
1480
1992
  const artifact = await client.call<Record<string, unknown>, CliArtifact>("tasks.contain", {
1481
- parent_id: id, child_id: dependencyId, actor: "user", source: "cli", ...(reason ? { reason } : {}), ...sessionScope,
1993
+ parent_id: id,
1994
+ child_id: dependencyId,
1995
+ actor: "user",
1996
+ source: "cli",
1997
+ ...(reason ? { reason } : {}),
1998
+ ...sessionScope,
1482
1999
  });
1483
2000
  result = artifact;
1484
2001
  human = `Contained: ${dependencyId} → ${artifactLabel(artifact)}`;
@@ -1487,7 +2004,12 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
1487
2004
  case "uncontain": {
1488
2005
  if (!id || !dependencyId || positional.length !== 3) throw new Error("tasks uncontain requires a parent id and child id");
1489
2006
  const artifact = await client.call<Record<string, unknown>, CliArtifact>("tasks.uncontain", {
1490
- parent_id: id, child_id: dependencyId, actor: "user", source: "cli", ...(reason ? { reason } : {}), ...sessionScope,
2007
+ parent_id: id,
2008
+ child_id: dependencyId,
2009
+ actor: "user",
2010
+ source: "cli",
2011
+ ...(reason ? { reason } : {}),
2012
+ ...sessionScope,
1491
2013
  });
1492
2014
  result = artifact;
1493
2015
  human = `Removed ${dependencyId} from ${artifactLabel(artifact)}`;
@@ -1495,12 +2017,17 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
1495
2017
  }
1496
2018
  case "update": {
1497
2019
  if (!id || dependencyId) throw new Error("tasks update requires exactly one task id");
1498
- if (status !== undefined && status !== "todo") throw new Error("tasks update --status only supports todo for accidental creation recovery");
2020
+ if (status !== undefined && status !== "todo")
2021
+ throw new Error("tasks update --status only supports todo for accidental creation recovery");
1499
2022
  if (Object.keys(updateInput).length === 0) throw new Error("tasks update requires --title, --body, --labels-json, or --status todo");
1500
2023
  if (updateInput.status !== undefined && !reason?.trim()) throw new Error("tasks update --status requires --reason");
1501
2024
  if (reason !== undefined && updateInput.status === undefined) throw new Error("tasks update --reason requires --status todo");
1502
2025
  const artifact = await client.call<Record<string, unknown>, CliArtifact>("tasks.update", {
1503
- id, ...updateInput, ...(reason ? { reason } : {}), actor: "user", source: "cli",
2026
+ id,
2027
+ ...updateInput,
2028
+ ...(reason ? { reason } : {}),
2029
+ actor: "user",
2030
+ source: "cli",
1504
2031
  });
1505
2032
  result = artifact;
1506
2033
  human = `Updated: ${artifactLabel(artifact)}`;
@@ -1508,16 +2035,28 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
1508
2035
  }
1509
2036
  case "history": {
1510
2037
  if (!id || dependencyId) throw new Error("tasks history requires exactly one task id");
1511
- const page = await client.call<{ id: string; direction: "desc" }, import("./domain/task-event.ts").TaskHistoryPage>("tasks.history", { id, direction: "desc" });
2038
+ const page = await client.call<{ id: string; direction: "desc" }, import("./domain/task-event.ts").TaskHistoryPage>("tasks.history", {
2039
+ id,
2040
+ direction: "desc",
2041
+ });
1512
2042
  result = page;
1513
- human = page.events.length === 0
1514
- ? `No recorded history for ${id}.`
1515
- : [...page.events].reverse().map((event) => `${event.occurredAt} ${event.type} ${event.fromStatus ?? "∅"} → ${event.toStatus ?? "∅"} · ${event.actor}/${event.source}${event.reason ? ` · ${event.reason}` : ""}`).join("\n");
2043
+ human =
2044
+ page.events.length === 0
2045
+ ? `No recorded history for ${id}.`
2046
+ : [...page.events]
2047
+ .reverse()
2048
+ .map(
2049
+ (event) =>
2050
+ `${event.occurredAt} ${event.type} ${event.fromStatus ?? "∅"} → ${event.toStatus ?? "∅"} · ${event.actor}/${event.source}${event.reason ? ` · ${event.reason}` : ""}`,
2051
+ )
2052
+ .join("\n");
1516
2053
  break;
1517
2054
  }
1518
2055
  case "scope": {
1519
2056
  if (!id) {
1520
- const selection = await client.call<Record<string, string>, import("./domain/task-scope.ts").TaskViewSelection>("tasks.scope", { project_root: projectRoot });
2057
+ const selection = await client.call<Record<string, string>, import("./domain/task-scope.ts").TaskViewSelection>("tasks.scope", {
2058
+ project_root: projectRoot,
2059
+ });
1521
2060
  result = selection;
1522
2061
  human = `Task scope: ${selection.label}`;
1523
2062
  break;
@@ -1548,17 +2087,33 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
1548
2087
  }
1549
2088
  case "focus": {
1550
2089
  if (!id || dependencyId) throw new Error("tasks focus requires exactly one task id");
1551
- const active = await client.call<Record<string, unknown>, CliArtifact>("tasks.focus", { id, actor: "user", source: "cli", ...sessionScope, ...sessionSecretField });
2090
+ const active = await client.call<Record<string, unknown>, CliArtifact>("tasks.focus", {
2091
+ id,
2092
+ actor: "user",
2093
+ source: "cli",
2094
+ ...sessionScope,
2095
+ ...sessionSecretField,
2096
+ });
1552
2097
  result = active;
1553
2098
  human = `Active: ${artifactLabel(active)}`;
1554
2099
  break;
1555
2100
  }
1556
2101
  case "graph": {
1557
2102
  if (id) throw new Error("tasks graph accepts no positional arguments");
1558
- const graph = await client.call<Record<string, unknown>, {
1559
- nodes: Array<{ dependencyIds: string[]; childIds: string[] }>;
1560
- rootIds: string[];
1561
- }>("tasks.graph", { limit: TASK_EXECUTION_MAX_NODES + 1, labels, project_root: projectRoot, scope: listScope, root_task_id: rootTaskId, ...sessionScope });
2103
+ const graph = await client.call<
2104
+ Record<string, unknown>,
2105
+ {
2106
+ nodes: Array<{ dependencyIds: string[]; childIds: string[] }>;
2107
+ rootIds: string[];
2108
+ }
2109
+ >("tasks.graph", {
2110
+ limit: TASK_EXECUTION_MAX_NODES + 1,
2111
+ labels,
2112
+ project_root: projectRoot,
2113
+ scope: listScope,
2114
+ root_task_id: rootTaskId,
2115
+ ...sessionScope,
2116
+ });
1562
2117
  result = graph;
1563
2118
  const dependencies = graph.nodes.reduce((count, node) => count + node.dependencyIds.length, 0);
1564
2119
  const children = graph.nodes.reduce((count, node) => count + node.childIds.length, 0);
@@ -1567,19 +2122,29 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
1567
2122
  }
1568
2123
  case "plan": {
1569
2124
  if (id) throw new Error("tasks plan accepts no positional arguments");
1570
- const plan = await client.call<Record<string, unknown>, TaskExecutionPlan>("tasks.plan", { project_root: projectRoot, ...sessionScope });
2125
+ const plan = await client.call<Record<string, unknown>, TaskExecutionPlan>("tasks.plan", {
2126
+ project_root: projectRoot,
2127
+ ...sessionScope,
2128
+ });
1571
2129
  result = plan;
1572
2130
  human = planText(plan);
1573
2131
  break;
1574
2132
  }
1575
2133
  case "complete": {
1576
2134
  if (!id || dependencyId) throw new Error("tasks complete requires exactly one task id");
1577
- const completion = await client.call<Record<string, unknown>, CliCompletion>("tasks.complete", { id, actor: "user", source: "cli", ...sessionScope });
2135
+ const completion = await client.call<Record<string, unknown>, CliCompletion>("tasks.complete", {
2136
+ id,
2137
+ actor: "user",
2138
+ source: "cli",
2139
+ ...sessionScope,
2140
+ });
1578
2141
  result = completion;
1579
2142
  const lines = [`${completion.completed ? "Completed" : "Rejected"}: ${artifactLabel(completion.artifact)}`];
1580
2143
  if (completion.focused) lines.push(`Active: ${artifactLabel(completion.focused)}`);
1581
2144
  if (completion.blocked.length > 0) {
1582
- lines.push(`Blocked: ${completion.blocked.map((entry) => `${artifactLabel(entry.artifact)} waits for ${entry.dependencyIds.join(", ")}`).join("; ")}`);
2145
+ lines.push(
2146
+ `Blocked: ${completion.blocked.map((entry) => `${artifactLabel(entry.artifact)} waits for ${entry.dependencyIds.join(", ")}`).join("; ")}`,
2147
+ );
1583
2148
  }
1584
2149
  for (const gate of completion.gates) lines.push(`${gate.passed ? "✓" : "✗"} ${gate.gate.type}: ${gate.gate.target} — ${gate.output}`);
1585
2150
  human = lines.join("\n");
@@ -1587,7 +2152,12 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
1587
2152
  }
1588
2153
  case "start": {
1589
2154
  if (!id || dependencyId) throw new Error("tasks start requires exactly one task id");
1590
- const artifact = await client.call<Record<string, unknown>, CliArtifact>("tasks.start", { id, actor: "user", source: "cli", ...sessionScope });
2155
+ const artifact = await client.call<Record<string, unknown>, CliArtifact>("tasks.start", {
2156
+ id,
2157
+ actor: "user",
2158
+ source: "cli",
2159
+ ...sessionScope,
2160
+ });
1591
2161
  result = artifact;
1592
2162
  human = `Started: ${artifactLabel(artifact)}`;
1593
2163
  break;
@@ -1598,14 +2168,24 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
1598
2168
  case "cancel": {
1599
2169
  if (!id || dependencyId) throw new Error(`tasks ${action} requires exactly one task id`);
1600
2170
  const operation = `tasks.${action}` as "tasks.submit" | "tasks.reject" | "tasks.retry" | "tasks.cancel";
1601
- const artifact = await client.call<Record<string, unknown>, CliArtifact>(operation, { id, actor: "user", source: "cli", ...sessionScope });
2171
+ const artifact = await client.call<Record<string, unknown>, CliArtifact>(operation, {
2172
+ id,
2173
+ actor: "user",
2174
+ source: "cli",
2175
+ ...sessionScope,
2176
+ });
1602
2177
  result = artifact;
1603
2178
  human = `${action[0]!.toUpperCase()}${action.slice(1)}: ${artifactLabel(artifact)}`;
1604
2179
  break;
1605
2180
  }
1606
2181
  case "cancel-subtree": {
1607
2182
  if (!id || dependencyId) throw new Error("tasks cancel-subtree requires exactly one task id");
1608
- const outcome = await client.call<Record<string, unknown>, { canceled: string[]; skipped: string[] }>("tasks.cancel_subtree", { id, actor: "user", source: "cli", ...sessionScope });
2183
+ const outcome = await client.call<Record<string, unknown>, { canceled: string[]; skipped: string[] }>("tasks.cancel_subtree", {
2184
+ id,
2185
+ actor: "user",
2186
+ source: "cli",
2187
+ ...sessionScope,
2188
+ });
1609
2189
  result = outcome;
1610
2190
  human = `Canceled ${outcome.canceled.length} task(s)${outcome.skipped.length > 0 ? `, skipped ${outcome.skipped.length} already-terminal` : ""}.`;
1611
2191
  break;
@@ -1613,7 +2193,12 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
1613
2193
  case "depend": {
1614
2194
  if (!id || !dependencyId || positional.length !== 3) throw new Error("tasks depend requires a task id and prerequisite id");
1615
2195
  const artifact = await client.call<Record<string, unknown>, CliArtifact>("tasks.depend", {
1616
- id, dependency_id: dependencyId, actor: "user", source: "cli", ...(reason ? { reason } : {}), ...sessionScope,
2196
+ id,
2197
+ dependency_id: dependencyId,
2198
+ actor: "user",
2199
+ source: "cli",
2200
+ ...(reason ? { reason } : {}),
2201
+ ...sessionScope,
1617
2202
  });
1618
2203
  result = artifact;
1619
2204
  human = `Dependency added: ${artifactLabel(artifact)} waits for ${dependencyId}`;
@@ -1622,21 +2207,31 @@ export async function runTaskCli(args: string[], client: TaskCliClient, projectR
1622
2207
  case "undepend": {
1623
2208
  if (!id || !dependencyId || positional.length !== 3) throw new Error("tasks undepend requires a task id and prerequisite id");
1624
2209
  const artifact = await client.call<Record<string, unknown>, CliArtifact>("tasks.undepend", {
1625
- id, dependency_id: dependencyId, actor: "user", source: "cli", ...(reason ? { reason } : {}), ...sessionScope,
2210
+ id,
2211
+ dependency_id: dependencyId,
2212
+ actor: "user",
2213
+ source: "cli",
2214
+ ...(reason ? { reason } : {}),
2215
+ ...sessionScope,
1626
2216
  });
1627
2217
  result = artifact;
1628
2218
  human = `Dependency removed: ${artifactLabel(artifact)} no longer waits for ${dependencyId}`;
1629
2219
  break;
1630
2220
  }
1631
2221
  default:
1632
- throw new Error("tasks action must be create, list, show, active, focused, focus, pause, unpause, clear-focus, update, graph, plan, context, history, scope, assign-project, complete, start, submit, reject, retry, cancel, cancel-subtree, depend, undepend, contain, uncontain, run-gates, set-checklist, or set-gates");
2222
+ throw new Error(
2223
+ "tasks action must be create, list, show, active, focused, focus, pause, unpause, clear-focus, update, graph, plan, context, history, scope, assign-project, complete, start, submit, reject, retry, cancel, cancel-subtree, depend, undepend, contain, uncontain, run-gates, set-checklist, or set-gates",
2224
+ );
1633
2225
  }
1634
2226
  return json ? JSON.stringify(result) : human;
1635
2227
  }
1636
2228
 
1637
2229
  export async function main(args: string[] = process.argv.slice(2)): Promise<void> {
1638
2230
  const [command, action] = args;
1639
- if (command === "serve") { serveMain(); return; }
2231
+ if (command === "serve") {
2232
+ serveMain();
2233
+ return;
2234
+ }
1640
2235
  if (command === "tasks") {
1641
2236
  const client = await connectPapyrusClient();
1642
2237
  console.log(await runTaskCli(args.slice(1), client));
@@ -1708,12 +2303,23 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
1708
2303
  }
1709
2304
  if (command !== "service") usage();
1710
2305
  switch (action) {
1711
- case "install": installService(); break;
1712
- case "start": systemctl("start", DAEMON_UNIT_NAME); break;
1713
- case "stop": systemctl("stop", DAEMON_UNIT_NAME); break;
1714
- case "restart": systemctl("restart", DAEMON_UNIT_NAME); break;
1715
- case "status": systemctl("status", DAEMON_UNIT_NAME); break;
1716
- default: usage();
2306
+ case "install":
2307
+ installService();
2308
+ break;
2309
+ case "start":
2310
+ systemctl("start", DAEMON_UNIT_NAME);
2311
+ break;
2312
+ case "stop":
2313
+ systemctl("stop", DAEMON_UNIT_NAME);
2314
+ break;
2315
+ case "restart":
2316
+ systemctl("restart", DAEMON_UNIT_NAME);
2317
+ break;
2318
+ case "status":
2319
+ systemctl("status", DAEMON_UNIT_NAME);
2320
+ break;
2321
+ default:
2322
+ usage();
1717
2323
  }
1718
2324
  }
1719
2325