wicked-crew 0.8.4 → 0.8.6

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 (135) hide show
  1. package/dist/api/ask-paths.d.ts +17 -0
  2. package/dist/api/ask-paths.d.ts.map +1 -1
  3. package/dist/api/ask-paths.js +13 -0
  4. package/dist/api/ask-paths.js.map +1 -1
  5. package/dist/api/ask-relay.d.ts +2 -0
  6. package/dist/api/ask-relay.d.ts.map +1 -1
  7. package/dist/api/ask-relay.js +35 -0
  8. package/dist/api/ask-relay.js.map +1 -1
  9. package/dist/api/chat-scope.d.ts +4 -4
  10. package/dist/api/chat-scope.d.ts.map +1 -1
  11. package/dist/api/chat-scope.js +1 -1
  12. package/dist/api/chat-turns.d.ts +12 -4
  13. package/dist/api/chat-turns.d.ts.map +1 -1
  14. package/dist/api/chat-turns.js +9 -4
  15. package/dist/api/chat-turns.js.map +1 -1
  16. package/dist/api/delivery-index.d.ts +53 -0
  17. package/dist/api/delivery-index.d.ts.map +1 -1
  18. package/dist/api/delivery-index.js +117 -0
  19. package/dist/api/delivery-index.js.map +1 -1
  20. package/dist/api/mcp.d.ts +4 -4
  21. package/dist/api/onboarding-capture.d.ts +58 -0
  22. package/dist/api/onboarding-capture.d.ts.map +1 -0
  23. package/dist/api/onboarding-capture.js +125 -0
  24. package/dist/api/onboarding-capture.js.map +1 -0
  25. package/dist/api/routes.d.ts +11 -3
  26. package/dist/api/routes.d.ts.map +1 -1
  27. package/dist/api/routes.js +84 -9
  28. package/dist/api/routes.js.map +1 -1
  29. package/dist/api/run-timing-index.d.ts +20 -0
  30. package/dist/api/run-timing-index.d.ts.map +1 -1
  31. package/dist/api/run-timing-index.js +46 -0
  32. package/dist/api/run-timing-index.js.map +1 -1
  33. package/dist/api/server.d.ts.map +1 -1
  34. package/dist/api/server.js +52 -37
  35. package/dist/api/server.js.map +1 -1
  36. package/dist/api/skills.d.ts.map +1 -1
  37. package/dist/api/skills.js +3 -1
  38. package/dist/api/skills.js.map +1 -1
  39. package/dist/api/watch-routes.d.ts +3 -1
  40. package/dist/api/watch-routes.d.ts.map +1 -1
  41. package/dist/api/watch-routes.js +3 -0
  42. package/dist/api/watch-routes.js.map +1 -1
  43. package/dist/core/adapter.d.ts +10 -36
  44. package/dist/core/adapter.d.ts.map +1 -1
  45. package/dist/core/adapter.js +81 -57
  46. package/dist/core/adapter.js.map +1 -1
  47. package/dist/core/deliver-exclusions.d.ts +15 -1
  48. package/dist/core/deliver-exclusions.d.ts.map +1 -1
  49. package/dist/core/deliver-exclusions.js +25 -3
  50. package/dist/core/deliver-exclusions.js.map +1 -1
  51. package/dist/core/deliver-text.d.ts +84 -10
  52. package/dist/core/deliver-text.d.ts.map +1 -1
  53. package/dist/core/deliver-text.js +279 -10
  54. package/dist/core/deliver-text.js.map +1 -1
  55. package/dist/core/deliver.d.ts +45 -0
  56. package/dist/core/deliver.d.ts.map +1 -1
  57. package/dist/core/deliver.js +136 -27
  58. package/dist/core/deliver.js.map +1 -1
  59. package/dist/core/types.d.ts.map +1 -1
  60. package/dist/core/types.js +3 -0
  61. package/dist/core/types.js.map +1 -1
  62. package/dist/interactive/bridge-pool.d.ts +17 -0
  63. package/dist/interactive/bridge-pool.d.ts.map +1 -1
  64. package/dist/interactive/bridge-pool.js +69 -3
  65. package/dist/interactive/bridge-pool.js.map +1 -1
  66. package/dist/interactive/bridge-root.d.ts +7 -0
  67. package/dist/interactive/bridge-root.d.ts.map +1 -1
  68. package/dist/interactive/bridge-root.js +11 -0
  69. package/dist/interactive/bridge-root.js.map +1 -1
  70. package/dist/projects/routes.d.ts +6 -2
  71. package/dist/projects/routes.d.ts.map +1 -1
  72. package/dist/projects/routes.js +6 -0
  73. package/dist/projects/routes.js.map +1 -1
  74. package/dist/projects/settings.d.ts +2 -0
  75. package/dist/projects/settings.d.ts.map +1 -1
  76. package/dist/projects/settings.js +4 -0
  77. package/dist/projects/settings.js.map +1 -1
  78. package/dist/skills/refs.d.ts +6 -0
  79. package/dist/skills/refs.d.ts.map +1 -1
  80. package/dist/skills/refs.js +8 -0
  81. package/dist/skills/refs.js.map +1 -1
  82. package/dist/skills/runtime.d.ts +17 -2
  83. package/dist/skills/runtime.d.ts.map +1 -1
  84. package/dist/skills/runtime.js +30 -1
  85. package/dist/skills/runtime.js.map +1 -1
  86. package/dist/skills/store.d.ts.map +1 -1
  87. package/dist/skills/store.js +9 -1
  88. package/dist/skills/store.js.map +1 -1
  89. package/dist/standing-orders/parse.d.ts +9 -5
  90. package/dist/standing-orders/parse.d.ts.map +1 -1
  91. package/dist/standing-orders/parse.js +85 -37
  92. package/dist/standing-orders/parse.js.map +1 -1
  93. package/dist/standing-orders/routes.d.ts +21 -21
  94. package/dist/standing-orders/store.d.ts +9 -9
  95. package/dist/studio/assets/{index-B-ce66JW.js → index-CUNMURZ4.js} +122 -122
  96. package/dist/studio/assets/index-DPqNRzrT.css +32 -0
  97. package/dist/studio/editors/wicked-page/editor.json +1 -1
  98. package/dist/studio/editors/wicked-page/index.html +1 -1
  99. package/dist/studio/index.html +2 -2
  100. package/dist/studio/testid-inventory.json +141 -3
  101. package/dist/watch/checks/claim-vs-evidence.js +2 -2
  102. package/dist/watch/checks/claim-vs-evidence.js.map +1 -1
  103. package/dist/watch/checks/deliver-audit.js +2 -2
  104. package/dist/watch/checks/deliver-audit.js.map +1 -1
  105. package/dist/watch/checks/discovery.d.ts.map +1 -1
  106. package/dist/watch/checks/discovery.js +5 -3
  107. package/dist/watch/checks/discovery.js.map +1 -1
  108. package/dist/watch/checks/index.d.ts.map +1 -1
  109. package/dist/watch/checks/index.js +5 -0
  110. package/dist/watch/checks/index.js.map +1 -1
  111. package/dist/watch/checks/quiet-after-claim.js +2 -2
  112. package/dist/watch/checks/quiet-after-claim.js.map +1 -1
  113. package/dist/watch/checks/scope-drift.d.ts.map +1 -1
  114. package/dist/watch/checks/scope-drift.js +4 -3
  115. package/dist/watch/checks/scope-drift.js.map +1 -1
  116. package/dist/watch/checks/team-path.d.ts +38 -0
  117. package/dist/watch/checks/team-path.d.ts.map +1 -0
  118. package/dist/watch/checks/team-path.js +220 -0
  119. package/dist/watch/checks/team-path.js.map +1 -0
  120. package/dist/watch/checks/ungated.js +2 -2
  121. package/dist/watch/checks/ungated.js.map +1 -1
  122. package/dist/watch/checks/warned-rule.js +2 -2
  123. package/dist/watch/checks/warned-rule.js.map +1 -1
  124. package/dist/watch/registry.d.ts +18 -2
  125. package/dist/watch/registry.d.ts.map +1 -1
  126. package/dist/watch/registry.js +23 -4
  127. package/dist/watch/registry.js.map +1 -1
  128. package/dist/watch/types.d.ts +8 -2
  129. package/dist/watch/types.d.ts.map +1 -1
  130. package/endpoint-manifest.json +1 -1
  131. package/package.json +3 -3
  132. package/watch/entries/help-unanswered.json +16 -0
  133. package/watch/entries/path-repicked.json +13 -0
  134. package/watch/entries/reviewer-absent.json +13 -0
  135. package/dist/studio/assets/index-TvS52qj5.css +0 -32
@@ -23,7 +23,7 @@ import { chatSeatAdmission } from './seat-standing.js';
23
23
  import { rosterWithStandingFactory } from './roster-standing.js';
24
24
  import { ChatTurnIndex } from './chat-turns.js';
25
25
  import { ChatScopeIndex, chatScopeDeps, chatScopeStatement, prepareChatScratch, removeChatScratch, namedKindRefusal, resolveChatScope, } from './chat-scope.js';
26
- import { AskPathIndex, answerStep } from './ask-paths.js';
26
+ import { ASK_TURN_BUDGET_SECS, AskPathIndex, answerStep, isAskTurn } from './ask-paths.js';
27
27
  import { allowedRootsFor, isInsideRoot, openWithSystemDefault } from './open-path.js';
28
28
  import { InvalidDiffBaseError, MERGE_BASE_LITERAL, NotARegularFileError, UnresolvableDiffBaseError, branchDiff, isGitStopped, isPlainRef, readFileCapped, worktreeDiff, } from './run-files.js';
29
29
  import { DocRunIndex } from '../interactive/doc-run-index.js';
@@ -52,7 +52,7 @@ import { noEligibleSeatBody, parseNoEligibleSeat } from '../core/engine-roster.j
52
52
  import { ProjectSettingsStore } from '../projects/settings.js';
53
53
  import { isHumanOperator } from './watch-routes.js';
54
54
  import { boundOrigin, InteractiveBridgePool } from '../interactive/bridge-pool.js';
55
- import { composeDeliverText, configuredPublicOrigin, extractFollowUps, factsFromRun, FOLLOW_UPS_MAX, framedDeliverText, runUrlFor, } from '../core/deliver-text.js';
55
+ import { composeDeliverText, configuredPublicOrigin, extractFollowUps, factsFromRun, reportsFromOutputs, FOLLOW_UPS_MAX, framedDeliverText, runUrlFor, } from '../core/deliver-text.js';
56
56
  import { deliverRepoFor, deliverTargetView, isGitHubLogin, readDeliverOriginUrl, resolvePullRequest as resolvePullRequestViaGh, } from '../core/deliver.js';
57
57
  import { registerInteractiveProxy } from '../interactive/proxy-routes.js';
58
58
  import { registerInteractiveDocDelete } from '../interactive/doc-delete-routes.js';
@@ -274,6 +274,8 @@ const RegisterRepoSchema = z
274
274
  /** crew#496 / crew#632: the launch surface and caller, recorded on the run's launch entry. */
275
275
  channel: z.enum(['studio', 'cli', 'api']).optional(),
276
276
  actor: z.string().min(1).max(256).optional(),
277
+ /** crew#552: `false` = this onboarding chains no capture-learnings run (a one-off). */
278
+ autoCapture: z.boolean().optional(),
277
279
  })
278
280
  .strict()
279
281
  .refine((d) => {
@@ -289,6 +291,8 @@ const OnboardBodySchema = z
289
291
  projectId: z.string().min(1).max(128).optional(),
290
292
  channel: z.enum(['studio', 'cli', 'api']).optional(),
291
293
  actor: z.string().min(1).max(256).optional(),
294
+ /** crew#552: `false` = this onboarding chains no capture-learnings run (a one-off). */
295
+ autoCapture: z.boolean().optional(),
292
296
  })
293
297
  .strict();
294
298
  /**
@@ -670,6 +674,15 @@ runtime = {}) {
670
674
  // no def ⇒ `runCanDeliver(view, null)` ⇒ candidate — byte-for-byte today's read.
671
675
  const canDeliver = runtime.canDeliver ??
672
676
  canDeliverResolver(() => (typeof adapter.listWorkflows === 'function' ? adapter.listWorkflows() : []), (m) => app.log.warn(m));
677
+ const deliveryResolver = runtime.deliveryResolver;
678
+ /** crew#851: await the shared resolution for every served view still inside the completion
679
+ * window — BOUNDED (`READ_PATH_SETTLE_MS`): a hung engine read costs this poll the bound, not
680
+ * the request; the run falls through to its stat-derived label until the read lands. */
681
+ const settleDeliveries = async (views) => {
682
+ if (deliveryResolver === undefined)
683
+ return;
684
+ await deliveryResolver.settle(views, runtime.deliverySettleMs);
685
+ };
673
686
  const deliveryCache = runtime.deliveryCache ??
674
687
  new DeliveryDerivationCache({
675
688
  listViews: () => adapter.sessionsDetail(),
@@ -775,6 +788,15 @@ runtime = {}) {
775
788
  }
776
789
  return out.slice(0, FOLLOW_UPS_MAX);
777
790
  };
791
+ /** crew#860: the creator and evaluator seats' final reports (and the recipe exit codes they
792
+ * pasted), read from their captured output for the PR body's done-when evidence. Absent when
793
+ * the adapter keeps no transcripts — the body then says the output could not be read. */
794
+ const seatReports = async (view) => {
795
+ if (typeof adapter.workOutput !== 'function')
796
+ return undefined;
797
+ const read = adapter.workOutput.bind(adapter);
798
+ return reportsFromOutputs(view, (u) => read(coreUnitId(view.session.id, u)));
799
+ };
778
800
  // The run-DTO joins (DES-UX-001 §8.2/§8.3, DES-UX-002 §7.2): `project_id` from the membership
779
801
  // record — `null` = genuinely unfiled, so the field is ALWAYS present on served runs —
780
802
  // `retry_of` from the lineage index and `guidance` from the guidance index, each set only
@@ -910,6 +932,17 @@ runtime = {}) {
910
932
  const chatId = runTimingIndex.chatIdFor(view.session.id);
911
933
  if (chatId !== undefined)
912
934
  view.session.chat_id = chatId;
935
+ // crew#552: the onboarding run a chained capture was launched from — ABSENT otherwise.
936
+ const chainedFrom = runTimingIndex.chainedFromOf(view.session.id);
937
+ if (chainedFrom !== undefined)
938
+ view.session.chained_from = chainedFrom;
939
+ // crew#854: an ask's run, and whether it waits at its TURN gate (answered by the next message,
940
+ // never a gate for the Needs-you count).
941
+ if (runTimingIndex.isAskPath(view.session.id)) {
942
+ view.session.ask_path = true;
943
+ if (isAskTurn(view))
944
+ view.session.ask_turn = true;
945
+ }
913
946
  return view;
914
947
  };
915
948
  // Resolved ONCE and shared by the project routes (which read/write `interactiveRoot`) and the
@@ -1423,6 +1456,7 @@ runtime = {}) {
1423
1456
  recordRunLaunched(audit, runTimingIndex, actorOf(req), runId, {
1424
1457
  workflow: 'onboarding',
1425
1458
  repoRef: repoId,
1459
+ ...(opts.autoCapture === false ? { autoCapture: false } : {}),
1426
1460
  ...(opts.projectId !== undefined ? { projectId: opts.projectId } : {}),
1427
1461
  ...(opts.channel !== undefined ? { channel: opts.channel } : {}),
1428
1462
  ...(opts.actor !== undefined ? { actor: opts.actor } : {}),
@@ -1473,7 +1507,7 @@ runtime = {}) {
1473
1507
  const refused = await onboardRefusal();
1474
1508
  if (refused !== null)
1475
1509
  return reply.code(refused.status).send(refused.body);
1476
- const { name, rootPath, gitUrl, projectId, channel, actor } = parsed.data;
1510
+ const { name, rootPath, gitUrl, projectId, channel, actor, autoCapture } = parsed.data;
1477
1511
  let repo;
1478
1512
  let runId;
1479
1513
  try {
@@ -1496,7 +1530,7 @@ runtime = {}) {
1496
1530
  catch (err) {
1497
1531
  return reply.code(400).send({ error: message(err) });
1498
1532
  }
1499
- const projectAttachError = await recordOnboardingLaunch(req, runId, repo.id, { projectId, channel, actor });
1533
+ const projectAttachError = await recordOnboardingLaunch(req, runId, repo.id, { projectId, channel, actor, autoCapture });
1500
1534
  return reply.code(201).send({ repo, onboardRunId: runId, ...(projectAttachError !== undefined ? { projectAttachError } : {}) });
1501
1535
  });
1502
1536
  // Return the onboarding run id for a repo (so the UI can navigate to it).
@@ -1950,7 +1984,9 @@ runtime = {}) {
1950
1984
  : views.filter((v) => v.session.archived_at == null);
1951
1985
  const visible = doc === undefined ? unarchived : unarchived.filter((v) => docRuns.documentOf(v.session.id) === doc.trim());
1952
1986
  const ordered = sortActionableFirst(visible);
1953
- return { runs: (cap !== undefined ? ordered.slice(0, cap) : ordered).map(decorateRun) };
1987
+ const served = cap !== undefined ? ordered.slice(0, cap) : ordered;
1988
+ await settleDeliveries(served);
1989
+ return { runs: served.map(decorateRun) };
1954
1990
  });
1955
1991
  // ── Run archival (crew#265) — write-off, not delete ────────────────────────
1956
1992
  const ArchiveSchema = z.object({
@@ -2010,6 +2046,7 @@ runtime = {}) {
2010
2046
  const run = views.find((v) => v.session.id === id);
2011
2047
  if (!run)
2012
2048
  return reply.code(404).send({ error: 'Run not found' });
2049
+ await settleDeliveries([run]);
2013
2050
  return { run: decorateRun(run) };
2014
2051
  });
2015
2052
  // ── Deliver text (crew#524 / F-3R2-014) — the PR title + body a run's delivery carries ──
@@ -2027,11 +2064,13 @@ runtime = {}) {
2027
2064
  if (!run)
2028
2065
  return reply.code(404).send({ error: 'Run not found' });
2029
2066
  const followUps = await evaluatorFollowUps(run);
2067
+ const reports = await seatReports(run);
2030
2068
  // The run's preset or workflow NAME, as the engine recorded it (seam X2).
2031
2069
  const text = composeDeliverText(factsFromRun(decorateRun(run), runUrlFor(configuredPublicOrigin(), id), {
2032
2070
  workflowId: runIdentityOf(run).name,
2033
2071
  revisesPr: retryIndex.revisesPrFor(id) ?? null,
2034
2072
  ...(followUps !== undefined ? { followUps } : {}),
2073
+ ...(reports !== undefined ? { reports } : {}),
2035
2074
  }));
2036
2075
  return reply.type('text/plain; charset=utf-8').send(framedDeliverText(text));
2037
2076
  });
@@ -2187,6 +2226,7 @@ runtime = {}) {
2187
2226
  revisesPr = { number: revising.number, headRef: again.pr.headRef, url: again.pr.url };
2188
2227
  }
2189
2228
  const followUps = await evaluatorFollowUps(run);
2229
+ const reports = await seatReports(run);
2190
2230
  result = await deliverExec(workdir, s.problem ?? undefined, {
2191
2231
  runId: id,
2192
2232
  apiOrigin: origin,
@@ -2194,6 +2234,7 @@ runtime = {}) {
2194
2234
  workflowId: workflowName,
2195
2235
  revisesPr: revisesPr === null ? null : { number: revisesPr.number, url: revisesPr.url },
2196
2236
  ...(followUps !== undefined ? { followUps } : {}),
2237
+ ...(reports !== undefined ? { reports } : {}),
2197
2238
  }),
2198
2239
  revisesPr,
2199
2240
  // R1 (Copilot on crew#736): the post-hoc delivery binds `gh pr create --repo` to the
@@ -2436,7 +2477,7 @@ runtime = {}) {
2436
2477
  const admissionKind = scope.kind === 'repos' && scope.repos.length === 1 ? 'path-bound' : 'path';
2437
2478
  const refused = [];
2438
2479
  // The standing roster, read ONCE: the default admission below and the engine-drop
2439
- // attribution after `chatOpen` both consult it (F-A45-011).
2480
+ // attribution after the engine's open both consult it (F-A45-011).
2440
2481
  const standing = rosterWithStanding();
2441
2482
  // ASK-C1: admission applies to the DEFAULT roster and to seats NAMED in `clis` alike — the
2442
2483
  // engine no longer refuses a seat at open (nothing is warmed), so this is the only gate
@@ -2663,7 +2704,8 @@ runtime = {}) {
2663
2704
  ...(path.runId !== undefined ? { runId: path.runId } : {}),
2664
2705
  });
2665
2706
  }
2666
- const turn = chatTurns.begin(id, audience, text);
2707
+ // crew#826: the turn lives as long as its answer step may (`budget_secs`), not 3× the pool knob.
2708
+ const turn = chatTurns.begin(id, audience, text, ASK_TURN_BUDGET_SECS);
2667
2709
  const turnId = turn?.turnId ?? randomUUID();
2668
2710
  // What an ACCEPTED message records — the engine has it, so a persistence failure here is
2669
2711
  // logged, never a retraction of a turn that is already being answered (codex on #808 r2, 9).
@@ -2698,9 +2740,12 @@ runtime = {}) {
2698
2740
  const roster = rosterWithStanding().filter((seat) => path.eligible.includes(String(seat.key)));
2699
2741
  const rules = (await considerations.inForceFor(scope?.projectId ?? null)).rules;
2700
2742
  const pending = await considerations.pendingPreface(id, { inForce: rules });
2701
- const instructions = withPreface(pending.preface);
2743
+ // crew#823: the run is titled by the operator's question (every reader titles a run from
2744
+ // `problem`); the scope statement grounds the seats from the first answer step's
2745
+ // instructions instead of standing in as the run's title.
2746
+ const instructions = scope !== undefined ? `${chatScopeStatement(id, scope, rules)}\n\n${withPreface(pending.preface)}` : withPreface(pending.preface);
2702
2747
  const input = {
2703
- problem: scope !== undefined ? chatScopeStatement(id, scope, rules) : text,
2748
+ problem: text,
2704
2749
  sessionId: randomUUID(),
2705
2750
  clisJson: JSON.stringify(roster),
2706
2751
  plan: { steps: [answerStep(stepId, instructions)], monitors: { asked: 1 } },
@@ -2719,6 +2764,13 @@ runtime = {}) {
2719
2764
  pending.commit();
2720
2765
  persistAccepted(pending.preface, () => {
2721
2766
  runtime.linkChatRun?.(id, runId);
2767
+ if (projectId !== undefined) {
2768
+ // crew#823: the engine attached the crew.run membership with the launch record (as for
2769
+ // POST /runs); this is the post-commit half — the run DTO's `project_id` and the /ws
2770
+ // frames read the index, and the membership event says the run joined the chat's project.
2771
+ projects.index.set(runId, projectId);
2772
+ projects.bus?.emit(MEMBERSHIP_ATTACHED, { project_id: projectId, member: { kind: 'crew.run', ref: runId }, actor: actorOf(req).id }, membershipAttachedKey(projectId, 'crew.run', runId, Date.now()));
2773
+ }
2722
2774
  recordRunLaunched(audit, runTimingIndex, actorOf(req), runId, {
2723
2775
  chatId: id,
2724
2776
  askPath: true,
@@ -4625,6 +4677,23 @@ runtime = {}) {
4625
4677
  if (typeof login === 'string')
4626
4678
  patch.deliverIdentityLogin = login.trim();
4627
4679
  }
4680
+ // deliverIdentityByRepo (crew#737): repo id → a GitHub login, each checked like
4681
+ // deliverIdentityLogin; an entry of '' removes that repo's pin. A typo is a 400 naming the repo.
4682
+ if (Object.hasOwn(patch, 'deliverIdentityByRepo')) {
4683
+ const m = patch.deliverIdentityByRepo;
4684
+ if (m === null || typeof m !== 'object' || Array.isArray(m)) {
4685
+ return reply.code(400).send({ error: 'deliverIdentityByRepo must be an object of repo id → GitHub login' });
4686
+ }
4687
+ const kept = {};
4688
+ for (const [repo, login] of Object.entries(m)) {
4689
+ if (typeof login !== 'string' || (login.trim() !== '' && !isGitHubLogin(login.trim()))) {
4690
+ return reply.code(400).send({ error: `deliverIdentityByRepo.${repo} must be a GitHub login (letters, digits and single hyphens, up to 39 characters), or "" to use the daemon-wide identity` });
4691
+ }
4692
+ if (login.trim() !== '')
4693
+ kept[repo] = login.trim();
4694
+ }
4695
+ patch.deliverIdentityByRepo = kept;
4696
+ }
4628
4697
  // deliverDefault (crew#393): the repo-scoped launch delivery default. Two values only —
4629
4698
  // this knob decides whether completed code runs open PRs, so a typo must be a 400, never
4630
4699
  // a silently-dropped key that leaves the operator believing they flipped it.
@@ -4636,6 +4705,10 @@ runtime = {}) {
4636
4705
  .send({ error: "deliverDefault must be 'pr' or 'none'" });
4637
4706
  }
4638
4707
  }
4708
+ // onboardingAutoCapture (crew#552): whether a completed onboarding chains capture-learnings.
4709
+ if (Object.hasOwn(patch, 'onboardingAutoCapture') && typeof patch.onboardingAutoCapture !== 'boolean') {
4710
+ return reply.code(400).send({ error: 'onboardingAutoCapture must be true or false' });
4711
+ }
4639
4712
  // baseSkillRef / baseSkillPolicy (crew#554 / wicked-core#468): the discipline skill EVERY
4640
4713
  // governed unit is told to follow, and what a snapshot without it means. Both decide whether
4641
4714
  // launches carry the directive or are refused at intake, so a typo is a 400, never a silently
@@ -4716,7 +4789,9 @@ runtime = {}) {
4716
4789
  'workerStallEscalateAction',
4717
4790
  'workerStallMaxEscalations',
4718
4791
  'deliverDefault',
4792
+ 'onboardingAutoCapture',
4719
4793
  'deliverIdentityLogin',
4794
+ 'deliverIdentityByRepo',
4720
4795
  'baseSkillRef',
4721
4796
  'baseSkillPolicy',
4722
4797
  ];