@deftai/directive-core 0.105.0 → 0.107.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 (173) hide show
  1. package/dist/authz/classify.js +591 -34
  2. package/dist/check/gate-lists.js +2 -0
  3. package/dist/check/named-cause.js +1 -1
  4. package/dist/design-critique/exclusive-chip.d.ts +33 -0
  5. package/dist/design-critique/exclusive-chip.js +68 -0
  6. package/dist/design-critique/parent-audit.d.ts +59 -0
  7. package/dist/design-critique/parent-audit.js +121 -0
  8. package/dist/doctor/checks.d.ts +6 -0
  9. package/dist/doctor/checks.js +35 -0
  10. package/dist/forward-coverage/diff-coverage.d.ts +39 -0
  11. package/dist/forward-coverage/diff-coverage.js +112 -0
  12. package/dist/forward-coverage/diff-lines.d.ts +19 -0
  13. package/dist/forward-coverage/diff-lines.js +173 -0
  14. package/dist/forward-coverage/evaluate.d.ts +24 -9
  15. package/dist/forward-coverage/evaluate.js +125 -19
  16. package/dist/hooks/dest-form.d.ts +46 -0
  17. package/dist/hooks/dest-form.js +575 -0
  18. package/dist/hooks/dispatcher.d.ts +17 -1
  19. package/dist/hooks/dispatcher.js +82 -11
  20. package/dist/hooks/index.d.ts +1 -0
  21. package/dist/hooks/index.js +1 -0
  22. package/dist/init-deposit/agent-hooks.d.ts +1 -1
  23. package/dist/init-deposit/agent-hooks.js +1 -1
  24. package/dist/intake/clause-derivation.d.ts +9 -0
  25. package/dist/intake/clause-derivation.js +30 -3
  26. package/dist/intake/github-auth-modes-cli.js +14 -2
  27. package/dist/intake/github-auth-modes.d.ts +54 -9
  28. package/dist/intake/github-auth-modes.js +326 -123
  29. package/dist/lifecycle/completed-tracked-on-delivery.d.ts +26 -0
  30. package/dist/lifecycle/completed-tracked-on-delivery.js +37 -14
  31. package/dist/lifecycle/completed-write-guard.d.ts +48 -0
  32. package/dist/lifecycle/completed-write-guard.js +373 -0
  33. package/dist/lifecycle/index.d.ts +1 -0
  34. package/dist/lifecycle/index.js +1 -0
  35. package/dist/lifecycle-visible/evaluate.d.ts +98 -0
  36. package/dist/lifecycle-visible/evaluate.js +710 -0
  37. package/dist/lifecycle-visible/index.d.ts +2 -0
  38. package/dist/lifecycle-visible/index.js +2 -0
  39. package/dist/literal-acceptance/capture.d.ts +16 -0
  40. package/dist/literal-acceptance/capture.js +78 -10
  41. package/dist/literal-acceptance/evaluate.js +25 -3
  42. package/dist/literal-acceptance/index.d.ts +2 -2
  43. package/dist/literal-acceptance/index.js +2 -2
  44. package/dist/literal-acceptance/run.js +9 -3
  45. package/dist/literal-acceptance/types.d.ts +6 -0
  46. package/dist/literal-acceptance/types.js +6 -0
  47. package/dist/orchestration/judgment-policy.d.ts +13 -0
  48. package/dist/orchestration/judgment-policy.js +15 -0
  49. package/dist/orchestration/probe-session.js +13 -8
  50. package/dist/orchestration/verify-judgment-gates.d.ts +4 -0
  51. package/dist/orchestration/verify-judgment-gates.js +43 -11
  52. package/dist/policy/ceremony-dial.js +7 -7
  53. package/dist/policy/host-hooks.d.ts +20 -0
  54. package/dist/policy/host-hooks.js +121 -2
  55. package/dist/policy/org-force-on-migration.js +2 -1
  56. package/dist/policy/plan-extensions.d.ts +1 -1
  57. package/dist/policy/plan-extensions.js +2 -0
  58. package/dist/policy/product-signal.js +6 -7
  59. package/dist/policy/require-human-merge.js +8 -6
  60. package/dist/policy/resolve.d.ts +7 -2
  61. package/dist/policy/resolve.js +25 -8
  62. package/dist/policy/runtime-authority.d.ts +18 -0
  63. package/dist/policy/runtime-authority.js +11 -0
  64. package/dist/policy/value-feedback.js +7 -8
  65. package/dist/policy/write-fence.js +3 -0
  66. package/dist/preflight-cache/evaluate.d.ts +12 -0
  67. package/dist/preflight-cache/evaluate.js +24 -4
  68. package/dist/preflight-cache/index.d.ts +1 -1
  69. package/dist/preflight-cache/index.js +1 -1
  70. package/dist/product-first-done-gate/evaluate.d.ts +4 -0
  71. package/dist/product-first-done-gate/evaluate.js +82 -10
  72. package/dist/release/closed-verb-gate.d.ts +12 -0
  73. package/dist/release/closed-verb-gate.js +51 -0
  74. package/dist/release/index.d.ts +1 -0
  75. package/dist/release/index.js +1 -0
  76. package/dist/release/pipeline.js +7 -0
  77. package/dist/release/types.d.ts +12 -0
  78. package/dist/render/constants.d.ts +2 -1
  79. package/dist/render/constants.js +2 -1
  80. package/dist/render/framework-commands.js +4 -0
  81. package/dist/render/spec-validate.js +23 -12
  82. package/dist/run-summary/types.d.ts +4 -0
  83. package/dist/scm/design-critique-chip.d.ts +37 -0
  84. package/dist/scm/design-critique-chip.js +171 -0
  85. package/dist/scm/index.d.ts +1 -0
  86. package/dist/scm/index.js +1 -0
  87. package/dist/scm/main.d.ts +3 -0
  88. package/dist/scm/main.js +15 -1
  89. package/dist/scm/readiness-cli.d.ts +2 -0
  90. package/dist/scm/readiness-cli.js +42 -1
  91. package/dist/scm/readiness.d.ts +2 -1
  92. package/dist/scm/readiness.js +7 -1
  93. package/dist/scope/decompose.js +2 -1
  94. package/dist/scope/delivery-evidence.d.ts +27 -1
  95. package/dist/scope/index.d.ts +1 -0
  96. package/dist/scope/index.js +1 -0
  97. package/dist/scope/lifecycle-write.d.ts +24 -0
  98. package/dist/scope/lifecycle-write.js +64 -0
  99. package/dist/scope/main.js +18 -6
  100. package/dist/scope/promote-from-issue.js +5 -5
  101. package/dist/scope/transition.js +90 -11
  102. package/dist/scope-provenance/evaluate.js +8 -4
  103. package/dist/session/ac-pass-banking.d.ts +5 -0
  104. package/dist/session/ac-pass-banking.js +3 -2
  105. package/dist/session/git.d.ts +18 -0
  106. package/dist/session/git.js +81 -1
  107. package/dist/session/occupancy.d.ts +2 -1
  108. package/dist/session/occupancy.js +11 -4
  109. package/dist/session/process-cost.d.ts +32 -10
  110. package/dist/session/process-cost.js +189 -1
  111. package/dist/session/product-state-hash.js +41 -24
  112. package/dist/session/ritual-entrypoint.js +16 -1
  113. package/dist/session/session-start.d.ts +18 -0
  114. package/dist/session/session-start.js +49 -2
  115. package/dist/session/verify-session-ritual.d.ts +46 -4
  116. package/dist/session/verify-session-ritual.js +118 -10
  117. package/dist/swarm/finalize-cohort-cli.d.ts +7 -2
  118. package/dist/swarm/finalize-cohort-cli.js +165 -35
  119. package/dist/swarm/finalize-cohort.d.ts +1 -0
  120. package/dist/swarm/finalize-cohort.js +14 -2
  121. package/dist/swarm/launch.d.ts +69 -0
  122. package/dist/swarm/launch.js +191 -1
  123. package/dist/swarm/routing-set-cli.js +2 -0
  124. package/dist/swarm/routing-verify.d.ts +1 -1
  125. package/dist/swarm/routing-verify.js +2 -2
  126. package/dist/triage/actions/index.d.ts +4 -0
  127. package/dist/triage/actions/index.js +6 -0
  128. package/dist/triage/bootstrap/index.js +6 -6
  129. package/dist/triage/evaluate/evaluate.d.ts +11 -0
  130. package/dist/triage/evaluate/evaluate.js +169 -0
  131. package/dist/triage/evaluate/github.d.ts +16 -0
  132. package/dist/triage/evaluate/github.js +153 -0
  133. package/dist/triage/evaluate/index.d.ts +9 -0
  134. package/dist/triage/evaluate/index.js +8 -0
  135. package/dist/triage/evaluate/paths.d.ts +7 -0
  136. package/dist/triage/evaluate/paths.js +25 -0
  137. package/dist/triage/evaluate/sink.d.ts +5 -0
  138. package/dist/triage/evaluate/sink.js +52 -0
  139. package/dist/triage/evaluate/types.d.ts +93 -0
  140. package/dist/triage/evaluate/types.js +13 -0
  141. package/dist/triage/evaluate/validity.d.ts +8 -0
  142. package/dist/triage/evaluate/validity.js +87 -0
  143. package/dist/triage/evaluate/value.d.ts +12 -0
  144. package/dist/triage/evaluate/value.js +33 -0
  145. package/dist/triage/evaluate/wip-census.d.ts +5 -0
  146. package/dist/triage/evaluate/wip-census.js +39 -0
  147. package/dist/triage/evaluate/worktrees.d.ts +8 -0
  148. package/dist/triage/evaluate/worktrees.js +68 -0
  149. package/dist/triage/evaluate/xbrief-refs.d.ts +8 -0
  150. package/dist/triage/evaluate/xbrief-refs.js +60 -0
  151. package/dist/triage/help/registry-data.d.ts +22 -8
  152. package/dist/triage/help/registry-data.js +54 -6
  153. package/dist/triage/index.d.ts +1 -0
  154. package/dist/triage/index.js +1 -0
  155. package/dist/triage/welcome/writers.d.ts +1 -1
  156. package/dist/triage/welcome/writers.js +10 -5
  157. package/dist/value/feedback-file.d.ts +17 -2
  158. package/dist/value/feedback-file.js +80 -6
  159. package/dist/value/readback.d.ts +4 -1
  160. package/dist/value/readback.js +51 -12
  161. package/dist/vbrief-reconcile/labels.d.ts +1 -0
  162. package/dist/vbrief-reconcile/labels.js +30 -0
  163. package/dist/vbrief-validate/schema.d.ts +4 -0
  164. package/dist/vbrief-validate/schema.js +1 -1
  165. package/dist/vbrief-validation/story-quality.d.ts +7 -0
  166. package/dist/vbrief-validation/story-quality.js +8 -1
  167. package/dist/verify-env/agent-hook-readiness.d.ts +2 -2
  168. package/dist/verify-env/agent-hook-readiness.js +12 -8
  169. package/dist/verify-env/agent-hooks-live-probe.d.ts +5 -1
  170. package/dist/verify-env/agent-hooks-live-probe.js +26 -5
  171. package/dist/verify-env/agent-hooks.js +3 -4
  172. package/dist/xbrief-migrate/agents-header.js +69 -7
  173. package/package.json +7 -3
@@ -1,67 +1,181 @@
1
1
  #!/usr/bin/env node
2
2
  import { fileURLToPath } from "node:url";
3
+ import { EXIT_CONFIG_ERROR, EXIT_OK } from "./constants.js";
3
4
  import { finalizeCohort } from "./finalize-cohort.js";
5
+ export const FINALIZE_COHORT_USAGE = `Usage: task swarm:finalize-cohort -- [--pr <n>[,<n>...]] [--stories <ids|paths>] [options]
6
+
7
+ After cohort PRs merge, sweep story briefs active/ -> completed/ and open the lifecycle PR.
8
+
9
+ Options:
10
+ --pr <n>[,<n>...] Merged PR numbers (closing issues map to active stories)
11
+ --stories <ids|paths> Explicit story tokens (repeatable)
12
+ --repo OWNER/REPO GitHub repo (or $GH_REPO)
13
+ --project-root <path> Project root (default: cwd)
14
+ --base-branch <name> Sweep/PR base (default: resolved delivery branch)
15
+ --delivery-branch <name> Delivery-branch override when policy is untyped
16
+ --label <name> Feature-branch label
17
+ --dry-run Print plan; no git mutation
18
+ --no-commit Sweep without committing / opening a PR
19
+ --no-open-pr Commit without opening a PR
20
+ --json Machine-readable result
21
+ -h, --help Show this help
22
+ `;
23
+ function equalsValue(arg, flag) {
24
+ return arg.slice(flag.length + 1);
25
+ }
26
+ function parsePrList(raw, flag, prNumbers, reject) {
27
+ if (raw.trim().length === 0) {
28
+ reject(flag);
29
+ return;
30
+ }
31
+ for (const piece of raw.split(",")) {
32
+ const trimmed = piece.trim();
33
+ if (trimmed.length === 0) {
34
+ reject(flag);
35
+ return;
36
+ }
37
+ if (!/^\d+$/.test(trimmed)) {
38
+ reject(flag);
39
+ return;
40
+ }
41
+ prNumbers.push(Number.parseInt(trimmed, 10));
42
+ }
43
+ }
4
44
  export function parseFinalizeCohortArgv(argv) {
5
45
  const prNumbers = [];
6
46
  const storyTokens = [];
7
47
  let repo = null;
8
48
  let projectRoot = ".";
9
- let baseBranch = "master";
49
+ let baseBranch;
10
50
  let deliveryBranch = null;
11
51
  let label = null;
12
52
  let dryRun = false;
13
53
  let noCommit = false;
14
54
  let noOpenPr = false;
15
55
  let emitJson = false;
56
+ let help = false;
57
+ let error = null;
58
+ const reject = (arg) => {
59
+ error ??= `unrecognized argument: ${arg}`;
60
+ };
61
+ const takeValue = (flag, nextValue) => {
62
+ if (nextValue === undefined || nextValue.startsWith("-")) {
63
+ reject(flag);
64
+ return null;
65
+ }
66
+ return nextValue;
67
+ };
16
68
  for (let i = 0; i < argv.length; i += 1) {
17
69
  const arg = argv[i];
18
70
  const next = argv[i + 1];
19
- if (arg === "--pr" && next !== undefined) {
20
- for (const piece of next.split(",")) {
21
- const trimmed = piece.trim();
22
- if (/^\d+$/.test(trimmed)) {
23
- prNumbers.push(Number.parseInt(trimmed, 10));
24
- }
71
+ if (arg === "--help" || arg === "-h") {
72
+ help = true;
73
+ }
74
+ else if (arg === "--pr") {
75
+ const value = takeValue(arg, next);
76
+ if (value !== null) {
77
+ parsePrList(value, arg, prNumbers, reject);
78
+ i += 1;
25
79
  }
26
- i += 1;
27
80
  }
28
81
  else if (arg?.startsWith("--pr=")) {
29
- for (const piece of arg.slice("--pr=".length).split(",")) {
30
- const trimmed = piece.trim();
31
- if (/^\d+$/.test(trimmed)) {
32
- prNumbers.push(Number.parseInt(trimmed, 10));
33
- }
82
+ parsePrList(equalsValue(arg, "--pr"), arg, prNumbers, reject);
83
+ }
84
+ else if (arg === "--stories") {
85
+ const value = takeValue(arg, next);
86
+ if (value !== null) {
87
+ storyTokens.push(value);
88
+ i += 1;
34
89
  }
35
90
  }
36
- else if (arg === "--stories" && next !== undefined) {
37
- storyTokens.push(next);
38
- i += 1;
91
+ else if (arg?.startsWith("--stories=")) {
92
+ const value = equalsValue(arg, "--stories");
93
+ if (value.length === 0) {
94
+ reject(arg);
95
+ }
96
+ else {
97
+ storyTokens.push(value);
98
+ }
39
99
  }
40
- else if (arg === "--repo" && next !== undefined) {
41
- repo = next;
42
- i += 1;
100
+ else if (arg === "--repo") {
101
+ const value = takeValue(arg, next);
102
+ if (value !== null) {
103
+ repo = value;
104
+ i += 1;
105
+ }
43
106
  }
44
107
  else if (arg?.startsWith("--repo=")) {
45
- repo = arg.slice("--repo=".length);
108
+ const value = equalsValue(arg, "--repo");
109
+ if (value.length === 0) {
110
+ reject(arg);
111
+ }
112
+ else {
113
+ repo = value;
114
+ }
115
+ }
116
+ else if (arg === "--project-root") {
117
+ const value = takeValue(arg, next);
118
+ if (value !== null) {
119
+ projectRoot = value;
120
+ i += 1;
121
+ }
46
122
  }
47
- else if (arg === "--project-root" && next !== undefined) {
48
- projectRoot = next;
49
- i += 1;
123
+ else if (arg?.startsWith("--project-root=")) {
124
+ const value = equalsValue(arg, "--project-root");
125
+ if (value.length === 0) {
126
+ reject(arg);
127
+ }
128
+ else {
129
+ projectRoot = value;
130
+ }
50
131
  }
51
- else if (arg === "--base-branch" && next !== undefined) {
52
- baseBranch = next;
53
- i += 1;
132
+ else if (arg === "--base-branch") {
133
+ const value = takeValue(arg, next);
134
+ if (value !== null) {
135
+ baseBranch = value;
136
+ i += 1;
137
+ }
54
138
  }
55
- else if (arg === "--delivery-branch" && next !== undefined) {
56
- deliveryBranch = next;
57
- i += 1;
139
+ else if (arg?.startsWith("--base-branch=")) {
140
+ const value = equalsValue(arg, "--base-branch");
141
+ if (value.length === 0) {
142
+ reject(arg);
143
+ }
144
+ else {
145
+ baseBranch = value;
146
+ }
147
+ }
148
+ else if (arg === "--delivery-branch") {
149
+ const value = takeValue(arg, next);
150
+ if (value !== null) {
151
+ deliveryBranch = value;
152
+ i += 1;
153
+ }
58
154
  }
59
155
  else if (arg?.startsWith("--delivery-branch=")) {
60
- deliveryBranch = arg.slice("--delivery-branch=".length);
156
+ const value = equalsValue(arg, "--delivery-branch");
157
+ if (value.length === 0) {
158
+ reject(arg);
159
+ }
160
+ else {
161
+ deliveryBranch = value;
162
+ }
163
+ }
164
+ else if (arg === "--label") {
165
+ const value = takeValue(arg, next);
166
+ if (value !== null) {
167
+ label = value;
168
+ i += 1;
169
+ }
61
170
  }
62
- else if (arg === "--label" && next !== undefined) {
63
- label = next;
64
- i += 1;
171
+ else if (arg?.startsWith("--label=")) {
172
+ const value = equalsValue(arg, "--label");
173
+ if (value.length === 0) {
174
+ reject(arg);
175
+ }
176
+ else {
177
+ label = value;
178
+ }
65
179
  }
66
180
  else if (arg === "--dry-run") {
67
181
  dryRun = true;
@@ -78,23 +192,37 @@ export function parseFinalizeCohortArgv(argv) {
78
192
  else if (arg !== undefined && !arg.startsWith("-")) {
79
193
  storyTokens.push(arg);
80
194
  }
195
+ else if (arg !== undefined) {
196
+ reject(arg);
197
+ }
81
198
  }
82
199
  return {
83
200
  prNumbers,
84
201
  storyTokens,
85
202
  repo,
86
203
  projectRoot,
87
- baseBranch,
204
+ ...(baseBranch !== undefined ? { baseBranch } : {}),
88
205
  deliveryBranch,
89
206
  label,
90
207
  dryRun,
91
208
  noCommit,
92
209
  noOpenPr,
93
210
  emitJson,
211
+ help,
212
+ error,
94
213
  };
95
214
  }
96
215
  export function finalizeCohortMain(argv = process.argv.slice(2)) {
97
- const result = finalizeCohort(parseFinalizeCohortArgv(argv));
216
+ const parsed = parseFinalizeCohortArgv(argv);
217
+ if (parsed.help) {
218
+ process.stdout.write(FINALIZE_COHORT_USAGE);
219
+ return EXIT_OK;
220
+ }
221
+ if (parsed.error !== null) {
222
+ process.stderr.write(`${parsed.error}\n`);
223
+ return EXIT_CONFIG_ERROR;
224
+ }
225
+ const result = finalizeCohort(parsed);
98
226
  if (result.stdout.length > 0) {
99
227
  process.stdout.write(result.stdout);
100
228
  }
@@ -103,7 +231,9 @@ export function finalizeCohortMain(argv = process.argv.slice(2)) {
103
231
  }
104
232
  return result.exitCode;
105
233
  }
234
+ /* v8 ignore start -- entry guard */
106
235
  if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1]) {
107
236
  process.exit(finalizeCohortMain());
108
237
  }
238
+ /* v8 ignore stop */
109
239
  //# sourceMappingURL=finalize-cohort-cli.js.map
@@ -13,6 +13,7 @@ export interface FinalizeCohortResult {
13
13
  readonly branch: string | null;
14
14
  readonly pr_url: string | null;
15
15
  readonly delivery_branch: string | null;
16
+ readonly sweep_base: string;
16
17
  readonly delivery_errors: readonly string[];
17
18
  readonly errors: readonly string[];
18
19
  readonly warnings: readonly string[];
@@ -327,8 +327,6 @@ export function finalizeCohort(args) {
327
327
  const prNumbers = [...(args.prNumbers ?? [])].sort((a, b) => a - b);
328
328
  const storyTokens = splitCsv(args.storyTokens ?? []);
329
329
  const repo = args.repo ?? process.env.GH_REPO ?? null;
330
- // baseBranch is for the lifecycle sweep PR only — not delivery proof (#3041).
331
- const baseBranch = args.baseBranch ?? "master";
332
330
  const dryRun = args.dryRun ?? false;
333
331
  const noCommit = args.noCommit ?? false;
334
332
  const noOpenPr = args.noOpenPr ?? false;
@@ -351,6 +349,12 @@ export function finalizeCohort(args) {
351
349
  const deliveryBranch = policyDelivery.source === "typed"
352
350
  ? policyDelivery.branch
353
351
  : (cliDelivery ?? policyDelivery.branch);
352
+ // Sweep/PR base is distinct from delivery proof (#3041). Default via the resolved
353
+ // delivery branch — never a hardcoded "master". --base-branch remains the override.
354
+ const explicitSweepBase = args.baseBranch !== undefined && args.baseBranch !== null && args.baseBranch.trim().length > 0
355
+ ? args.baseBranch.trim()
356
+ : null;
357
+ const baseBranch = explicitSweepBase ?? deliveryBranch;
354
358
  if (!existsSync(projectRoot)) {
355
359
  return buildResponse({
356
360
  projectRoot,
@@ -364,6 +368,7 @@ export function finalizeCohort(args) {
364
368
  branch: null,
365
369
  prUrl: null,
366
370
  deliveryBranch,
371
+ sweepBase: baseBranch,
367
372
  deliveryErrors: [],
368
373
  errors: [`project root does not exist: ${projectRoot}`],
369
374
  warnings: [],
@@ -385,6 +390,7 @@ export function finalizeCohort(args) {
385
390
  branch: null,
386
391
  prUrl: null,
387
392
  deliveryBranch,
393
+ sweepBase: baseBranch,
388
394
  deliveryErrors: [],
389
395
  errors: [`no xbrief/ directory under project root: ${projectRoot}`],
390
396
  warnings: [],
@@ -546,6 +552,7 @@ export function finalizeCohort(args) {
546
552
  branch: null,
547
553
  prUrl: null,
548
554
  deliveryBranch,
555
+ sweepBase: baseBranch,
549
556
  deliveryErrors,
550
557
  errors,
551
558
  warnings,
@@ -572,6 +579,7 @@ export function finalizeCohort(args) {
572
579
  branch: null,
573
580
  prUrl: null,
574
581
  deliveryBranch,
582
+ sweepBase: baseBranch,
575
583
  deliveryErrors,
576
584
  errors,
577
585
  warnings,
@@ -673,6 +681,7 @@ export function finalizeCohort(args) {
673
681
  branch: null,
674
682
  prUrl: null,
675
683
  deliveryBranch,
684
+ sweepBase: baseBranch,
676
685
  deliveryErrors,
677
686
  errors,
678
687
  warnings,
@@ -712,6 +721,7 @@ export function finalizeCohort(args) {
712
721
  branch,
713
722
  prUrl,
714
723
  deliveryBranch,
724
+ sweepBase: baseBranch,
715
725
  deliveryErrors,
716
726
  errors,
717
727
  warnings,
@@ -733,6 +743,7 @@ function buildResponse(input) {
733
743
  branch: input.branch,
734
744
  pr_url: input.prUrl,
735
745
  delivery_branch: input.deliveryBranch,
746
+ sweep_base: input.sweepBase,
736
747
  delivery_errors: input.deliveryErrors,
737
748
  errors: input.errors,
738
749
  warnings: input.warnings,
@@ -754,6 +765,7 @@ function buildResponse(input) {
754
765
  if (input.deliveryBranch !== null) {
755
766
  lines.push(` Delivery branch: ${input.deliveryBranch}`);
756
767
  }
768
+ lines.push(` Sweep base: ${input.sweepBase}`);
757
769
  if (input.prNumbers.length > 0) {
758
770
  lines.push(` PRs: ${input.prNumbers.map((n) => `#${n}`).join(", ")}`);
759
771
  }
@@ -1,3 +1,4 @@
1
+ import { type ExpectedGithubWorkerPrincipal, type GhRunner, GITHUB_AUTH_MODE_HOST_GH, GITHUB_AUTH_MODE_INJECTED_TOKEN } from "../intake/github-auth-modes.js";
1
2
  import { EXIT_CONFIG_ERROR, EXIT_GATE_FAILED, EXIT_OK } from "./constants.js";
2
3
  import { type WorktreeRecord } from "./worktrees.js";
3
4
  export interface ResolvedStory {
@@ -21,6 +22,70 @@ export type RuntimeAuthProbeFn = () => [string, string];
21
22
  export declare const defaultPreflightGate: PreflightGateFn;
22
23
  export declare const defaultReadinessGate: ReadinessGateFn;
23
24
  export declare const defaultRuntimeAuthProbe: RuntimeAuthProbeFn;
25
+ export type WorkerCredentialDispatchPath = "grok-build" | "local-hybrid";
26
+ /**
27
+ * Dispatch-envelope half of the runtime / GitHub auth-mode contract (#1557 / #1351).
28
+ * Policy labels only — never a token value.
29
+ */
30
+ export declare function formatDispatchAuthEnvelope(fields: {
31
+ runtimeMode: string;
32
+ githubAuthMode: string;
33
+ expectedGithubLogin?: string | null;
34
+ }): string;
35
+ export interface WorkerCredentialInjectionRequest {
36
+ environ?: NodeJS.ProcessEnv;
37
+ githubAuthMode: string;
38
+ runtimeMode?: string | null;
39
+ dispatchPath?: WorkerCredentialDispatchPath;
40
+ expectedPrincipal?: ExpectedGithubWorkerPrincipal | null;
41
+ repo?: string;
42
+ runGh?: GhRunner;
43
+ cwd?: string;
44
+ }
45
+ export type WorkerCredentialInjectionResult = {
46
+ ok: true;
47
+ injected: true;
48
+ githubAuthMode: typeof GITHUB_AUTH_MODE_INJECTED_TOKEN;
49
+ runtimeMode: string | null;
50
+ expectedLogin: string;
51
+ spawnEnv: {
52
+ GH_TOKEN: string;
53
+ GITHUB_TOKEN: string;
54
+ GH_ENTERPRISE_TOKEN: string;
55
+ DEFT_EXPECTED_GITHUB_LOGIN: string;
56
+ };
57
+ envelopeSection: string;
58
+ } | {
59
+ ok: true;
60
+ injected: false;
61
+ githubAuthMode: typeof GITHUB_AUTH_MODE_HOST_GH;
62
+ runtimeMode: string | null;
63
+ expectedLogin: string | null;
64
+ spawnEnv: Record<string, never>;
65
+ envelopeSection: string;
66
+ } | {
67
+ ok: false;
68
+ blocked: true;
69
+ githubAuthMode: string;
70
+ runtimeMode: string | null;
71
+ missingCredential: string;
72
+ remedy: string;
73
+ detail: string;
74
+ failureKind: string | null;
75
+ envelopeSection: string;
76
+ };
77
+ /**
78
+ * Validate a held user-bearing worker credential and prepare spawn-time injection (#1351).
79
+ *
80
+ * Comparison is opt-in on the validator. This path always stamps
81
+ * `DEFT_EXPECTED_GITHUB_LOGIN` after a successful user-principal check so the
82
+ * worker envelope does not inherit "any user token is approved."
83
+ *
84
+ * Installation credentials fail closed via the existing validator (#3693).
85
+ * Token values belong only in the returned `spawnEnv` — never in prompts,
86
+ * transcripts, or manifest entries.
87
+ */
88
+ export declare function prepareWorkerCredentialInjection(request: WorkerCredentialInjectionRequest): WorkerCredentialInjectionResult;
24
89
  /**
25
90
  * True when a brief in `xbrief/completed/` (or the legacy `vbrief/completed/`)
26
91
  * references the given issue number in its `references[]` / `Traces`. Used by
@@ -74,6 +139,7 @@ export declare function buildManifest(resolved: readonly ResolvedStory[], option
74
139
  modelSource?: string | null;
75
140
  runtimeMode?: string | null;
76
141
  githubAuthMode?: string | null;
142
+ expectedGithubLogin?: string | null;
77
143
  occupancySessionId?: string | null;
78
144
  }): Record<string, unknown>[];
79
145
  export declare function orderCohort(resolved: readonly ResolvedStory[], _projectRoot: string): ResolvedStory[];
@@ -103,6 +169,9 @@ export interface LaunchArgs {
103
169
  * Defaults to `process.env` when unset.
104
170
  */
105
171
  environ?: NodeJS.ProcessEnv;
172
+ /** Optional gh runner for identity-bound credential injection (#1351). */
173
+ runGh?: GhRunner;
174
+ expectedPrincipal?: ExpectedGithubWorkerPrincipal | null;
106
175
  }
107
176
  export declare function swarmLaunch(args: LaunchArgs): {
108
177
  exitCode: number;
@@ -1,7 +1,7 @@
1
1
  import { existsSync, mkdirSync, readdirSync, readFileSync } from "node:fs";
2
2
  import { basename, dirname, isAbsolute, join, resolve } from "node:path";
3
3
  import { containedWrite } from "../fs/contained-write.js";
4
- import { inferGithubAuthMode } from "../intake/github-auth-modes.js";
4
+ import { ENV_EXPECTED_GITHUB_LOGIN, FAILURE_INSTALLATION_IDENTITY_UNVERIFIABLE, FAILURE_MISSING_INJECTED_TOKEN, findInjectedToken, GITHUB_AUTH_MODE_HOST_GH, GITHUB_AUTH_MODE_INJECTED_TOKEN, inferGithubAuthMode, validateGithubAuthForWorker, } from "../intake/github-auth-modes.js";
5
5
  import { getPlatformCapabilities } from "../intake/platform-capabilities.js";
6
6
  import { hasArtifactSuffix, resolveLifecycleFolder, stripArtifactSuffix, } from "../layout/resolve.js";
7
7
  import { evaluate as preflightEvaluate } from "../preflight/evaluate.js";
@@ -25,6 +25,164 @@ export const defaultRuntimeAuthProbe = () => {
25
25
  const report = getPlatformCapabilities();
26
26
  return [report.runtimeMode, inferGithubAuthMode(report)];
27
27
  };
28
+ const CREDENTIAL_VALUE_PATTERN = /^(ghp_|gho_|ghu_|ghs_|github_pat_)/i;
29
+ const INJECTION_MISSING_CREDENTIAL = "GH_TOKEN (or GITHUB_TOKEN / GH_ENTERPRISE_TOKEN)";
30
+ const INJECTION_DISPATCHER_REMEDY = "Dispatcher-side remedy: load an approved user-bearing worker credential into the dispatcher environment, then call prepareWorkerCredentialInjection before spawn. Do not fall back to the host gh token or continue under the maintainer identity. Installation credentials are not injectable; see #3693.";
31
+ const HELD_WORKER_TOKEN_VARS = [
32
+ "GH_TOKEN",
33
+ "GITHUB_TOKEN",
34
+ "GH_ENTERPRISE_TOKEN",
35
+ "GITHUB_ENTERPRISE_TOKEN",
36
+ ];
37
+ function looksLikeCredentialValue(value) {
38
+ return CREDENTIAL_VALUE_PATTERN.test(value.trim());
39
+ }
40
+ function assertNoCredentialValue(value, label) {
41
+ if (looksLikeCredentialValue(value)) {
42
+ throw new Error(`${label} must not contain a credential value`);
43
+ }
44
+ }
45
+ function sanitizeEnvelopeField(value, label) {
46
+ const cleaned = value.replace(/\r?\n/g, " ").trim();
47
+ assertNoCredentialValue(cleaned, label);
48
+ return cleaned;
49
+ }
50
+ /**
51
+ * Bind validation to the selected token. Stamp it into every gh token slot and
52
+ * drop GH_CONFIG_DIR so a host/enterprise config cannot authenticate as someone
53
+ * else. GH_HOST stays: it selects the API host, not the secret.
54
+ */
55
+ function isolateHeldWorkerToken(environ, token) {
56
+ const isolated = { ...environ };
57
+ for (const name of HELD_WORKER_TOKEN_VARS) {
58
+ isolated[name] = token;
59
+ }
60
+ delete isolated.GH_CONFIG_DIR;
61
+ return isolated;
62
+ }
63
+ function boundWorkerSpawnEnv(token, expectedLogin) {
64
+ return {
65
+ GH_TOKEN: token,
66
+ GITHUB_TOKEN: token,
67
+ GH_ENTERPRISE_TOKEN: token,
68
+ [ENV_EXPECTED_GITHUB_LOGIN]: expectedLogin,
69
+ };
70
+ }
71
+ /**
72
+ * Dispatch-envelope half of the runtime / GitHub auth-mode contract (#1557 / #1351).
73
+ * Policy labels only — never a token value.
74
+ */
75
+ export function formatDispatchAuthEnvelope(fields) {
76
+ const runtimeMode = sanitizeEnvelopeField(fields.runtimeMode, "runtime_mode");
77
+ const githubAuthMode = sanitizeEnvelopeField(fields.githubAuthMode, "github_auth_mode");
78
+ const lines = [
79
+ "## Runtime and GitHub auth mode",
80
+ "",
81
+ `- runtime_mode: ${runtimeMode}`,
82
+ `- github_auth_mode: ${githubAuthMode}`,
83
+ ];
84
+ if (fields.expectedGithubLogin !== undefined && fields.expectedGithubLogin !== null) {
85
+ const login = sanitizeEnvelopeField(fields.expectedGithubLogin, "expected_github_login");
86
+ if (login.length > 0) {
87
+ lines.push(`- expected_github_login: ${login}`);
88
+ }
89
+ }
90
+ return `${lines.join("\n")}\n`;
91
+ }
92
+ function blockedInjection(fields) {
93
+ const detail = fields.detail.startsWith("BLOCKED") ? fields.detail : `BLOCKED: ${fields.detail}`;
94
+ return {
95
+ ok: false,
96
+ blocked: true,
97
+ githubAuthMode: fields.githubAuthMode,
98
+ runtimeMode: fields.runtimeMode,
99
+ missingCredential: fields.missingCredential ?? INJECTION_MISSING_CREDENTIAL,
100
+ remedy: INJECTION_DISPATCHER_REMEDY,
101
+ detail,
102
+ failureKind: fields.failureKind,
103
+ envelopeSection: formatDispatchAuthEnvelope({
104
+ runtimeMode: fields.runtimeMode ?? "unknown",
105
+ githubAuthMode: fields.githubAuthMode,
106
+ }),
107
+ };
108
+ }
109
+ /**
110
+ * Validate a held user-bearing worker credential and prepare spawn-time injection (#1351).
111
+ *
112
+ * Comparison is opt-in on the validator. This path always stamps
113
+ * `DEFT_EXPECTED_GITHUB_LOGIN` after a successful user-principal check so the
114
+ * worker envelope does not inherit "any user token is approved."
115
+ *
116
+ * Installation credentials fail closed via the existing validator (#3693).
117
+ * Token values belong only in the returned `spawnEnv` — never in prompts,
118
+ * transcripts, or manifest entries.
119
+ */
120
+ export function prepareWorkerCredentialInjection(request) {
121
+ const environ = request.environ ?? process.env;
122
+ const runtimeMode = request.runtimeMode ?? null;
123
+ const token = findInjectedToken(environ);
124
+ const grokBuild = request.dispatchPath === "grok-build";
125
+ const effectiveMode = grokBuild || token !== null ? GITHUB_AUTH_MODE_INJECTED_TOKEN : request.githubAuthMode;
126
+ if (effectiveMode === GITHUB_AUTH_MODE_HOST_GH && token === null) {
127
+ return {
128
+ ok: true,
129
+ injected: false,
130
+ githubAuthMode: GITHUB_AUTH_MODE_HOST_GH,
131
+ runtimeMode,
132
+ expectedLogin: null,
133
+ spawnEnv: {},
134
+ envelopeSection: formatDispatchAuthEnvelope({
135
+ runtimeMode: runtimeMode ?? "local-unsandboxed",
136
+ githubAuthMode: GITHUB_AUTH_MODE_HOST_GH,
137
+ }),
138
+ };
139
+ }
140
+ if (token === null) {
141
+ return blockedInjection({
142
+ githubAuthMode: GITHUB_AUTH_MODE_INJECTED_TOKEN,
143
+ runtimeMode,
144
+ failureKind: FAILURE_MISSING_INJECTED_TOKEN,
145
+ missingCredential: INJECTION_MISSING_CREDENTIAL,
146
+ detail: "missing worker credential GH_TOKEN (or GITHUB_TOKEN / GH_ENTERPRISE_TOKEN) for a write-requiring injected-token dispatch",
147
+ });
148
+ }
149
+ const isolatedEnviron = isolateHeldWorkerToken(environ, token);
150
+ const validated = validateGithubAuthForWorker(GITHUB_AUTH_MODE_INJECTED_TOKEN, {
151
+ environ: isolatedEnviron,
152
+ runtimeReport: { runtimeMode: runtimeMode ?? "cloud-headless" },
153
+ repo: request.repo,
154
+ runGh: request.runGh,
155
+ expectedPrincipal: request.expectedPrincipal,
156
+ cwd: request.cwd,
157
+ });
158
+ if (!validated.ok || validated.login === null || validated.login.trim().length === 0) {
159
+ const installation = validated.failureKind === FAILURE_INSTALLATION_IDENTITY_UNVERIFIABLE;
160
+ return blockedInjection({
161
+ githubAuthMode: GITHUB_AUTH_MODE_INJECTED_TOKEN,
162
+ runtimeMode,
163
+ failureKind: validated.failureKind,
164
+ detail: installation
165
+ ? `${validated.detail} Installation credentials are not injectable (#3693).`
166
+ : validated.detail,
167
+ });
168
+ }
169
+ const expectedLogin = validated.login.trim();
170
+ assertNoCredentialValue(expectedLogin, "expected_github_login");
171
+ const spawnEnv = boundWorkerSpawnEnv(token, expectedLogin);
172
+ return {
173
+ ok: true,
174
+ injected: true,
175
+ githubAuthMode: GITHUB_AUTH_MODE_INJECTED_TOKEN,
176
+ runtimeMode,
177
+ expectedLogin,
178
+ spawnEnv,
179
+ envelopeSection: formatDispatchAuthEnvelope({
180
+ runtimeMode: runtimeMode ?? "cloud-headless",
181
+ githubAuthMode: GITHUB_AUTH_MODE_INJECTED_TOKEN,
182
+ expectedGithubLogin: expectedLogin,
183
+ }),
184
+ };
185
+ }
28
186
  function loadJson(path) {
29
187
  try {
30
188
  const data = JSON.parse(readFileSync(path, "utf8"));
@@ -498,6 +656,10 @@ export function buildManifest(resolved, options) {
498
656
  if (options.githubAuthMode !== undefined && options.githubAuthMode !== null) {
499
657
  entry.github_auth_mode = options.githubAuthMode;
500
658
  }
659
+ if (options.expectedGithubLogin !== undefined && options.expectedGithubLogin !== null) {
660
+ assertNoCredentialValue(options.expectedGithubLogin, "expected_github_login");
661
+ entry.expected_github_login = options.expectedGithubLogin;
662
+ }
501
663
  manifest.push(entry);
502
664
  }
503
665
  return manifest;
@@ -692,6 +854,33 @@ export function swarmLaunch(args) {
692
854
  stderr: `${occupancy.message}\n`,
693
855
  };
694
856
  }
857
+ const launchEnviron = args.environ ?? process.env;
858
+ let expectedGithubLogin = null;
859
+ // Only the injected-token launch path auto-validates (and fail-closes on a
860
+ // missing token). An ambient GH_TOKEN on a host-gh probe is often the
861
+ // maintainer workaround, not a worker credential; local-hybrid injection is
862
+ // the explicit prepareWorkerCredentialInjection call.
863
+ if (githubAuthMode === GITHUB_AUTH_MODE_INJECTED_TOKEN) {
864
+ const injection = prepareWorkerCredentialInjection({
865
+ environ: launchEnviron,
866
+ githubAuthMode,
867
+ runtimeMode,
868
+ dispatchPath: "grok-build",
869
+ runGh: args.runGh,
870
+ expectedPrincipal: args.expectedPrincipal,
871
+ cwd: projectRoot,
872
+ });
873
+ if (!injection.ok) {
874
+ return {
875
+ exitCode: EXIT_GATE_FAILED,
876
+ stdout: "",
877
+ stderr: `${injection.detail}\n${injection.remedy}\n`,
878
+ };
879
+ }
880
+ if (injection.injected) {
881
+ expectedGithubLogin = injection.expectedLogin;
882
+ }
883
+ }
695
884
  const manifest = buildManifest(ordered, {
696
885
  projectRoot,
697
886
  group: args.group ?? null,
@@ -708,6 +897,7 @@ export function swarmLaunch(args) {
708
897
  modelSource,
709
898
  runtimeMode,
710
899
  githubAuthMode,
900
+ expectedGithubLogin,
711
901
  occupancySessionId: occupancy.sessionId,
712
902
  });
713
903
  const rendered = `${JSON.stringify(manifest, null, 2)}\n`;
@@ -22,6 +22,8 @@ export function routingSetMain(argv = process.argv.slice(2), environ = process.e
22
22
  let harnessDefault = false;
23
23
  for (let i = 0; i < argv.length; i += 1) {
24
24
  const arg = argv[i];
25
+ if (arg === "--")
26
+ continue;
25
27
  if (arg === "--project-root" && argv[i + 1] !== undefined) {
26
28
  projectRoot = argv[i + 1] ?? ".";
27
29
  i += 1;