opencode-swarm 7.140.1 → 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 (45) hide show
  1. package/.opencode/skills/swarm-pr-feedback/SKILL.md +19 -3
  2. package/.opencode/skills/swarm-pr-review/SKILL.md +61 -14
  3. package/dist/background/candidate-contract.d.ts +3 -0
  4. package/dist/cli/{config-doctor-0b5z13vq.js → config-doctor-shc0n4q1.js} +3 -3
  5. package/dist/cli/{core-m5y25n0j.js → core-p5cyej3r.js} +2 -2
  6. package/dist/cli/{curation-policy-bn7g8n40.js → curation-policy-7n6t8he4.js} +2 -2
  7. package/dist/cli/{curator-llm-factory-0mkbw2hg.js → curator-llm-factory-pr4bdfqw.js} +14 -14
  8. package/dist/cli/{curator-b7f6nt8h.js → curator-zp5qq24c.js} +14 -14
  9. package/dist/cli/{guardrail-explain-p2htdqkz.js → guardrail-explain-thddj6jv.js} +15 -15
  10. package/dist/cli/{guardrail-log-rqt9wrzw.js → guardrail-log-w8fmxe1x.js} +4 -4
  11. package/dist/cli/{hive-promoter-61sjdrgt.js → hive-promoter-0yg3gxg6.js} +14 -14
  12. package/dist/cli/{index-0an7an2s.js → index-0tevsq1a.js} +2 -2
  13. package/dist/cli/{index-hnprx6qv.js → index-1j5my2qy.js} +2 -2
  14. package/dist/cli/{index-dbg3agxz.js → index-325zmfyj.js} +4 -4
  15. package/dist/cli/{index-ff7b0cr5.js → index-4rje8dc0.js} +16 -16
  16. package/dist/cli/{index-dn3nkwbc.js → index-7ax0bkqh.js} +1 -1
  17. package/dist/cli/{index-vw75bhf3.js → index-898f16j9.js} +1 -1
  18. package/dist/cli/{index-yf6p4wc5.js → index-9txca231.js} +3 -3
  19. package/dist/cli/{index-jq7n7w9y.js → index-bmmzfrc7.js} +2 -2
  20. package/dist/cli/{index-acc18zrj.js → index-chx92bes.js} +2 -2
  21. package/dist/cli/{index-wysn7pcz.js → index-jfp1bgt1.js} +1 -1
  22. package/dist/cli/{index-v1d4qz8y.js → index-n408rw7n.js} +1 -1
  23. package/dist/cli/{index-7f72x6nv.js → index-n831qq2v.js} +435 -172
  24. package/dist/cli/{index-a98msw3z.js → index-t59cdg48.js} +1 -1
  25. package/dist/cli/{index-hgrzjhsf.js → index-vjqe1z2w.js} +3 -3
  26. package/dist/cli/{index-m9csth1h.js → index-x6mtyj6q.js} +2 -2
  27. package/dist/cli/{index-6fwr2w8v.js → index-y98ngs4g.js} +2 -2
  28. package/dist/cli/{index-7nhrn9sy.js → index-yt3vz0e1.js} +1 -1
  29. package/dist/cli/{index-r7tg8kjh.js → index-z6md3t8m.js} +5 -5
  30. package/dist/cli/index.js +14 -14
  31. package/dist/cli/{knowledge-escalator-twwz5a0d.js → knowledge-escalator-58tw74js.js} +3 -3
  32. package/dist/cli/{knowledge-events-hgaz06m8.js → knowledge-events-knr0pfq4.js} +1 -1
  33. package/dist/cli/{knowledge-store-46jrzgvm.js → knowledge-store-e2srcd89.js} +1 -1
  34. package/dist/cli/{knowledge-validator-7vnqwjtv.js → knowledge-validator-1dtj02q8.js} +4 -4
  35. package/dist/cli/{scan-cursor-acsjpv2n.js → scan-cursor-7tqsvbp8.js} +2 -2
  36. package/dist/cli/{schema-4dy7emnw.js → schema-b046zss2.js} +2 -2
  37. package/dist/cli/{skill-generator-a48t83hf.js → skill-generator-bvph9d98.js} +5 -5
  38. package/dist/commands/registry.d.ts +1 -1
  39. package/dist/git/pr-workflow-state.d.ts +3 -1
  40. package/dist/hooks/pr-workflow-gate.d.ts +19 -13
  41. package/dist/index.js +104 -99
  42. package/dist/tools/complete-pr-workflow.d.ts +6 -0
  43. package/dist/tools/gh-evidence.d.ts +6 -0
  44. package/dist/tools/prepare-pr-workflow-checkout.d.ts +11 -0
  45. 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-m9csth1h.js";
19
+ } from "./index-x6mtyj6q.js";
20
20
  import {
21
21
  detectStraySwarmDirs,
22
22
  readDoctorArtifact,
23
23
  removeStraySwarmDir,
24
24
  runConfigDoctor
25
- } from "./index-acc18zrj.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-yf6p4wc5.js";
125
+ } from "./index-9txca231.js";
126
126
  import {
127
127
  readRecentEscalations
128
- } from "./index-r7tg8kjh.js";
128
+ } from "./index-z6md3t8m.js";
129
129
  import {
130
130
  alreadyCuratedThisGeneration,
131
131
  claimNextScanBatch,
132
132
  resolveCohortId
133
- } from "./index-7nhrn9sy.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-hnprx6qv.js";
225
+ } from "./index-1j5my2qy.js";
226
226
  import {
227
227
  authorizeCuration,
228
228
  buildConfigFingerprintInput
229
- } from "./index-a98msw3z.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-dbg3agxz.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-wysn7pcz.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-0an7an2s.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-jq7n7w9y.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-v1d4qz8y.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);
@@ -2353,9 +2497,6 @@ async function abortPrWorkflow(directory, sessionID, options = { kind: "recovery
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
- if (options.kind === "recovery" && state.prHeadSha && !state.checkoutRecovery) {
2357
- throw new Error(`BLOCKED: recovery abort of a bound ${state.mode} (pr_head_sha ${state.prHeadSha}) requires a controller-recorded terminal recovery condition (checkoutRecovery). Binding succeeded, so "checkout unreachable" no longer justifies a recovery abort \u2014 complete the workflow, resolve the Git recovery state, or use the user-authorized force abort (/swarm abort-pr-workflow) to override.`);
2358
- }
2359
2500
  const sanitizedReason = typeof options.reason === "string" && options.reason.trim().length > 0 ? options.reason.trim().slice(0, 500) : undefined;
2360
2501
  if (sanitizedReason === undefined) {
2361
2502
  throw new Error(`BLOCKED: ${state.mode} abort requires a non-empty reason (recorded to the audit trail).`);
@@ -2383,47 +2524,45 @@ async function abortPrWorkflow(directory, sessionID, options = { kind: "recovery
2383
2524
  openLanes: openLanes.length
2384
2525
  };
2385
2526
  }
2386
- async function bindPrWorkflowHead(directory, sessionID, prHeadSha) {
2527
+ async function bindPrWorkflowHeadWhileLocked(directory, normalizedSessionID, prHeadSha) {
2387
2528
  const normalizedHead = normalizePrHeadSha(prHeadSha);
2388
- return withPrWorkflowCheckoutMutationLock(directory, async () => withSessionStateMutation(directory, normalizeSessionID(sessionID), async () => {
2389
- const state = await readPrWorkflowGateStateFromDisk(directory, normalizeSessionID(sessionID));
2390
- if (!state) {
2391
- 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.");
2392
- }
2393
- await assertCurrentCheckoutHead(directory, normalizedHead, state.mode);
2394
- if (state.prHeadSha && state.prHeadSha !== normalizedHead) {
2395
- throw new Error(`BLOCKED: active ${state.mode} workflow is bound to PR head "${state.prHeadSha}"; received "${normalizedHead}"`);
2396
- }
2397
- if (!state.prHeadSha) {
2398
- await assertPrReviewCleanCheckout(directory, state.mode);
2399
- }
2400
- 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") {
2401
2545
  await assertPrFeedbackTrackingCheckout(directory, normalizedHead);
2402
2546
  }
2403
- if (state.prHeadSha === normalizedHead) {
2404
- if (state.mode === "PR_FEEDBACK") {
2405
- await assertPrFeedbackTrackingCheckout(directory, normalizedHead);
2406
- }
2407
- return state;
2408
- }
2409
- const nextState = {
2410
- ...state,
2411
- prHeadSha: normalizedHead,
2412
- checkoutRecovery: undefined,
2413
- updatedAt: isoNow()
2414
- };
2415
- await _test_exports.beforePrFeedbackTrackingPersist?.();
2416
- return writeStateWhileLocked(directory, nextState);
2417
- }));
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);
2418
2557
  }
2419
2558
  async function assertCurrentCheckoutHead(directory, expectedHead, mode = "PR_REVIEW") {
2420
2559
  const normalizedExpected = normalizePrHeadSha(expectedHead);
2421
2560
  const currentHead = (await _test_exports.resolveCurrentGitHeadAsync(directory))?.trim();
2422
2561
  if (!currentHead) {
2423
- 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).`);
2424
2563
  }
2425
2564
  if (currentHead.toLowerCase() !== normalizedExpected.toLowerCase()) {
2426
- 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\`.`;
2427
2566
  throw new Error(`BLOCKED: current checkout HEAD "${currentHead}" does not match PR head "${normalizedExpected}" ` + `(working directory: "${directory}"). ${remediation}`);
2428
2567
  }
2429
2568
  return normalizedExpected;
@@ -2945,6 +3084,7 @@ var _test_exports = {
2945
3084
  _test_exports.openCheckoutLock = openCheckoutLockFile;
2946
3085
  _test_exports.removeCheckoutLock = removeCheckoutLockFile;
2947
3086
  _test_exports.checkoutMutationActionTimeoutMs = CHECKOUT_MUTATION_ACTION_TIMEOUT_MS;
3087
+ _test_exports.classifyPrWorkflowGitStateAsync = classifyPrWorkflowGitState;
2948
3088
  },
2949
3089
  beforeTerminalClear: undefined,
2950
3090
  beforePrFeedbackTransitionLock: undefined,
@@ -2960,6 +3100,7 @@ var _test_exports = {
2960
3100
  openCheckoutLock: openCheckoutLockFile,
2961
3101
  removeCheckoutLock: removeCheckoutLockFile,
2962
3102
  checkoutMutationActionTimeoutMs: CHECKOUT_MUTATION_ACTION_TIMEOUT_MS,
3103
+ classifyPrWorkflowGitStateAsync: classifyPrWorkflowGitState,
2963
3104
  resolveCurrentGitHead,
2964
3105
  resolveCurrentGitHeadAsync,
2965
3106
  resolveCurrentUpstreamPushTarget,
@@ -3072,8 +3213,128 @@ var PR_REVIEW_READ_ONLY_TOOL_NAMES = new Set([
3072
3213
  "webfetch",
3073
3214
  "websearch"
3074
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
+ }
3075
3336
  function hasUnsafeShellControlSyntax(command) {
3076
- return /[;&|<>`\r\n]/.test(command) || /\$\(/.test(command);
3337
+ return classifyPrWorkflowShellSyntax(command).unsafe;
3077
3338
  }
3078
3339
  function isSafeStandaloneGitCommit(command) {
3079
3340
  if (hasUnsafeShellControlSyntax(command))
@@ -3850,7 +4111,8 @@ function analyzePrReviewDiscoveryArtifact(text, expectedWorkflowLane, ownedWorkf
3850
4111
  const issues = [];
3851
4112
  const normalized = normalizeCandidateArtifact(text, fallbackFamily);
3852
4113
  const canonicalText = normalized.text;
3853
- const salvaged = normalized.synthesizedHeader;
4114
+ const repairKinds = normalized.repairKinds;
4115
+ const salvaged = repairKinds.length > 0;
3854
4116
  const header = selectCandidateHeader(canonicalText.split(/\r?\n/));
3855
4117
  if (header === null || !header.markerBearing || header.family !== fallbackFamily) {
3856
4118
  appendBoundedCandidateIssue(issues, `field header: expected one exact canonical ${fallbackFamily} [CANDIDATE] header before discovery rows`);
@@ -3859,6 +4121,7 @@ function analyzePrReviewDiscoveryArtifact(text, expectedWorkflowLane, ownedWorkf
3859
4121
  evidence: null,
3860
4122
  issues,
3861
4123
  salvaged,
4124
+ repairKinds,
3862
4125
  failurePredicate: "discovery.header"
3863
4126
  };
3864
4127
  }
@@ -3902,6 +4165,7 @@ function analyzePrReviewDiscoveryArtifact(text, expectedWorkflowLane, ownedWorkf
3902
4165
  evidence: null,
3903
4166
  issues,
3904
4167
  salvaged,
4168
+ repairKinds,
3905
4169
  failurePredicate: "discovery.row"
3906
4170
  };
3907
4171
  }
@@ -3919,7 +4183,8 @@ function analyzePrReviewDiscoveryArtifact(text, expectedWorkflowLane, ownedWorkf
3919
4183
  matchingCandidate.confidence
3920
4184
  ].join("\x00"),
3921
4185
  issues,
3922
- salvaged
4186
+ salvaged,
4187
+ repairKinds
3923
4188
  };
3924
4189
  }
3925
4190
  const clean = parsed.clean_attestation;
@@ -3928,7 +4193,8 @@ function analyzePrReviewDiscoveryArtifact(text, expectedWorkflowLane, ownedWorkf
3928
4193
  covered: true,
3929
4194
  evidence: `${clean.coverage_scope}\x00${clean.evidence}`,
3930
4195
  issues,
3931
- salvaged
4196
+ salvaged,
4197
+ repairKinds
3932
4198
  };
3933
4199
  }
3934
4200
  return {
@@ -3936,6 +4202,7 @@ function analyzePrReviewDiscoveryArtifact(text, expectedWorkflowLane, ownedWorkf
3936
4202
  evidence: null,
3937
4203
  issues,
3938
4204
  salvaged,
4205
+ repairKinds,
3939
4206
  failurePredicate: hasParseFailure ? "discovery.row" : "discovery.coverage"
3940
4207
  };
3941
4208
  }
@@ -3983,7 +4250,7 @@ function validatePrReviewDiscoveryLaneCompletion(input) {
3983
4250
  if (!analysis.salvaged && analysis.issues.length === 0)
3984
4251
  continue;
3985
4252
  salvagedLanes.push(workflowLane);
3986
- 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";
3987
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)"}`);
3988
4255
  }
3989
4256
  const failedCoverage = analyses.find(({ analysis }) => !analysis.covered);
@@ -4131,13 +4398,6 @@ function pipeFields(line) {
4131
4398
  return [];
4132
4399
  return line.split("|").map((field) => field.trim());
4133
4400
  }
4134
- async function persistState(directory, state) {
4135
- const validated = PrWorkflowGateStateSchema.parse(state);
4136
- await withSessionStateMutation(directory, validated.sessionID, async () => {
4137
- const nextState = await writeStateWhileLocked(directory, validated);
4138
- Object.assign(state, nextState);
4139
- });
4140
- }
4141
4401
  async function writeStateWhileLocked(directory, state, options = {}) {
4142
4402
  const validated = PrWorkflowGateStateSchema.parse(state);
4143
4403
  const current = await readPrWorkflowGateStateFromDisk(directory, validated.sessionID);
@@ -4537,6 +4797,9 @@ function isProcessAlive(pid) {
4537
4797
  }
4538
4798
  async function readPrWorkflowGateStateFromDisk(directory, sessionID) {
4539
4799
  const filePath = workflowGateStatePath(directory, sessionID);
4800
+ return readPrWorkflowGateStateFileFromDisk(filePath, sessionID);
4801
+ }
4802
+ async function readPrWorkflowGateStateFileFromDisk(filePath, stateLabel) {
4540
4803
  let raw;
4541
4804
  try {
4542
4805
  raw = await fsp2.readFile(filePath, "utf-8");
@@ -4549,11 +4812,11 @@ async function readPrWorkflowGateStateFromDisk(directory, sessionID) {
4549
4812
  try {
4550
4813
  parsedJson = JSON.parse(raw);
4551
4814
  } catch {
4552
- 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`);
4553
4816
  }
4554
4817
  const parsed = PrWorkflowGateStateSchema.safeParse(parsedJson);
4555
4818
  if (!parsed.success) {
4556
- 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`);
4557
4820
  }
4558
4821
  return parsed.data;
4559
4822
  }
@@ -4724,9 +4987,9 @@ var USAGE = [
4724
4987
  "Usage: /swarm abort-pr-workflow [PR_REVIEW|PR_FEEDBACK] <reason...>",
4725
4988
  "",
4726
4989
  "Clear an active PR_REVIEW or PR_FEEDBACK mechanical gate for the current session",
4727
- "and stop the auto-resume loop. This is the human-only FORCE escape hatch: it may",
4728
- "clear a BOUND gate (one whose PR head was successfully checked out) without a",
4729
- "recovery condition, which the agent's own recovery abort is refused for. Use when",
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",
4730
4993
  "a PR review or feedback workflow is unrecoverably stuck (e.g. the working tree",
4731
4994
  "cannot reach the PR head, a compound shell command was rejected, or the wake",
4732
4995
  "budget is suspended).",
@@ -4766,7 +5029,7 @@ ${USAGE}`;
4766
5029
  ...knownMode ? { expectedMode: knownMode } : {}
4767
5030
  });
4768
5031
  const headLine = summary.prHeadSha ? ` (was bound to PR head ${summary.prHeadSha})` : " (was not bound to a PR head)";
4769
- 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.`;
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).`;
4770
5033
  } catch (error2) {
4771
5034
  const message = error2 instanceof Error ? error2.message : String(error2);
4772
5035
  return `Error: ${message}
@@ -14569,8 +14832,8 @@ function dcValidateTargets(targets, cwd) {
14569
14832
  const lstatBlock = dcLstatAncestorWalk(filesystemTarget, cwd);
14570
14833
  if (lstatBlock)
14571
14834
  return lstatBlock;
14572
- const basename5 = path23.basename(filesystemTarget);
14573
- 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)) {
14574
14837
  continue;
14575
14838
  }
14576
14839
  if (DC_FS_ROOTS.has(t) || DC_FS_ROOTS.has(t.replace(/\//g, "\\"))) {
@@ -14656,8 +14919,8 @@ function dcEvaluateRecursiveDeleteTargets(input) {
14656
14919
  reason: `BLOCKED: Recursive delete target "${safeTarget}" is not strictly contained by the workspace.`
14657
14920
  };
14658
14921
  }
14659
- const basename5 = path23.basename(resolved);
14660
- if (!DC_SAFE_TARGETS.has(basename5)) {
14922
+ const basename6 = path23.basename(resolved);
14923
+ if (!DC_SAFE_TARGETS.has(basename6)) {
14661
14924
  return {
14662
14925
  allowed: false,
14663
14926
  code: "DESTRUCTIVE_TARGET_NOT_SAFE_ARTIFACT",
@@ -22007,7 +22270,7 @@ function planMerges(active, threshold, maxComparisons, maxMerges) {
22007
22270
  };
22008
22271
  }
22009
22272
  async function sweepActiveNearDuplicates(directory, options = {}) {
22010
- const { loadPluginConfigWithMeta: loadPluginConfigWithMeta2 } = await import("./index-hgrzjhsf.js");
22273
+ const { loadPluginConfigWithMeta: loadPluginConfigWithMeta2 } = await import("./index-vjqe1z2w.js");
22011
22274
  const { config } = loadPluginConfigWithMeta2(directory);
22012
22275
  const sweepConfig = config.learning?.dedup_sweep;
22013
22276
  if (sweepConfig?.enabled === false)
@@ -23091,7 +23354,7 @@ async function runCuratorPhase(directory, phase, agentsDispatched, config, knowl
23091
23354
  }
23092
23355
  if (config.skill_generation_enabled === true && skillCandidates.length > 0) {
23093
23356
  try {
23094
- const skillModule = await import("./skill-generator-a48t83hf.js");
23357
+ const skillModule = await import("./skill-generator-bvph9d98.js");
23095
23358
  for (const cand of skillCandidates) {
23096
23359
  if (cand.confidence < (config.min_skill_confidence ?? DEFAULT_SKILL_MIN_CONFIDENCE)) {
23097
23360
  continue;
@@ -23551,7 +23814,7 @@ async function applyCuratorKnowledgeUpdates(directory, recommendations, knowledg
23551
23814
  } catch {}
23552
23815
  }
23553
23816
  if (contradictedEntries.length > 0 && knowledgeConfig.contradiction_threshold_action === "quarantine") {
23554
- const { maybeQuarantineOnContradiction } = await import("./knowledge-escalator-twwz5a0d.js");
23817
+ const { maybeQuarantineOnContradiction } = await import("./knowledge-escalator-58tw74js.js");
23555
23818
  for (const { id } of contradictedEntries) {
23556
23819
  try {
23557
23820
  await maybeQuarantineOnContradiction(directory, id, knowledgeConfig.contradiction_quarantine_threshold, knowledgeConfig.contradiction_quarantine_window_days);
@@ -24370,7 +24633,7 @@ async function runCuratorPostMortem(directory, options = {}) {
24370
24633
  warnings.push("Failed to collect knowledge summary.");
24371
24634
  }
24372
24635
  try {
24373
- const { getScanStatus } = await import("./scan-cursor-acsjpv2n.js");
24636
+ const { getScanStatus } = await import("./scan-cursor-7tqsvbp8.js");
24374
24637
  const scanStatus = await getScanStatus(directory);
24375
24638
  if (scanStatus.remaining_estimate > 0) {
24376
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.`);
@@ -24542,9 +24805,9 @@ var _internals28 = {
24542
24805
  verifyPostMortemKnowledgeActions,
24543
24806
  repairPostMortemActions,
24544
24807
  loadDefaultKnowledgeConfig: async (directory) => {
24545
- const { KnowledgeConfigSchema: KnowledgeConfigSchema2 } = await import("./schema-4dy7emnw.js");
24808
+ const { KnowledgeConfigSchema: KnowledgeConfigSchema2 } = await import("./schema-b046zss2.js");
24546
24809
  try {
24547
- const { loadPluginConfigWithMeta: loadPluginConfigWithMeta2 } = await import("./index-hgrzjhsf.js");
24810
+ const { loadPluginConfigWithMeta: loadPluginConfigWithMeta2 } = await import("./index-vjqe1z2w.js");
24548
24811
  const loaded = loadPluginConfigWithMeta2(directory);
24549
24812
  return KnowledgeConfigSchema2.parse(loaded.config.knowledge ?? {});
24550
24813
  } catch {
@@ -24552,11 +24815,11 @@ var _internals28 = {
24552
24815
  }
24553
24816
  },
24554
24817
  applyCuratorKnowledgeUpdates: async (directory, recommendations, knowledgeConfig, generation) => {
24555
- const { applyCuratorKnowledgeUpdates: applyCuratorKnowledgeUpdates2 } = await import("./curator-b7f6nt8h.js");
24818
+ const { applyCuratorKnowledgeUpdates: applyCuratorKnowledgeUpdates2 } = await import("./curator-zp5qq24c.js");
24556
24819
  return applyCuratorKnowledgeUpdates2(directory, recommendations, knowledgeConfig, generation);
24557
24820
  },
24558
24821
  checkHivePromotions: async (entries, knowledgeConfig, directory) => {
24559
- const { checkHivePromotions } = await import("./hive-promoter-61sjdrgt.js");
24822
+ const { checkHivePromotions } = await import("./hive-promoter-0yg3gxg6.js");
24560
24823
  return checkHivePromotions(entries, knowledgeConfig, directory);
24561
24824
  },
24562
24825
  applyProposalTriage: async (directory, triage) => {
@@ -24565,7 +24828,7 @@ var _internals28 = {
24565
24828
  activateProposal,
24566
24829
  listSkills: listSkills2,
24567
24830
  sanitizeSlug
24568
- } = await import("./skill-generator-a48t83hf.js");
24831
+ } = await import("./skill-generator-bvph9d98.js");
24569
24832
  const result = {
24570
24833
  approved: [],
24571
24834
  rejected: [],
@@ -25790,9 +26053,9 @@ function hashContent(content) {
25790
26053
  return createHash9("sha1").update(content).digest("hex");
25791
26054
  }
25792
26055
  async function loadConfigForPolicyCurator(directory) {
25793
- const { KnowledgeConfigSchema: KnowledgeConfigSchema2 } = await import("./schema-4dy7emnw.js");
26056
+ const { KnowledgeConfigSchema: KnowledgeConfigSchema2 } = await import("./schema-b046zss2.js");
25794
26057
  try {
25795
- const { loadPluginConfigWithMeta: loadPluginConfigWithMeta2 } = await import("./index-hgrzjhsf.js");
26058
+ const { loadPluginConfigWithMeta: loadPluginConfigWithMeta2 } = await import("./index-vjqe1z2w.js");
25796
26059
  const { config: loadedConfig } = loadPluginConfigWithMeta2(directory);
25797
26060
  return KnowledgeConfigSchema2.parse(loadedConfig.knowledge ?? {});
25798
26061
  } catch {
@@ -29801,7 +30064,7 @@ async function runFinalizeStage(ctx) {
29801
30064
  }
29802
30065
  }
29803
30066
  try {
29804
- const { CuratorConfigSchema: CCS } = await import("./schema-4dy7emnw.js");
30067
+ const { CuratorConfigSchema: CCS } = await import("./schema-b046zss2.js");
29805
30068
  const { config: pmLoadedConfig } = _internals39.loadPluginConfigWithMeta(ctx.directory);
29806
30069
  const curatorCfg = CCS.parse(pmLoadedConfig.curator ?? {});
29807
30070
  if (curatorCfg.enabled && curatorCfg.postmortem_enabled) {
@@ -32074,9 +32337,9 @@ var _internals44 = {
32074
32337
  loadPluginConfigWithMeta,
32075
32338
  loadCuratorDeps: async () => {
32076
32339
  const [{ CuratorConfigSchema }, curator, { createCuratorLLMDelegate: createCuratorLLMDelegate2 }] = await Promise.all([
32077
- import("./schema-4dy7emnw.js"),
32078
- import("./curator-b7f6nt8h.js"),
32079
- import("./curator-llm-factory-0mkbw2hg.js")
32340
+ import("./schema-b046zss2.js"),
32341
+ import("./curator-zp5qq24c.js"),
32342
+ import("./curator-llm-factory-pr4bdfqw.js")
32080
32343
  ]);
32081
32344
  return { CuratorConfigSchema, curator, createCuratorLLMDelegate: createCuratorLLMDelegate2 };
32082
32345
  }
@@ -32981,7 +33244,7 @@ async function computeCurationDiagnostics(directory, entries) {
32981
33244
  let scanCompleted = false;
32982
33245
  let scanRemaining = 0;
32983
33246
  try {
32984
- const { getScanStatus } = await import("./scan-cursor-acsjpv2n.js");
33247
+ const { getScanStatus } = await import("./scan-cursor-7tqsvbp8.js");
32985
33248
  const status = await getScanStatus(directory);
32986
33249
  scanGeneration = status.generation;
32987
33250
  scanCompleted = status.completed;
@@ -32991,17 +33254,17 @@ async function computeCurationDiagnostics(directory, entries) {
32991
33254
  try {
32992
33255
  const { isLinked: isLinked2 } = await import("./knowledge-link-q46k9adx.js");
32993
33256
  if (isLinked2(directory)) {
32994
- const { KnowledgeConfigSchema: KnowledgeConfigSchema2 } = await import("./schema-4dy7emnw.js");
33257
+ const { KnowledgeConfigSchema: KnowledgeConfigSchema2 } = await import("./schema-b046zss2.js");
32995
33258
  let config;
32996
33259
  try {
32997
- const { loadPluginConfigWithMeta: loadPluginConfigWithMeta2 } = await import("./index-hgrzjhsf.js");
33260
+ const { loadPluginConfigWithMeta: loadPluginConfigWithMeta2 } = await import("./index-vjqe1z2w.js");
32998
33261
  const loaded = loadPluginConfigWithMeta2(directory);
32999
33262
  config = KnowledgeConfigSchema2.parse(loaded.config.knowledge ?? {});
33000
33263
  } catch {
33001
33264
  config = KnowledgeConfigSchema2.parse({});
33002
33265
  }
33003
33266
  const { cohortConfigFingerprint: cohortConfigFingerprint2 } = await import("./config-fingerprint-v22xh5a2.js");
33004
- const { buildConfigFingerprintInput: buildConfigFingerprintInput2, readCohortConfigFingerprint } = await import("./curation-policy-bn7g8n40.js");
33267
+ const { buildConfigFingerprintInput: buildConfigFingerprintInput2, readCohortConfigFingerprint } = await import("./curation-policy-7n6t8he4.js");
33005
33268
  const currentFp = cohortConfigFingerprint2(buildConfigFingerprintInput2(config));
33006
33269
  const cohortFp = await readCohortConfigFingerprint(directory);
33007
33270
  configMatch = cohortFp === null ? null : currentFp === cohortFp;
@@ -34212,7 +34475,7 @@ async function handleDoctorCommand(directory, args) {
34212
34475
  const result = runConfigDoctor(config, directory);
34213
34476
  let output;
34214
34477
  if (enableAutoFix && result.hasAutoFixableIssues) {
34215
- const { runConfigDoctorWithFixes } = await import("./config-doctor-0b5z13vq.js");
34478
+ const { runConfigDoctorWithFixes } = await import("./config-doctor-shc0n4q1.js");
34216
34479
  const fixResult = await runConfigDoctorWithFixes(directory, config, true, {
34217
34480
  applyLossy: true
34218
34481
  });
@@ -36366,7 +36629,7 @@ function countCodeLines(content) {
36366
36629
  return lines.length;
36367
36630
  }
36368
36631
  function isTestFile(filePath) {
36369
- const basename11 = path61.basename(filePath);
36632
+ const basename12 = path61.basename(filePath);
36370
36633
  const _ext = path61.extname(filePath).toLowerCase();
36371
36634
  const testPatterns = [
36372
36635
  ".test.",
@@ -36382,7 +36645,7 @@ function isTestFile(filePath) {
36382
36645
  ".spec.jsx"
36383
36646
  ];
36384
36647
  for (const pattern of testPatterns) {
36385
- if (basename11.includes(pattern)) {
36648
+ if (basename12.includes(pattern)) {
36386
36649
  return true;
36387
36650
  }
36388
36651
  }
@@ -45860,8 +46123,8 @@ function toSafeRelativePath(rootDir, targetPath) {
45860
46123
  if (!relative11 || relative11 === ".")
45861
46124
  return ".";
45862
46125
  if (relative11.startsWith("..") || path78.isAbsolute(relative11)) {
45863
- const basename16 = path78.basename(targetPath).replace(/\\/g, "/");
45864
- return basename16 || ".";
46126
+ const basename17 = path78.basename(targetPath).replace(/\\/g, "/");
46127
+ return basename17 || ".";
45865
46128
  }
45866
46129
  return relative11;
45867
46130
  }
@@ -47717,21 +47980,21 @@ function hasCompoundTestExtension(filename) {
47717
47980
  const lower = filename.toLowerCase();
47718
47981
  return COMPOUND_TEST_EXTENSIONS.some((ext) => lower.endsWith(ext));
47719
47982
  }
47720
- function isLanguageSpecificTestFile(basename17) {
47721
- const lower = basename17.toLowerCase();
47983
+ function isLanguageSpecificTestFile(basename18) {
47984
+ const lower = basename18.toLowerCase();
47722
47985
  if (lower.endsWith("_test.go"))
47723
47986
  return true;
47724
47987
  if (lower.endsWith(".py") && (lower.startsWith("test_") || lower.endsWith("_test.py")))
47725
47988
  return true;
47726
47989
  if (lower.endsWith("_spec.rb"))
47727
47990
  return true;
47728
- 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")))
47729
47992
  return true;
47730
47993
  if (lower.endsWith(".cs") && (lower.endsWith("test.cs") || lower.endsWith("tests.cs")))
47731
47994
  return true;
47732
- 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")))
47733
47996
  return true;
47734
- 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")))
47735
47998
  return true;
47736
47999
  if (lower.endsWith(".tests.ps1"))
47737
48000
  return true;
@@ -47739,23 +48002,23 @@ function isLanguageSpecificTestFile(basename17) {
47739
48002
  }
47740
48003
  function isConventionTestFilePath(filePath) {
47741
48004
  const normalizedPath = filePath.replace(/\\/g, "/");
47742
- const basename17 = path82.basename(filePath);
47743
- 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);
47744
48007
  }
47745
48008
  function getTestFilesFromConvention(sourceFiles, workingDir = process.cwd()) {
47746
48009
  const testFiles = [];
47747
48010
  for (const file of sourceFiles) {
47748
48011
  const absoluteFile = resolveWorkspacePath(file, workingDir);
47749
48012
  const relativeFile = path82.relative(workingDir, absoluteFile);
47750
- const basename17 = path82.basename(absoluteFile);
48013
+ const basename18 = path82.basename(absoluteFile);
47751
48014
  const dirname30 = path82.dirname(absoluteFile);
47752
48015
  const preferRelativeOutput = !path82.isAbsolute(file);
47753
48016
  if (isConventionTestFilePath(relativeFile) || isConventionTestFilePath(file)) {
47754
48017
  dedupePush(testFiles, toWorkspaceOutputPath(absoluteFile, workingDir, preferRelativeOutput));
47755
48018
  continue;
47756
48019
  }
47757
- const nameWithoutExt = basename17.replace(/\.[^.]+$/, "");
47758
- const ext = path82.extname(basename17);
48020
+ const nameWithoutExt = basename18.replace(/\.[^.]+$/, "");
48021
+ const ext = path82.extname(basename18);
47759
48022
  const genericTestNames = [
47760
48023
  `${nameWithoutExt}.spec${ext}`,
47761
48024
  `${nameWithoutExt}.test${ext}`
@@ -47766,7 +48029,7 @@ function getTestFilesFromConvention(sourceFiles, workingDir = process.cwd()) {
47766
48029
  ...languageSpecificTestNames
47767
48030
  ].map((candidateName) => path82.join(dirname30, candidateName));
47768
48031
  const testDirectoryNames = [
47769
- basename17,
48032
+ basename18,
47770
48033
  ...genericTestNames,
47771
48034
  ...languageSpecificTestNames
47772
48035
  ];
@@ -57551,7 +57814,7 @@ function buildDetailedHelp(commandName, entry) {
57551
57814
  async function handleHelpCommand(ctx) {
57552
57815
  const targetCommand = ctx.args.join(" ");
57553
57816
  if (!targetCommand) {
57554
- const { buildHelpText } = await import("./index-ff7b0cr5.js");
57817
+ const { buildHelpText } = await import("./index-4rje8dc0.js");
57555
57818
  return buildHelpText();
57556
57819
  }
57557
57820
  const tokens = targetCommand.split(/\s+/);
@@ -57560,7 +57823,7 @@ async function handleHelpCommand(ctx) {
57560
57823
  return _internals18.buildDetailedHelp(resolved.key, resolved.entry);
57561
57824
  }
57562
57825
  const similar = _internals18.findSimilarCommands(targetCommand);
57563
- const { buildHelpText: fullHelp } = await import("./index-ff7b0cr5.js");
57826
+ const { buildHelpText: fullHelp } = await import("./index-4rje8dc0.js");
57564
57827
  if (similar.length > 0) {
57565
57828
  return `Command '/swarm ${targetCommand}' not found.
57566
57829
 
@@ -57737,7 +58000,7 @@ var COMMAND_REGISTRY = {
57737
58000
  },
57738
58001
  "guardrail explain": {
57739
58002
  handler: async (ctx) => {
57740
- const { handleGuardrailExplain } = await import("./guardrail-explain-p2htdqkz.js");
58003
+ const { handleGuardrailExplain } = await import("./guardrail-explain-thddj6jv.js");
57741
58004
  return handleGuardrailExplain(ctx.directory, ctx.args);
57742
58005
  },
57743
58006
  description: "Dry-run: show what the guardrails would do to a command or write target (executes nothing)",
@@ -57747,7 +58010,7 @@ var COMMAND_REGISTRY = {
57747
58010
  },
57748
58011
  "guardrail-explain": {
57749
58012
  handler: async (ctx) => {
57750
- const { handleGuardrailExplain } = await import("./guardrail-explain-p2htdqkz.js");
58013
+ const { handleGuardrailExplain } = await import("./guardrail-explain-thddj6jv.js");
57751
58014
  return handleGuardrailExplain(ctx.directory, ctx.args);
57752
58015
  },
57753
58016
  description: "Dry-run: show what the guardrails would do to a command or write target (executes nothing)",
@@ -57757,7 +58020,7 @@ var COMMAND_REGISTRY = {
57757
58020
  },
57758
58021
  "guardrail-log": {
57759
58022
  handler: async (ctx) => {
57760
- const { handleGuardrailLog } = await import("./guardrail-log-rqt9wrzw.js");
58023
+ const { handleGuardrailLog } = await import("./guardrail-log-w8fmxe1x.js");
57761
58024
  return handleGuardrailLog(ctx.directory, ctx.args);
57762
58025
  },
57763
58026
  description: "Read the guardrail decision log (use --blocks-only for blocks)",
@@ -58203,7 +58466,7 @@ Subcommands:
58203
58466
  handler: (ctx) => handleAbortPrWorkflowCommand(ctx.directory, ctx.args, ctx.sessionID),
58204
58467
  description: "Clear a stuck PR_REVIEW/PR_FEEDBACK mechanical gate and stop the auto-resume loop [mode] [reason]",
58205
58468
  args: "[PR_REVIEW|PR_FEEDBACK] [reason...]",
58206
- 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.",
58207
58470
  category: "utility",
58208
58471
  toolPolicy: "restricted"
58209
58472
  },
@@ -59646,15 +59909,15 @@ HARD CONSTRAINTS (apply regardless of skill load success):
59646
59909
  - Test execution, explorer lanes, reviewer dispatch, and critic challenge are all permitted within this mode
59647
59910
  - Quality is the only metric \u2014 there is no speed, efficiency, or time exception; time, tokens, and agent dispatches are irrelevant to correctness
59648
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.
59649
- - 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.
59650
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.
59651
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.
59652
- - 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.
59653
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.
59654
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.
59655
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.
59656
- - 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.
59657
- - 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"\`, \`kind: "recovery"\`, and a one-line \`reason\` instead of looping. The tool refuses while PR workflow lanes are in flight (collect their results first) AND refuses a recovery abort of a bound review (pr_head_sha set) unless a controller-recorded terminal recovery condition (checkoutRecovery) exists \u2014 binding succeeded, so "checkout unreachable" no longer justifies recovery. Aborting clears the durable gate and stops the auto-resume loop; the user can force-clear a bound gate via \`/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.
59658
59921
  - Honor any free-text instructions that follow the closing bracket of the signal as additional reviewer focus, without weakening the validation ladder above.
59659
59922
 
59660
59923
  ### MODE: PR_FEEDBACK
@@ -59682,7 +59945,7 @@ HARD CONSTRAINTS (apply regardless of skill load success):
59682
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.
59683
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.
59684
59947
  - Patch only confirmed items plus the tests/docs they require; report closure status for every ledger item including disproved ones.
59685
- - 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"\`, \`kind: "recovery"\`, and a one-line \`reason\` instead of looping (the same bound-recovery refusal applies: a recovery abort of a bound gate requires a checkoutRecovery terminal condition). 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.
59686
59949
  - Do NOT resolve or mark GitHub review threads resolved unless the user explicitly instructs it.
59687
59950
  - Honor any free-text instructions that follow the closing bracket of the signal as additional scope, without dropping any ledger item.
59688
59951
  - Quality is the only metric \u2014 there is no speed, efficiency, or time exception; time, tokens, and agent dispatches are irrelevant to correctness