@deftai/directive-core 0.90.0 → 0.92.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.
Files changed (101) hide show
  1. package/dist/cache/fetch.js +3 -2
  2. package/dist/cache/io.d.ts +13 -2
  3. package/dist/cache/io.js +36 -7
  4. package/dist/cache/operations.js +5 -3
  5. package/dist/doctor/main.js +37 -0
  6. package/dist/doctor/openclaw-skills.d.ts +42 -8
  7. package/dist/doctor/openclaw-skills.js +281 -38
  8. package/dist/eval/health.d.ts +10 -1
  9. package/dist/eval/health.js +16 -16
  10. package/dist/hooks/dispatcher.d.ts +6 -1
  11. package/dist/hooks/dispatcher.js +30 -0
  12. package/dist/init-deposit/gitignore.js +3 -0
  13. package/dist/init-deposit/hygiene.d.ts +43 -1
  14. package/dist/init-deposit/hygiene.js +112 -7
  15. package/dist/init-deposit/init-deposit.d.ts +5 -0
  16. package/dist/init-deposit/init-deposit.js +37 -2
  17. package/dist/init-deposit/scaffold.js +3 -1
  18. package/dist/intake/github-auth-modes.d.ts +1 -1
  19. package/dist/intake/github-auth-modes.js +25 -3
  20. package/dist/intake/issue-ingest-cli.js +13 -0
  21. package/dist/intake/reconcile-issues-cli.js +13 -0
  22. package/dist/orchestration/probe-session.d.ts +5 -1
  23. package/dist/orchestration/probe-session.js +24 -13
  24. package/dist/platform/agents-md.js +1 -1
  25. package/dist/policy/deft-directive-disable.d.ts +86 -0
  26. package/dist/policy/deft-directive-disable.js +167 -0
  27. package/dist/policy/delivery-branch.d.ts +33 -0
  28. package/dist/policy/delivery-branch.js +124 -0
  29. package/dist/policy/index.d.ts +2 -0
  30. package/dist/policy/index.js +17 -1
  31. package/dist/policy/plan-extensions.d.ts +10 -0
  32. package/dist/policy/plan-extensions.js +16 -0
  33. package/dist/pr-closing-keywords/detect.d.ts +13 -0
  34. package/dist/pr-closing-keywords/detect.js +73 -8
  35. package/dist/pr-closing-keywords/index.d.ts +2 -2
  36. package/dist/pr-closing-keywords/index.js +1 -1
  37. package/dist/pr-closing-keywords/main.js +119 -90
  38. package/dist/pr-closing-keywords/types.d.ts +10 -0
  39. package/dist/render/constants.d.ts +16 -0
  40. package/dist/render/constants.js +16 -1
  41. package/dist/render/export-spec.d.ts +7 -1
  42. package/dist/render/export-spec.js +51 -5
  43. package/dist/render/index.d.ts +3 -2
  44. package/dist/render/index.js +2 -2
  45. package/dist/render/roadmap-render.js +164 -60
  46. package/dist/render/scope-outlook.d.ts +5 -0
  47. package/dist/render/scope-outlook.js +3 -0
  48. package/dist/render/spec-render.d.ts +28 -2
  49. package/dist/render/spec-render.js +111 -14
  50. package/dist/render/text-utils.d.ts +6 -0
  51. package/dist/render/text-utils.js +23 -0
  52. package/dist/scm/binary.d.ts +3 -0
  53. package/dist/scm/binary.js +10 -2
  54. package/dist/scm/call.d.ts +5 -0
  55. package/dist/scm/call.js +5 -0
  56. package/dist/scm/index.d.ts +1 -0
  57. package/dist/scm/index.js +1 -0
  58. package/dist/scm/main.d.ts +5 -0
  59. package/dist/scm/main.js +27 -0
  60. package/dist/scm/readiness-cli.d.ts +24 -0
  61. package/dist/scm/readiness-cli.js +85 -0
  62. package/dist/scm/readiness.d.ts +104 -0
  63. package/dist/scm/readiness.js +421 -0
  64. package/dist/scope/batch-promote.d.ts +26 -0
  65. package/dist/scope/batch-promote.js +167 -0
  66. package/dist/scope/brief-io.d.ts +3 -1
  67. package/dist/scope/brief-io.js +5 -3
  68. package/dist/scope/delivery-evidence.d.ts +112 -0
  69. package/dist/scope/delivery-evidence.js +419 -0
  70. package/dist/scope/index.d.ts +2 -0
  71. package/dist/scope/index.js +2 -0
  72. package/dist/scope/main.d.ts +7 -0
  73. package/dist/scope/main.js +155 -9
  74. package/dist/scope/registry-artifact-sync.js +4 -1
  75. package/dist/scope/transition.d.ts +11 -1
  76. package/dist/scope/transition.js +30 -4
  77. package/dist/scope/wip-cap-check.d.ts +6 -0
  78. package/dist/scope/wip-cap-check.js +14 -2
  79. package/dist/session/ritual-sentinel.js +21 -12
  80. package/dist/session/session-start-hook.d.ts +3 -0
  81. package/dist/session/session-start-hook.js +21 -0
  82. package/dist/session/session-start.d.ts +7 -0
  83. package/dist/session/session-start.js +85 -0
  84. package/dist/swarm/complete-cohort.d.ts +20 -1
  85. package/dist/swarm/complete-cohort.js +56 -9
  86. package/dist/swarm/finalize-cohort-cli.js +9 -0
  87. package/dist/swarm/finalize-cohort.d.ts +8 -0
  88. package/dist/swarm/finalize-cohort.js +217 -21
  89. package/dist/triage/welcome/index.d.ts +1 -1
  90. package/dist/triage/welcome/index.js +1 -1
  91. package/dist/triage/welcome/onboard.js +7 -1
  92. package/dist/triage/welcome/prior-state.d.ts +7 -0
  93. package/dist/triage/welcome/prior-state.js +16 -2
  94. package/dist/triage/welcome/writers.d.ts +10 -0
  95. package/dist/triage/welcome/writers.js +72 -2
  96. package/dist/user-config/experimental-rules.d.ts +43 -0
  97. package/dist/user-config/experimental-rules.js +162 -0
  98. package/dist/user-config/index.d.ts +1 -0
  99. package/dist/user-config/index.js +1 -0
  100. package/dist/verify-source/contained-writes.js +1 -1
  101. package/package.json +3 -3
@@ -3,12 +3,14 @@ import { runningInsideDeftRepo } from "../doctor/paths.js";
3
3
  import { emitSessionEvalReadback } from "../eval/readback.js";
4
4
  import { MIGRATE_COMPLETION_NUDGE, shouldEmitMigrateNudge } from "../init-deposit/migrate.js";
5
5
  import { detectEnvironmentContext, environmentContextToDict, formatEnvironmentContext, } from "../platform/shell-context.js";
6
+ import { DEFT_DIRECTIVE_DISABLE_FLAG_NAME, DEFT_DIRECTIVE_DISABLE_STATUS, detectDeftDirectiveDisable, formatDeftDirectiveDisableMessage, isDeftDirectiveDisableActive, } from "../policy/deft-directive-disable.js";
6
7
  import { disclosureLine } from "../policy/disclosure.js";
7
8
  import { detectNoDeftDirective, NO_DEFT_DIRECTIVE_DISABLED_MESSAGE, NO_DEFT_DIRECTIVE_FLAG_NAME, NO_DEFT_DIRECTIVE_INCONSISTENT_MESSAGE, NO_DEFT_DIRECTIVE_INCONSISTENT_POLICY, } from "../policy/no-deft-directive.js";
8
9
  import { humanMergeDisclosureLine, resolveHumanMergePolicy, } from "../policy/require-human-merge.js";
9
10
  import { resolvePolicy } from "../policy/resolve.js";
10
11
  import { maybeFormatProductSignalConsentPrompt } from "../product-signal/consent-prompt.js";
11
12
  import { formatFrameworkCommand } from "../render/framework-commands.js";
13
+ import { formatScmReadinessLines, probeScmReadiness, scmReadinessToDict, } from "../scm/readiness.js";
12
14
  import { maybeRunStalenessTickler } from "../staleness-tickler/run.js";
13
15
  import { runDefaultMode } from "../triage/welcome/default-mode.js";
14
16
  import { resolveUserMdPath } from "../user-config/resolve-user-md.js";
@@ -292,6 +294,44 @@ export function defaultBranchSync(projectRoot, runGit = defaultGitRunner) {
292
294
  }
293
295
  return { branch, upstream, ahead, behind, warning };
294
296
  }
297
+ /**
298
+ * Resolve SCM readiness for session orientation (#2275).
299
+ * Shallow by default (PATH + auth status); deep when optional network is on.
300
+ * Never throws — session:start must not hard-block on SCM absence.
301
+ */
302
+ function resolveSessionScmReadiness(options, allowOptionalNetwork) {
303
+ const probe = options.probeScm ?? probeScmReadiness;
304
+ try {
305
+ return probe({
306
+ depth: allowOptionalNetwork ? "deep" : "shallow",
307
+ env: options.env,
308
+ });
309
+ }
310
+ catch {
311
+ return {
312
+ ready: false,
313
+ binary: null,
314
+ binaryPath: null,
315
+ authState: "unknown",
316
+ githubAuthMode: "host-gh",
317
+ runtimeMode: "local-unsandboxed",
318
+ injectedTokenPresent: false,
319
+ depth: allowOptionalNetwork ? "deep" : "shallow",
320
+ detail: "SCM readiness probe failed unexpectedly; treating SCM-dependent gates as skipped",
321
+ remediation: null,
322
+ skippedGates: [
323
+ "triage:queue",
324
+ "issue:ingest",
325
+ "pr:*",
326
+ "reconcile:issues",
327
+ "cache:fetch-all",
328
+ "scm:*",
329
+ ],
330
+ login: null,
331
+ failureKind: "probe_error",
332
+ };
333
+ }
334
+ }
295
335
  function runReadOnlySessionStart(projectRoot, options, instant, environment) {
296
336
  const lines = [];
297
337
  const resolveUserMd = options.resolveUserMd ?? ((root) => resolveUserMdPath({ projectRoot: root }));
@@ -301,9 +341,12 @@ function runReadOnlySessionStart(projectRoot, options, instant, environment) {
301
341
  const userMdLine = userMd.found
302
342
  ? `USER.md resolved (${userMd.rung}): ${safePath}`
303
343
  : safeDiagnostic;
344
+ // #2275: report SCM availability even on read-only alignment (shallow; no network).
345
+ const scm = resolveSessionScmReadiness(options, false);
304
346
  lines.push(READ_ONLY_ALIGNMENT_MESSAGE);
305
347
  lines.push(userMdLine);
306
348
  lines.push(formatEnvironmentContext(environment));
349
+ lines.push(...formatScmReadinessLines(scm));
307
350
  const resultPayload = {
308
351
  ready: true,
309
352
  exit_code: 0,
@@ -324,6 +367,7 @@ function runReadOnlySessionStart(projectRoot, options, instant, environment) {
324
367
  diagnostic: userMd.diagnostic,
325
368
  },
326
369
  environment: environmentContextToDict(environment),
370
+ scm: scmReadinessToDict(scm),
327
371
  message: READ_ONLY_RESULT_MESSAGE,
328
372
  };
329
373
  return { code: 0, payload: resultPayload, lines };
@@ -364,10 +408,13 @@ function runSessionRearm(projectRoot, options, instant, environment) {
364
408
  ? `USER.md resolved (${userMd.rung}): ${safePath}`
365
409
  : safeDiagnostic;
366
410
  const alignmentMessage = `${READ_ONLY_ALIGNMENT_MESSAGE} ${userMdLine}`;
411
+ // #2275: re-arm still reports SCM state (shallow; no network).
412
+ const scm = resolveSessionScmReadiness(options, false);
367
413
  const lines = [
368
414
  READ_ONLY_ALIGNMENT_MESSAGE,
369
415
  userMdLine,
370
416
  formatEnvironmentContext(environment),
417
+ ...formatScmReadinessLines(scm),
371
418
  REARM_SKIPPED_FAT_PATH_MESSAGE,
372
419
  ];
373
420
  // Light branch-policy disclosure (local only) so re-arm still surfaces policy state.
@@ -484,6 +531,7 @@ function runSessionRearm(projectRoot, options, instant, environment) {
484
531
  diagnostic: userMd.diagnostic,
485
532
  },
486
533
  environment: environmentContextToDict(environment),
534
+ scm: scmReadinessToDict(scm),
487
535
  message: code === 0 ? "session ritual re-armed" : "session ritual re-arm failed",
488
536
  },
489
537
  lines,
@@ -496,6 +544,36 @@ export function runSessionStart(projectRoot, options = {}) {
496
544
  const runGit = options.runGit ?? defaultGitRunner;
497
545
  const environment = (options.probeEnvironment ?? detectEnvironmentContext)();
498
546
  const ceremonyTier = options.ceremonyTier ?? COLD_CEREMONY_TIER;
547
+ // #3039: local (untracked) test kill-switch — skip ritual write; deposit may remain.
548
+ // Recovery requires delete + new session. Tracked flags do not short-circuit.
549
+ if (isDeftDirectiveDisableActive(projectRoot)) {
550
+ const killSwitch = detectDeftDirectiveDisable(projectRoot);
551
+ const optOutAlso = detectNoDeftDirective(projectRoot);
552
+ const message = formatDeftDirectiveDisableMessage({
553
+ permanentOptOutAlsoPresent: optOutAlso.present,
554
+ trackedByGit: false,
555
+ });
556
+ const lines = message.split("\n");
557
+ return {
558
+ code: 0,
559
+ payload: {
560
+ ready: false,
561
+ exit_code: 0,
562
+ disabled: true,
563
+ disabled_via: DEFT_DIRECTIVE_DISABLE_FLAG_NAME,
564
+ status: DEFT_DIRECTIVE_DISABLE_STATUS,
565
+ kill_switch: true,
566
+ inconsistent: false,
567
+ deposit_present: killSwitch.depositPresent,
568
+ tracked_by_git: false,
569
+ permanent_opt_out_also_present: optOutAlso.present,
570
+ posture,
571
+ environment: environmentContextToDict(environment),
572
+ message,
573
+ },
574
+ lines,
575
+ };
576
+ }
499
577
  // #2926: official root opt-out wins locally — skip Directive session ritual.
500
578
  // disabled = skip ritual (exit 0 clean / 1 inconsistent). ready stays false so
501
579
  // automation does not treat opt-out as "session fully initialized for work".
@@ -594,6 +672,12 @@ export function runSessionStart(projectRoot, options = {}) {
594
672
  stepTimings.push({ name: "alignment", duration_ms: 0, skipped: true });
595
673
  }
596
674
  lines.push(formatEnvironmentContext(environment));
675
+ // #2275: SCM tooling + auth readiness — shallow on hot path, deep with --with-network.
676
+ // Never fails session:start; reports which SCM-dependent gates are skipped.
677
+ const scmStepStarted = performance.now();
678
+ const scm = resolveSessionScmReadiness(options, allowOptionalNetwork);
679
+ lines.push(...formatScmReadinessLines(scm));
680
+ stepTimings.push({ name: "scm_readiness", duration_ms: elapsedMs(scmStepStarted) });
597
681
  if (!quickSteps.branch_policy) {
598
682
  const stepStarted = performance.now();
599
683
  const result = resolvePolicy(projectRoot);
@@ -793,6 +877,7 @@ export function runSessionStart(projectRoot, options = {}) {
793
877
  diagnostic: userMd.diagnostic,
794
878
  },
795
879
  environment: environmentContextToDict(environment),
880
+ scm: scmReadinessToDict(scm),
796
881
  message: code === 0 ? "session ritual recorded" : "session ritual failed",
797
882
  };
798
883
  // #2994: local process-cost event (best-effort; never blocks ceremony).
@@ -1,3 +1,14 @@
1
+ import { type DeliveryEvidenceInput, type NonDeliveryDisposition } from "../scope/delivery-evidence.js";
2
+ /** Per-story or default delivery evidence for cohort completion (#3041). */
3
+ export interface CohortDeliveryContext {
4
+ /** Evidence keyed by absolute story path (preferred). */
5
+ readonly evidenceByPath?: ReadonlyMap<string, DeliveryEvidenceInput> | null;
6
+ /** Shared evidence applied to every story when per-path map misses. */
7
+ readonly defaultEvidence?: DeliveryEvidenceInput | null;
8
+ readonly nonDeliveryDisposition?: NonDeliveryDisposition | null;
9
+ readonly assumeEvidenceValidated?: boolean;
10
+ readonly verifier?: string;
11
+ }
1
12
  export interface TransitionRecord {
2
13
  kind: "story" | "epic";
3
14
  path: string;
@@ -17,7 +28,13 @@ export declare function resolveCohortPaths(positional: readonly string[], cohort
17
28
  paths: string[];
18
29
  errors: string[];
19
30
  };
20
- export declare function sweepCohort(storyPaths: readonly string[], projectRoot: string, dryRun: boolean): SweepResult;
31
+ export interface SweepCohortArgs {
32
+ readonly storyPaths: readonly string[];
33
+ readonly projectRoot: string;
34
+ readonly dryRun: boolean;
35
+ readonly delivery?: CohortDeliveryContext | null;
36
+ }
37
+ export declare function sweepCohort(storyPaths: readonly string[], projectRoot: string, dryRun: boolean, delivery?: CohortDeliveryContext | null): SweepResult;
21
38
  export declare function sweepResultToDict(result: SweepResult): Record<string, unknown>;
22
39
  export declare function renderSweepText(result: SweepResult): string;
23
40
  export declare function completeCohort(args: {
@@ -26,6 +43,8 @@ export declare function completeCohort(args: {
26
43
  projectRoot: string;
27
44
  dryRun?: boolean;
28
45
  emitJson?: boolean;
46
+ /** Per-story delivery evidence; without it code-bearing stories fail closed (#3041). */
47
+ delivery?: CohortDeliveryContext | null;
29
48
  }): {
30
49
  exitCode: number;
31
50
  stdout: string;
@@ -2,6 +2,7 @@ import { existsSync, readdirSync, readFileSync } from "node:fs";
2
2
  import { basename, dirname, isAbsolute, join, resolve } from "node:path";
3
3
  import { hasArtifactSuffix, resolveLifecycleRoot } from "../layout/resolve.js";
4
4
  import { detectLifecycleFolder } from "../scope/decomposed-refs.js";
5
+ import { classifyStoredDeliveryDisposition, } from "../scope/delivery-evidence.js";
5
6
  import { runTransition } from "../scope/transition.js";
6
7
  import { collectChildUris, collectPlanRefs, resolveVbriefRef } from "../scope/vbrief-ref.js";
7
8
  import { MAX_FIXPOINT_PASSES, TERMINAL_FOLDERS } from "./constants.js";
@@ -115,17 +116,38 @@ function parentCandidatesFrom(plan, vbriefDir) {
115
116
  }
116
117
  return out;
117
118
  }
118
- function completeStory(storyPath, vbriefDir, projectRoot, settled, dryRun) {
119
+ function transitionOptionsFor(storyPath, delivery) {
120
+ if (delivery === null || delivery === undefined) {
121
+ return { verifier: "swarm:complete-cohort" };
122
+ }
123
+ const pathKey = resolve(storyPath);
124
+ const evidence = delivery.evidenceByPath?.get(pathKey) ??
125
+ delivery.evidenceByPath?.get(storyPath) ??
126
+ delivery.defaultEvidence ??
127
+ null;
128
+ return {
129
+ deliveryEvidence: evidence,
130
+ nonDeliveryDisposition: delivery.nonDeliveryDisposition,
131
+ assumeEvidenceValidated: delivery.assumeEvidenceValidated,
132
+ verifier: delivery.verifier ?? "swarm:complete-cohort",
133
+ };
134
+ }
135
+ function completeStory(args) {
136
+ const { storyPath, vbriefDir, projectRoot, settled, dryRun, delivery } = args;
119
137
  const folder = detectLifecycleFolder(storyPath);
120
138
  const relpath = rel(storyPath, projectRoot);
121
139
  if (folder !== null && TERMINAL_FOLDERS.includes(folder)) {
122
140
  settled.add(resolve(storyPath));
141
+ const plan = loadPlan(storyPath);
142
+ const disposition = folder === "completed" && plan !== null ? classifyStoredDeliveryDisposition(plan) : "unknown";
123
143
  return {
124
144
  kind: "story",
125
145
  path: relpath,
126
146
  action: "noop",
127
147
  ok: true,
128
- detail: `already in ${folder}/`,
148
+ detail: folder === "completed"
149
+ ? `already in ${folder}/ (deliveryDisposition=${disposition})`
150
+ : `already in ${folder}/`,
129
151
  };
130
152
  }
131
153
  if (folder !== "active") {
@@ -147,7 +169,7 @@ function completeStory(storyPath, vbriefDir, projectRoot, settled, dryRun) {
147
169
  detail: "would complete active/ -> completed/",
148
170
  };
149
171
  }
150
- const result = runTransition("complete", storyPath);
172
+ const result = runTransition("complete", storyPath, new Date(), transitionOptionsFor(storyPath, delivery));
151
173
  if (result.ok) {
152
174
  settled.add(resolve(join(vbriefDir, "completed", storyPath.split(/[/\\]/).pop() ?? "")));
153
175
  }
@@ -159,7 +181,8 @@ function completeStory(storyPath, vbriefDir, projectRoot, settled, dryRun) {
159
181
  detail: result.message,
160
182
  };
161
183
  }
162
- function completeParent(parentPath, vbriefDir, projectRoot, settled, dryRun) {
184
+ function completeParent(args) {
185
+ const { parentPath, vbriefDir, projectRoot, settled, dryRun, delivery } = args;
163
186
  const folder = detectLifecycleFolder(parentPath);
164
187
  const relpath = rel(parentPath, projectRoot);
165
188
  if (folder !== null && TERMINAL_FOLDERS.includes(folder)) {
@@ -217,7 +240,7 @@ function completeParent(parentPath, vbriefDir, projectRoot, settled, dryRun) {
217
240
  }
218
241
  current = join(vbriefDir, "active", parentPath.split(/[/\\]/).pop() ?? "");
219
242
  }
220
- const completeResult = runTransition("complete", current);
243
+ const completeResult = runTransition("complete", current, new Date(), transitionOptionsFor(current, delivery));
221
244
  if (completeResult.ok) {
222
245
  settled.add(resolve(join(vbriefDir, "completed", parentPath.split(/[/\\]/).pop() ?? "")));
223
246
  }
@@ -229,7 +252,12 @@ function completeParent(parentPath, vbriefDir, projectRoot, settled, dryRun) {
229
252
  detail: completeResult.message,
230
253
  };
231
254
  }
232
- export function sweepCohort(storyPaths, projectRoot, dryRun) {
255
+ export function sweepCohort(storyPaths, projectRoot, dryRun, delivery) {
256
+ return sweepCohortWithArgs({ storyPaths, projectRoot, dryRun, delivery: delivery ?? null });
257
+ }
258
+ function sweepCohortWithArgs(args) {
259
+ const { storyPaths, projectRoot, dryRun } = args;
260
+ const delivery = args.delivery ?? null;
233
261
  let vbriefDir;
234
262
  try {
235
263
  vbriefDir = resolveLifecycleRoot(projectRoot);
@@ -275,7 +303,14 @@ export function sweepCohort(storyPaths, projectRoot, dryRun) {
275
303
  }
276
304
  }
277
305
  }
278
- result.stories.push(completeStory(storyPath, vbriefDir, projectRoot, settled, dryRun));
306
+ result.stories.push(completeStory({
307
+ storyPath,
308
+ vbriefDir,
309
+ projectRoot,
310
+ settled,
311
+ dryRun,
312
+ delivery,
313
+ }));
279
314
  }
280
315
  const finalized = new Set();
281
316
  let passes = 0;
@@ -294,7 +329,14 @@ export function sweepCohort(storyPaths, projectRoot, dryRun) {
294
329
  if (!allChildrenSettled(parentPlan, vbriefDir, settled, dryRun)) {
295
330
  continue;
296
331
  }
297
- const record = completeParent(candidate, vbriefDir, projectRoot, settled, dryRun);
332
+ const record = completeParent({
333
+ parentPath: candidate,
334
+ vbriefDir,
335
+ projectRoot,
336
+ settled,
337
+ dryRun,
338
+ delivery,
339
+ });
298
340
  result.parents.push(record);
299
341
  finalized.add(candidate);
300
342
  progressed = true;
@@ -403,7 +445,12 @@ export function completeCohort(args) {
403
445
  }
404
446
  return { exitCode: 2, stdout: "", stderr };
405
447
  }
406
- const result = sweepCohort(paths, projectRoot, args.dryRun ?? false);
448
+ const result = sweepCohortWithArgs({
449
+ storyPaths: paths,
450
+ projectRoot,
451
+ dryRun: args.dryRun ?? false,
452
+ delivery: args.delivery ?? null,
453
+ });
407
454
  result.errors.push(...errors);
408
455
  if (args.emitJson) {
409
456
  return {
@@ -7,6 +7,7 @@ export function parseFinalizeCohortArgv(argv) {
7
7
  let repo = null;
8
8
  let projectRoot = ".";
9
9
  let baseBranch = "master";
10
+ let deliveryBranch = null;
10
11
  let label = null;
11
12
  let dryRun = false;
12
13
  let noCommit = false;
@@ -51,6 +52,13 @@ export function parseFinalizeCohortArgv(argv) {
51
52
  baseBranch = next;
52
53
  i += 1;
53
54
  }
55
+ else if (arg === "--delivery-branch" && next !== undefined) {
56
+ deliveryBranch = next;
57
+ i += 1;
58
+ }
59
+ else if (arg?.startsWith("--delivery-branch=")) {
60
+ deliveryBranch = arg.slice("--delivery-branch=".length);
61
+ }
54
62
  else if (arg === "--label" && next !== undefined) {
55
63
  label = next;
56
64
  i += 1;
@@ -77,6 +85,7 @@ export function parseFinalizeCohortArgv(argv) {
77
85
  repo,
78
86
  projectRoot,
79
87
  baseBranch,
88
+ deliveryBranch,
80
89
  label,
81
90
  dryRun,
82
91
  noCommit,
@@ -12,6 +12,8 @@ export interface FinalizeCohortResult {
12
12
  readonly commit_sha: string | null;
13
13
  readonly branch: string | null;
14
14
  readonly pr_url: string | null;
15
+ readonly delivery_branch: string | null;
16
+ readonly delivery_errors: readonly string[];
15
17
  readonly errors: readonly string[];
16
18
  readonly warnings: readonly string[];
17
19
  readonly ok: boolean;
@@ -21,7 +23,13 @@ export interface FinalizeCohortArgs {
21
23
  readonly storyTokens?: readonly string[];
22
24
  readonly repo?: string | null;
23
25
  readonly projectRoot?: string;
26
+ /**
27
+ * Swarm/PR base for the post-merge lifecycle sweep PR only.
28
+ * Distinct from plan.policy.deliveryBranch (#3041).
29
+ */
24
30
  readonly baseBranch?: string;
31
+ /** Override delivery branch for this run (defaults to policy/git default). */
32
+ readonly deliveryBranch?: string | null;
25
33
  readonly label?: string | null;
26
34
  readonly dryRun?: boolean;
27
35
  readonly noCommit?: boolean;