owlrunkit 0.17.1 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -138,11 +138,24 @@ import {
138
138
  import { OFFICIAL_NPM_REGISTRY } from "./registry-adoption-gate.mjs";
139
139
  import {
140
140
  appendTeamProjectEventV1,
141
+ assignTeamProjectV1,
141
142
  buildTeamProjectTakeoverV1,
143
+ checkpointTeamProjectV1,
144
+ closeTeamProjectVerificationV1,
145
+ deferTeamProjectVerificationV1,
142
146
  formatTeamProjectStatusHumanV1,
147
+ formatTeamProjectTakeoverHumanV1,
148
+ handoffTeamProjectV1,
143
149
  initializeTeamProjectV1,
150
+ integrateTeamProjectV1,
151
+ openTeamProjectDecisionV1,
144
152
  readTeamProjectStatusV1,
153
+ resolveTeamProjectDecisionV1,
145
154
  } from "./team-project.mjs";
155
+ import {
156
+ formatTeamProjectSuccessorHumanV1,
157
+ successorTeamProjectV1,
158
+ } from "./team-project-successor.mjs";
146
159
 
147
160
  const TOP_LEVEL_HELP = `OwlRunKit ${CORE_VERSION}
148
161
 
@@ -179,7 +192,7 @@ Fleet and status
179
192
  owlrunkit fleet discover; owlrunkit status --workspace .
180
193
 
181
194
  Agent team projects
182
- owlrunkit project <init|assign|checkpoint|handoff|decision|capture|status|takeover> [options]
195
+ owlrunkit project <init|assign|checkpoint|handoff|decision|verification|capture|status|takeover|successor> [options]
183
196
  Durable Agent work, dependencies, decisions, gates, progress, and handoffs.
184
197
  Core successor
185
198
  owlrunkit core-successor <plan|apply|resume> --workspace . [...]
@@ -340,9 +353,35 @@ action without granting any authority.
340
353
  `,
341
354
  project: `Usage:
342
355
  owlrunkit project init --workspace <directory> --definition <project.json>
343
- owlrunkit project <assign|checkpoint|handoff|decision|capture> --workspace <directory> --request <event.json>
356
+ owlrunkit project assign --workspace <directory> --assignment-id <id> --at <ISO-UTC> --work-item <id> --agent <id>
357
+ [--supersedes <current-assignment-id>] [--execution-run-id <id> --execution-work-item <id>] [--json]
358
+ owlrunkit project handoff --workspace <directory> --handoff-id <id> --at <ISO-UTC>
359
+ --assignment-id <current> --work-item <id> --from-agent <id> --to-agent <id>
360
+ --summary <text> --next <text> --evidence <ref> [--evidence <ref> ...] [--json]
361
+ owlrunkit project decision --workspace <directory> --open --decision-id <id> --at <ISO-UTC>
362
+ --title <text> --question <text> --owner-agent <id>
363
+ [--blocking-work-item <id> ...] --option <text> [--option <text> ...] [--json]
364
+ owlrunkit project decision --workspace <directory> --resolve --decision-id <id> --at <ISO-UTC>
365
+ --resolution <text> --rationale <text> --evidence <ref> [--evidence <ref> ...] [--json]
366
+ owlrunkit project checkpoint --workspace <directory> --checkpoint-id <id> --at <ISO-UTC>
367
+ --assignment-id <current> --work-item <id>
368
+ --state <active|waiting_dependency|waiting_decision|verifying|ready_to_integrate|completed|failed>
369
+ --summary <text> [--completed-units <nonnegative-int>]
370
+ [--evidence <ref> ...] [--blocker <ref> ...] [--decision <id> ...]
371
+ [--next <text>] [--source-fingerprint <sha256>] [--json]
372
+ owlrunkit project verification --workspace <directory> --defer --verification-id <id> --at <ISO-UTC>
373
+ --work-item <id> --owner-agent <id> --check <check-id> [--check <check-id> ...]
374
+ --reason <text> --due-gate <gate-id> [--json]
375
+ owlrunkit project verification --workspace <directory> --close --verification-id <id> --at <ISO-UTC>
376
+ --disposition <verified|no_longer_required> --summary <text>
377
+ [--evidence <ref> ...] [--decision <resolved-decision-id> ...] [--json]
378
+ owlrunkit project integrate --workspace <directory> --gate <gate-id> --at <ISO-UTC>
379
+ --summary <text> --evidence <ref> [--evidence <ref> ...] [--json]
380
+ owlrunkit project <checkpoint|handoff|decision|verification|integrate|capture> --workspace <directory> --request <event.json>
344
381
  owlrunkit project status --workspace <directory> [--json]
345
382
  owlrunkit project takeover --workspace <directory> --agent <id> [--json]
383
+ owlrunkit project successor --workspace <directory> --transition-id <id> --at <ISO-UTC>
384
+ --definition <project.json> --reason <text> [--json]
346
385
 
347
386
  Project status is derived from durable work items, Agent assignments,
348
387
  dependencies, decisions, checkpoints, integration gates, and evidence refs.
@@ -355,6 +394,13 @@ No self-reported percentage is accepted.
355
394
  owlrunkit project takeover --workspace <directory> --agent <id> [--json]
356
395
 
357
396
  Builds a read-only blank-session recovery packet for one Agent.
397
+ `,
398
+ "project successor": `Usage:
399
+ owlrunkit project successor --workspace <directory> --transition-id <id> --at <ISO-UTC>
400
+ --definition <project.json> --reason <text> [--json]
401
+
402
+ Archives one derived-completed active project and installs one fresh active
403
+ project with no inherited events. The receipt grants no repository or release authority.
358
404
  `,
359
405
  });
360
406
 
@@ -368,13 +414,100 @@ function withHumanOutput(result, humanOutput) {
368
414
  return result;
369
415
  }
370
416
 
417
+ function formatTeamProjectAssignmentHuman(result) {
418
+ const disposition = result.resumed ? "Resumed" : "Assigned";
419
+ const nextAction = result.nextAction === null
420
+ ? "none"
421
+ : String(result.nextAction).replace(/\s+/gu, " ").trim();
422
+ return [
423
+ `${disposition} WorkItem ${result.workItemId} to Agent ${result.agentId}.`,
424
+ `Project next: ${nextAction}${result.nextActorId ? ` (owner ${result.nextActorId})` : ""}`,
425
+ `Assignment: ${result.assignmentId}`,
426
+ "",
427
+ ].join("\n");
428
+ }
429
+
430
+ function formatTeamProjectHandoffHuman(result) {
431
+ const disposition = result.resumed ? "Resumed" : "Handed off";
432
+ const targetNextAction = result.targetNextAction === null
433
+ ? "none"
434
+ : String(result.targetNextAction).replace(/\s+/gu, " ").trim();
435
+ const projectNextAction = result.nextAction === null
436
+ ? "none"
437
+ : String(result.nextAction).replace(/\s+/gu, " ").trim();
438
+ return [
439
+ `${disposition} WorkItem ${result.workItemId} from Agent ${result.fromAgentId} to Agent ${result.toAgentId}.`,
440
+ `Target next: ${targetNextAction}`,
441
+ `Project next: ${projectNextAction}${result.nextActorId ? ` (owner ${result.nextActorId})` : ""}`,
442
+ `Handoff: ${result.handoffId}`,
443
+ "",
444
+ ].join("\n");
445
+ }
446
+
447
+ function formatTeamProjectDecisionHuman(result) {
448
+ const disposition = result.resumed
449
+ ? "Resumed"
450
+ : result.operation === "opened" ? "Opened" : "Resolved";
451
+ const owner = result.ownerAgentId ? ` for Agent ${result.ownerAgentId}` : "";
452
+ const nextAction = result.nextAction === null
453
+ ? "none"
454
+ : String(result.nextAction).replace(/\s+/gu, " ").trim();
455
+ return [
456
+ `${disposition} decision ${result.decisionId}${owner}.`,
457
+ `Project next: ${nextAction}${result.nextActorId ? ` (owner ${result.nextActorId})` : ""}`,
458
+ `Decision: ${result.decisionId}`,
459
+ "",
460
+ ].join("\n");
461
+ }
462
+
463
+ function formatTeamProjectVerificationHuman(result) {
464
+ const disposition = result.resumed
465
+ ? "Resumed"
466
+ : result.operation === "deferred" ? "Deferred" : "Closed";
467
+ const nextAction = result.nextAction === null
468
+ ? "none"
469
+ : String(result.nextAction).replace(/\s+/gu, " ").trim();
470
+ return [
471
+ `${disposition} project verification ${result.verificationId}: ${result.verificationStatus}.`,
472
+ `Due gate: ${result.dueGateId}; gate state: ${result.gateStatus}.`,
473
+ `Project state: ${result.overall}; next: ${nextAction}${result.nextActorId ? ` (owner ${result.nextActorId})` : ""}`,
474
+ "No verification command, Git, or release action was performed.",
475
+ "",
476
+ ].join("\n");
477
+ }
478
+
479
+ function formatTeamProjectIntegrationHuman(result) {
480
+ const disposition = result.resumed ? "Resumed" : "Passed";
481
+ const nextAction = result.nextAction === null
482
+ ? "none"
483
+ : String(result.nextAction).replace(/\s+/gu, " ").trim();
484
+ return [
485
+ `${disposition} project integration gate ${result.gateId}.`,
486
+ `Project state: ${result.overall}; next: ${nextAction}${result.nextActorId ? ` (owner ${result.nextActorId})` : ""}`,
487
+ "Git/release not performed.",
488
+ "",
489
+ ].join("\n");
490
+ }
491
+
492
+ function formatTeamProjectCheckpointHuman(result) {
493
+ const disposition = result.resumed ? "Resumed" : "Recorded";
494
+ const nextAction = result.nextAction === null
495
+ ? "none"
496
+ : String(result.nextAction).replace(/\s+/gu, " ").trim();
497
+ return [
498
+ `${disposition} checkpoint ${result.checkpointId} for WorkItem ${result.workItemId}: ${result.state}.`,
499
+ `Project state: ${result.overall}; next: ${nextAction}${result.nextActorId ? ` (owner ${result.nextActorId})` : ""}`,
500
+ "",
501
+ ].join("\n");
502
+ }
503
+
371
504
  function profilesReadiness(root) {
372
505
  if (!root) return { valid: false, launcherWarnings: [] };
373
506
  try {
374
507
  const validation = validateProfiles({ workspaceRoot: root });
375
508
  const launchers = readProfileLauncherReadiness({ workspaceRoot: root });
376
509
  return {
377
- valid: validation.valid === true && launchers.ready,
510
+ valid: validation.valid === true && launchers.status === "ok",
378
511
  launcherWarnings: launchers.launcherWarnings,
379
512
  };
380
513
  } catch {
@@ -389,8 +522,17 @@ function onboardingProjection(root, recovery = null) {
389
522
  : { status: "missing" };
390
523
  const profilesReady = profiles.valid;
391
524
  const adoptionReady = adoption.status === "ok";
525
+ const maintenanceNextAction = profiles.launcherWarnings.length > 0
526
+ ? "review_profile_launchers"
527
+ : null;
392
528
  if (profilesReady && adoptionReady) {
393
529
  return {
530
+ ...(maintenanceNextAction
531
+ ? {
532
+ status: "completed_with_maintenance_warnings",
533
+ maintenanceNextAction,
534
+ }
535
+ : {}),
394
536
  nextAllowedAction: recovery?.nextAllowedAction ?? "plan_new_execution",
395
537
  completed: true,
396
538
  steps: [],
@@ -402,12 +544,7 @@ function onboardingProjection(root, recovery = null) {
402
544
  command: "owlrunkit doctor --workspace .",
403
545
  },
404
546
  ...(!profilesReady
405
- ? profiles.launcherWarnings.length > 0
406
- ? [{
407
- id: "profile_launchers_review",
408
- command: "owlrunkit doctor --workspace .",
409
- }]
410
- : [{
547
+ ? [{
411
548
  id: "profiles_detect",
412
549
  command: "owlrunkit profiles detect --workspace . --dry-run",
413
550
  }]
@@ -421,6 +558,7 @@ function onboardingProjection(root, recovery = null) {
421
558
  ];
422
559
  return {
423
560
  nextAllowedAction: "run_onboarding_diagnostics",
561
+ ...(maintenanceNextAction ? { maintenanceNextAction } : {}),
424
562
  completed: false,
425
563
  steps,
426
564
  };
@@ -460,6 +598,30 @@ function requireOption(options, name) {
460
598
  return value;
461
599
  }
462
600
 
601
+ function assertOnlyOptions(options, allowedNames) {
602
+ const allowed = new Set(allowedNames);
603
+ const unsupported = Object.keys(options)
604
+ .filter(name => !allowed.has(name))
605
+ .sort();
606
+ if (unsupported.length > 0) {
607
+ throw new Error(
608
+ `Unsupported option${unsupported.length === 1 ? "" : "s"}: `
609
+ + unsupported.map(name => `--${name}`).join(", "),
610
+ );
611
+ }
612
+ }
613
+
614
+ function parseCanonicalNonnegativeSafeInteger(value, label) {
615
+ if (typeof value !== "string" || !/^(?:0|[1-9]\d*)$/u.test(value)) {
616
+ throw new Error(`--${label} must be a canonical non-negative safe integer.`);
617
+ }
618
+ const parsed = Number(value);
619
+ if (!Number.isSafeInteger(parsed)) {
620
+ throw new Error(`--${label} must be a canonical non-negative safe integer.`);
621
+ }
622
+ return parsed;
623
+ }
624
+
463
625
  function readWorkspaceArtifact(root, value, label) {
464
626
  const relativePath = safeRelativePath(value, label);
465
627
  return readJson(realpathSync(path.join(root, relativePath)));
@@ -1387,7 +1549,10 @@ function inspect(options) {
1387
1549
  if (requestedRunId && !executions.some(execution => execution.runId === requestedRunId)) {
1388
1550
  throw new Error(`Execution was not found: ${requestedRunId}`);
1389
1551
  }
1390
- const summary = buildInspectSummary(result);
1552
+ const onboarding = onboardingProjection(root, recovery);
1553
+ const summary = buildInspectSummary(result, {
1554
+ maintenanceNextAction: onboarding.maintenanceNextAction ?? null,
1555
+ });
1391
1556
  const view = options.history
1392
1557
  ? { mode: "history" }
1393
1558
  : requestedRunId
@@ -1398,7 +1563,7 @@ function inspect(options) {
1398
1563
  execution: executions.find(execution => execution.runId === requestedRunId),
1399
1564
  }
1400
1565
  : { mode: "summary" };
1401
- return { ...result, summary, view, onboarding: onboardingProjection(root, recovery) };
1566
+ return { ...result, summary, view, onboarding };
1402
1567
  }
1403
1568
 
1404
1569
  function closeout(options) {
@@ -1650,12 +1815,26 @@ export async function runCli(argv = process.argv.slice(2), runtimeHooks = {}) {
1650
1815
  || command === "start"
1651
1816
  || (command === "formal" && action === "start")
1652
1817
  ? ["owned-path"]
1818
+ : command === "project" && action === "handoff"
1819
+ ? ["evidence"]
1820
+ : command === "project" && action === "integrate"
1821
+ ? ["evidence"]
1822
+ : command === "project" && action === "verification"
1823
+ ? ["check", "evidence", "decision"]
1824
+ : command === "project" && action === "checkpoint"
1825
+ ? ["evidence", "blocker", "decision"]
1826
+ : command === "project" && action === "decision"
1827
+ ? ["blocking-work-item", "option", "evidence"]
1653
1828
  : [],
1654
1829
  boolean: command === "inspect"
1655
1830
  ? ["json", "verbose", "history"]
1656
1831
  : command === "init"
1657
1832
  ? ["json"]
1658
- : command === "project" && new Set(["status", "takeover"]).has(action)
1833
+ : command === "project" && action === "decision"
1834
+ ? ["json", "open", "resolve"]
1835
+ : command === "project" && action === "verification"
1836
+ ? ["json", "defer", "close"]
1837
+ : command === "project" && new Set(["assign", "handoff", "checkpoint", "integrate", "status", "takeover", "successor"]).has(action)
1659
1838
  ? ["json"]
1660
1839
  : command === "doctor" || command === "adopt" || command === "status"
1661
1840
  ? ["json"]
@@ -2405,6 +2584,40 @@ export async function runCli(argv = process.argv.slice(2), runtimeHooks = {}) {
2405
2584
  );
2406
2585
  }
2407
2586
  if (command === "project") {
2587
+ const allowedProjectOptions = {
2588
+ init: ["workspace", "definition"],
2589
+ assign: [
2590
+ "workspace", "request", "assignment-id", "at", "work-item", "agent",
2591
+ "supersedes", "execution-run-id", "execution-work-item", "json",
2592
+ ],
2593
+ handoff: [
2594
+ "workspace", "request", "handoff-id", "at", "assignment-id", "work-item",
2595
+ "from-agent", "to-agent", "summary", "next", "evidence", "json",
2596
+ ],
2597
+ decision: [
2598
+ "workspace", "request", "open", "resolve", "decision-id", "at", "title",
2599
+ "question", "owner-agent", "blocking-work-item", "option", "resolution",
2600
+ "rationale", "evidence", "json",
2601
+ ],
2602
+ checkpoint: [
2603
+ "workspace", "request", "checkpoint-id", "at", "assignment-id", "work-item",
2604
+ "state", "summary", "completed-units", "evidence", "blocker", "decision",
2605
+ "next", "source-fingerprint", "json",
2606
+ ],
2607
+ verification: [
2608
+ "workspace", "request", "defer", "close", "verification-id", "at",
2609
+ "work-item", "owner-agent", "check", "reason", "due-gate",
2610
+ "disposition", "summary", "evidence", "decision", "json",
2611
+ ],
2612
+ integrate: ["workspace", "request", "gate", "at", "summary", "evidence", "json"],
2613
+ capture: ["workspace", "request"],
2614
+ status: ["workspace", "json"],
2615
+ takeover: ["workspace", "agent", "json"],
2616
+ successor: ["workspace", "transition-id", "at", "definition", "reason", "json"],
2617
+ };
2618
+ if (Object.hasOwn(allowedProjectOptions, action)) {
2619
+ assertOnlyOptions(options, allowedProjectOptions[action]);
2620
+ }
2408
2621
  const root = requireOption(options, "workspace");
2409
2622
  if (action === "init") {
2410
2623
  return initializeTeamProjectV1({
@@ -2412,7 +2625,334 @@ export async function runCli(argv = process.argv.slice(2), runtimeHooks = {}) {
2412
2625
  definition: readJson(realpathSync(requireOption(options, "definition"))),
2413
2626
  });
2414
2627
  }
2415
- if (new Set(["assign", "checkpoint", "handoff", "decision", "capture"]).has(action)) {
2628
+ if (action === "assign") {
2629
+ const directAssignmentFields = [
2630
+ "assignment-id",
2631
+ "at",
2632
+ "work-item",
2633
+ "agent",
2634
+ "supersedes",
2635
+ "execution-run-id",
2636
+ "execution-work-item",
2637
+ ];
2638
+ const hasDirectAssignmentFields = directAssignmentFields.some(
2639
+ name => options[name] !== undefined,
2640
+ );
2641
+ if (options.request !== undefined && hasDirectAssignmentFields) {
2642
+ throw new Error("--request cannot be combined with direct assignment fields.");
2643
+ }
2644
+ if (options.request !== undefined) {
2645
+ return appendTeamProjectEventV1({
2646
+ workspaceRoot: root,
2647
+ event: readJson(realpathSync(requireOption(options, "request"))),
2648
+ });
2649
+ }
2650
+ const assigned = assignTeamProjectV1({
2651
+ workspaceRoot: root,
2652
+ assignmentId: options["assignment-id"],
2653
+ occurredAt: options.at,
2654
+ workItemId: options["work-item"],
2655
+ agentId: options.agent,
2656
+ supersedesAssignmentId: options.supersedes,
2657
+ executionRunId: options["execution-run-id"],
2658
+ executionWorkItemId: options["execution-work-item"],
2659
+ });
2660
+ return options.json === true
2661
+ ? assigned
2662
+ : withHumanOutput(assigned, formatTeamProjectAssignmentHuman(assigned));
2663
+ }
2664
+ if (action === "handoff") {
2665
+ const directHandoffFields = [
2666
+ "handoff-id",
2667
+ "at",
2668
+ "assignment-id",
2669
+ "work-item",
2670
+ "from-agent",
2671
+ "to-agent",
2672
+ "summary",
2673
+ "next",
2674
+ "evidence",
2675
+ ];
2676
+ const hasDirectHandoffFields = directHandoffFields.some(
2677
+ name => options[name] !== undefined,
2678
+ );
2679
+ if (options.request !== undefined && hasDirectHandoffFields) {
2680
+ throw new Error("--request cannot be combined with direct handoff fields.");
2681
+ }
2682
+ if (options.request !== undefined) {
2683
+ return appendTeamProjectEventV1({
2684
+ workspaceRoot: root,
2685
+ event: readJson(realpathSync(requireOption(options, "request"))),
2686
+ });
2687
+ }
2688
+ const handedOff = handoffTeamProjectV1({
2689
+ workspaceRoot: root,
2690
+ handoffId: options["handoff-id"],
2691
+ occurredAt: options.at,
2692
+ assignmentId: options["assignment-id"],
2693
+ workItemId: options["work-item"],
2694
+ fromAgentId: options["from-agent"],
2695
+ toAgentId: options["to-agent"],
2696
+ summary: options.summary,
2697
+ nextAction: options.next,
2698
+ evidenceRefs: options.evidence,
2699
+ });
2700
+ return options.json === true
2701
+ ? handedOff
2702
+ : withHumanOutput(handedOff, formatTeamProjectHandoffHuman(handedOff));
2703
+ }
2704
+ if (action === "decision") {
2705
+ const directDecisionFields = [
2706
+ "open",
2707
+ "resolve",
2708
+ "decision-id",
2709
+ "at",
2710
+ "title",
2711
+ "question",
2712
+ "owner-agent",
2713
+ "blocking-work-item",
2714
+ "option",
2715
+ "resolution",
2716
+ "rationale",
2717
+ "evidence",
2718
+ ];
2719
+ const hasDirectDecisionFields = directDecisionFields.some(
2720
+ name => options[name] !== undefined,
2721
+ );
2722
+ if (options.request !== undefined && hasDirectDecisionFields) {
2723
+ throw new Error("--request cannot be combined with direct decision fields.");
2724
+ }
2725
+ if (options.request !== undefined) {
2726
+ return appendTeamProjectEventV1({
2727
+ workspaceRoot: root,
2728
+ event: readJson(realpathSync(requireOption(options, "request"))),
2729
+ });
2730
+ }
2731
+ if (options.open === options.resolve) {
2732
+ throw new Error("Exactly one of --open or --resolve is required.");
2733
+ }
2734
+ const decisionId = requireOption(options, "decision-id");
2735
+ const occurredAt = requireOption(options, "at");
2736
+ if (options.open === true) {
2737
+ if (options.resolution !== undefined
2738
+ || options.rationale !== undefined
2739
+ || options.evidence !== undefined) {
2740
+ throw new Error("Decision open cannot accept resolution, rationale, or evidence.");
2741
+ }
2742
+ if ((options.option ?? []).length === 0) {
2743
+ throw new Error("Decision open requires at least one --option.");
2744
+ }
2745
+ const opened = openTeamProjectDecisionV1({
2746
+ workspaceRoot: root,
2747
+ decisionId,
2748
+ occurredAt,
2749
+ title: requireOption(options, "title"),
2750
+ question: requireOption(options, "question"),
2751
+ ownerAgentId: requireOption(options, "owner-agent"),
2752
+ blockingWorkItemIds: options["blocking-work-item"] ?? [],
2753
+ options: options.option,
2754
+ });
2755
+ return options.json === true
2756
+ ? opened
2757
+ : withHumanOutput(opened, formatTeamProjectDecisionHuman(opened));
2758
+ }
2759
+ if (options.title !== undefined
2760
+ || options.question !== undefined
2761
+ || options["owner-agent"] !== undefined
2762
+ || options["blocking-work-item"] !== undefined
2763
+ || options.option !== undefined) {
2764
+ throw new Error("Decision resolve cannot accept title, question, owner-agent, blocking-work-item, or option.");
2765
+ }
2766
+ if ((options.evidence ?? []).length === 0) {
2767
+ throw new Error("Decision resolve requires at least one --evidence.");
2768
+ }
2769
+ const resolved = resolveTeamProjectDecisionV1({
2770
+ workspaceRoot: root,
2771
+ decisionId,
2772
+ occurredAt,
2773
+ resolution: requireOption(options, "resolution"),
2774
+ rationale: requireOption(options, "rationale"),
2775
+ evidenceRefs: options.evidence,
2776
+ });
2777
+ return options.json === true
2778
+ ? resolved
2779
+ : withHumanOutput(resolved, formatTeamProjectDecisionHuman(resolved));
2780
+ }
2781
+ if (action === "verification") {
2782
+ const directVerificationFields = [
2783
+ "defer",
2784
+ "close",
2785
+ "verification-id",
2786
+ "at",
2787
+ "work-item",
2788
+ "owner-agent",
2789
+ "check",
2790
+ "reason",
2791
+ "due-gate",
2792
+ "disposition",
2793
+ "summary",
2794
+ "evidence",
2795
+ "decision",
2796
+ ];
2797
+ const hasDirectVerificationFields = directVerificationFields.some(
2798
+ name => options[name] !== undefined,
2799
+ );
2800
+ if (options.request !== undefined && hasDirectVerificationFields) {
2801
+ throw new Error("--request cannot be combined with direct verification fields.");
2802
+ }
2803
+ if (options.request !== undefined) {
2804
+ const requestedEvent = readJson(realpathSync(requireOption(options, "request")));
2805
+ if (!new Set(["verification_deferred", "verification_closed"])
2806
+ .has(requestedEvent?.type)) {
2807
+ throw new Error(
2808
+ "Project verification request only accepts type=verification_deferred or verification_closed.",
2809
+ );
2810
+ }
2811
+ return appendTeamProjectEventV1({
2812
+ workspaceRoot: root,
2813
+ event: requestedEvent,
2814
+ });
2815
+ }
2816
+ if (options.defer === options.close) {
2817
+ throw new Error("Exactly one of --defer or --close is required.");
2818
+ }
2819
+ const verificationId = requireOption(options, "verification-id");
2820
+ const occurredAt = requireOption(options, "at");
2821
+ if (options.defer === true) {
2822
+ if (options.disposition !== undefined
2823
+ || options.summary !== undefined
2824
+ || options.evidence !== undefined
2825
+ || options.decision !== undefined) {
2826
+ throw new Error(
2827
+ "Verification defer cannot accept disposition, summary, evidence, or decision.",
2828
+ );
2829
+ }
2830
+ if ((options.check ?? []).length === 0) {
2831
+ throw new Error("Verification defer requires at least one --check.");
2832
+ }
2833
+ const deferred = deferTeamProjectVerificationV1({
2834
+ workspaceRoot: root,
2835
+ verificationId,
2836
+ occurredAt,
2837
+ workItemId: requireOption(options, "work-item"),
2838
+ ownerAgentId: requireOption(options, "owner-agent"),
2839
+ checkIds: options.check,
2840
+ reason: requireOption(options, "reason"),
2841
+ dueGateId: requireOption(options, "due-gate"),
2842
+ });
2843
+ return options.json === true
2844
+ ? deferred
2845
+ : withHumanOutput(deferred, formatTeamProjectVerificationHuman(deferred));
2846
+ }
2847
+ if (options["work-item"] !== undefined
2848
+ || options["owner-agent"] !== undefined
2849
+ || options.check !== undefined
2850
+ || options.reason !== undefined
2851
+ || options["due-gate"] !== undefined) {
2852
+ throw new Error(
2853
+ "Verification close cannot accept work-item, owner-agent, check, reason, or due-gate.",
2854
+ );
2855
+ }
2856
+ const closed = closeTeamProjectVerificationV1({
2857
+ workspaceRoot: root,
2858
+ verificationId,
2859
+ occurredAt,
2860
+ disposition: requireOption(options, "disposition"),
2861
+ summary: requireOption(options, "summary"),
2862
+ evidenceRefs: options.evidence ?? [],
2863
+ decisionIds: options.decision ?? [],
2864
+ });
2865
+ return options.json === true
2866
+ ? closed
2867
+ : withHumanOutput(closed, formatTeamProjectVerificationHuman(closed));
2868
+ }
2869
+ if (action === "checkpoint") {
2870
+ const directCheckpointFields = [
2871
+ "checkpoint-id",
2872
+ "at",
2873
+ "assignment-id",
2874
+ "work-item",
2875
+ "state",
2876
+ "summary",
2877
+ "completed-units",
2878
+ "evidence",
2879
+ "blocker",
2880
+ "decision",
2881
+ "next",
2882
+ "source-fingerprint",
2883
+ ];
2884
+ const hasDirectCheckpointFields = directCheckpointFields.some(
2885
+ name => options[name] !== undefined,
2886
+ );
2887
+ if (options.request !== undefined && hasDirectCheckpointFields) {
2888
+ throw new Error("--request cannot be combined with direct checkpoint fields.");
2889
+ }
2890
+ if (options.request !== undefined) {
2891
+ const requestedEvent = readJson(realpathSync(requireOption(options, "request")));
2892
+ if (requestedEvent?.type !== "checkpoint_recorded") {
2893
+ throw new Error("Project checkpoint request only accepts type=checkpoint_recorded.");
2894
+ }
2895
+ return appendTeamProjectEventV1({
2896
+ workspaceRoot: root,
2897
+ event: requestedEvent,
2898
+ });
2899
+ }
2900
+ const completedUnits = options["completed-units"] === undefined
2901
+ ? undefined
2902
+ : parseCanonicalNonnegativeSafeInteger(options["completed-units"], "completed-units");
2903
+ const checkpointed = checkpointTeamProjectV1({
2904
+ workspaceRoot: root,
2905
+ checkpointId: requireOption(options, "checkpoint-id"),
2906
+ occurredAt: requireOption(options, "at"),
2907
+ assignmentId: requireOption(options, "assignment-id"),
2908
+ workItemId: requireOption(options, "work-item"),
2909
+ state: requireOption(options, "state"),
2910
+ summary: requireOption(options, "summary"),
2911
+ completedUnits,
2912
+ evidenceRefs: options.evidence ?? [],
2913
+ blockerRefs: options.blocker ?? [],
2914
+ decisionRefs: options.decision ?? [],
2915
+ nextAction: options.next ?? null,
2916
+ sourceFingerprint: options["source-fingerprint"],
2917
+ });
2918
+ return options.json === true
2919
+ ? checkpointed
2920
+ : withHumanOutput(checkpointed, formatTeamProjectCheckpointHuman(checkpointed));
2921
+ }
2922
+ if (action === "integrate") {
2923
+ const directIntegrationFields = ["gate", "at", "summary", "evidence"];
2924
+ const hasDirectIntegrationFields = directIntegrationFields.some(
2925
+ name => options[name] !== undefined,
2926
+ );
2927
+ if (options.request !== undefined && hasDirectIntegrationFields) {
2928
+ throw new Error("--request cannot be combined with direct integrate fields.");
2929
+ }
2930
+ if (options.request !== undefined) {
2931
+ const requestedEvent = readJson(realpathSync(requireOption(options, "request")));
2932
+ if (requestedEvent?.type !== "integration_gate_passed") {
2933
+ throw new Error("Project integrate request only accepts type=integration_gate_passed.");
2934
+ }
2935
+ return appendTeamProjectEventV1({
2936
+ workspaceRoot: root,
2937
+ event: requestedEvent,
2938
+ });
2939
+ }
2940
+ const evidenceRefs = options.evidence ?? [];
2941
+ if (evidenceRefs.length === 0) {
2942
+ throw new Error("Project integrate requires at least one --evidence.");
2943
+ }
2944
+ const integrated = integrateTeamProjectV1({
2945
+ workspaceRoot: root,
2946
+ gateId: requireOption(options, "gate"),
2947
+ occurredAt: requireOption(options, "at"),
2948
+ summary: requireOption(options, "summary"),
2949
+ evidenceRefs,
2950
+ });
2951
+ return options.json === true
2952
+ ? integrated
2953
+ : withHumanOutput(integrated, formatTeamProjectIntegrationHuman(integrated));
2954
+ }
2955
+ if (new Set(["checkpoint", "capture"]).has(action)) {
2416
2956
  return appendTeamProjectEventV1({
2417
2957
  workspaceRoot: root,
2418
2958
  event: readJson(realpathSync(requireOption(options, "request"))),
@@ -2431,15 +2971,22 @@ export async function runCli(argv = process.argv.slice(2), runtimeHooks = {}) {
2431
2971
  });
2432
2972
  return options.json === true
2433
2973
  ? takeover
2434
- : withHumanOutput(takeover, [
2435
- `${takeover.projectId}: ${takeover.objective}`,
2436
- `Agent: ${takeover.agentId}`,
2437
- `Responsibility: ${takeover.currentResponsibility?.workItemId ?? "none"}`,
2438
- `Next: ${takeover.nextAction ?? "none"}`,
2439
- "",
2440
- ].join("\n"));
2974
+ : withHumanOutput(takeover, formatTeamProjectTakeoverHumanV1(takeover));
2975
+ }
2976
+ if (action === "successor") {
2977
+ const successor = successorTeamProjectV1({
2978
+ workspaceRoot: root,
2979
+ transitionId: requireOption(options, "transition-id"),
2980
+ occurredAt: requireOption(options, "at"),
2981
+ definitionPath: requireOption(options, "definition"),
2982
+ reason: requireOption(options, "reason"),
2983
+ onDurableStep: runtimeHooks.onTeamProjectSuccessorStep,
2984
+ });
2985
+ return options.json === true
2986
+ ? successor
2987
+ : withHumanOutput(successor, formatTeamProjectSuccessorHumanV1(successor));
2441
2988
  }
2442
- throw new Error("Usage: owlrunkit project <init|assign|checkpoint|handoff|decision|capture|status|takeover> [options]");
2989
+ throw new Error("Usage: owlrunkit project <init|assign|checkpoint|handoff|decision|verification|integrate|capture|status|takeover|successor> [options]");
2443
2990
  }
2444
2991
  if (command === "profiles") {
2445
2992
  const root = requireOption(options, "workspace");