opencode-swarm 7.140.0 → 7.140.2

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 (55) hide show
  1. package/.opencode/skills/commit-pr/SKILL.md +7 -0
  2. package/.opencode/skills/issue-ingest/SKILL.md +32 -1
  3. package/.opencode/skills/swarm-pr-feedback/SKILL.md +21 -5
  4. package/.opencode/skills/swarm-pr-review/SKILL.md +75 -17
  5. package/dist/background/candidate-contract.d.ts +3 -0
  6. package/dist/cli/{config-doctor-3bm8pvnb.js → config-doctor-shc0n4q1.js} +3 -3
  7. package/dist/cli/{core-jtj9j6z5.js → core-p5cyej3r.js} +2 -2
  8. package/dist/cli/{curation-policy-zkvkg4np.js → curation-policy-7n6t8he4.js} +2 -2
  9. package/dist/cli/{curator-llm-factory-kbqtk2wm.js → curator-llm-factory-pr4bdfqw.js} +14 -14
  10. package/dist/cli/{curator-78ejknh2.js → curator-zp5qq24c.js} +14 -14
  11. package/dist/cli/{guardrail-explain-d6emse0z.js → guardrail-explain-thddj6jv.js} +15 -15
  12. package/dist/cli/{guardrail-log-27d6r2xq.js → guardrail-log-w8fmxe1x.js} +4 -4
  13. package/dist/cli/{hive-promoter-r5x4a2ta.js → hive-promoter-0yg3gxg6.js} +14 -14
  14. package/dist/cli/{index-43k8ntw7.js → index-0tevsq1a.js} +2 -2
  15. package/dist/cli/{index-ma8vk3v1.js → index-1j5my2qy.js} +2 -2
  16. package/dist/cli/{index-s38e38qy.js → index-325zmfyj.js} +4 -4
  17. package/dist/cli/{index-scbp2d32.js → index-4rje8dc0.js} +16 -16
  18. package/dist/cli/{index-6yv6wda9.js → index-7ax0bkqh.js} +1 -1
  19. package/dist/cli/{index-hbnsmnb0.js → index-898f16j9.js} +1 -1
  20. package/dist/cli/{index-f8dhh53t.js → index-9txca231.js} +3 -3
  21. package/dist/cli/{index-0pqh1710.js → index-bmmzfrc7.js} +2 -2
  22. package/dist/cli/{index-0jbhmm8b.js → index-chx92bes.js} +2 -2
  23. package/dist/cli/{index-p6h8f59v.js → index-jfp1bgt1.js} +1 -1
  24. package/dist/cli/{index-r4t92q32.js → index-n408rw7n.js} +9 -1
  25. package/dist/cli/{index-mb34t6w9.js → index-n831qq2v.js} +459 -178
  26. package/dist/cli/{index-6ykm2eha.js → index-t59cdg48.js} +1 -1
  27. package/dist/cli/{index-chycpf9y.js → index-vjqe1z2w.js} +3 -3
  28. package/dist/cli/{index-zebb6yw0.js → index-x6mtyj6q.js} +2 -2
  29. package/dist/cli/{index-6d7jkk1s.js → index-y98ngs4g.js} +2 -2
  30. package/dist/cli/{index-bdnrrx3w.js → index-yt3vz0e1.js} +1 -1
  31. package/dist/cli/{index-fxw0zvxc.js → index-z6md3t8m.js} +5 -5
  32. package/dist/cli/index.js +14 -14
  33. package/dist/cli/{knowledge-escalator-qjdfv9sj.js → knowledge-escalator-58tw74js.js} +3 -3
  34. package/dist/cli/{knowledge-events-7szzg70x.js → knowledge-events-knr0pfq4.js} +1 -1
  35. package/dist/cli/{knowledge-store-9mr7a9n8.js → knowledge-store-e2srcd89.js} +1 -1
  36. package/dist/cli/{knowledge-validator-58s2j0dp.js → knowledge-validator-1dtj02q8.js} +4 -4
  37. package/dist/cli/{scan-cursor-h3j1egky.js → scan-cursor-7tqsvbp8.js} +2 -2
  38. package/dist/cli/{schema-9ryqandr.js → schema-b046zss2.js} +2 -2
  39. package/dist/cli/{skill-generator-66ez26yh.js → skill-generator-bvph9d98.js} +5 -5
  40. package/dist/commands/registry.d.ts +1 -1
  41. package/dist/git/pr-workflow-state.d.ts +3 -1
  42. package/dist/hooks/issue-trace-reducer.d.ts +37 -13
  43. package/dist/hooks/issue-trace-state.d.ts +38 -9
  44. package/dist/hooks/issue-trace.d.ts +15 -2
  45. package/dist/hooks/pr-workflow-gate.d.ts +21 -7
  46. package/dist/index.js +117 -107
  47. package/dist/tools/complete-pr-workflow.d.ts +6 -0
  48. package/dist/tools/gh-evidence.d.ts +6 -0
  49. package/dist/tools/index.d.ts +2 -0
  50. package/dist/tools/manifest.d.ts +2 -0
  51. package/dist/tools/prepare-pr-workflow-checkout.d.ts +11 -0
  52. package/dist/tools/record-issue-publication.d.ts +16 -0
  53. package/dist/tools/record-issue-reproduction.d.ts +22 -0
  54. package/dist/tools/tool-metadata.d.ts +8 -0
  55. package/package.json +1 -1
@@ -16,13 +16,13 @@ import {
16
16
  } from "./index-1m1ksyqt.js";
17
17
  import {
18
18
  stableCanonicalStringify
19
- } from "./index-zebb6yw0.js";
19
+ } from "./index-x6mtyj6q.js";
20
20
  import {
21
21
  detectStraySwarmDirs,
22
22
  readDoctorArtifact,
23
23
  removeStraySwarmDir,
24
24
  runConfigDoctor
25
- } from "./index-0jbhmm8b.js";
25
+ } from "./index-chx92bes.js";
26
26
  import {
27
27
  MAX_TRANSIENT_RETRIES,
28
28
  archiveEvidence,
@@ -122,15 +122,15 @@ import {
122
122
  retireSkill,
123
123
  selectCandidateEntries,
124
124
  syncBundledProjectSkillsIfMissingAsync
125
- } from "./index-f8dhh53t.js";
125
+ } from "./index-9txca231.js";
126
126
  import {
127
127
  readRecentEscalations
128
- } from "./index-fxw0zvxc.js";
128
+ } from "./index-z6md3t8m.js";
129
129
  import {
130
130
  alreadyCuratedThisGeneration,
131
131
  claimNextScanBatch,
132
132
  resolveCohortId
133
- } from "./index-bdnrrx3w.js";
133
+ } from "./index-yt3vz0e1.js";
134
134
  import {
135
135
  CURATOR_INIT_PROMPT,
136
136
  CURATOR_PHASE_PROMPT,
@@ -222,11 +222,11 @@ import {
222
222
  validateActionableFields,
223
223
  validateLesson,
224
224
  validateSkillPath
225
- } from "./index-ma8vk3v1.js";
225
+ } from "./index-1j5my2qy.js";
226
226
  import {
227
227
  authorizeCuration,
228
228
  buildConfigFingerprintInput
229
- } from "./index-6ykm2eha.js";
229
+ } from "./index-t59cdg48.js";
230
230
  import {
231
231
  cohortConfigFingerprint
232
232
  } from "./index-rtry5xyf.js";
@@ -259,7 +259,7 @@ import {
259
259
  transactFile,
260
260
  transactKnowledge,
261
261
  wordBigrams
262
- } from "./index-s38e38qy.js";
262
+ } from "./index-325zmfyj.js";
263
263
  import {
264
264
  MAX_EVENT_LOG_ENTRIES,
265
265
  _internals,
@@ -269,7 +269,7 @@ import {
269
269
  recordHiveKnowledgeEvent,
270
270
  recordKnowledgeEvent,
271
271
  resolveKnowledgeEventsPath
272
- } from "./index-p6h8f59v.js";
272
+ } from "./index-jfp1bgt1.js";
273
273
  import {
274
274
  resolveHiveDataDir,
275
275
  resolveHiveEventsPath,
@@ -313,7 +313,7 @@ import {
313
313
  init_loader,
314
314
  loadPluginConfig,
315
315
  loadPluginConfigWithMeta
316
- } from "./index-43k8ntw7.js";
316
+ } from "./index-0tevsq1a.js";
317
317
  import {
318
318
  RetrospectiveEvidenceSchema
319
319
  } from "./index-gprxjmm7.js";
@@ -345,7 +345,7 @@ import {
345
345
  package_default,
346
346
  resolveAutoReviewConfig,
347
347
  stripKnownSwarmPrefix
348
- } from "./index-0pqh1710.js";
348
+ } from "./index-bmmzfrc7.js";
349
349
  import {
350
350
  AGENT_TOOL_MAP,
351
351
  ALL_AGENT_NAMES,
@@ -357,7 +357,7 @@ import {
357
357
  init_agent_names,
358
358
  init_constants,
359
359
  init_tool_metadata
360
- } from "./index-r4t92q32.js";
360
+ } from "./index-n408rw7n.js";
361
361
  import {
362
362
  KNOWLEDGE_SCHEMA_VERSION,
363
363
  OUTCOME_BLOCK_THRESHOLD,
@@ -509,6 +509,122 @@ function removeCandidateCodeFences(text) {
509
509
  return lines.join(`
510
510
  `);
511
511
  }
512
+ function parseClosedMarkdownFences(text) {
513
+ const lines = text.split(/\r?\n/);
514
+ const blocks = [];
515
+ const fencedLineIndexes = new Set;
516
+ let openingLine = -1;
517
+ let openingWidth = 0;
518
+ for (let index = 0;index < lines.length; index += 1) {
519
+ const line = lines[index];
520
+ if (openingLine < 0) {
521
+ const opening = line.match(/^\s{0,3}(`{3,})([^`]*)$/);
522
+ if (!opening)
523
+ continue;
524
+ openingLine = index;
525
+ openingWidth = opening[1].length;
526
+ continue;
527
+ }
528
+ const closing = line.match(/^\s{0,3}(`{3,})\s*$/);
529
+ if (!closing || closing[1].length < openingWidth)
530
+ continue;
531
+ const content = lines.slice(openingLine + 1, index);
532
+ blocks.push({ openingLine, closingLine: index, content });
533
+ for (let fenced = openingLine;fenced <= index; fenced += 1) {
534
+ fencedLineIndexes.add(fenced);
535
+ }
536
+ openingLine = -1;
537
+ openingWidth = 0;
538
+ }
539
+ if (openingLine >= 0)
540
+ return null;
541
+ return {
542
+ lines,
543
+ blocks,
544
+ unfencedLines: lines.filter((_, index) => !fencedLineIndexes.has(index))
545
+ };
546
+ }
547
+ function lineCarriesCandidateProtocol(line) {
548
+ const fields = splitPipeFields(line.trim()).map((field) => field.trim());
549
+ return fields[0] === CANDIDATE_MARKER || fields[0] === CLEAN_MARKER || candidateHeaderFamily(fields) !== null;
550
+ }
551
+ function lastUnescapedPipeIndex(line) {
552
+ let last = -1;
553
+ for (let index = 0;index < line.length; index += 1) {
554
+ if (line[index] === "\\" && line[index + 1] === "|") {
555
+ index += 1;
556
+ continue;
557
+ }
558
+ if (line[index] === "|")
559
+ last = index;
560
+ }
561
+ return last;
562
+ }
563
+ function repairRedundantCleanConfidenceLine(line, family) {
564
+ const fields = splitPipeFields(line).map((field) => field.trim());
565
+ if (fields.length !== CLEAN_FIELD_COUNT + 1 || fields[0] !== CLEAN_MARKER || !isCandidateConfidence(fields[CLEAN_FIELD_COUNT]) || !analyzeCleanFields(fields.slice(0, CLEAN_FIELD_COUNT), family).valid) {
566
+ return { line, repaired: false };
567
+ }
568
+ const delimiter = lastUnescapedPipeIndex(line);
569
+ if (delimiter < 0)
570
+ return { line, repaired: false };
571
+ return { line: line.slice(0, delimiter).trimEnd(), repaired: true };
572
+ }
573
+ function repairRedundantCleanConfidence(text, family) {
574
+ let repaired = false;
575
+ const lines = text.split(/\r?\n/).map((line) => {
576
+ const result = repairRedundantCleanConfidenceLine(line, family);
577
+ repaired ||= result.repaired;
578
+ return result.line;
579
+ });
580
+ return { text: lines.join(`
581
+ `), repaired };
582
+ }
583
+ function isStrictProtocolDataLine(line, family) {
584
+ const clean = repairRedundantCleanConfidenceLine(line, family).line;
585
+ const fields = splitPipeFields(clean).map((field) => field.trim());
586
+ if (fields[0] === CLEAN_MARKER) {
587
+ return analyzeCleanFields(fields, family).valid;
588
+ }
589
+ if (fields[0] === CANDIDATE_MARKER) {
590
+ return analyzeCandidateFields(fields.slice(1), family).valid;
591
+ }
592
+ return analyzeCandidateFields(fields, family).valid;
593
+ }
594
+ function recoverTerminalProtocolFence(rawText, family) {
595
+ const parsed = parseClosedMarkdownFences(rawText);
596
+ if (!parsed || parsed.blocks.length === 0)
597
+ return null;
598
+ if (parsed.unfencedLines.some(lineCarriesCandidateProtocol))
599
+ return null;
600
+ let lastNonblankLine = parsed.lines.length - 1;
601
+ while (lastNonblankLine >= 0 && parsed.lines[lastNonblankLine].trim().length === 0) {
602
+ lastNonblankLine -= 1;
603
+ }
604
+ const terminal = parsed.blocks.at(-1);
605
+ if (terminal.closingLine !== lastNonblankLine)
606
+ return null;
607
+ if (parsed.blocks.slice(0, -1).some((block) => block.content.some(lineCarriesCandidateProtocol))) {
608
+ return null;
609
+ }
610
+ const content = terminal.content.slice().map((line) => line.trimEnd());
611
+ while (content[0]?.trim() === "")
612
+ content.shift();
613
+ while (content.at(-1)?.trim() === "")
614
+ content.pop();
615
+ if (content[0]?.trim() !== CANDIDATE_HEADERS[family])
616
+ return null;
617
+ const rows = content.slice(1).filter((line) => line.trim() !== "");
618
+ if (rows.length === 0 || !rows.every((line) => isStrictProtocolDataLine(line, family))) {
619
+ return null;
620
+ }
621
+ const outside = parsed.unfencedLines.join(`
622
+ `).trimEnd();
623
+ const protocol = content.join(`
624
+ `);
625
+ return outside ? `${outside}
626
+ ${protocol}` : protocol;
627
+ }
512
628
  function declaredCanonicalFamilies(source) {
513
629
  const families = [];
514
630
  for (const line of source.split(/\r?\n/)) {
@@ -519,16 +635,23 @@ function declaredCanonicalFamilies(source) {
519
635
  return families;
520
636
  }
521
637
  function normalizeCandidateArtifact(rawText, fallbackFamily) {
522
- const text = removeCandidateCodeFences(rawText);
638
+ const repairKinds = [];
639
+ const recoveredFence = recoverTerminalProtocolFence(rawText, fallbackFamily);
640
+ if (recoveredFence !== null)
641
+ repairKinds.push("terminal-protocol-fence");
642
+ const cleanRepair = repairRedundantCleanConfidence(recoveredFence ?? removeCandidateCodeFences(rawText), fallbackFamily);
643
+ if (cleanRepair.repaired)
644
+ repairKinds.push("redundant-clean-confidence");
645
+ const text = cleanRepair.text;
523
646
  const header = selectCandidateHeader(text.split(/\r?\n/));
524
647
  if (header?.markerBearing && header.family !== null) {
525
- return { text, synthesizedHeader: false };
648
+ return { text, synthesizedHeader: false, repairKinds };
526
649
  }
527
650
  if (declaredCanonicalFamilies(text).length > 0) {
528
- return { text, synthesizedHeader: false };
651
+ return { text, synthesizedHeader: false, repairKinds };
529
652
  }
530
653
  if (declaredCanonicalFamilies(rawText).some((family) => family !== fallbackFamily)) {
531
- return { text, synthesizedHeader: false };
654
+ return { text, synthesizedHeader: false, repairKinds };
532
655
  }
533
656
  const lines = text.split(/\r?\n/);
534
657
  const hasSalvageableRow = lines.some((line) => {
@@ -540,11 +663,12 @@ function normalizeCandidateArtifact(rawText, fallbackFamily) {
540
663
  return analyzeCleanFields(fields, fallbackFamily).valid;
541
664
  });
542
665
  if (!hasSalvageableRow)
543
- return { text, synthesizedHeader: false };
666
+ return { text, synthesizedHeader: false, repairKinds };
544
667
  const firstSalvageableIndex = lines.findIndex((line) => {
545
668
  const marker = splitPipeFields(line)[0]?.trim();
546
669
  return marker === CANDIDATE_MARKER || marker === CLEAN_MARKER;
547
670
  });
671
+ repairKinds.push("synthesized-header");
548
672
  return {
549
673
  text: [
550
674
  ...lines.slice(0, firstSalvageableIndex),
@@ -552,7 +676,8 @@ function normalizeCandidateArtifact(rawText, fallbackFamily) {
552
676
  ...lines.slice(firstSalvageableIndex)
553
677
  ].join(`
554
678
  `),
555
- synthesizedHeader: true
679
+ synthesizedHeader: true,
680
+ repairKinds
556
681
  };
557
682
  }
558
683
  function analyzeCandidateLaneContext(context) {
@@ -1914,7 +2039,7 @@ async function activeOperations(gitDir) {
1914
2039
  }));
1915
2040
  return checks.filter((value) => value !== null);
1916
2041
  }
1917
- async function classifyPrWorkflowGitState(directory) {
2042
+ async function classifyPrWorkflowGitState(directory, options = {}) {
1918
2043
  const [statusResult, rootResult, gitDirResult] = await Promise.all([
1919
2044
  _internals4.runGitCapture(directory, [
1920
2045
  "status",
@@ -1937,16 +2062,25 @@ async function classifyPrWorkflowGitState(directory) {
1937
2062
  }
1938
2063
  const worktreeRoot = rootResult.stdout.trim();
1939
2064
  const gitDir = gitDirResult.stdout.trim();
1940
- const parsed = parsePorcelainV2Snapshot(statusResult.stdout);
1941
- if (!worktreeRoot || !gitDir || !parsed) {
2065
+ const parsedRaw = parsePorcelainV2Snapshot(statusResult.stdout);
2066
+ if (!worktreeRoot || !gitDir || !parsedRaw) {
1942
2067
  return {
1943
2068
  kind: "indeterminate",
1944
2069
  code: "GIT_STATE_INDETERMINATE",
1945
2070
  retryable: false,
1946
2071
  requiredAction: "Resolve the malformed or incomplete Git status before starting the PR workflow.",
1947
- evidence: evidence(parsed, worktreeRoot || null, gitDir || null, [], "Git returned an incomplete status snapshot")
2072
+ evidence: evidence(parsedRaw, worktreeRoot || null, gitDir || null, [], "Git returned an incomplete status snapshot")
1948
2073
  };
1949
2074
  }
2075
+ const ignoredPaths = new Set((options.ignoredPaths ?? []).map((value) => value.replace(/\\/g, "/")));
2076
+ const keepPath = (value) => !ignoredPaths.has(value.replace(/\\/g, "/"));
2077
+ const parsed = {
2078
+ ...parsedRaw,
2079
+ dirtyTrackedPaths: parsedRaw.dirtyTrackedPaths.filter(keepPath),
2080
+ untrackedPaths: parsedRaw.untrackedPaths.filter(keepPath),
2081
+ unmergedPaths: parsedRaw.unmergedPaths.filter(keepPath),
2082
+ dirtySubmodulePaths: parsedRaw.dirtySubmodulePaths.filter(keepPath)
2083
+ };
1950
2084
  let operations;
1951
2085
  try {
1952
2086
  operations = await _internals4.activeOperations(path2.resolve(gitDir));
@@ -2059,6 +2193,7 @@ var STATE_MUTATION_LOCK_RETRY_DELAY_MS = 10;
2059
2193
  var STATE_MUTATION_LOCK_UNINITIALIZED_STALE_MS = 30000;
2060
2194
  var MAX_COMPLETED_CHECKOUT_LOCK_OWNERS = 64;
2061
2195
  var CHECKOUT_MUTATION_ACTION_TIMEOUT_MS = 5 * 60000;
2196
+ var MAX_PR_WORKFLOW_GATE_DIRECTORY_ENTRIES = MAX_TRACKED_SESSIONS * 2 + 1;
2062
2197
  var MAX_CANDIDATE_ISSUES_PER_ARTIFACT = 8;
2063
2198
  var MAX_BASE_COVERAGE_DIAGNOSTICS = 8;
2064
2199
  var MAX_BASE_COVERAGE_DIAGNOSTIC_CHARS = 1000;
@@ -2257,57 +2392,66 @@ function formatCheckoutRecoveryBlock(state) {
2257
2392
  }
2258
2393
  async function activatePrWorkflow(directory, sessionID, mode, options = {}) {
2259
2394
  const normalizedSessionID = normalizeSessionID(sessionID);
2260
- const existing = await readPrWorkflowGateState(directory, normalizedSessionID);
2261
- if (existing?.mode === mode) {
2262
- let activeState = existing;
2263
- if (mode === "PR_FEEDBACK" && options.prUrl) {
2264
- const requestedTarget = canonicalGitHubPrUrl(options.prUrl);
2265
- if (!requestedTarget) {
2266
- throw new Error("BLOCKED: PR_FEEDBACK target must be a canonical GitHub PR URL");
2267
- }
2268
- const existingTarget = existing.prFeedbackTargetUrl ? canonicalGitHubPrUrl(existing.prFeedbackTargetUrl) : null;
2269
- if (existingTarget && requestedTarget !== existingTarget) {
2270
- throw new Error(`BLOCKED: active PR_FEEDBACK targets a different GitHub pull request`);
2271
- }
2272
- if (!existingTarget) {
2273
- activeState = {
2274
- ...existing,
2275
- prFeedbackTargetUrl: options.prUrl,
2276
- updatedAt: isoNow()
2277
- };
2278
- await persistState(directory, activeState);
2395
+ const initiallyActive = await readPrWorkflowGateState(directory, normalizedSessionID);
2396
+ const checkoutPreflight = !initiallyActive && options.requireCheckoutPreflight ? await _test_exports.classifyPrWorkflowGitStateAsync(directory) : null;
2397
+ return withPrWorkflowCheckoutMutationLock(directory, async () => withSessionStateMutation(directory, normalizedSessionID, async () => {
2398
+ let existing = await readPrWorkflowGateStateFromDisk(directory, normalizedSessionID);
2399
+ if (existing?.mode === mode) {
2400
+ if (mode === "PR_FEEDBACK" && options.prUrl) {
2401
+ const requestedTarget = canonicalGitHubPrUrl(options.prUrl);
2402
+ if (!requestedTarget) {
2403
+ throw new Error("BLOCKED: PR_FEEDBACK target must be a canonical GitHub PR URL");
2404
+ }
2405
+ const existingTarget = existing.prFeedbackTargetUrl ? canonicalGitHubPrUrl(existing.prFeedbackTargetUrl) : null;
2406
+ if (existingTarget && requestedTarget !== existingTarget) {
2407
+ throw new Error(`BLOCKED: active PR_FEEDBACK targets a different GitHub pull request`);
2408
+ }
2409
+ if (!existingTarget) {
2410
+ existing = await writeStateWhileLocked(directory, {
2411
+ ...existing,
2412
+ prFeedbackTargetUrl: options.prUrl,
2413
+ updatedAt: isoNow()
2414
+ });
2415
+ }
2279
2416
  }
2417
+ return options.prHeadSha ? bindPrWorkflowHeadWhileLocked(directory, normalizedSessionID, options.prHeadSha) : existing;
2280
2418
  }
2281
- return options.prHeadSha ? bindPrWorkflowHead(directory, normalizedSessionID, options.prHeadSha) : activeState;
2282
- }
2283
- if (existing) {
2284
- throw new Error(`BLOCKED: session "${normalizedSessionID}" already has an active ${existing.mode} workflow; complete it before starting ${mode}`);
2285
- }
2286
- if (options.requireCheckoutPreflight) {
2287
- const checkoutState = await classifyPrWorkflowGitState(directory);
2288
- if (checkoutState.kind === "recovery-required" || checkoutState.kind === "indeterminate") {
2289
- throw new Error(formatCheckoutRecoveryBlock(checkoutState));
2419
+ if (existing) {
2420
+ throw new Error(`BLOCKED: session "${normalizedSessionID}" already has an active ${existing.mode} workflow; complete it before starting ${mode}`);
2290
2421
  }
2291
- }
2292
- const initialHead = options.prHeadSha ? await assertCurrentCheckoutHead(directory, options.prHeadSha, mode) : undefined;
2293
- const timestamp = isoNow();
2294
- const feedbackTargetUrl = mode === "PR_FEEDBACK" && options.prUrl ? canonicalGitHubPrUrl(options.prUrl) ? options.prUrl : null : undefined;
2295
- if (feedbackTargetUrl === null) {
2296
- throw new Error("BLOCKED: PR_FEEDBACK target must be a canonical GitHub PR URL");
2297
- }
2298
- const nextState = {
2299
- schemaVersion: GATE_SCHEMA_VERSION,
2300
- revision: 0,
2301
- workflowInstanceId: randomUUID(),
2302
- sessionID: normalizedSessionID,
2303
- mode,
2304
- activatedAt: timestamp,
2305
- updatedAt: timestamp,
2306
- ...initialHead ? { prHeadSha: initialHead } : {},
2307
- ...feedbackTargetUrl ? { prFeedbackTargetUrl: feedbackTargetUrl } : {}
2308
- };
2309
- await persistState(directory, nextState);
2310
- return nextState;
2422
+ if (options.requireCheckoutPreflight) {
2423
+ if (!checkoutPreflight) {
2424
+ throw new Error("BLOCKED: PR workflow gate state changed during checkout preflight; retry activation against the current project state");
2425
+ }
2426
+ const lockedCheckoutPreflight = await _test_exports.classifyPrWorkflowGitStateAsync(directory, {
2427
+ ignoredPaths: [
2428
+ path3.join(".swarm", workflowCheckoutMutationLockRelativePath()),
2429
+ path3.join(".swarm", workflowGateStateLockRelativePath(normalizedSessionID))
2430
+ ]
2431
+ });
2432
+ if (lockedCheckoutPreflight.kind === "recovery-required" || lockedCheckoutPreflight.kind === "indeterminate") {
2433
+ throw new Error(formatCheckoutRecoveryBlock(lockedCheckoutPreflight));
2434
+ }
2435
+ }
2436
+ const initialHead = options.prHeadSha ? await assertCurrentCheckoutHead(directory, options.prHeadSha, mode) : undefined;
2437
+ const timestamp = isoNow();
2438
+ const feedbackTargetUrl = mode === "PR_FEEDBACK" && options.prUrl ? canonicalGitHubPrUrl(options.prUrl) ? options.prUrl : null : undefined;
2439
+ if (feedbackTargetUrl === null) {
2440
+ throw new Error("BLOCKED: PR_FEEDBACK target must be a canonical GitHub PR URL");
2441
+ }
2442
+ const nextState = {
2443
+ schemaVersion: GATE_SCHEMA_VERSION,
2444
+ revision: 0,
2445
+ workflowInstanceId: randomUUID(),
2446
+ sessionID: normalizedSessionID,
2447
+ mode,
2448
+ activatedAt: timestamp,
2449
+ updatedAt: timestamp,
2450
+ ...initialHead ? { prHeadSha: initialHead } : {},
2451
+ ...feedbackTargetUrl ? { prFeedbackTargetUrl: feedbackTargetUrl } : {}
2452
+ };
2453
+ return writeStateWhileLocked(directory, nextState);
2454
+ }));
2311
2455
  }
2312
2456
  async function readPrWorkflowGateState(directory, sessionID) {
2313
2457
  const normalizedSessionID = normalizeSessionID(sessionID);
@@ -2340,7 +2484,7 @@ async function clearPrWorkflowGateState(directory, sessionID, expectedRevision)
2340
2484
  trackedStatesByProjectSession.delete(stateCacheKey(directory, normalizedSessionID));
2341
2485
  });
2342
2486
  }
2343
- async function abortPrWorkflow(directory, sessionID, options = {}) {
2487
+ async function abortPrWorkflow(directory, sessionID, options = { kind: "recovery", reason: "" }) {
2344
2488
  const state = await requireAnyActiveState(directory, sessionID);
2345
2489
  if (options.expectedMode && state.mode !== options.expectedMode) {
2346
2490
  throw wrongModeError(state, options.expectedMode);
@@ -2353,15 +2497,20 @@ async function abortPrWorkflow(directory, sessionID, options = {}) {
2353
2497
  const laneIds = openLanes.map((record) => record.laneId ?? record.subagentSessionId ?? "unknown").filter(Boolean).slice(0, 10).join(", ");
2354
2498
  throw new Error(`BLOCKED: ${state.mode} abort refused while ${openLanes.length} PR workflow lane(s) are still in flight (lane ids: ${laneIds}). Collect their results or let them settle before aborting.`);
2355
2499
  }
2356
- const sanitizedReason = typeof options.reason === "string" ? options.reason.trim().slice(0, 500) : undefined;
2500
+ const sanitizedReason = typeof options.reason === "string" && options.reason.trim().length > 0 ? options.reason.trim().slice(0, 500) : undefined;
2501
+ if (sanitizedReason === undefined) {
2502
+ throw new Error(`BLOCKED: ${state.mode} abort requires a non-empty reason (recorded to the audit trail).`);
2503
+ }
2357
2504
  const abortEvent = {
2358
2505
  type: "pr_workflow_aborted",
2359
2506
  timestamp: isoNow(),
2360
2507
  sessionID: state.sessionID,
2361
2508
  mode: state.mode,
2509
+ kind: options.kind,
2362
2510
  ...state.prHeadSha ? { prHeadSha: state.prHeadSha } : {},
2511
+ ...state.checkoutRecovery ? { hadTerminalRecovery: true } : {},
2363
2512
  openLanes: openLanes.length,
2364
- ...sanitizedReason ? { reason: sanitizedReason } : {}
2513
+ reason: sanitizedReason
2365
2514
  };
2366
2515
  try {
2367
2516
  const eventsPath = validateSwarmPath(directory, "events.jsonl");
@@ -2375,46 +2524,45 @@ async function abortPrWorkflow(directory, sessionID, options = {}) {
2375
2524
  openLanes: openLanes.length
2376
2525
  };
2377
2526
  }
2378
- async function bindPrWorkflowHead(directory, sessionID, prHeadSha) {
2527
+ async function bindPrWorkflowHeadWhileLocked(directory, normalizedSessionID, prHeadSha) {
2379
2528
  const normalizedHead = normalizePrHeadSha(prHeadSha);
2380
- return withPrWorkflowCheckoutMutationLock(directory, async () => withSessionStateMutation(directory, normalizeSessionID(sessionID), async () => {
2381
- const state = await readPrWorkflowGateStateFromDisk(directory, normalizeSessionID(sessionID));
2382
- if (!state) {
2383
- throw new Error(`BLOCKED: no active PR workflow gate for session "${normalizeSessionID(sessionID)}". ` + "The gate must be activated before the PR head can be bound.");
2384
- }
2385
- await assertCurrentCheckoutHead(directory, normalizedHead, state.mode);
2386
- if (state.prHeadSha && state.prHeadSha !== normalizedHead) {
2387
- throw new Error(`BLOCKED: active ${state.mode} workflow is bound to PR head "${state.prHeadSha}"; received "${normalizedHead}"`);
2388
- }
2389
- if (!state.prHeadSha) {
2390
- await assertPrReviewCleanCheckout(directory, state.mode);
2391
- }
2392
- if (!state.prHeadSha && state.mode === "PR_FEEDBACK") {
2529
+ const state = await readPrWorkflowGateStateFromDisk(directory, normalizedSessionID);
2530
+ if (!state) {
2531
+ throw new Error(`BLOCKED: no active PR workflow gate for session "${normalizedSessionID}". ` + "The gate must be activated before the PR head can be bound.");
2532
+ }
2533
+ await assertCurrentCheckoutHead(directory, normalizedHead, state.mode);
2534
+ if (state.prHeadSha && state.prHeadSha !== normalizedHead) {
2535
+ throw new Error(`BLOCKED: active ${state.mode} workflow is bound to PR head "${state.prHeadSha}"; received "${normalizedHead}"`);
2536
+ }
2537
+ if (!state.prHeadSha) {
2538
+ await assertPrReviewCleanCheckout(directory, state.mode);
2539
+ }
2540
+ if (!state.prHeadSha && state.mode === "PR_FEEDBACK") {
2541
+ await assertPrFeedbackTrackingCheckout(directory, normalizedHead);
2542
+ }
2543
+ if (state.prHeadSha === normalizedHead) {
2544
+ if (state.mode === "PR_FEEDBACK") {
2393
2545
  await assertPrFeedbackTrackingCheckout(directory, normalizedHead);
2394
2546
  }
2395
- if (state.prHeadSha === normalizedHead) {
2396
- if (state.mode === "PR_FEEDBACK") {
2397
- await assertPrFeedbackTrackingCheckout(directory, normalizedHead);
2398
- }
2399
- return state;
2400
- }
2401
- const nextState = {
2402
- ...state,
2403
- prHeadSha: normalizedHead,
2404
- updatedAt: isoNow()
2405
- };
2406
- await _test_exports.beforePrFeedbackTrackingPersist?.();
2407
- return writeStateWhileLocked(directory, nextState);
2408
- }));
2547
+ return state;
2548
+ }
2549
+ const nextState = {
2550
+ ...state,
2551
+ prHeadSha: normalizedHead,
2552
+ checkoutRecovery: undefined,
2553
+ updatedAt: isoNow()
2554
+ };
2555
+ await _test_exports.beforePrFeedbackTrackingPersist?.();
2556
+ return writeStateWhileLocked(directory, nextState);
2409
2557
  }
2410
2558
  async function assertCurrentCheckoutHead(directory, expectedHead, mode = "PR_REVIEW") {
2411
2559
  const normalizedExpected = normalizePrHeadSha(expectedHead);
2412
2560
  const currentHead = (await _test_exports.resolveCurrentGitHeadAsync(directory))?.trim();
2413
2561
  if (!currentHead) {
2414
- throw new Error(`BLOCKED: cannot resolve the current Git HEAD in "${directory}" to verify against PR head "${normalizedExpected}". ` + `This means Git could not resolve HEAD (the working directory may not be a Git repository, ` + `HEAD may be unborn, the commit object may be missing in a shallow clone, the Git binary may not be on PATH, ` + `or the bounded Git invocation may have timed out). ` + `Verify with: git -C "${directory}" rev-parse --verify HEAD^{commit}`);
2562
+ throw new Error(`BLOCKED: cannot resolve the current Git HEAD in "${directory}" to verify against PR head "${normalizedExpected}". ` + `This means Git could not resolve HEAD (the working directory may not be a Git repository, ` + `HEAD may be unborn, the commit object may be missing in a shallow clone, the Git binary may not be on PATH, ` + `or the bounded Git invocation may have timed out). ` + `Verify with two separate standalone commands. First run: git -C "${directory}" rev-parse --verify HEAD^0. Then run: git -C "${directory}" cat-file -t HEAD (which must print commit).`);
2415
2563
  }
2416
2564
  if (currentHead.toLowerCase() !== normalizedExpected.toLowerCase()) {
2417
- const remediation = mode === "PR_FEEDBACK" ? "Check out the intended local tracking branch at that exact head (for example with a safe standalone `gh pr checkout <number-or-url>` or `git switch -c <local> --track <remote>/<branch>`), then retry the bind. Do not create a new detached feedback checkout." : `Run these bare, standalone commands from that directory: if the commit is not present locally, \`git fetch origin <pr-head-ref>\`; then \`git switch --detach ${normalizedExpected}\`. Do not prefix the switch with \`git -C\`; the read-only shell classifier refuses \`git -C ... switch\`.`;
2565
+ const remediation = mode === "PR_FEEDBACK" ? "Check out the intended local tracking branch at that exact head (for example with a safe standalone `gh pr checkout <number-or-url>` or `git switch -c <local> --track <remote>/<branch>`), then retry the bind. Do not create a new detached feedback checkout." : `Run bare, standalone commands from that directory. If the commit is not present locally, first run \`git fetch origin <pr-head-ref>\`. Then run \`git switch --detach ${normalizedExpected}\`. Do not prefix the switch with \`git -C\`; the read-only shell classifier refuses \`git -C ... switch\`.`;
2418
2566
  throw new Error(`BLOCKED: current checkout HEAD "${currentHead}" does not match PR head "${normalizedExpected}" ` + `(working directory: "${directory}"). ${remediation}`);
2419
2567
  }
2420
2568
  return normalizedExpected;
@@ -2936,6 +3084,7 @@ var _test_exports = {
2936
3084
  _test_exports.openCheckoutLock = openCheckoutLockFile;
2937
3085
  _test_exports.removeCheckoutLock = removeCheckoutLockFile;
2938
3086
  _test_exports.checkoutMutationActionTimeoutMs = CHECKOUT_MUTATION_ACTION_TIMEOUT_MS;
3087
+ _test_exports.classifyPrWorkflowGitStateAsync = classifyPrWorkflowGitState;
2939
3088
  },
2940
3089
  beforeTerminalClear: undefined,
2941
3090
  beforePrFeedbackTransitionLock: undefined,
@@ -2951,6 +3100,7 @@ var _test_exports = {
2951
3100
  openCheckoutLock: openCheckoutLockFile,
2952
3101
  removeCheckoutLock: removeCheckoutLockFile,
2953
3102
  checkoutMutationActionTimeoutMs: CHECKOUT_MUTATION_ACTION_TIMEOUT_MS,
3103
+ classifyPrWorkflowGitStateAsync: classifyPrWorkflowGitState,
2954
3104
  resolveCurrentGitHead,
2955
3105
  resolveCurrentGitHeadAsync,
2956
3106
  resolveCurrentUpstreamPushTarget,
@@ -3063,8 +3213,128 @@ var PR_REVIEW_READ_ONLY_TOOL_NAMES = new Set([
3063
3213
  "webfetch",
3064
3214
  "websearch"
3065
3215
  ]);
3216
+ var PR_WORKFLOW_CD_PREFIX_PATTERN = /^cd\s+(?:\/d\s+)?(?:"[A-Za-z0-9 _.\\/:~+-]+"|'[A-Za-z0-9 _.\\/:~+-]+'|[A-Za-z0-9_.\\/:~+-]+)\s*&&\s*/i;
3217
+ var PR_WORKFLOW_TRAILING_STDERR_MERGE_PATTERN = /\s+2>&1\s*$/;
3218
+ var PR_WORKFLOW_MAX_CD_PREFIX_STRIPS = 3;
3219
+ function normalizePrWorkflowShellCommand(command) {
3220
+ let normalized = command.trim();
3221
+ const withoutTrailingMerge = normalized.replace(PR_WORKFLOW_TRAILING_STDERR_MERGE_PATTERN, "");
3222
+ if (withoutTrailingMerge !== normalized)
3223
+ normalized = withoutTrailingMerge.trim();
3224
+ let strippedCdPrefix = false;
3225
+ for (let index = 0;index < PR_WORKFLOW_MAX_CD_PREFIX_STRIPS; index += 1) {
3226
+ const next = normalized.replace(PR_WORKFLOW_CD_PREFIX_PATTERN, "");
3227
+ if (next === normalized)
3228
+ break;
3229
+ normalized = next.trim();
3230
+ strippedCdPrefix = true;
3231
+ }
3232
+ return { normalized, strippedCdPrefix };
3233
+ }
3234
+ function classifyPrWorkflowShellSyntax(command) {
3235
+ const { normalized: inner } = normalizePrWorkflowShellCommand(command);
3236
+ const compact = inner.replace(/\s+/g, " ").trim();
3237
+ if (!compact)
3238
+ return { unsafe: true, reason: "compound-syntax" };
3239
+ if (/\$\(|@\(/.test(inner)) {
3240
+ return { unsafe: true, reason: "command-substitution" };
3241
+ }
3242
+ if (/\\"|\^"/.test(inner)) {
3243
+ return { unsafe: true, reason: "ambiguous-escaped-quote" };
3244
+ }
3245
+ if (/[\r\n;&<>`]/.test(inner)) {
3246
+ return { unsafe: true, reason: "compound-syntax" };
3247
+ }
3248
+ const tokens = [];
3249
+ let current = "";
3250
+ let quoted = false;
3251
+ let quote = null;
3252
+ let pipeIsDoubleQuoted = true;
3253
+ const flush = () => {
3254
+ if (current.length > 0 || quoted) {
3255
+ tokens.push({ value: current, quoted, pipeIsDoubleQuoted });
3256
+ }
3257
+ current = "";
3258
+ quoted = false;
3259
+ quote = null;
3260
+ pipeIsDoubleQuoted = true;
3261
+ };
3262
+ for (let index = 0;index < inner.length; index += 1) {
3263
+ const ch = inner[index];
3264
+ if (quote === null) {
3265
+ if (/\s/.test(ch)) {
3266
+ flush();
3267
+ continue;
3268
+ }
3269
+ if (ch === "'" || ch === '"') {
3270
+ quoted = true;
3271
+ quote = ch;
3272
+ continue;
3273
+ }
3274
+ if (ch === ";" || ch === "<" || ch === ">" || ch === "`" || ch === "|") {
3275
+ return { unsafe: true, reason: "compound-syntax" };
3276
+ }
3277
+ if (ch === "$" && inner[index + 1] === "(") {
3278
+ return { unsafe: true, reason: "command-substitution" };
3279
+ }
3280
+ if (ch === "@" && inner[index + 1] === "(") {
3281
+ return { unsafe: true, reason: "command-substitution" };
3282
+ }
3283
+ current += ch;
3284
+ continue;
3285
+ }
3286
+ if (quote === "'") {
3287
+ if (ch === "'") {
3288
+ quote = null;
3289
+ continue;
3290
+ }
3291
+ if (ch === "|")
3292
+ pipeIsDoubleQuoted = false;
3293
+ current += ch;
3294
+ continue;
3295
+ }
3296
+ if (ch === '"') {
3297
+ quote = null;
3298
+ continue;
3299
+ }
3300
+ if (ch === "\\" && inner[index + 1] === '"') {
3301
+ return { unsafe: true, reason: "ambiguous-escaped-quote" };
3302
+ }
3303
+ if (ch === "`") {
3304
+ return { unsafe: true, reason: "command-substitution" };
3305
+ }
3306
+ if (ch === "$" && inner[index + 1] === "(") {
3307
+ return { unsafe: true, reason: "command-substitution" };
3308
+ }
3309
+ if (ch === "@" && inner[index + 1] === "(") {
3310
+ return { unsafe: true, reason: "command-substitution" };
3311
+ }
3312
+ current += ch;
3313
+ }
3314
+ if (quote !== null) {
3315
+ return { unsafe: true, reason: "unmatched-quote" };
3316
+ }
3317
+ flush();
3318
+ const pipeTokens = tokens.filter((token) => token.value.includes("|"));
3319
+ if (pipeTokens.length === 0)
3320
+ return { unsafe: false, reason: null };
3321
+ if (pipeTokens.length !== 1 || !pipeTokens[0].quoted || !pipeTokens[0].pipeIsDoubleQuoted) {
3322
+ return { unsafe: true, reason: "gh-api-jq-pipe" };
3323
+ }
3324
+ const quotedPipeToken = pipeTokens[0];
3325
+ if (!/^gh\s+api(?:\s|$)/i.test(compact)) {
3326
+ return { unsafe: true, reason: "gh-api-jq-pipe" };
3327
+ }
3328
+ for (let index = 0;index < tokens.length; index += 1) {
3329
+ const token = tokens[index];
3330
+ if (token.value === "--jq" && tokens[index + 1] === quotedPipeToken) {
3331
+ return { unsafe: false, reason: null };
3332
+ }
3333
+ }
3334
+ return { unsafe: true, reason: "gh-api-jq-pipe" };
3335
+ }
3066
3336
  function hasUnsafeShellControlSyntax(command) {
3067
- return /[;&|<>`\r\n]/.test(command) || /\$\(/.test(command);
3337
+ return classifyPrWorkflowShellSyntax(command).unsafe;
3068
3338
  }
3069
3339
  function isSafeStandaloneGitCommit(command) {
3070
3340
  if (hasUnsafeShellControlSyntax(command))
@@ -3841,7 +4111,8 @@ function analyzePrReviewDiscoveryArtifact(text, expectedWorkflowLane, ownedWorkf
3841
4111
  const issues = [];
3842
4112
  const normalized = normalizeCandidateArtifact(text, fallbackFamily);
3843
4113
  const canonicalText = normalized.text;
3844
- const salvaged = normalized.synthesizedHeader;
4114
+ const repairKinds = normalized.repairKinds;
4115
+ const salvaged = repairKinds.length > 0;
3845
4116
  const header = selectCandidateHeader(canonicalText.split(/\r?\n/));
3846
4117
  if (header === null || !header.markerBearing || header.family !== fallbackFamily) {
3847
4118
  appendBoundedCandidateIssue(issues, `field header: expected one exact canonical ${fallbackFamily} [CANDIDATE] header before discovery rows`);
@@ -3850,6 +4121,7 @@ function analyzePrReviewDiscoveryArtifact(text, expectedWorkflowLane, ownedWorkf
3850
4121
  evidence: null,
3851
4122
  issues,
3852
4123
  salvaged,
4124
+ repairKinds,
3853
4125
  failurePredicate: "discovery.header"
3854
4126
  };
3855
4127
  }
@@ -3893,6 +4165,7 @@ function analyzePrReviewDiscoveryArtifact(text, expectedWorkflowLane, ownedWorkf
3893
4165
  evidence: null,
3894
4166
  issues,
3895
4167
  salvaged,
4168
+ repairKinds,
3896
4169
  failurePredicate: "discovery.row"
3897
4170
  };
3898
4171
  }
@@ -3910,7 +4183,8 @@ function analyzePrReviewDiscoveryArtifact(text, expectedWorkflowLane, ownedWorkf
3910
4183
  matchingCandidate.confidence
3911
4184
  ].join("\x00"),
3912
4185
  issues,
3913
- salvaged
4186
+ salvaged,
4187
+ repairKinds
3914
4188
  };
3915
4189
  }
3916
4190
  const clean = parsed.clean_attestation;
@@ -3919,7 +4193,8 @@ function analyzePrReviewDiscoveryArtifact(text, expectedWorkflowLane, ownedWorkf
3919
4193
  covered: true,
3920
4194
  evidence: `${clean.coverage_scope}\x00${clean.evidence}`,
3921
4195
  issues,
3922
- salvaged
4196
+ salvaged,
4197
+ repairKinds
3923
4198
  };
3924
4199
  }
3925
4200
  return {
@@ -3927,6 +4202,7 @@ function analyzePrReviewDiscoveryArtifact(text, expectedWorkflowLane, ownedWorkf
3927
4202
  evidence: null,
3928
4203
  issues,
3929
4204
  salvaged,
4205
+ repairKinds,
3930
4206
  failurePredicate: hasParseFailure ? "discovery.row" : "discovery.coverage"
3931
4207
  };
3932
4208
  }
@@ -3974,7 +4250,7 @@ function validatePrReviewDiscoveryLaneCompletion(input) {
3974
4250
  if (!analysis.salvaged && analysis.issues.length === 0)
3975
4251
  continue;
3976
4252
  salvagedLanes.push(workflowLane);
3977
- const reason = analysis.salvaged ? `canonical ${resolvePrReviewRowFamily(workflowLane, input.expected.mode)} header was absent and was synthesized from valid marker rows` : "one or more rows were dropped as malformed or out-of-ownership";
4253
+ const reason = analysis.repairKinds.length > 0 ? `structural repairs applied: ${analysis.repairKinds.join(", ")}` : "one or more rows were dropped as malformed or out-of-ownership";
3978
4254
  warn(`[pr-workflow-gate] discovery artifact salvaged: batch=${input.record.batchId ?? "(missing)"} lane=${input.record.laneId ?? "(missing)"} workflow_lane=${workflowLane} \u2014 ${reason}; retained coverage from the valid rows. Dropped-row diagnostics: ${analysis.issues.join("; ") || "(none)"}`);
3979
4255
  }
3980
4256
  const failedCoverage = analyses.find(({ analysis }) => !analysis.covered);
@@ -4122,13 +4398,6 @@ function pipeFields(line) {
4122
4398
  return [];
4123
4399
  return line.split("|").map((field) => field.trim());
4124
4400
  }
4125
- async function persistState(directory, state) {
4126
- const validated = PrWorkflowGateStateSchema.parse(state);
4127
- await withSessionStateMutation(directory, validated.sessionID, async () => {
4128
- const nextState = await writeStateWhileLocked(directory, validated);
4129
- Object.assign(state, nextState);
4130
- });
4131
- }
4132
4401
  async function writeStateWhileLocked(directory, state, options = {}) {
4133
4402
  const validated = PrWorkflowGateStateSchema.parse(state);
4134
4403
  const current = await readPrWorkflowGateStateFromDisk(directory, validated.sessionID);
@@ -4528,6 +4797,9 @@ function isProcessAlive(pid) {
4528
4797
  }
4529
4798
  async function readPrWorkflowGateStateFromDisk(directory, sessionID) {
4530
4799
  const filePath = workflowGateStatePath(directory, sessionID);
4800
+ return readPrWorkflowGateStateFileFromDisk(filePath, sessionID);
4801
+ }
4802
+ async function readPrWorkflowGateStateFileFromDisk(filePath, stateLabel) {
4531
4803
  let raw;
4532
4804
  try {
4533
4805
  raw = await fsp2.readFile(filePath, "utf-8");
@@ -4540,11 +4812,11 @@ async function readPrWorkflowGateStateFromDisk(directory, sessionID) {
4540
4812
  try {
4541
4813
  parsedJson = JSON.parse(raw);
4542
4814
  } catch {
4543
- throw new Error(`BLOCKED: PR workflow gate state for session "${sessionID}" is not valid JSON`);
4815
+ throw new Error(`BLOCKED: PR workflow gate state for session "${stateLabel}" is not valid JSON`);
4544
4816
  }
4545
4817
  const parsed = PrWorkflowGateStateSchema.safeParse(parsedJson);
4546
4818
  if (!parsed.success) {
4547
- throw new Error(`BLOCKED: PR workflow gate state for session "${sessionID}" is invalid`);
4819
+ throw new Error(`BLOCKED: PR workflow gate state for session "${stateLabel}" is invalid`);
4548
4820
  }
4549
4821
  return parsed.data;
4550
4822
  }
@@ -4712,16 +4984,21 @@ init_logger();
4712
4984
 
4713
4985
  // src/commands/abort-pr-workflow.ts
4714
4986
  var USAGE = [
4715
- "Usage: /swarm abort-pr-workflow [PR_REVIEW|PR_FEEDBACK] [reason...]",
4987
+ "Usage: /swarm abort-pr-workflow [PR_REVIEW|PR_FEEDBACK] <reason...>",
4716
4988
  "",
4717
4989
  "Clear an active PR_REVIEW or PR_FEEDBACK mechanical gate for the current session",
4718
- "and stop the auto-resume loop. Use when a PR review or feedback workflow is stuck",
4719
- "(e.g. the working tree cannot reach the PR head, a compound shell command was",
4720
- "rejected, or the wake budget is suspended).",
4990
+ "and stop the auto-resume loop. This is the human-only FORCE escape hatch, usable",
4991
+ "even when the architect cannot call its audited recovery abort. Both paths may clear",
4992
+ "a BOUND gate after lanes settle; the force path does not require agent cooperation. Use when",
4993
+ "a PR review or feedback workflow is unrecoverably stuck (e.g. the working tree",
4994
+ "cannot reach the PR head, a compound shell command was rejected, or the wake",
4995
+ "budget is suspended).",
4721
4996
  "",
4722
4997
  "Arguments:",
4723
4998
  " mode Optional: PR_REVIEW or PR_FEEDBACK. If omitted, aborts whichever is active.",
4724
- " reason Optional free-text reason (recorded to .swarm/events.jsonl).",
4999
+ " reason Optional free-text reason recorded to the audit trail (.swarm/events.jsonl).",
5000
+ ' If omitted, a default ("user-initiated force abort ...") is recorded so the',
5001
+ " gate always has a non-empty reason. The agent tool call must supply its own.",
4725
5002
  "",
4726
5003
  "Refuses while the workflow is armed for publication (call complete_pr_workflow",
4727
5004
  "instead) or while PR workflow lanes are still in flight (collect their results first)."
@@ -4733,7 +5010,7 @@ async function handleAbortPrWorkflowCommand(directory, args, sessionID) {
4733
5010
  const modeToken = tokens[0]?.toUpperCase();
4734
5011
  const knownMode = modeToken && KNOWN_MODES.has(modeToken) ? modeToken : undefined;
4735
5012
  const reasonStartIndex = knownMode ? 1 : 0;
4736
- const reason = tokens.slice(reasonStartIndex).join(" ").trim().slice(0, 500) || undefined;
5013
+ const explicitReason = tokens.slice(reasonStartIndex).join(" ").trim().slice(0, 500);
4737
5014
  if (modeToken && !knownMode) {
4738
5015
  return `Error: Unknown mode "${modeToken}". Expected PR_REVIEW or PR_FEEDBACK.
4739
5016
 
@@ -4744,13 +5021,15 @@ ${USAGE}`;
4744
5021
 
4745
5022
  ${USAGE}`;
4746
5023
  }
5024
+ const reason = explicitReason || "user-initiated force abort via /swarm abort-pr-workflow (no explicit reason provided)";
4747
5025
  try {
4748
5026
  const summary = await abortPrWorkflow(directory, sessionID, {
4749
- ...knownMode ? { expectedMode: knownMode } : {},
4750
- ...reason ? { reason } : {}
5027
+ kind: "force",
5028
+ reason,
5029
+ ...knownMode ? { expectedMode: knownMode } : {}
4751
5030
  });
4752
5031
  const headLine = summary.prHeadSha ? ` (was bound to PR head ${summary.prHeadSha})` : " (was not bound to a PR head)";
4753
- return `Aborted active ${summary.mode} mechanical gate for session ${sessionID}${headLine}. The durable gate state has been cleared and the auto-resume loop will stop. An audit event was appended to .swarm/events.jsonl.`;
5032
+ return `Aborted active ${summary.mode} mechanical gate for session ${sessionID}${headLine} (force). The durable gate state has been cleared and the auto-resume loop will stop. An audit event was appended to .swarm/events.jsonl. If checkout preparation preserved changes, continue with prepare_pr_workflow_checkout operation=restore (or follow the preserved receipt manually).`;
4754
5033
  } catch (error2) {
4755
5034
  const message = error2 instanceof Error ? error2.message : String(error2);
4756
5035
  return `Error: ${message}
@@ -14553,8 +14832,8 @@ function dcValidateTargets(targets, cwd) {
14553
14832
  const lstatBlock = dcLstatAncestorWalk(filesystemTarget, cwd);
14554
14833
  if (lstatBlock)
14555
14834
  return lstatBlock;
14556
- const basename5 = path23.basename(filesystemTarget);
14557
- if (dcPathComponents(t).length === 1 && DC_SAFE_TARGETS.has(basename5)) {
14835
+ const basename6 = path23.basename(filesystemTarget);
14836
+ if (dcPathComponents(t).length === 1 && DC_SAFE_TARGETS.has(basename6)) {
14558
14837
  continue;
14559
14838
  }
14560
14839
  if (DC_FS_ROOTS.has(t) || DC_FS_ROOTS.has(t.replace(/\//g, "\\"))) {
@@ -14640,8 +14919,8 @@ function dcEvaluateRecursiveDeleteTargets(input) {
14640
14919
  reason: `BLOCKED: Recursive delete target "${safeTarget}" is not strictly contained by the workspace.`
14641
14920
  };
14642
14921
  }
14643
- const basename5 = path23.basename(resolved);
14644
- if (!DC_SAFE_TARGETS.has(basename5)) {
14922
+ const basename6 = path23.basename(resolved);
14923
+ if (!DC_SAFE_TARGETS.has(basename6)) {
14645
14924
  return {
14646
14925
  allowed: false,
14647
14926
  code: "DESTRUCTIVE_TARGET_NOT_SAFE_ARTIFACT",
@@ -21991,7 +22270,7 @@ function planMerges(active, threshold, maxComparisons, maxMerges) {
21991
22270
  };
21992
22271
  }
21993
22272
  async function sweepActiveNearDuplicates(directory, options = {}) {
21994
- const { loadPluginConfigWithMeta: loadPluginConfigWithMeta2 } = await import("./index-chycpf9y.js");
22273
+ const { loadPluginConfigWithMeta: loadPluginConfigWithMeta2 } = await import("./index-vjqe1z2w.js");
21995
22274
  const { config } = loadPluginConfigWithMeta2(directory);
21996
22275
  const sweepConfig = config.learning?.dedup_sweep;
21997
22276
  if (sweepConfig?.enabled === false)
@@ -23075,7 +23354,7 @@ async function runCuratorPhase(directory, phase, agentsDispatched, config, knowl
23075
23354
  }
23076
23355
  if (config.skill_generation_enabled === true && skillCandidates.length > 0) {
23077
23356
  try {
23078
- const skillModule = await import("./skill-generator-66ez26yh.js");
23357
+ const skillModule = await import("./skill-generator-bvph9d98.js");
23079
23358
  for (const cand of skillCandidates) {
23080
23359
  if (cand.confidence < (config.min_skill_confidence ?? DEFAULT_SKILL_MIN_CONFIDENCE)) {
23081
23360
  continue;
@@ -23535,7 +23814,7 @@ async function applyCuratorKnowledgeUpdates(directory, recommendations, knowledg
23535
23814
  } catch {}
23536
23815
  }
23537
23816
  if (contradictedEntries.length > 0 && knowledgeConfig.contradiction_threshold_action === "quarantine") {
23538
- const { maybeQuarantineOnContradiction } = await import("./knowledge-escalator-qjdfv9sj.js");
23817
+ const { maybeQuarantineOnContradiction } = await import("./knowledge-escalator-58tw74js.js");
23539
23818
  for (const { id } of contradictedEntries) {
23540
23819
  try {
23541
23820
  await maybeQuarantineOnContradiction(directory, id, knowledgeConfig.contradiction_quarantine_threshold, knowledgeConfig.contradiction_quarantine_window_days);
@@ -24354,7 +24633,7 @@ async function runCuratorPostMortem(directory, options = {}) {
24354
24633
  warnings.push("Failed to collect knowledge summary.");
24355
24634
  }
24356
24635
  try {
24357
- const { getScanStatus } = await import("./scan-cursor-h3j1egky.js");
24636
+ const { getScanStatus } = await import("./scan-cursor-7tqsvbp8.js");
24358
24637
  const scanStatus = await getScanStatus(directory);
24359
24638
  if (scanStatus.remaining_estimate > 0) {
24360
24639
  warnings.push(`Fair scan: reviewed ${knowledgeSummary.length} entries this sweep (generation ${scanStatus.generation}); ${scanStatus.remaining_estimate} more eligible entries will be visited in future curation sweeps.`);
@@ -24526,9 +24805,9 @@ var _internals28 = {
24526
24805
  verifyPostMortemKnowledgeActions,
24527
24806
  repairPostMortemActions,
24528
24807
  loadDefaultKnowledgeConfig: async (directory) => {
24529
- const { KnowledgeConfigSchema: KnowledgeConfigSchema2 } = await import("./schema-9ryqandr.js");
24808
+ const { KnowledgeConfigSchema: KnowledgeConfigSchema2 } = await import("./schema-b046zss2.js");
24530
24809
  try {
24531
- const { loadPluginConfigWithMeta: loadPluginConfigWithMeta2 } = await import("./index-chycpf9y.js");
24810
+ const { loadPluginConfigWithMeta: loadPluginConfigWithMeta2 } = await import("./index-vjqe1z2w.js");
24532
24811
  const loaded = loadPluginConfigWithMeta2(directory);
24533
24812
  return KnowledgeConfigSchema2.parse(loaded.config.knowledge ?? {});
24534
24813
  } catch {
@@ -24536,11 +24815,11 @@ var _internals28 = {
24536
24815
  }
24537
24816
  },
24538
24817
  applyCuratorKnowledgeUpdates: async (directory, recommendations, knowledgeConfig, generation) => {
24539
- const { applyCuratorKnowledgeUpdates: applyCuratorKnowledgeUpdates2 } = await import("./curator-78ejknh2.js");
24818
+ const { applyCuratorKnowledgeUpdates: applyCuratorKnowledgeUpdates2 } = await import("./curator-zp5qq24c.js");
24540
24819
  return applyCuratorKnowledgeUpdates2(directory, recommendations, knowledgeConfig, generation);
24541
24820
  },
24542
24821
  checkHivePromotions: async (entries, knowledgeConfig, directory) => {
24543
- const { checkHivePromotions } = await import("./hive-promoter-r5x4a2ta.js");
24822
+ const { checkHivePromotions } = await import("./hive-promoter-0yg3gxg6.js");
24544
24823
  return checkHivePromotions(entries, knowledgeConfig, directory);
24545
24824
  },
24546
24825
  applyProposalTriage: async (directory, triage) => {
@@ -24549,7 +24828,7 @@ var _internals28 = {
24549
24828
  activateProposal,
24550
24829
  listSkills: listSkills2,
24551
24830
  sanitizeSlug
24552
- } = await import("./skill-generator-66ez26yh.js");
24831
+ } = await import("./skill-generator-bvph9d98.js");
24553
24832
  const result = {
24554
24833
  approved: [],
24555
24834
  rejected: [],
@@ -25774,9 +26053,9 @@ function hashContent(content) {
25774
26053
  return createHash9("sha1").update(content).digest("hex");
25775
26054
  }
25776
26055
  async function loadConfigForPolicyCurator(directory) {
25777
- const { KnowledgeConfigSchema: KnowledgeConfigSchema2 } = await import("./schema-9ryqandr.js");
26056
+ const { KnowledgeConfigSchema: KnowledgeConfigSchema2 } = await import("./schema-b046zss2.js");
25778
26057
  try {
25779
- const { loadPluginConfigWithMeta: loadPluginConfigWithMeta2 } = await import("./index-chycpf9y.js");
26058
+ const { loadPluginConfigWithMeta: loadPluginConfigWithMeta2 } = await import("./index-vjqe1z2w.js");
25780
26059
  const { config: loadedConfig } = loadPluginConfigWithMeta2(directory);
25781
26060
  return KnowledgeConfigSchema2.parse(loadedConfig.knowledge ?? {});
25782
26061
  } catch {
@@ -29785,7 +30064,7 @@ async function runFinalizeStage(ctx) {
29785
30064
  }
29786
30065
  }
29787
30066
  try {
29788
- const { CuratorConfigSchema: CCS } = await import("./schema-9ryqandr.js");
30067
+ const { CuratorConfigSchema: CCS } = await import("./schema-b046zss2.js");
29789
30068
  const { config: pmLoadedConfig } = _internals39.loadPluginConfigWithMeta(ctx.directory);
29790
30069
  const curatorCfg = CCS.parse(pmLoadedConfig.curator ?? {});
29791
30070
  if (curatorCfg.enabled && curatorCfg.postmortem_enabled) {
@@ -32058,9 +32337,9 @@ var _internals44 = {
32058
32337
  loadPluginConfigWithMeta,
32059
32338
  loadCuratorDeps: async () => {
32060
32339
  const [{ CuratorConfigSchema }, curator, { createCuratorLLMDelegate: createCuratorLLMDelegate2 }] = await Promise.all([
32061
- import("./schema-9ryqandr.js"),
32062
- import("./curator-78ejknh2.js"),
32063
- import("./curator-llm-factory-kbqtk2wm.js")
32340
+ import("./schema-b046zss2.js"),
32341
+ import("./curator-zp5qq24c.js"),
32342
+ import("./curator-llm-factory-pr4bdfqw.js")
32064
32343
  ]);
32065
32344
  return { CuratorConfigSchema, curator, createCuratorLLMDelegate: createCuratorLLMDelegate2 };
32066
32345
  }
@@ -32965,7 +33244,7 @@ async function computeCurationDiagnostics(directory, entries) {
32965
33244
  let scanCompleted = false;
32966
33245
  let scanRemaining = 0;
32967
33246
  try {
32968
- const { getScanStatus } = await import("./scan-cursor-h3j1egky.js");
33247
+ const { getScanStatus } = await import("./scan-cursor-7tqsvbp8.js");
32969
33248
  const status = await getScanStatus(directory);
32970
33249
  scanGeneration = status.generation;
32971
33250
  scanCompleted = status.completed;
@@ -32975,17 +33254,17 @@ async function computeCurationDiagnostics(directory, entries) {
32975
33254
  try {
32976
33255
  const { isLinked: isLinked2 } = await import("./knowledge-link-q46k9adx.js");
32977
33256
  if (isLinked2(directory)) {
32978
- const { KnowledgeConfigSchema: KnowledgeConfigSchema2 } = await import("./schema-9ryqandr.js");
33257
+ const { KnowledgeConfigSchema: KnowledgeConfigSchema2 } = await import("./schema-b046zss2.js");
32979
33258
  let config;
32980
33259
  try {
32981
- const { loadPluginConfigWithMeta: loadPluginConfigWithMeta2 } = await import("./index-chycpf9y.js");
33260
+ const { loadPluginConfigWithMeta: loadPluginConfigWithMeta2 } = await import("./index-vjqe1z2w.js");
32982
33261
  const loaded = loadPluginConfigWithMeta2(directory);
32983
33262
  config = KnowledgeConfigSchema2.parse(loaded.config.knowledge ?? {});
32984
33263
  } catch {
32985
33264
  config = KnowledgeConfigSchema2.parse({});
32986
33265
  }
32987
33266
  const { cohortConfigFingerprint: cohortConfigFingerprint2 } = await import("./config-fingerprint-v22xh5a2.js");
32988
- const { buildConfigFingerprintInput: buildConfigFingerprintInput2, readCohortConfigFingerprint } = await import("./curation-policy-zkvkg4np.js");
33267
+ const { buildConfigFingerprintInput: buildConfigFingerprintInput2, readCohortConfigFingerprint } = await import("./curation-policy-7n6t8he4.js");
32989
33268
  const currentFp = cohortConfigFingerprint2(buildConfigFingerprintInput2(config));
32990
33269
  const cohortFp = await readCohortConfigFingerprint(directory);
32991
33270
  configMatch = cohortFp === null ? null : currentFp === cohortFp;
@@ -34196,7 +34475,7 @@ async function handleDoctorCommand(directory, args) {
34196
34475
  const result = runConfigDoctor(config, directory);
34197
34476
  let output;
34198
34477
  if (enableAutoFix && result.hasAutoFixableIssues) {
34199
- const { runConfigDoctorWithFixes } = await import("./config-doctor-3bm8pvnb.js");
34478
+ const { runConfigDoctorWithFixes } = await import("./config-doctor-shc0n4q1.js");
34200
34479
  const fixResult = await runConfigDoctorWithFixes(directory, config, true, {
34201
34480
  applyLossy: true
34202
34481
  });
@@ -36350,7 +36629,7 @@ function countCodeLines(content) {
36350
36629
  return lines.length;
36351
36630
  }
36352
36631
  function isTestFile(filePath) {
36353
- const basename11 = path61.basename(filePath);
36632
+ const basename12 = path61.basename(filePath);
36354
36633
  const _ext = path61.extname(filePath).toLowerCase();
36355
36634
  const testPatterns = [
36356
36635
  ".test.",
@@ -36366,7 +36645,7 @@ function isTestFile(filePath) {
36366
36645
  ".spec.jsx"
36367
36646
  ];
36368
36647
  for (const pattern of testPatterns) {
36369
- if (basename11.includes(pattern)) {
36648
+ if (basename12.includes(pattern)) {
36370
36649
  return true;
36371
36650
  }
36372
36651
  }
@@ -40566,7 +40845,7 @@ ${USAGE9}`;
40566
40845
  const traceState = {
40567
40846
  issueNumber: issueInfo.number,
40568
40847
  lastTransition: null,
40569
- completed: false
40848
+ status: "in_progress"
40570
40849
  };
40571
40850
  let oldTraceState = null;
40572
40851
  try {
@@ -45844,8 +46123,8 @@ function toSafeRelativePath(rootDir, targetPath) {
45844
46123
  if (!relative11 || relative11 === ".")
45845
46124
  return ".";
45846
46125
  if (relative11.startsWith("..") || path78.isAbsolute(relative11)) {
45847
- const basename16 = path78.basename(targetPath).replace(/\\/g, "/");
45848
- return basename16 || ".";
46126
+ const basename17 = path78.basename(targetPath).replace(/\\/g, "/");
46127
+ return basename17 || ".";
45849
46128
  }
45850
46129
  return relative11;
45851
46130
  }
@@ -47701,21 +47980,21 @@ function hasCompoundTestExtension(filename) {
47701
47980
  const lower = filename.toLowerCase();
47702
47981
  return COMPOUND_TEST_EXTENSIONS.some((ext) => lower.endsWith(ext));
47703
47982
  }
47704
- function isLanguageSpecificTestFile(basename17) {
47705
- const lower = basename17.toLowerCase();
47983
+ function isLanguageSpecificTestFile(basename18) {
47984
+ const lower = basename18.toLowerCase();
47706
47985
  if (lower.endsWith("_test.go"))
47707
47986
  return true;
47708
47987
  if (lower.endsWith(".py") && (lower.startsWith("test_") || lower.endsWith("_test.py")))
47709
47988
  return true;
47710
47989
  if (lower.endsWith("_spec.rb"))
47711
47990
  return true;
47712
- if (lower.endsWith(".java") && (/^Test[A-Z]/.test(basename17) || basename17.endsWith("Test.java") || basename17.endsWith("Tests.java") || lower.endsWith("it.java")))
47991
+ if (lower.endsWith(".java") && (/^Test[A-Z]/.test(basename18) || basename18.endsWith("Test.java") || basename18.endsWith("Tests.java") || lower.endsWith("it.java")))
47713
47992
  return true;
47714
47993
  if (lower.endsWith(".cs") && (lower.endsWith("test.cs") || lower.endsWith("tests.cs")))
47715
47994
  return true;
47716
- if (lower.endsWith(".kt") && (/^Test[A-Z]/.test(basename17) || lower.endsWith("test.kt") || lower.endsWith("tests.kt")))
47995
+ if (lower.endsWith(".kt") && (/^Test[A-Z]/.test(basename18) || lower.endsWith("test.kt") || lower.endsWith("tests.kt")))
47717
47996
  return true;
47718
- if (lower.endsWith(".php") && (/^Test[A-Z]/.test(basename17) || basename17.endsWith("Test.php") || basename17.endsWith("Tests.php")))
47997
+ if (lower.endsWith(".php") && (/^Test[A-Z]/.test(basename18) || basename18.endsWith("Test.php") || basename18.endsWith("Tests.php")))
47719
47998
  return true;
47720
47999
  if (lower.endsWith(".tests.ps1"))
47721
48000
  return true;
@@ -47723,23 +48002,23 @@ function isLanguageSpecificTestFile(basename17) {
47723
48002
  }
47724
48003
  function isConventionTestFilePath(filePath) {
47725
48004
  const normalizedPath = filePath.replace(/\\/g, "/");
47726
- const basename17 = path82.basename(filePath);
47727
- return hasCompoundTestExtension(basename17) || basename17.includes(".spec.") || basename17.includes(".test.") || isLanguageSpecificTestFile(basename17) || isTestDirectoryPath(normalizedPath);
48005
+ const basename18 = path82.basename(filePath);
48006
+ return hasCompoundTestExtension(basename18) || basename18.includes(".spec.") || basename18.includes(".test.") || isLanguageSpecificTestFile(basename18) || isTestDirectoryPath(normalizedPath);
47728
48007
  }
47729
48008
  function getTestFilesFromConvention(sourceFiles, workingDir = process.cwd()) {
47730
48009
  const testFiles = [];
47731
48010
  for (const file of sourceFiles) {
47732
48011
  const absoluteFile = resolveWorkspacePath(file, workingDir);
47733
48012
  const relativeFile = path82.relative(workingDir, absoluteFile);
47734
- const basename17 = path82.basename(absoluteFile);
48013
+ const basename18 = path82.basename(absoluteFile);
47735
48014
  const dirname30 = path82.dirname(absoluteFile);
47736
48015
  const preferRelativeOutput = !path82.isAbsolute(file);
47737
48016
  if (isConventionTestFilePath(relativeFile) || isConventionTestFilePath(file)) {
47738
48017
  dedupePush(testFiles, toWorkspaceOutputPath(absoluteFile, workingDir, preferRelativeOutput));
47739
48018
  continue;
47740
48019
  }
47741
- const nameWithoutExt = basename17.replace(/\.[^.]+$/, "");
47742
- const ext = path82.extname(basename17);
48020
+ const nameWithoutExt = basename18.replace(/\.[^.]+$/, "");
48021
+ const ext = path82.extname(basename18);
47743
48022
  const genericTestNames = [
47744
48023
  `${nameWithoutExt}.spec${ext}`,
47745
48024
  `${nameWithoutExt}.test${ext}`
@@ -47750,7 +48029,7 @@ function getTestFilesFromConvention(sourceFiles, workingDir = process.cwd()) {
47750
48029
  ...languageSpecificTestNames
47751
48030
  ].map((candidateName) => path82.join(dirname30, candidateName));
47752
48031
  const testDirectoryNames = [
47753
- basename17,
48032
+ basename18,
47754
48033
  ...genericTestNames,
47755
48034
  ...languageSpecificTestNames
47756
48035
  ];
@@ -50815,7 +51094,9 @@ async function handleResetCommand(directory, args) {
50815
51094
  "plan-export/SWARM_PLAN.json",
50816
51095
  "checkpoints.json",
50817
51096
  "events.jsonl",
50818
- "run-memory.jsonl"
51097
+ "run-memory.jsonl",
51098
+ "reproduction.json",
51099
+ "issue-publication.json"
50819
51100
  ];
50820
51101
  const results = [];
50821
51102
  try {
@@ -57533,7 +57814,7 @@ function buildDetailedHelp(commandName, entry) {
57533
57814
  async function handleHelpCommand(ctx) {
57534
57815
  const targetCommand = ctx.args.join(" ");
57535
57816
  if (!targetCommand) {
57536
- const { buildHelpText } = await import("./index-scbp2d32.js");
57817
+ const { buildHelpText } = await import("./index-4rje8dc0.js");
57537
57818
  return buildHelpText();
57538
57819
  }
57539
57820
  const tokens = targetCommand.split(/\s+/);
@@ -57542,7 +57823,7 @@ async function handleHelpCommand(ctx) {
57542
57823
  return _internals18.buildDetailedHelp(resolved.key, resolved.entry);
57543
57824
  }
57544
57825
  const similar = _internals18.findSimilarCommands(targetCommand);
57545
- const { buildHelpText: fullHelp } = await import("./index-scbp2d32.js");
57826
+ const { buildHelpText: fullHelp } = await import("./index-4rje8dc0.js");
57546
57827
  if (similar.length > 0) {
57547
57828
  return `Command '/swarm ${targetCommand}' not found.
57548
57829
 
@@ -57719,7 +58000,7 @@ var COMMAND_REGISTRY = {
57719
58000
  },
57720
58001
  "guardrail explain": {
57721
58002
  handler: async (ctx) => {
57722
- const { handleGuardrailExplain } = await import("./guardrail-explain-d6emse0z.js");
58003
+ const { handleGuardrailExplain } = await import("./guardrail-explain-thddj6jv.js");
57723
58004
  return handleGuardrailExplain(ctx.directory, ctx.args);
57724
58005
  },
57725
58006
  description: "Dry-run: show what the guardrails would do to a command or write target (executes nothing)",
@@ -57729,7 +58010,7 @@ var COMMAND_REGISTRY = {
57729
58010
  },
57730
58011
  "guardrail-explain": {
57731
58012
  handler: async (ctx) => {
57732
- const { handleGuardrailExplain } = await import("./guardrail-explain-d6emse0z.js");
58013
+ const { handleGuardrailExplain } = await import("./guardrail-explain-thddj6jv.js");
57733
58014
  return handleGuardrailExplain(ctx.directory, ctx.args);
57734
58015
  },
57735
58016
  description: "Dry-run: show what the guardrails would do to a command or write target (executes nothing)",
@@ -57739,7 +58020,7 @@ var COMMAND_REGISTRY = {
57739
58020
  },
57740
58021
  "guardrail-log": {
57741
58022
  handler: async (ctx) => {
57742
- const { handleGuardrailLog } = await import("./guardrail-log-27d6r2xq.js");
58023
+ const { handleGuardrailLog } = await import("./guardrail-log-w8fmxe1x.js");
57743
58024
  return handleGuardrailLog(ctx.directory, ctx.args);
57744
58025
  },
57745
58026
  description: "Read the guardrail decision log (use --blocks-only for blocks)",
@@ -58185,7 +58466,7 @@ Subcommands:
58185
58466
  handler: (ctx) => handleAbortPrWorkflowCommand(ctx.directory, ctx.args, ctx.sessionID),
58186
58467
  description: "Clear a stuck PR_REVIEW/PR_FEEDBACK mechanical gate and stop the auto-resume loop [mode] [reason]",
58187
58468
  args: "[PR_REVIEW|PR_FEEDBACK] [reason...]",
58188
- details: "Human-only escape hatch for an unrecoverable PR_REVIEW or PR_FEEDBACK mechanical gate. When the architect cannot reach complete_pr_workflow \u2014 for example a compound `git fetch && git checkout` was rejected as read-only shell syntax, the PR head cannot be fetched, or the working tree is on the wrong branch \u2014 running this clears the durable gate state for the current session and stops the auto-resume loop without depending on the trapped model. The agent itself cannot run this command; it must call the abort_pr_workflow tool (or ask you to run this command). Both paths funnel into the same fail-closed abortPrWorkflow hook, which refuses while the workflow is armed for publication or while PR workflow lanes are still in flight. An audit event is appended to .swarm/events.jsonl.",
58469
+ details: "Human-only escape hatch for an unrecoverable PR_REVIEW or PR_FEEDBACK mechanical gate. When the architect cannot reach complete_pr_workflow \u2014 for example a compound `git fetch && git checkout` was rejected as read-only shell syntax, the PR head cannot be fetched, or the working tree is on the wrong branch \u2014 running this clears the durable gate state for the current session and stops the auto-resume loop without depending on the trapped model. The agent itself cannot run this command; it must call the abort_pr_workflow tool (or ask you to run this command). Both paths funnel into the same fail-closed abortPrWorkflow hook, which refuses while the workflow is armed for publication or while PR workflow lanes are still in flight. When checkout preparation preserved a stash, the result instructs the caller to run prepare_pr_workflow_checkout operation=restore after the clear. An audit event is appended to .swarm/events.jsonl.",
58189
58470
  category: "utility",
58190
58471
  toolPolicy: "restricted"
58191
58472
  },
@@ -59628,15 +59909,15 @@ HARD CONSTRAINTS (apply regardless of skill load success):
59628
59909
  - Test execution, explorer lanes, reviewer dispatch, and critic challenge are all permitted within this mode
59629
59910
  - Quality is the only metric \u2014 there is no speed, efficiency, or time exception; time, tokens, and agent dispatches are irrelevant to correctness
59630
59911
  - FOLLOW THE SKILL EXACTLY: execute every phase of the loaded SKILL.md in order with no shortcuts, no phase-skipping, and no premature synthesis. If a required coverage phase cannot complete, apply the skill's coverage gate (retry or verified equivalent alternative). If the gap still cannot be closed, stop and surface the lane failure to the user as BLOCKED; do not produce a degraded review, partial verdict, or final synthesis.
59631
- - CHECK OUT THE EXACT PR HEAD LOCALLY before dispatching explorer lanes: resolve the authoritative full PR head SHA, verify the working tree is clean (git status --porcelain), and if it is dirty call \`prepare_pr_workflow_checkout\` before checkout \u2014 either with no \`paths\` to auto-discover and preserve every dirty and untracked change in one auditable stash, or with an explicit exact dirty tracked path set. It returns the stash OID and recovery command; an already-clean tree is a no-op. Do NOT run \`git stash\` through shell. Then use standalone commands: fetch the PR head, verify \`git cat-file -e <full_pr_head_sha>^{commit}\`, run \`git switch --detach <full_pr_head_sha>\`, confirm HEAD equals that SHA, and bind it through the PR-review controller. Do not use \`--track FETCH_HEAD\`. Explorers read the working-tree filesystem (Read/Glob/Grep), so without this checkout they read the base branch and produce invalid candidates. Always pass the base..head commit range in explorer delegations.
59912
+ - CHECK OUT THE EXACT PR HEAD LOCALLY before dispatching explorer lanes: resolve the authoritative full PR head SHA, verify the working tree is clean (git status --porcelain), and if it is dirty call \`prepare_pr_workflow_checkout\` before checkout \u2014 either with no \`paths\` to auto-discover and preserve every dirty and untracked change in one auditable stash, or with an explicit exact dirty tracked path set. It returns the stash OID and recovery command; an already-clean tree is a no-op. Do NOT run \`git stash\` through shell. Then use standalone commands: fetch the PR head, verify it portably with \`git rev-parse --verify <full_pr_head_sha>^0\` followed by \`git cat-file -t <full_pr_head_sha>\` (which must print \`commit\`), run \`git switch --detach <full_pr_head_sha>\`, confirm HEAD equals that SHA, and bind it through the PR-review controller. Do not use \`--track FETCH_HEAD\`. Explorers read the working-tree filesystem (Read/Glob/Grep), so without this checkout they read the base branch and produce invalid candidates. Always pass the base..head commit range in explorer delegations. Under Profile A, do not create a scratch context-pack file after the gate activates: put bounded shared scope, obligations, and deterministic signals in \`common_prompt\`; every lane must inspect the exact bound diff itself.
59632
59913
  - Treat the controller Git-state result as final for the attempt: \`clean\` proceeds; \`stashable\` permits one preparation call; \`recovery-required\` or \`indeterminate\` means report the typed \`required_action\`, abort/clear any already-active gate, and STOP unless \`retryable: true\`. Never loop on stash against an unmerged index or in-progress Git operation.
59633
59914
  - RUN ALL BASE LANES: the default PR_REVIEW path always launches exactly six repository-agnostic base check-type lanes from the skill. Use \`mode: "swarm-pr-review:base"\` and the exact six \`workflow_lane\` identifiers. The runtime rejects partial, duplicate, or mislabelled waves. Do not collapse, omit, or scale down the base lanes for a small, docs-only, or CI-only PR.
59634
- - RETRY STRUCTURALLY: retry only failed base obligations in later \`swarm-pr-review:base\` async batches with the same exact \`pr_head_sha\`. Blocking \`dispatch_lanes\` and direct Task explorer/reviewer/critic dispatch are not provenance-equivalent and are rejected.
59915
+ - RETRY STRUCTURALLY: retry only failed base obligations in later \`swarm-pr-review:base\` async batches with the same exact \`pr_head_sha\`. Blocking \`dispatch_lanes\` and direct Task explorer/reviewer/critic dispatch are not provenance-equivalent and are rejected. After the second failed retry, collect every lane to settlement, do not probe downstream writers or micro lanes, call \`abort_pr_workflow\` with \`mode: "PR_REVIEW"\`, \`kind: "recovery"\`, and a non-empty one-line \`reason\` naming the failed lane and exhausted retries, then call \`prepare_pr_workflow_checkout\` with \`operation: "restore"\` before reporting the blocker.
59635
59916
  - USE ASYNC DISPATCH WITHOUT IDLING: launch the base lanes with one \`dispatch_lanes_async\` call when available, record the \`batch_id\`, then keep doing non-dependent architect work while they run. Poll with \`collect_lane_results\` without \`wait\` (or \`wait: false\`) to process settled lanes and continue independent work between polls; use \`wait: true\` only as the final join when no independent work remains.
59636
59917
  - EVALUATE ALL RISK FAMILIES: after the base explorer lanes settle, evaluate every repository-agnostic trigger row against the exact diff/context pack. Record applicable families as \`MATCHED\` and inapplicable families as provenance-free \`NOT_TRIGGERED\` with concrete absence evidence; \`unclassified-risk\` always remains \`MATCHED\`. Launch \`swarm-pr-review:micro\` lanes only for the \`MATCHED\` IDs. The first micro dispatch MUST pass the complete exact eleven-row \`trigger_evaluation\`, which freezes it for this session; any subsequent same-session micro batch may omit it and reuse the frozen ledger, while an explicitly supplied copy must remain exactly identical. Use each dispatched trigger ID as \`workflow_lane\`. Missing an evaluation row or a matched-family attestation is BLOCKED; a \`NOT_TRIGGERED\` family must not create a micro artifact.
59637
59918
  - RUN HEAD-BOUND VALIDATION: dispatch independent reviewers with \`mode: "swarm-pr-review:reviewer"\` and critics with \`mode: "swarm-pr-review:critic"\`, unique \`workflow_lane\` obligations, role-correct agents, and the same exact \`pr_head_sha\`. Critic dispatch is blocked until all declared reviewer obligations have successful artifacts.
59638
- - COMPLETE EXPLICITLY: after final artifacts and synthesis, call \`complete_pr_workflow\` with \`mode: "PR_REVIEW"\` and the bound \`pr_head_sha\`; never leave the session trapped in a stale workflow gate.
59639
- - ABORT IF UNRECOVERABLE: if the PR head cannot be fetched/checked out (the working tree stays on the wrong branch), a compound \`git fetch && git checkout\` keeps being rejected as read-only shell syntax (run them as TWO separate standalone commands first), or the merge-base bind is unreachable for any reason, call \`abort_pr_workflow\` with \`mode: "PR_REVIEW"\` and a one-line \`reason\` instead of looping. The tool refuses while PR workflow lanes are in flight (collect their results first). Aborting clears the durable gate and stops the auto-resume loop; the user can also run \`/swarm abort-pr-workflow\`. Do NOT abort as a shortcut to avoid coverage obligations \u2014 only when the bind/checkout path is genuinely unreachable.
59919
+ - COMPLETE EXPLICITLY: after final artifacts and synthesis, call \`complete_pr_workflow\` with \`mode: "PR_REVIEW"\` and the bound \`pr_head_sha\`; never leave the session trapped in a stale workflow gate. When completion reports checkout restoration is required, call \`prepare_pr_workflow_checkout\` with \`operation: "restore"\` before returning to the user.
59920
+ - ABORT IF UNRECOVERABLE: if the PR head cannot be fetched/checked out (the working tree stays on the wrong branch), a compound \`git fetch && git checkout\` keeps being rejected as read-only shell syntax (run them as TWO separate standalone commands first), the merge-base bind is unreachable, or the bounded lane retries above are exhausted, call \`abort_pr_workflow\` with \`mode: "PR_REVIEW"\`, \`kind: "recovery"\`, and a one-line \`reason\` instead of looping. The tool accepts unbound and bound workflows but refuses while PR workflow lanes are in flight (collect their results first). Aborting clears the durable gate and stops the auto-resume loop; the user can force-clear via \`/swarm abort-pr-workflow\`. Do NOT abort while useful bounded recovery work remains.
59640
59921
  - Honor any free-text instructions that follow the closing bracket of the signal as additional reviewer focus, without weakening the validation ladder above.
59641
59922
 
59642
59923
  ### MODE: PR_FEEDBACK
@@ -59664,7 +59945,7 @@ HARD CONSTRAINTS (apply regardless of skill load success):
59664
59945
  - DO NOT PUBLISH EARLY: commits, pushes, PR comments/updates, review-thread mutations, and other remote writes remain blocked until every ordered feedback gate settles positively on the same revision digest.
59665
59946
  - After every local gate passes, create the reviewed commit with one standalone \`git commit\`, then call \`complete_pr_workflow\` once. It requires a clean index/worktree and a non-merge direct child whose sole parent is the immutable intake head before binding that post-commit HEAD and arming publication. Push only with \`git push <bound-remote> <bound-commit>:refs/heads/<bound-branch>\`; the literal bound commit and exact bound branch are required, and force flags, wrappers, aliases, fetch-based local-ref forgery, extra refspecs, \`git -C\`, and other remote writes fail closed. Perform read-only remote checks, then call \`complete_pr_workflow\` a second time with the same immutable verification \`pr_head_sha\`; the second call clears only after both the actual remote ref and its local tracking ref resolve to the exact bound commit. Only then perform any explicitly authorized PR comment/body/thread writes.
59666
59947
  - Patch only confirmed items plus the tests/docs they require; report closure status for every ledger item including disproved ones.
59667
- - ABORT IF UNRECOVERABLE (pre-armed only): if the PR head cannot be fetched/checked out or the verification bind is unreachable for any reason, call \`abort_pr_workflow\` with \`mode: "PR_FEEDBACK"\` and a one-line \`reason\` instead of looping. The tool refuses while PR workflow lanes are in flight (collect their results first) AND refuses once \`prFeedbackReadyToPublish\` is armed \u2014 after arming, you MUST complete the workflow with \`complete_pr_workflow\` (or push the bound commit first); aborting an armed gate would drop the immutable-commit binding and leave a half-published commit. Do NOT abort as a shortcut to skip the mechanical gates \u2014 only when the bind/verification path is genuinely unreachable.
59948
+ - ABORT IF UNRECOVERABLE (pre-armed only): if the PR head cannot be fetched/checked out, the verification bind is unreachable, or bounded recovery is exhausted, call \`abort_pr_workflow\` with \`mode: "PR_FEEDBACK"\`, \`kind: "recovery"\`, and a one-line \`reason\` instead of looping. The tool accepts unbound and bound workflows, refuses while PR workflow lanes are in flight (collect their results first), and refuses once \`prFeedbackReadyToPublish\` is armed \u2014 after arming, you MUST complete the workflow with \`complete_pr_workflow\` (or push the bound commit first); aborting an armed gate would drop the immutable-commit binding and leave a half-published commit. Do NOT abort while useful bounded recovery work remains.
59668
59949
  - Do NOT resolve or mark GitHub review threads resolved unless the user explicitly instructs it.
59669
59950
  - Honor any free-text instructions that follow the closing bracket of the signal as additional scope, without dropping any ledger item.
59670
59951
  - Quality is the only metric \u2014 there is no speed, efficiency, or time exception; time, tokens, and agent dispatches are irrelevant to correctness