@deftai/directive-runtime 0.129.6 → 0.129.8

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 (149) hide show
  1. package/Taskfile.yml +103 -16
  2. package/commands.md +8 -3
  3. package/dist/session-start.d.ts +8 -2
  4. package/dist/session-start.js +33 -12
  5. package/dist/vbrief-preflight.d.ts +4 -0
  6. package/dist/vbrief-preflight.js +34 -0
  7. package/dist/verify-scope-provenance.d.ts +13 -1
  8. package/dist/verify-scope-provenance.js +44 -1
  9. package/docs/getting-started.md +2 -0
  10. package/docs/scope-provenance.md +15 -1
  11. package/package.json +1 -1
  12. package/packs/skills/skills-pack-0.1.json +1 -1
  13. package/scm/github.md +5 -0
  14. package/skills/deft-directive-review-cycle/SKILL.md +15 -6
  15. package/skills/deft-directive-swarm/references/host-claude-code.md +11 -1
  16. package/skills/deft-directive-swarm/references/host-grok-build.md +3 -2
  17. package/vendor/directive-content/Taskfile.yml +103 -16
  18. package/vendor/directive-content/commands.md +8 -3
  19. package/vendor/directive-content/docs/getting-started.md +2 -0
  20. package/vendor/directive-content/docs/scope-provenance.md +15 -1
  21. package/vendor/directive-content/packs/skills/skills-pack-0.1.json +1 -1
  22. package/vendor/directive-content/scm/github.md +5 -0
  23. package/vendor/directive-content/skills/deft-directive-review-cycle/SKILL.md +15 -6
  24. package/vendor/directive-content/skills/deft-directive-swarm/references/host-claude-code.md +11 -1
  25. package/vendor/directive-content/skills/deft-directive-swarm/references/host-grok-build.md +3 -2
  26. package/vendor/directive-core/dist/.tsbuildinfo +1 -1
  27. package/vendor/directive-core/dist/check/context.d.ts.map +1 -1
  28. package/vendor/directive-core/dist/check/context.js +3 -31
  29. package/vendor/directive-core/dist/check/context.js.map +1 -1
  30. package/vendor/directive-core/dist/check/gate-lists.d.ts.map +1 -1
  31. package/vendor/directive-core/dist/check/gate-lists.js +4 -32
  32. package/vendor/directive-core/dist/check/gate-lists.js.map +1 -1
  33. package/vendor/directive-core/dist/check/walk-test-files.d.ts +3 -0
  34. package/vendor/directive-core/dist/check/walk-test-files.d.ts.map +1 -0
  35. package/vendor/directive-core/dist/check/walk-test-files.js +67 -0
  36. package/vendor/directive-core/dist/check/walk-test-files.js.map +1 -0
  37. package/vendor/directive-core/dist/fs/contained-write.d.ts +8 -2
  38. package/vendor/directive-core/dist/fs/contained-write.d.ts.map +1 -1
  39. package/vendor/directive-core/dist/fs/contained-write.js +22 -6
  40. package/vendor/directive-core/dist/fs/contained-write.js.map +1 -1
  41. package/vendor/directive-core/dist/hooks/dispatcher.d.ts +7 -1
  42. package/vendor/directive-core/dist/hooks/dispatcher.d.ts.map +1 -1
  43. package/vendor/directive-core/dist/hooks/dispatcher.js +180 -10
  44. package/vendor/directive-core/dist/hooks/dispatcher.js.map +1 -1
  45. package/vendor/directive-core/dist/hooks/scope.d.ts +54 -0
  46. package/vendor/directive-core/dist/hooks/scope.d.ts.map +1 -1
  47. package/vendor/directive-core/dist/hooks/scope.js +83 -2
  48. package/vendor/directive-core/dist/hooks/scope.js.map +1 -1
  49. package/vendor/directive-core/dist/init-deposit/agent-hooks.d.ts +26 -1
  50. package/vendor/directive-core/dist/init-deposit/agent-hooks.d.ts.map +1 -1
  51. package/vendor/directive-core/dist/init-deposit/agent-hooks.js +52 -3
  52. package/vendor/directive-core/dist/init-deposit/agent-hooks.js.map +1 -1
  53. package/vendor/directive-core/dist/init-deposit/host-hook-outer.d.ts +43 -0
  54. package/vendor/directive-core/dist/init-deposit/host-hook-outer.d.ts.map +1 -1
  55. package/vendor/directive-core/dist/init-deposit/host-hook-outer.js +148 -0
  56. package/vendor/directive-core/dist/init-deposit/host-hook-outer.js.map +1 -1
  57. package/vendor/directive-core/dist/intake/reconcile-issues.d.ts +33 -1
  58. package/vendor/directive-core/dist/intake/reconcile-issues.d.ts.map +1 -1
  59. package/vendor/directive-core/dist/intake/reconcile-issues.js +288 -12
  60. package/vendor/directive-core/dist/intake/reconcile-issues.js.map +1 -1
  61. package/vendor/directive-core/dist/intake/thin-brief.d.ts +78 -0
  62. package/vendor/directive-core/dist/intake/thin-brief.d.ts.map +1 -0
  63. package/vendor/directive-core/dist/intake/thin-brief.js +224 -0
  64. package/vendor/directive-core/dist/intake/thin-brief.js.map +1 -0
  65. package/vendor/directive-core/dist/policy/ceremony-dial.d.ts.map +1 -1
  66. package/vendor/directive-core/dist/policy/ceremony-dial.js +2 -1
  67. package/vendor/directive-core/dist/policy/ceremony-dial.js.map +1 -1
  68. package/vendor/directive-core/dist/policy/small-change-lane.d.ts +193 -0
  69. package/vendor/directive-core/dist/policy/small-change-lane.d.ts.map +1 -0
  70. package/vendor/directive-core/dist/policy/small-change-lane.js +690 -0
  71. package/vendor/directive-core/dist/policy/small-change-lane.js.map +1 -0
  72. package/vendor/directive-core/dist/pr-merge-readiness/slizard-gate.d.ts +10 -5
  73. package/vendor/directive-core/dist/pr-merge-readiness/slizard-gate.d.ts.map +1 -1
  74. package/vendor/directive-core/dist/pr-merge-readiness/slizard-gate.js +41 -18
  75. package/vendor/directive-core/dist/pr-merge-readiness/slizard-gate.js.map +1 -1
  76. package/vendor/directive-core/dist/preflight/evaluate.d.ts +3 -0
  77. package/vendor/directive-core/dist/preflight/evaluate.d.ts.map +1 -1
  78. package/vendor/directive-core/dist/preflight/evaluate.js +15 -0
  79. package/vendor/directive-core/dist/preflight/evaluate.js.map +1 -1
  80. package/vendor/directive-core/dist/preflight-gh/pre-push.d.ts +3 -2
  81. package/vendor/directive-core/dist/preflight-gh/pre-push.d.ts.map +1 -1
  82. package/vendor/directive-core/dist/preflight-gh/pre-push.js +19 -18
  83. package/vendor/directive-core/dist/preflight-gh/pre-push.js.map +1 -1
  84. package/vendor/directive-core/dist/product-first-done-gate/evaluate.d.ts +6 -0
  85. package/vendor/directive-core/dist/product-first-done-gate/evaluate.d.ts.map +1 -1
  86. package/vendor/directive-core/dist/product-first-done-gate/evaluate.js +26 -0
  87. package/vendor/directive-core/dist/product-first-done-gate/evaluate.js.map +1 -1
  88. package/vendor/directive-core/dist/scope/decomposed-refs.d.ts +7 -0
  89. package/vendor/directive-core/dist/scope/decomposed-refs.d.ts.map +1 -1
  90. package/vendor/directive-core/dist/scope/decomposed-refs.js +27 -10
  91. package/vendor/directive-core/dist/scope/decomposed-refs.js.map +1 -1
  92. package/vendor/directive-core/dist/scope/delivery-evidence.d.ts +218 -19
  93. package/vendor/directive-core/dist/scope/delivery-evidence.d.ts.map +1 -1
  94. package/vendor/directive-core/dist/scope/delivery-evidence.js +973 -107
  95. package/vendor/directive-core/dist/scope/delivery-evidence.js.map +1 -1
  96. package/vendor/directive-core/dist/scope/transition.d.ts +7 -1
  97. package/vendor/directive-core/dist/scope/transition.d.ts.map +1 -1
  98. package/vendor/directive-core/dist/scope/transition.js +45 -5
  99. package/vendor/directive-core/dist/scope/transition.js.map +1 -1
  100. package/vendor/directive-core/dist/scope-provenance/base-fence.d.ts +13 -3
  101. package/vendor/directive-core/dist/scope-provenance/base-fence.d.ts.map +1 -1
  102. package/vendor/directive-core/dist/scope-provenance/base-fence.js +25 -2
  103. package/vendor/directive-core/dist/scope-provenance/base-fence.js.map +1 -1
  104. package/vendor/directive-core/dist/scope-provenance/evaluate.d.ts +12 -0
  105. package/vendor/directive-core/dist/scope-provenance/evaluate.d.ts.map +1 -1
  106. package/vendor/directive-core/dist/scope-provenance/evaluate.js +41 -5
  107. package/vendor/directive-core/dist/scope-provenance/evaluate.js.map +1 -1
  108. package/vendor/directive-core/dist/scope-provenance/forge-approval.d.ts +106 -0
  109. package/vendor/directive-core/dist/scope-provenance/forge-approval.d.ts.map +1 -0
  110. package/vendor/directive-core/dist/scope-provenance/forge-approval.js +382 -0
  111. package/vendor/directive-core/dist/scope-provenance/forge-approval.js.map +1 -0
  112. package/vendor/directive-core/dist/session/orientation-compression.d.ts +18 -2
  113. package/vendor/directive-core/dist/session/orientation-compression.d.ts.map +1 -1
  114. package/vendor/directive-core/dist/session/orientation-compression.js +38 -5
  115. package/vendor/directive-core/dist/session/orientation-compression.js.map +1 -1
  116. package/vendor/directive-core/dist/session/session-start.d.ts +47 -2
  117. package/vendor/directive-core/dist/session/session-start.d.ts.map +1 -1
  118. package/vendor/directive-core/dist/session/session-start.js +149 -1
  119. package/vendor/directive-core/dist/session/session-start.js.map +1 -1
  120. package/vendor/directive-core/dist/swarm/approach1-babysitter.d.ts +18 -4
  121. package/vendor/directive-core/dist/swarm/approach1-babysitter.d.ts.map +1 -1
  122. package/vendor/directive-core/dist/swarm/approach1-babysitter.js +28 -4
  123. package/vendor/directive-core/dist/swarm/approach1-babysitter.js.map +1 -1
  124. package/vendor/directive-core/dist/swarm/finalize-cohort.d.ts +48 -0
  125. package/vendor/directive-core/dist/swarm/finalize-cohort.d.ts.map +1 -1
  126. package/vendor/directive-core/dist/swarm/finalize-cohort.js +175 -19
  127. package/vendor/directive-core/dist/swarm/finalize-cohort.js.map +1 -1
  128. package/vendor/directive-core/dist/swarm/finalize-owed.d.ts +18 -3
  129. package/vendor/directive-core/dist/swarm/finalize-owed.d.ts.map +1 -1
  130. package/vendor/directive-core/dist/swarm/finalize-owed.js +139 -32
  131. package/vendor/directive-core/dist/swarm/finalize-owed.js.map +1 -1
  132. package/vendor/directive-core/dist/swarm/launch.d.ts.map +1 -1
  133. package/vendor/directive-core/dist/swarm/launch.js +4 -8
  134. package/vendor/directive-core/dist/swarm/launch.js.map +1 -1
  135. package/vendor/directive-core/dist/swarm/worker-auth-assignment.d.ts +7 -0
  136. package/vendor/directive-core/dist/swarm/worker-auth-assignment.d.ts.map +1 -1
  137. package/vendor/directive-core/dist/swarm/worker-auth-assignment.js +16 -1
  138. package/vendor/directive-core/dist/swarm/worker-auth-assignment.js.map +1 -1
  139. package/vendor/directive-core/dist/task-surface/index.d.ts.map +1 -1
  140. package/vendor/directive-core/dist/task-surface/index.js +41 -0
  141. package/vendor/directive-core/dist/task-surface/index.js.map +1 -1
  142. package/vendor/directive-core/dist/triage/evaluate/worktrees.d.ts.map +1 -1
  143. package/vendor/directive-core/dist/triage/evaluate/worktrees.js +219 -60
  144. package/vendor/directive-core/dist/triage/evaluate/worktrees.js.map +1 -1
  145. package/vendor/directive-core/dist/vbrief-reconcile/types.d.ts +5 -1
  146. package/vendor/directive-core/dist/vbrief-reconcile/types.d.ts.map +1 -1
  147. package/vendor/directive-core/dist/vbrief-reconcile/umbrellas.d.ts.map +1 -1
  148. package/vendor/directive-core/dist/vbrief-reconcile/umbrellas.js +19 -7
  149. package/vendor/directive-core/dist/vbrief-reconcile/umbrellas.js.map +1 -1
@@ -12,13 +12,36 @@ import { policyColonInvocation } from "../policy/policy-invocation.js";
12
12
  import { ENV_ALLOW_BOT_MERGE, evaluateAgentMerge, resolveHumanMergePolicy, } from "../policy/require-human-merge.js";
13
13
  import { parseAllDeftStoryMarks } from "../pr-closing-keywords/main.js";
14
14
  import { defaultRunGh, fetchClosingIssuesReferences } from "../pr-protected-issues/gh.js";
15
- import { classifyStoredDeliveryDisposition, evidenceFromPrPayload, verifyDeliveryAncestry, } from "../scope/delivery-evidence.js";
15
+ import { classifyStoredDeliveryDisposition, defaultFetchPrChangedFiles, evidenceFromPrPayload, isDeliveryBriefLandPath, SPEC_DRIFT_LEDGER_REL, verifyDeliveryAncestry, } from "../scope/delivery-evidence.js";
16
16
  import { materializeRetainedBrief, SOURCE_RECOVERY_REMEDIATION } from "./brief-transport.js";
17
17
  import { completeCohort } from "./complete-cohort.js";
18
18
  import { EXIT_CONFIG_ERROR, EXIT_GATE_FAILED, EXIT_INCOMPLETE, EXIT_OK } from "./constants.js";
19
19
  import { cancelledBriefReferencesIssue, completedBriefReferencesIssue, resolveStories, terminalBriefReferencesIssue, } from "./launch.js";
20
20
  import { evaluateLifecycleDiff, expectedLifecycleRels, parseStagedXbriefPaths, } from "./lifecycle-diff.js";
21
21
  import { runText } from "./subprocess.js";
22
+ import { detectWorktreePm, installWorktreePackages, worktreeHasRuntimePin, } from "./worktree-pm-install.js";
23
+ /**
24
+ * Install packages on the lifecycle checkout before the completion sweep (#5882).
25
+ * Gated by the runtime pin, as linked-worktree prep gates it. npm/pnpm failure is
26
+ * `failed`; Yarn/Bun is diagnosed-only (`skipped-unsupported-pm`).
27
+ */
28
+ export function installLifecycleCheckoutPackages(checkout, runner) {
29
+ if (!worktreeHasRuntimePin(checkout)) {
30
+ return { kind: "not-pinned" };
31
+ }
32
+ if (detectWorktreePm(checkout) === null) {
33
+ return { kind: "skipped-unsupported-pm" };
34
+ }
35
+ const installed = installWorktreePackages(checkout, { runner });
36
+ if (!installed.ok) {
37
+ return { kind: "failed", pm: installed.pm, message: installed.message };
38
+ }
39
+ return { kind: "installed", pm: installed.pm };
40
+ }
41
+ export function formatLifecycleInstallDisposition(disposition) {
42
+ const pm = "pm" in disposition && disposition.pm !== null ? ` pm=${disposition.pm}` : "";
43
+ return `lifecycle install disposition=${disposition.kind}${pm}`;
44
+ }
22
45
  function splitCsv(values) {
23
46
  const out = [];
24
47
  for (const value of values) {
@@ -342,9 +365,13 @@ function collectProductPullRequestOrigins(projectRoot, prNumbers) {
342
365
  /**
343
366
  * PR-body `deft-story: N` may bind only when this PR is delivery for N (#4864 / #4919):
344
367
  * TIP_NONTERMINAL or completed brief for N with metadata.productPullRequest === this PR.
345
- * Absent stamp never binds (unrelated completed issue must not close).
368
+ * Absent stamp never binds on its own (unrelated completed issue must not close).
369
+ * Also reports a stamp naming another PR and whether an unstamped active brief
370
+ * exists, for the operator-story bind at the call site (#6212).
346
371
  */
347
372
  function deftStoryMarkBindsDelivery(projectRoot, issue, prNumber) {
373
+ let otherPr = null;
374
+ let unstampedActive = false;
348
375
  for (const folder of MARK_READER_FOLDERS) {
349
376
  const dir = resolve(projectRoot, folder);
350
377
  if (!existsSync(dir)) {
@@ -367,8 +394,15 @@ function deftStoryMarkBindsDelivery(projectRoot, issue, prNumber) {
367
394
  if (typeof plan !== "object" || plan === null || Array.isArray(plan)) {
368
395
  continue;
369
396
  }
370
- if (productPullRequestFromPlan(plan) === prNumber) {
371
- return true;
397
+ const stamped = productPullRequestFromPlan(plan);
398
+ if (stamped === prNumber) {
399
+ return { stampMatch: true, otherPr: null, unstampedActive: false };
400
+ }
401
+ if (stamped !== null) {
402
+ otherPr ??= stamped;
403
+ }
404
+ else if (folder.endsWith("/active")) {
405
+ unstampedActive = true;
372
406
  }
373
407
  }
374
408
  catch {
@@ -376,7 +410,16 @@ function deftStoryMarkBindsDelivery(projectRoot, issue, prNumber) {
376
410
  }
377
411
  }
378
412
  }
379
- return false;
413
+ return { stampMatch: false, otherPr, unstampedActive };
414
+ }
415
+ /**
416
+ * Lifecycle-shaped PR (#6212): every changed path is in the delivery-brief-land
417
+ * family (`isDeliveryBriefLandPath`, shared with the delivery gate, #680 F1).
418
+ * Activation and lifecycle PRs carry `deft-story` marks too, so they never bind
419
+ * as delivery. An empty list stays lifecycle-shaped, so it never binds either.
420
+ */
421
+ export function isLifecycleShapedPr(files) {
422
+ return files.every((f) => isDeliveryBriefLandPath(f.filename));
380
423
  }
381
424
  /**
382
425
  * Persist productPullRequest on an active brief before leftover-complete so the
@@ -760,6 +803,11 @@ function derivedLifecycleRelsFromSweep(projectRoot, storyPaths, sweep) {
760
803
  out.push(posixProjectRel(projectRoot, resolvedPath));
761
804
  };
762
805
  if (sweep !== null) {
806
+ // scope:complete writes the spec-drift ledger as verb output; it travels in
807
+ // the same lifecycle commit as the completed briefs (#680 P5 / #6231).
808
+ if (sweep.stories.some((rec) => rec.ok)) {
809
+ out.push(SPEC_DRIFT_LEDGER_REL);
810
+ }
763
811
  for (const rec of [...sweep.stories, ...sweep.parents]) {
764
812
  if (!rec.ok || rec.path.trim().length === 0) {
765
813
  continue;
@@ -1167,6 +1215,8 @@ function waitForLifecycleLand(args) {
1167
1215
  export function finalizeCohort(args) {
1168
1216
  const projectRoot = resolve(args.projectRoot ?? process.cwd());
1169
1217
  const runGh = args.runGh ?? defaultRunGh;
1218
+ const fetchPrChangedFiles = args.fetchPrChangedFiles ??
1219
+ ((prNumber, repository) => defaultFetchPrChangedFiles(prNumber, repository, runGh));
1170
1220
  const runGit = args.runGit ?? runText;
1171
1221
  const gitRunner = asGitRunner(runGit);
1172
1222
  const prNumbers = [...(args.prNumbers ?? [])].sort((a, b) => a - b);
@@ -1356,7 +1406,17 @@ export function finalizeCohort(args) {
1356
1406
  const validatedPrs = [];
1357
1407
  /** Full-story close intent from PR-body `deft-story: N` (#4864). Not Tracking/Refs scrape. */
1358
1408
  const fullStoryCloseIssues = new Set();
1409
+ /**
1410
+ * A failed changed-file lookup for a mark is a real forge error: it still stops
1411
+ * the stamp and sweep for the whole batch in every write mode (#6212).
1412
+ */
1413
+ let deftStoryMarkLookupFailed = false;
1414
+ /** Refused marks: non-fatal; only that story stays pending (#680 P3). */
1415
+ const refusedMarks = [];
1359
1416
  let closingLookupFailed = false;
1417
+ // Operator --stories issues: their deft-story marks may bind before the sweep stamp (#6212).
1418
+ const operatorResolved = storyTokens.length > 0 ? resolveStories(projectRoot, storyTokens) : null;
1419
+ const operatorStoryIssues = new Set(collectOriginIssueNumbers(operatorResolved?.resolved.map((story) => story.path) ?? [], storyTokens));
1360
1420
  if (prNumbers.length > 0 && errors.length === 0) {
1361
1421
  if (repo === null || repo.length === 0) {
1362
1422
  errors.push("--repo OWNER/REPO is required when --pr is supplied (or set $GH_REPO).");
@@ -1408,11 +1468,47 @@ export function finalizeCohort(args) {
1408
1468
  evidenceByIssue.set(issue, prEvidence);
1409
1469
  }
1410
1470
  const prBody = typeof snap.payload.body === "string" ? snap.payload.body : "";
1471
+ let prFiles;
1411
1472
  for (const issue of parseAllDeftStoryMarks(prBody)) {
1412
- if (!deftStoryMarkBindsDelivery(projectRoot, issue, prNumber)) {
1413
- errors.push(`#${String(issue)}: deft-story mark on PR #${String(prNumber)} does not bind PR delivery ` +
1414
- `(need active or completed brief #${String(issue)} with ` +
1415
- `metadata.productPullRequest=${String(prNumber)}) (#4864).`);
1473
+ const mark = deftStoryMarkBindsDelivery(projectRoot, issue, prNumber);
1474
+ let bound = mark.stampMatch;
1475
+ let lookupFailed = false;
1476
+ let reason = `need active or completed brief #${String(issue)} with ` +
1477
+ `metadata.productPullRequest=${String(prNumber)}`;
1478
+ // Operator named N in --stories (#6212): an unstamped active brief binds this
1479
+ // validated PR, and the sweep stamps it. A lifecycle-shaped PR never binds.
1480
+ if (!bound && operatorStoryIssues.has(issue)) {
1481
+ if (mark.otherPr !== null) {
1482
+ reason =
1483
+ `brief #${String(issue)} is already stamped ` +
1484
+ `metadata.productPullRequest=${String(mark.otherPr)}`;
1485
+ }
1486
+ else if (mark.unstampedActive) {
1487
+ if (prFiles === undefined) {
1488
+ prFiles = fetchPrChangedFiles(prNumber, repo);
1489
+ }
1490
+ if (prFiles === null) {
1491
+ reason = `changed-file lookup for PR #${String(prNumber)} failed`;
1492
+ lookupFailed = true;
1493
+ }
1494
+ else if (isLifecycleShapedPr(prFiles)) {
1495
+ reason = `PR #${String(prNumber)} changes only lifecycle paths: briefs, CHANGELOG.md, spec-drift ledger`;
1496
+ }
1497
+ else {
1498
+ bound = true;
1499
+ }
1500
+ }
1501
+ }
1502
+ if (!bound) {
1503
+ const detail = `#${String(issue)}: deft-story mark on PR #${String(prNumber)} does not bind PR delivery ` +
1504
+ `(${reason}) (#4864).`;
1505
+ if (lookupFailed) {
1506
+ deftStoryMarkLookupFailed = true;
1507
+ errors.push(detail);
1508
+ }
1509
+ else {
1510
+ refusedMarks.push({ issue, pr: prNumber, detail });
1511
+ }
1416
1512
  continue;
1417
1513
  }
1418
1514
  fullStoryCloseIssues.add(issue);
@@ -1432,7 +1528,10 @@ export function finalizeCohort(args) {
1432
1528
  evidenceByIssue.set(issue, bound);
1433
1529
  }
1434
1530
  }
1435
- if (storyTokens.length === 0 && closingIssues.size === 0 && fullStoryCloseIssues.size === 0) {
1531
+ if (storyTokens.length === 0 &&
1532
+ closingIssues.size === 0 &&
1533
+ fullStoryCloseIssues.size === 0 &&
1534
+ refusedMarks.length === 0) {
1436
1535
  errors.push("empty cohort: pass --pr <numbers> and/or --stories <ids|paths> " +
1437
1536
  "(or record full-story close intent via deft-story: N / productPullRequest).");
1438
1537
  }
@@ -1448,8 +1547,8 @@ export function finalizeCohort(args) {
1448
1547
  };
1449
1548
  // Operator-supplied story tokens keep the hard-error contract: an explicit
1450
1549
  // --stories token that does not resolve to an active brief is a real error.
1451
- if (storyTokens.length > 0) {
1452
- const resolved = resolveStories(projectRoot, storyTokens);
1550
+ if (operatorResolved !== null) {
1551
+ const resolved = operatorResolved;
1453
1552
  for (const story of resolved.resolved) {
1454
1553
  addStory(story.path);
1455
1554
  }
@@ -1550,9 +1649,32 @@ export function finalizeCohort(args) {
1550
1649
  errors.push(`#${issue}: full-story close intent recorded but no active or completed brief references this issue (#4864).`);
1551
1650
  }
1552
1651
  }
1652
+ // Refused marks (#680 P3): an issue no other PR bound stays pending. Its story
1653
+ // leaves the batch before stamp/sweep (#6212: never single-PR defaultEvidence),
1654
+ // and its origin is never closed. The other stories go on.
1655
+ // A stray mark with no brief for its issue warns only; it leaves nothing pending.
1656
+ // Selected story paths count too, so a nested brief path is never missed.
1657
+ const pendingMarkIssues = new Set(refusedMarks
1658
+ .map((m) => m.issue)
1659
+ .filter((issue) => !fullStoryCloseIssues.has(issue) &&
1660
+ !closingIssues.has(issue) &&
1661
+ (storyPaths.some((p) => githubIssueFromBrief(p) === issue) ||
1662
+ localBriefReferencesIssue(projectRoot, issue))));
1663
+ for (let i = storyPaths.length - 1; i >= 0; i -= 1) {
1664
+ const origin = githubIssueFromBrief(storyPaths[i] ?? "");
1665
+ if (origin !== null && pendingMarkIssues.has(origin)) {
1666
+ storyPaths.splice(i, 1);
1667
+ }
1668
+ }
1669
+ for (const mark of refusedMarks) {
1670
+ warnings.push(pendingMarkIssues.has(mark.issue)
1671
+ ? `${mark.detail} Story left pending, not stamped or swept (#680).`
1672
+ : mark.detail);
1673
+ }
1674
+ const markPending = pendingMarkIssues.size > 0;
1553
1675
  const originIssues = collectOriginIssueNumbers(storyPaths, storyTokens, [
1554
1676
  ...fullStoryCloseIssues,
1555
- ]);
1677
+ ]).filter((issue) => !pendingMarkIssues.has(issue));
1556
1678
  if (storyPaths.length === 0) {
1557
1679
  if (errors.length === 0 && originIssues.length > 0) {
1558
1680
  const originClose = closeOriginsAfterLeftoverComplete({
@@ -1571,7 +1693,7 @@ export function finalizeCohort(args) {
1571
1693
  ? originClosePendingState(originClose.pendingIssues, deliveryBranch)
1572
1694
  : null;
1573
1695
  const originFailed = errors.length > 0;
1574
- const originPending = !originFailed && pending !== null;
1696
+ const originPending = !originFailed && (pending !== null || markPending);
1575
1697
  return buildResponse({
1576
1698
  projectRoot,
1577
1699
  dryRun,
@@ -1589,6 +1711,7 @@ export function finalizeCohort(args) {
1589
1711
  errors,
1590
1712
  warnings,
1591
1713
  pending,
1714
+ refusedMarks,
1592
1715
  ok: !originFailed && !originPending,
1593
1716
  emitJson: args.emitJson ?? false,
1594
1717
  exitCode: originFailed ? EXIT_GATE_FAILED : originPending ? EXIT_INCOMPLETE : EXIT_OK,
@@ -1614,10 +1737,13 @@ export function finalizeCohort(args) {
1614
1737
  errors,
1615
1738
  warnings,
1616
1739
  pending: null,
1617
- ok: cleanNoop,
1740
+ refusedMarks,
1741
+ ok: cleanNoop && !markPending,
1618
1742
  emitJson: args.emitJson ?? false,
1619
1743
  exitCode: cleanNoop
1620
- ? EXIT_OK
1744
+ ? markPending
1745
+ ? EXIT_INCOMPLETE
1746
+ : EXIT_OK
1621
1747
  : errors.some((e) => e.includes("not merged") || e.includes("delivery"))
1622
1748
  ? EXIT_GATE_FAILED
1623
1749
  : EXIT_CONFIG_ERROR,
@@ -1642,6 +1768,7 @@ export function finalizeCohort(args) {
1642
1768
  errors,
1643
1769
  warnings,
1644
1770
  pending: null,
1771
+ refusedMarks,
1645
1772
  ok: false,
1646
1773
  emitJson: args.emitJson ?? false,
1647
1774
  exitCode: EXIT_GATE_FAILED,
@@ -1705,6 +1832,7 @@ export function finalizeCohort(args) {
1705
1832
  let pendingOrigin = null;
1706
1833
  let createdSweepBranch = null;
1707
1834
  let lifecycle = null;
1835
+ let lifecycleInstall = null;
1708
1836
  // Closure assignment is invisible to finally control flow, so a bare let stays null.
1709
1837
  const held = { outcome: null };
1710
1838
  const respond = (partial) => {
@@ -1721,6 +1849,7 @@ export function finalizeCohort(args) {
1721
1849
  errors,
1722
1850
  warnings,
1723
1851
  pending: partial.pending ?? null,
1852
+ refusedMarks,
1724
1853
  emitJson: args.emitJson ?? false,
1725
1854
  ...partial,
1726
1855
  });
@@ -1768,11 +1897,32 @@ export function finalizeCohort(args) {
1768
1897
  if (branchResult.branch === branchName) {
1769
1898
  createdSweepBranch = branchName;
1770
1899
  }
1900
+ // Sweep is required: install packages so Taskfile includes that
1901
+ // resolve through node_modules exist on this checkout (#5882).
1902
+ lifecycleInstall = installLifecycleCheckoutPackages(prepared.checkout, args.pmRunner);
1903
+ if (lifecycleInstall.kind === "failed") {
1904
+ errors.push(`environment/setup: ${formatLifecycleInstallDisposition(lifecycleInstall)}: ` +
1905
+ `${oneLine(lifecycleInstall.message)}. The completion sweep did not run; ` +
1906
+ "this is not an acceptance commands-failed result. Fix the package install " +
1907
+ `on origin/${deliveryBranch}, then re-run swarm:finalize-cohort.`);
1908
+ skipSweep = true;
1909
+ }
1910
+ else if (lifecycleInstall.kind === "skipped-unsupported-pm") {
1911
+ warnings.push(`${formatLifecycleInstallDisposition(lifecycleInstall)}: Yarn/Bun lifecycle ` +
1912
+ "checkout was not installed (AC5.7). Taskfile includes that resolve through " +
1913
+ "node_modules can be missing during the completion sweep.");
1914
+ }
1771
1915
  }
1772
1916
  }
1773
1917
  }
1774
1918
  }
1775
1919
  }
1920
+ // --no-commit skips the error stop above, but a mark whose lookup failed must
1921
+ // never let the single-PR default evidence stamp that PR; a retry would then
1922
+ // bind it (#6212). Refused marks already left the batch above (#680 P3).
1923
+ if (!dryRun && deftStoryMarkLookupFailed) {
1924
+ skipSweep = true;
1925
+ }
1776
1926
  let sweep = null;
1777
1927
  if (!skipSweep) {
1778
1928
  // Stamp on the sweep root (checkout or project) so leftover-complete preserves bind (#4864).
@@ -1824,7 +1974,9 @@ export function finalizeCohort(args) {
1824
1974
  });
1825
1975
  sweep = sweepResult.sweep;
1826
1976
  if (sweepResult.exitCode !== 0) {
1827
- errors.push("cohort completion sweep failed.");
1977
+ errors.push(lifecycleInstall === null
1978
+ ? "cohort completion sweep failed."
1979
+ : `cohort completion sweep failed (${formatLifecycleInstallDisposition(lifecycleInstall)}).`);
1828
1980
  if (sweep !== null) {
1829
1981
  errors.push(...sweepDetailLines(sweep));
1830
1982
  }
@@ -1977,7 +2129,7 @@ export function finalizeCohort(args) {
1977
2129
  pendingOrigin = originClosePendingState(originIssues, deliveryBranch);
1978
2130
  }
1979
2131
  const failed = errors.length > 0;
1980
- const incomplete = !failed && pendingOrigin !== null;
2132
+ const incomplete = !failed && (pendingOrigin !== null || markPending);
1981
2133
  return respond({
1982
2134
  sweep,
1983
2135
  commitSha,
@@ -2025,6 +2177,7 @@ function withReportedFailure(previous, error, emitJson) {
2025
2177
  errors: [...result.errors, error],
2026
2178
  warnings: [...result.warnings],
2027
2179
  pending: result.pending,
2180
+ refusedMarks: result.refused_marks ?? [],
2028
2181
  ok: false,
2029
2182
  emitJson,
2030
2183
  exitCode: EXIT_GATE_FAILED,
@@ -2048,6 +2201,7 @@ function buildResponse(input) {
2048
2201
  errors: input.errors,
2049
2202
  warnings: input.warnings,
2050
2203
  pending: input.pending,
2204
+ refused_marks: input.refusedMarks ?? [],
2051
2205
  ok: input.ok,
2052
2206
  };
2053
2207
  if (input.emitJson) {
@@ -2126,7 +2280,9 @@ function buildResponse(input) {
2126
2280
  ? `Result: FINALIZE CLEAN -- cohort briefs swept to completed/.${skipNote}`
2127
2281
  : input.pending !== null
2128
2282
  ? "Result: FINALIZE INCOMPLETE -- origin-close pending."
2129
- : "Result: FINALIZE INCOMPLETE -- see errors above.");
2283
+ : input.errors.length === 0 && (input.refusedMarks ?? []).length > 0
2284
+ ? "Result: FINALIZE INCOMPLETE -- refused deft-story mark(s); see warnings above."
2285
+ : "Result: FINALIZE INCOMPLETE -- see errors above.");
2130
2286
  return {
2131
2287
  exitCode: input.exitCode,
2132
2288
  stdout: `${lines.join("\n")}\n`,