@danmoisan/drm-copilot-mcp 1.1.19 → 1.1.20

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 (112) hide show
  1. package/out/mcp-server.js +165 -4
  2. package/package.json +1 -1
  3. package/resources/claude-customizations/.claude/agents/atomic-executor.md +17 -6
  4. package/resources/claude-customizations/.claude/agents/atomic-planner.md +10 -2
  5. package/resources/claude-customizations/.claude/agents/epic-orchestrator.md +5 -0
  6. package/resources/claude-customizations/.claude/agents/orchestrator.md +4 -1
  7. package/resources/claude-customizations/.claude/agents/parallel-orchestrator.md +4 -0
  8. package/resources/claude-customizations/.claude/agents/task-researcher.md +8 -0
  9. package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate-command.ps1 +190 -0
  10. package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate.ps1 +22 -53
  11. package/resources/claude-customizations/.claude/hooks/hook-docs-only-diff.ps1 +5 -1
  12. package/resources/claude-customizations/.claude/hooks/premortem-section.ps1 +149 -0
  13. package/resources/claude-customizations/.claude/hooks/validate-executor-output.ps1 +210 -84
  14. package/resources/claude-customizations/.claude/hooks/validate-planner-output.ps1 +13 -1
  15. package/resources/claude-customizations/.claude/hooks/validate-task-researcher-output.ps1 +24 -5
  16. package/resources/claude-customizations/.claude/lib/ci-gate/Invoke-MergeConfirmationParser.ps1 +203 -0
  17. package/resources/claude-customizations/.claude/rules/plan-acceptance-gates.md +46 -10
  18. package/resources/claude-customizations/.claude/skills/atomic-plan-contract/SKILL.md +48 -16
  19. package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/SKILL.md +7 -3
  20. package/resources/claude-customizations/.claude/skills/csharp-change-budget-router/SKILL.md +2 -2
  21. package/resources/claude-customizations/.claude/skills/epic-orchestrate/SKILL.md +12 -3
  22. package/resources/claude-customizations/.claude/skills/epic-plan/SKILL.md +4 -2
  23. package/resources/claude-customizations/.claude/skills/execution-environment/SKILL.md +134 -0
  24. package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +22 -9
  25. package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +29 -5
  26. package/resources/claude-customizations/.claude/skills/powershell-change-budget-router/SKILL.md +1 -1
  27. package/resources/claude-customizations/.claude/skills/python-change-budget-router/SKILL.md +1 -1
  28. package/resources/claude-customizations/.claude/skills/remediation-handoff-atomic-planner/SKILL.md +14 -6
  29. package/resources/claude-customizations/.claude/skills/research-issue/SKILL.md +6 -0
  30. package/resources/claude-customizations/pack-manifests/core.json +4 -0
  31. package/resources/codex-and-agents-customizations/.agents/skills/README.md +1 -0
  32. package/resources/codex-and-agents-customizations/.agents/skills/atomic-executor/SKILL.md +8 -7
  33. package/resources/codex-and-agents-customizations/.agents/skills/atomic-plan-contract/SKILL.md +39 -4
  34. package/resources/codex-and-agents-customizations/.agents/skills/atomic-planner/SKILL.md +8 -6
  35. package/resources/codex-and-agents-customizations/.agents/skills/csharp-change-budget-router/SKILL.md +2 -2
  36. package/resources/codex-and-agents-customizations/.agents/skills/epic-orchestrate/SKILL.md +8 -1
  37. package/resources/codex-and-agents-customizations/.agents/skills/execution-environment/SKILL.md +134 -0
  38. package/resources/codex-and-agents-customizations/.agents/skills/orchestrate/SKILL.md +12 -2
  39. package/resources/codex-and-agents-customizations/.agents/skills/powershell-change-budget-router/SKILL.md +1 -1
  40. package/resources/codex-and-agents-customizations/.agents/skills/python-change-budget-router/SKILL.md +1 -1
  41. package/resources/codex-and-agents-customizations/.agents/skills/remediation-handoff-atomic-planner/SKILL.md +7 -3
  42. package/resources/codex-and-agents-customizations/.agents/skills/research-issue/SKILL.md +6 -0
  43. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c1.toml +10 -6
  44. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c2.toml +10 -6
  45. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c3-elevated.toml +10 -6
  46. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c3.toml +10 -6
  47. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c4.toml +10 -6
  48. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor.toml +10 -6
  49. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c1.toml +5 -2
  50. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c2.toml +5 -2
  51. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c3-elevated.toml +5 -2
  52. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c3.toml +5 -2
  53. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c4.toml +5 -2
  54. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner.toml +5 -2
  55. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planning.toml +1 -1
  56. package/resources/codex-and-agents-customizations/.codex/agents/csharp-atomic-planning.toml +1 -1
  57. package/resources/codex-and-agents-customizations/.codex/agents/epic-orchestrator.toml +2 -0
  58. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c1.toml +5 -2
  59. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c2.toml +5 -2
  60. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c3-elevated.toml +5 -2
  61. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c3.toml +5 -2
  62. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c4.toml +5 -2
  63. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator.toml +5 -2
  64. package/resources/codex-and-agents-customizations/.codex/agents/powershell-atomic-planning.toml +1 -1
  65. package/resources/codex-and-agents-customizations/.codex/agents/python-atomic-planning.toml +1 -1
  66. package/resources/codex-and-agents-customizations/.codex/agents/task-researcher-c1.toml +6 -0
  67. package/resources/codex-and-agents-customizations/.codex/agents/task-researcher-c2.toml +6 -0
  68. package/resources/codex-and-agents-customizations/.codex/agents/task-researcher-c3-elevated.toml +6 -0
  69. package/resources/codex-and-agents-customizations/.codex/agents/task-researcher-c3.toml +6 -0
  70. package/resources/codex-and-agents-customizations/.codex/agents/task-researcher-c4.toml +6 -0
  71. package/resources/codex-and-agents-customizations/.codex/agents/task-researcher.toml +6 -0
  72. package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
  73. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-merge-gate-command.ps1 +190 -0
  74. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-merge-gate.ps1 +17 -38
  75. package/resources/codex-and-agents-customizations/.codex/hooks/hook-docs-only-diff.ps1 +5 -1
  76. package/resources/codex-and-agents-customizations/.codex/prompts/feature-review-remediate.md +6 -4
  77. package/resources/codex-and-agents-customizations/pack-manifests/core.json +2 -0
  78. package/resources/customizations/.github/agents/atomic_executor.agent.md +21 -11
  79. package/resources/customizations/.github/agents/atomic_planning.agent.md +8 -3
  80. package/resources/customizations/.github/agents/csharp-atomic-executor.agent.md +13 -9
  81. package/resources/customizations/.github/agents/csharp-atomic-planning.agent.md +2 -2
  82. package/resources/customizations/.github/agents/csharp-orchestrator.agent.md +2 -2
  83. package/resources/customizations/.github/agents/csharp-typed-engineer.agent.md +3 -3
  84. package/resources/customizations/.github/agents/orchestrator.agent.md +8 -5
  85. package/resources/customizations/.github/agents/powershell-atomic-executor.agent.md +13 -9
  86. package/resources/customizations/.github/agents/powershell-atomic-planning.agent.md +2 -2
  87. package/resources/customizations/.github/agents/powershell-orchestrator.agent.md +2 -2
  88. package/resources/customizations/.github/agents/powershell-typed-engineer.agent.md +3 -3
  89. package/resources/customizations/.github/agents/python-atomic-executor.agent.md +13 -9
  90. package/resources/customizations/.github/agents/python-atomic-planning.agent.md +2 -2
  91. package/resources/customizations/.github/agents/python-orchestrator.agent.md +2 -2
  92. package/resources/customizations/.github/agents/task-researcher.agent.md +6 -0
  93. package/resources/customizations/.github/prompts/generate-atomic-plan.prompt.md +16 -10
  94. package/resources/customizations/.github/prompts/orchestrate-csharp-work.prompt.md +2 -2
  95. package/resources/customizations/.github/prompts/orchestrate-powershell-work.prompt.md +1 -1
  96. package/resources/customizations/.github/prompts/orchestrate-python-work.prompt.md +1 -1
  97. package/resources/customizations/.github/prompts/orchestrate-work.prompt.md +2 -2
  98. package/resources/customizations/.github/skills/atomic-plan-contract/SKILL.md +39 -4
  99. package/resources/customizations/.github/skills/csharp-change-budget-router/SKILL.md +1 -1
  100. package/resources/customizations/.github/skills/execution-environment/SKILL.md +134 -0
  101. package/resources/customizations/.github/skills/powershell-change-budget-router/SKILL.md +1 -1
  102. package/resources/customizations/.github/skills/remediation-handoff-atomic-planner/SKILL.md +7 -3
  103. package/resources/feature-templates/bug/plan.yyyy-MM-ddTHH-mm.md +10 -0
  104. package/resources/feature-templates/feature/plan.yyyy-MM-ddTHH-mm.md +11 -0
  105. package/resources/feature-templates/refactor/plan.yyyy-MM-ddTHH-mm.md +11 -0
  106. package/resources/powershell/PoshQC/PoshQC.CoverageMerge.psm1 +163 -0
  107. package/resources/powershell/PoshQC/PoshQC.Shard.psm1 +296 -0
  108. package/resources/powershell/PoshQC/PoshQC.Testing.psm1 +23 -4
  109. package/resources/powershell/PoshQC/PoshQC.psd1 +7 -1
  110. package/resources/powershell/PoshQC/PoshQC.psm1 +17 -5
  111. package/resources/powershell/PoshQC/README.md +13 -1
  112. package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +3 -0
package/out/mcp-server.js CHANGED
@@ -22913,7 +22913,7 @@ var BASE_REWRITE_RULES = [
22913
22913
  },
22914
22914
  {
22915
22915
  pattern: /Return the finalized plan for validation-only preflight through `atomic-executor` and preserve the same target file path across revision loops\. Do not claim nested worker delegation from within planner execution\./g,
22916
- replacement: 'Before reporting completion, explicitly spawn the `atomic-executor` subagent for validation-only preflight. The delegated prompt MUST include the exact directive `DIRECTIVE: PREFLIGHT VALIDATION ONLY`.\n\n- `atomic-executor` MUST return exactly one of:\n - `PREFLIGHT: ALL CLEAR`\n - `PREFLIGHT: REVISIONS REQUIRED`\n- Treat executor preflight findings as binding plan defects.\n- If preflight returns `PREFLIGHT: REVISIONS REQUIRED`, revise the same target plan file using the executor\'s precise plan delta and re-run preflight.\n- Reuse the same target plan file for every preflight revision iteration in the same planning cycle.\n- If the required `atomic-executor` handoff cannot be started or completed, stop and report blocked state; do not self-approve the plan.\n- Before reporting completion, the target plan MUST pass the `validate_orchestration_artifacts` MCP tool with `artifact_type: "plan"` and `artifact_path: <plan-path>`.\n\nDo not claim nested worker delegation from within planner execution.',
22916
+ replacement: 'Before reporting completion, explicitly spawn the `atomic-executor` subagent for validation-only preflight. The delegated prompt MUST include the exact directive `DIRECTIVE: PREFLIGHT VALIDATION ONLY`.\n\n- `atomic-executor` MUST return exactly one of:\n - `PREFLIGHT: ALL CLEAR`\n - `PREFLIGHT: REVISIONS REQUIRED`\n- Treat executor preflight findings as binding plan defects.\n- If preflight returns `PREFLIGHT: REVISIONS REQUIRED`, revise the same target plan file once using the executor\'s precise plan delta and its full `PREFLIGHT-DEFECT:` list, and run one confirming preflight. Preflight is single-pass: trivial defects are fixed in place by the executor, and a plan returns to the planner at most once; a second substantive result halts with `blocked_reason: "validator_failed"`.\n- Reuse the same target plan file for the single permitted preflight revision in the same planning cycle.\n- If the required `atomic-executor` handoff cannot be started or completed, stop and report blocked state; do not self-approve the plan.\n- Before reporting completion, the target plan MUST pass the `validate_orchestration_artifacts` MCP tool with `artifact_type: "plan"` and `artifact_path: <plan-path>`.\n\nDo not claim nested worker delegation from within planner execution.',
22917
22917
  description: "Expand the Claude atomic-planner preflight shorthand into the strict Codex atomic-executor handoff contract."
22918
22918
  },
22919
22919
  {
@@ -28957,6 +28957,166 @@ function extractPlanCommands(text2) {
28957
28957
  return commands;
28958
28958
  }
28959
28959
 
28960
+ // ../../extensions/drm-copilot/src/lib/validate/plan-gate-document.ts
28961
+ var G11_SEVERITY = "blocking";
28962
+ var G12_SEVERITY = "blocking";
28963
+ var G13_SEVERITY = "blocking";
28964
+ var PLAN_TASK_GUIDELINE = 40;
28965
+ var PLAN_LINE_GUIDELINE = 400;
28966
+ var G11_PROSE_PATTERN = String.raw`\b[Ll]ines?[ \t]+[0-9]+`;
28967
+ var G11_PATH_PATTERN = String.raw`[A-Za-z0-9_./-]+\.[A-Za-z][A-Za-z0-9]{0,5}:[0-9]+`;
28968
+ var G11_ANCHOR_PATTERN = String.raw`#L[0-9]+`;
28969
+ var G12_SHA_PATTERN = String.raw`\b[0-9a-f]{7,40}\b`;
28970
+ var G13_TASK_PATTERN = String.raw`^- \[[ xX]\] \[P[0-9]+-T[0-9]+\] `;
28971
+ var INLINE_CODE_PATTERN = "`[^`]*`";
28972
+ var FENCE_RE2 = /^\s*```/;
28973
+ var PROSE_RE = new RegExp(G11_PROSE_PATTERN, "g");
28974
+ var PATH_RE = new RegExp(G11_PATH_PATTERN, "g");
28975
+ var ANCHOR_RE = new RegExp(G11_ANCHOR_PATTERN, "g");
28976
+ var SHA_RE = new RegExp(G12_SHA_PATTERN, "g");
28977
+ var TASK_RE = new RegExp(G13_TASK_PATTERN);
28978
+ var INLINE_CODE_RE = new RegExp(INLINE_CODE_PATTERN, "g");
28979
+ var WHITESPACE_RE = new RegExp(`^${PLAN_WHITESPACE_CLASS}$`);
28980
+ var JUSTIFICATION_HEADING_RE = /^##[ \t]+Size Justification[ \t]*$/;
28981
+ var SECTION_END_RE = /^#{1,2}(?:[ \t]|$)/;
28982
+ var HEADING_RE = /^#{1,6}(?:[ \t]|$)/;
28983
+ var DIGITS = "0123456789";
28984
+ var HEX_LETTERS = "abcdef";
28985
+ var SHA_BEFORE_REJECT = "-_.";
28986
+ var SHA_AFTER_REJECT = "-_";
28987
+ function unfencedLines(text2) {
28988
+ const result = [];
28989
+ let inFence = false;
28990
+ for (const [index, line] of splitPlanLines(text2).entries()) {
28991
+ if (FENCE_RE2.test(line)) {
28992
+ inFence = !inFence;
28993
+ continue;
28994
+ }
28995
+ if (!inFence) {
28996
+ result.push([index + 1, line]);
28997
+ }
28998
+ }
28999
+ return result;
29000
+ }
29001
+ function maskInlineCode(line) {
29002
+ return line.replace(INLINE_CODE_RE, (span) => " ".repeat(span.length));
29003
+ }
29004
+ function isWhitespace(character) {
29005
+ return character !== void 0 && WHITESPACE_RE.test(character);
29006
+ }
29007
+ function enclosingToken(line, start, end) {
29008
+ let left = start;
29009
+ while (left > 0 && !isWhitespace(line[left - 1])) {
29010
+ left -= 1;
29011
+ }
29012
+ let right = end;
29013
+ while (right < line.length && !isWhitespace(line[right])) {
29014
+ right += 1;
29015
+ }
29016
+ return line.slice(left, right);
29017
+ }
29018
+ function lineCitations(text2) {
29019
+ const findings = [];
29020
+ for (const [lineNumber, line] of unfencedLines(text2)) {
29021
+ for (const match of maskInlineCode(line).matchAll(PROSE_RE)) {
29022
+ findings.push([lineNumber, match[0]]);
29023
+ }
29024
+ for (const match of line.matchAll(PATH_RE)) {
29025
+ const token = enclosingToken(
29026
+ line,
29027
+ match.index,
29028
+ match.index + match[0].length
29029
+ );
29030
+ if (!token.includes("://")) {
29031
+ findings.push([lineNumber, match[0]]);
29032
+ }
29033
+ }
29034
+ for (const match of line.matchAll(ANCHOR_RE)) {
29035
+ findings.push([lineNumber, match[0]]);
29036
+ }
29037
+ }
29038
+ return findings;
29039
+ }
29040
+ function isShaCandidate(line, start, end) {
29041
+ const token = line.slice(start, end);
29042
+ if (![...token].some((character) => DIGITS.includes(character))) {
29043
+ return false;
29044
+ }
29045
+ if (![...token].some((character) => HEX_LETTERS.includes(character))) {
29046
+ return false;
29047
+ }
29048
+ const before = start > 0 ? line.charAt(start - 1) : "";
29049
+ if (before !== "" && SHA_BEFORE_REJECT.includes(before)) {
29050
+ return false;
29051
+ }
29052
+ const after = end < line.length ? line.charAt(end) : "";
29053
+ return !(after !== "" && SHA_AFTER_REJECT.includes(after));
29054
+ }
29055
+ function shaCitations(text2) {
29056
+ const findings = [];
29057
+ for (const [lineNumber, line] of unfencedLines(text2)) {
29058
+ for (const match of line.matchAll(SHA_RE)) {
29059
+ if (isShaCandidate(line, match.index, match.index + match[0].length)) {
29060
+ findings.push([lineNumber, match[0]]);
29061
+ }
29062
+ }
29063
+ }
29064
+ return findings;
29065
+ }
29066
+ function hasSizeJustification(text2) {
29067
+ let inSection = false;
29068
+ for (const [, line] of unfencedLines(text2)) {
29069
+ if (JUSTIFICATION_HEADING_RE.test(line)) {
29070
+ inSection = true;
29071
+ continue;
29072
+ }
29073
+ if (!inSection) {
29074
+ continue;
29075
+ }
29076
+ if (SECTION_END_RE.test(line)) {
29077
+ inSection = false;
29078
+ continue;
29079
+ }
29080
+ if (normalizePlanWhitespace(line) !== "" && !HEADING_RE.test(line)) {
29081
+ return true;
29082
+ }
29083
+ }
29084
+ return false;
29085
+ }
29086
+ function planSize(text2) {
29087
+ const tasks = unfencedLines(text2).filter(
29088
+ ([, line]) => TASK_RE.test(line)
29089
+ ).length;
29090
+ return [tasks, splitPlanLines(text2).length];
29091
+ }
29092
+ function lineCitationMessage(lineNumber, value) {
29093
+ return `Line ${String(lineNumber)}: line-number citation \`${value}\` anchors on a position that moves when main moves; cite the symbol, heading, or test name instead.`;
29094
+ }
29095
+ function shaCitationMessage(lineNumber, value) {
29096
+ return `Line ${String(lineNumber)}: commit-SHA citation \`${value}\` names a commit that goes stale when main moves; cite the branch, ref name, or merge base derivation instead.`;
29097
+ }
29098
+ function planSizeMessage(tasks, lines) {
29099
+ return `Line 1: plan size \`${String(tasks)} tasks, ${String(lines)} lines\` exceeds the guideline of ${String(PLAN_TASK_GUIDELINE)} tasks or ${String(PLAN_LINE_GUIDELINE)} lines and carries no \`## Size Justification\` section; split the item or state why the size is necessary.`;
29100
+ }
29101
+ function channelFor(report, severity) {
29102
+ return severity === "blocking" ? report.blocking : report.warnings;
29103
+ }
29104
+ function evaluateDocumentGates(report, text2) {
29105
+ const g11 = channelFor(report, G11_SEVERITY);
29106
+ for (const [lineNumber, value] of lineCitations(text2)) {
29107
+ g11.push(lineCitationMessage(lineNumber, value));
29108
+ }
29109
+ const g12 = channelFor(report, G12_SEVERITY);
29110
+ for (const [lineNumber, value] of shaCitations(text2)) {
29111
+ g12.push(shaCitationMessage(lineNumber, value));
29112
+ }
29113
+ const [tasks, lines] = planSize(text2);
29114
+ const overGuideline = tasks > PLAN_TASK_GUIDELINE || lines > PLAN_LINE_GUIDELINE;
29115
+ if (overGuideline && !hasSizeJustification(text2)) {
29116
+ channelFor(report, G13_SEVERITY).push(planSizeMessage(tasks, lines));
29117
+ }
29118
+ }
29119
+
28960
29120
  // ../../extensions/drm-copilot/src/lib/validate/plan-gate-rules.ts
28961
29121
  function emptyPlanGateReport() {
28962
29122
  return { blocking: [], warnings: [] };
@@ -29344,7 +29504,7 @@ var G10_SEVERITY = "blocking";
29344
29504
  var BACKTICK = "`";
29345
29505
  var SCRATCH_DEFINITION_PATTERN = String.raw`(?:^|[^A-Za-z0-9_<>$/.-])${BACKTICK}?SCRATCH${BACKTICK}?(?:\*\*)?[ \t]*(?:means\b|:=|=|:|is (?:the|a|an)\b|resolves to\b|abbreviates\b|denotes\b|refers to\b|stands for\b)`;
29346
29506
  var SCRATCH_NAMESPACE_PATTERN = String.raw`issue-(?:<issue-num>|[0-9]+)`;
29347
- var FENCE_RE2 = /^\s*```/;
29507
+ var FENCE_RE3 = /^\s*```/;
29348
29508
  var DEFINITION_RE = new RegExp(SCRATCH_DEFINITION_PATTERN);
29349
29509
  var NAMESPACE_RE = new RegExp(SCRATCH_NAMESPACE_PATTERN);
29350
29510
  var LEADING_WHITESPACE_RE = new RegExp(`^${PLAN_WHITESPACE_CLASS}`);
@@ -29352,7 +29512,7 @@ function continuesLogicalLine(line) {
29352
29512
  if (normalizePlanWhitespace(line) === "" || !LEADING_WHITESPACE_RE.test(line)) {
29353
29513
  return false;
29354
29514
  }
29355
- return !FENCE_RE2.test(line);
29515
+ return !FENCE_RE3.test(line);
29356
29516
  }
29357
29517
  function isBareDefinition(logical) {
29358
29518
  const match = DEFINITION_RE.exec(logical);
@@ -29380,7 +29540,7 @@ function bareScratchDefinitions(text2) {
29380
29540
  recordIfBare(findings, pending);
29381
29541
  pending = null;
29382
29542
  }
29383
- if (FENCE_RE2.test(line)) {
29543
+ if (FENCE_RE3.test(line)) {
29384
29544
  inFence = !inFence;
29385
29545
  continue;
29386
29546
  }
@@ -29504,6 +29664,7 @@ function evaluatePlanGates(text2, context) {
29504
29664
  evaluateLiteralRules(report, text2, commands, context);
29505
29665
  }
29506
29666
  evaluateScratchGate(report, text2);
29667
+ evaluateDocumentGates(report, text2);
29507
29668
  return report;
29508
29669
  }
29509
29670
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danmoisan/drm-copilot-mcp",
3
- "version": "1.1.19",
3
+ "version": "1.1.20",
4
4
  "description": "Stdio MCP server exposing drm-copilot repo-automation tools.",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",
@@ -22,11 +22,13 @@ tools:
22
22
  - "mcp__drm-copilot__run_poshqc_analyze"
23
23
  - "mcp__drm-copilot__run_poshqc_test"
24
24
  - "mcp__drm-copilot__run_poshqc_analyze_autofix"
25
+ - "mcp__drm-copilot__validate_orchestration_artifacts"
25
26
  skills:
26
27
  - policy-compliance-order
27
28
  - atomic-plan-contract
28
29
  - evidence-and-timestamp-conventions
29
30
  - acceptance-criteria-tracking
31
+ - execution-environment
30
32
  memory: project
31
33
  hooks:
32
34
  SubagentStop:
@@ -95,19 +97,28 @@ Concurrent child runs in one session share one session scratchpad, so every scra
95
97
 
96
98
  ## Preflight Validation
97
99
 
98
- When receiving a plan with directive `DIRECTIVE: PREFLIGHT VALIDATION ONLY`, perform only format and structure validation. Return exactly one of:
100
+ A run is a preflight run when its delegation prompt carries the directive `DIRECTIVE: PREFLIGHT VALIDATION ONLY`. The preflight directive belongs in the delegation prompt, never in a plan file. A directive line left in a plan file does not make an execution run a preflight run. Preflight follows `### Single-Pass Preflight` in the `atomic-plan-contract` skill:
99
101
 
100
- - `PREFLIGHT: ALL CLEAR`
101
- - `PREFLIGHT: REVISIONS REQUIRED` (with precise plan delta)
102
+ - **Review the entire plan in one pass.** Check every phase, task, and prose region; do not stop at the first defect.
103
+ - **Classify every defect.** Trivial defects (stale or misspelled anchors, citation corrections including G11 and G12 findings, typos, literal formatting that does not change what is asserted, evidence filename spelling) are fixed in place. Substantive defects (missing or extra tasks, wrong file set or design, an acceptance criterion without a task, an assertion change, ordering, scope) are returned.
104
+ - **Fix in task text only.** Edit task text and plan preamble in the same plan file. Never add, remove, or reorder tasks. Record every fix in `<FEATURE>/evidence/other/preflight-fixes.<timestamp>.md`.
105
+ - **Return exactly one signal**, `PREFLIGHT: ALL CLEAR` or `PREFLIGHT: REVISIONS REQUIRED`, followed by one `CONVERGENCE:` line and then one line per defect:
106
+ - `PREFLIGHT-FIX: <task id or heading> | <defect class> | <change made>`
107
+ - `PREFLIGHT-DEFECT: <task id or heading> | <defect class> | <required change>`
108
+ - `preflight-fix-record: <path>` when at least one fix was made.
109
+ - `PREFLIGHT: ALL CLEAR` carries no `PREFLIGHT-DEFECT:` line; `PREFLIGHT: REVISIONS REQUIRED` carries at least one and lists every substantive defect found. A plan returns to the planner at most once; do not request or expect a further round.
110
+ - Validate the plan with `mcp__drm-copilot__validate_orchestration_artifacts` (`artifact_type: "plan"`), or with `poetry run python -m scripts.dev_tools.validate_orchestration_artifacts plan <path>` when branch-current rules are required (see the `execution-environment` skill).
102
111
 
103
112
  ## Blocking Protocol
104
113
 
105
- Blocking is permitted only during preflight validation (before `[P0-T1]`). When preflight-blocked:
114
+ Blocking is permitted only during preflight validation (before `[P0-T1]`). This includes a combined preflight-and-execute run of a proportionate remediation cycle: the run first performs single-pass preflight, fixes trivial defects in place, and stops before `[P0-T1]` only when a substantive defect remains. When preflight-blocked:
106
115
 
107
116
  1. State: `BLOCKED at preflight (before [P0-T1])`.
108
117
  2. Provide a short, concrete explanation of why.
109
- 3. Provide a plan delta (exact new or modified task text) that `atomic-planner` can apply, preserving the plan's `[P#-T#]` ID conventions.
110
- 4. Request that `atomic-planner` produce the corrected plan or that the user explicitly approve the delta.
118
+ 3. Provide a plan delta (exact new or modified task text) that `atomic-planner` can apply, preserving the plan's `[P#-T#]` ID conventions, with one `PREFLIGHT-DEFECT:` line per substantive defect.
119
+ 4. Request that `atomic-planner` produce the corrected plan or that the user explicitly approve the delta. Leave every task unchecked.
120
+
121
+ Report the blocked state with the uppercase `BLOCKED` token: the SubagentStop hook matches only that token, so lowercase or title-case "blocked" prose is not read as a blocked claim.
111
122
 
112
123
  After execution begins, do not block. Continue to completion using allowed micro-actions within the current task.
113
124
 
@@ -10,10 +10,12 @@ tools:
10
10
  - "Edit(artifacts/**)"
11
11
  - "Write(docs/**)"
12
12
  - "Write(artifacts/**)"
13
+ - "mcp__drm-copilot__validate_orchestration_artifacts"
13
14
  skills:
14
15
  - policy-compliance-order
15
16
  - atomic-plan-contract
16
17
  - evidence-and-timestamp-conventions
18
+ - execution-environment
17
19
  memory: project
18
20
  hooks:
19
21
  SubagentStop:
@@ -45,7 +47,9 @@ Generate plans using the atomic plan contract defined in the `atomic-plan-contra
45
47
  - Task IDs: `- [ ] [P#-T#] <description>`
46
48
  - Sequential task numbering within each phase
47
49
  - Phase 0: baseline capture (repo-policy reading tasks + language-specific toolchain baselines)
48
- - Final phase: full QA loop for each applicable language
50
+ - Final phase: full QA loop for each applicable language, ending with the self-review task that writes `<FEATURE>/evidence/qa-gates/self-review.<timestamp>.md`
51
+
52
+ Every plan is design-level per `## Design-Level Plan Contract (Mandatory)` in the `atomic-plan-contract` skill: tasks name files, symbols, behavior, tests, and acceptance; anchors are symbols, headings, or test names, never line numbers or commit SHAs; literals appear only where they are the asserted value; tool routes and launchers are not restated because the `execution-environment` skill owns them; and a plan above 40 tasks or 400 lines carries a `## Size Justification` section. Every plan carries a `## Pre-Mortem` section with one bullet for each of `Review checklists`, `Coverage`, `Mirror parity`, `File size`, `Test isolation`, `CI-only behavior`, and `Evidence`; the planner SubagentStop hook blocks a plan without it.
49
53
 
50
54
  ## Requirements
51
55
 
@@ -67,10 +71,14 @@ Concurrent child runs in one session share one session scratchpad, so every plan
67
71
 
68
72
  ## Preflight Validation
69
73
 
70
- Before handing the plan to executor preflight, return the exact bounded line record defined in `atomic-plan-contract`, starting with `PLANNER-INTERNAL-REVIEW: PASS` and ending with the required `PREFLIGHT:` signal. The bounded record must include exactly one passing `CITATION-TO-TREE`, `AC-TRACEABILITY`, and `SCOPE-BOUNDARY` declaration; one or more current-tree `CITATION: <repository-relative path> | <locator>` records; one complete unique `AC-INVENTORY:`; one complete `AC-MAPPING: <ID> | IMPLEMENTATION: <identifier> | TESTS: <identifier> | EVIDENCE: <identifier>` for every and only inventory ID; and exactly one `UNRESOLVED-GAPS: NONE`. A missing, duplicate, malformed, failed, blocked, or out-of-bounds record declaration requires `SELF-REVIEW: BLOCKED` and stops handoff.
74
+ Before handing the plan to executor preflight, return the exact bounded line record defined in `atomic-plan-contract`, starting with `PLANNER-INTERNAL-REVIEW: PASS` and ending with the required `PREFLIGHT:` signal. The bounded record must include exactly one passing `CITATION-TO-TREE`, `AC-TRACEABILITY`, and `SCOPE-BOUNDARY` declaration; one or more current-tree `CITATION: <repository-relative path> | <symbol, heading, or test name>` records, none of which uses a line-reference locator; one complete unique `AC-INVENTORY:`; one complete `AC-MAPPING: <ID> | IMPLEMENTATION: <identifier> | TESTS: <identifier> | EVIDENCE: <identifier>` for every and only inventory ID; and exactly one `UNRESOLVED-GAPS: NONE`. A missing, duplicate, malformed, failed, blocked, or out-of-bounds record declaration requires `SELF-REVIEW: BLOCKED` and stops handoff.
75
+
76
+ Before handoff, validate the plan with `mcp__drm-copilot__validate_orchestration_artifacts` (`artifact_type: "plan"`) and correct every finding the validator reports. The installed validator applies only the rules of the installed extension release, as the `execution-environment` skill states.
71
77
 
72
78
  Return the finalized plan for validation-only preflight through `atomic-executor` and preserve the same target file path across revision loops. Do not claim nested worker delegation from within planner execution.
73
79
 
80
+ Preflight is single-pass (`### Single-Pass Preflight` in `atomic-plan-contract`): the preflight executor applies the rule "Review the entire plan in one pass", fixes trivial defects in place, and returns substantive defects, and a plan returns to the planner at most once. Trivial defects are stale anchors, citation corrections, typos, and formatting that does not change an assertion; Substantive defects are missing or extra tasks, a wrong file set or design, an uncovered acceptance criterion, an assertion change, ordering, and scope. The preflight directive belongs in the delegation prompt, never in a plan file. The preflight return carries `PREFLIGHT-FIX: <task id or heading> | <defect class> | <change made>` lines for fixes, `PREFLIGHT-DEFECT: <task id or heading> | <defect class> | <required change>` lines for returned defects, and `preflight-fix-record: <path>` when a fix was made. When it returns, it carries the full `PREFLIGHT-DEFECT:` list; apply every listed change in one revision of the same file. If the preflight that follows still reports a substantive defect, the orchestrator halts with `blocked_reason: "validator_failed"` rather than requesting another revision.
81
+
74
82
  ## Output
75
83
 
76
84
  Write the finalized plan to the target path provided by the calling agent. Return the plan path and final preflight signal.
@@ -14,6 +14,8 @@ tools:
14
14
  - "Edit(artifacts/orchestration/**)"
15
15
  - "Bash(git *)"
16
16
  - "Bash(gh *)"
17
+ - "Bash(pwsh -NoProfile -File .claude/lib/ci-gate/Invoke-CiGateParser.ps1*)"
18
+ - "Bash(pwsh -NoProfile -File .claude/lib/ci-gate/Invoke-MergeConfirmationParser.ps1*)"
17
19
  - "mcp__drm-copilot__collect_pr_context"
18
20
  - "mcp__drm-copilot__validate_orchestration_artifacts"
19
21
  skills:
@@ -161,6 +163,9 @@ Do not report completion until:
161
163
 
162
164
  1. Every feature in the manifest has `merge_status: "merged"` or `"worktree_removed"`.
163
165
  2. The final integration-to-`main` PR has merged (`epic_merge_pr.merge_commit_sha` recorded).
166
+ `epic_merge_pr.merge_commit_sha` is recorded only after the merge is confirmed by polling and
167
+ ancestry check, because under a merge queue on `main` `gh pr merge` can return before the
168
+ merge happens (issue #952).
164
169
  3. `docs/features/epics/<epic-slug>/epic-status.md` reflects the completed state.
165
170
  4. The epic checkpoint passes `validate_epic_orchestrator_state_text` with
166
171
  `require_complete=True` (no wave-barrier violations, all features merged/removed).
@@ -33,6 +33,7 @@ skills:
33
33
  - atomic-plan-contract
34
34
  - acceptance-criteria-tracking
35
35
  - evidence-and-timestamp-conventions
36
+ - execution-environment
36
37
  memory: project
37
38
  hooks:
38
39
  SubagentStop:
@@ -126,6 +127,8 @@ Malformed-cycle rules:
126
127
  - R10: each well-formed outcome's `verdict` must equal the verdict derived from its findings' classes.
127
128
  - R11: `opened_by_review`, when present, must be a non-negative integer that indexes an object entry of `review_outcomes` whose `verdict` is `REMEDIATION_REQUIRED`.
128
129
 
130
+ Remediation mode lives in the inputs file, not in the checkpoint: each `remediation-inputs.<entry-ts>.md` carries exactly one line `Remediation-Mode: proportionate` or `Remediation-Mode: design`, so no checkpoint field is added. A cycle is proportionate when every `autonomous` finding can be fixed without changing a spec acceptance criterion, a public interface, or the item's file inventory beyond the files already in the branch diff plus their tests and mirrors; CI-failure findings are normally proportionate. A proportionate cycle runs one `atomic-planner` delegation and one `atomic-executor` delegation that performs single-pass preflight with in-place trivial fixes and then executes; the orchestrator records `preflight: {iterations: 1, final_status: "clear"}` before `execution_status`, which satisfies the malformed-cycle rule above. When that preflight finds a substantive defect, the executor stops with `BLOCKED at preflight (before [P0-T1])` and no checked task, and the cycle converts to design mode for one planner revision.
131
+
129
132
  Cycle-aware `next_step` uses the form `remediation.cycle_N.{plan,preflight,execute,reaudit,exit_check}`, where `N` is `completed_attempts + 1` (the active cycle number) and the sub-step names the current position in the loop; `current_cycle` remains a record position in `cycles[]`.
130
133
 
131
134
  ### CI Monitoring and Post-PR Remediation
@@ -154,7 +157,7 @@ Each cycle produces exactly five artifacts:
154
157
 
155
158
  ### Preflight Sub-State Semantics
156
159
 
157
- `preflight.final_status` is one of `{clear, changes_requested, pending}`. A `changes_requested` status routes back to `atomic-planner` for plan revision. An `execution_status` in `{in_progress, complete, failed}` recorded while `final_status != clear` is malformed. `preflight.iterations` counts the number of preflight passes performed in the cycle.
160
+ `preflight.final_status` is one of `{clear, changes_requested, pending}`, extended by `blocked_preflight_iteration_limit` as defined in the remediation-handoff skill. A `changes_requested` status routes back to `atomic-planner` for plan revision at most once per cycle, carrying the full `PREFLIGHT-DEFECT:` list; when the confirming preflight still reports a substantive defect, the orchestrator records `blocked_preflight_iteration_limit` and halts the cycle. An `execution_status` in `{in_progress, complete, failed}` recorded while `final_status != clear` is malformed. `preflight.iterations` counts the number of preflight passes performed in the cycle; trivial defects fixed in place by the preflight executor do not add a pass.
158
161
 
159
162
  ### Scope-change Rule
160
163
 
@@ -22,6 +22,7 @@ tools:
22
22
  - "Bash(pwsh -NoProfile -File .claude/lib/project-file-merge/Resolve-MergeableConflict.ps1*)"
23
23
  - "Bash(pwsh -NoProfile -NonInteractive -File .claude/lib/parallel-drift/Invoke-ParallelDriftDetection.ps1*)"
24
24
  - "Bash(pwsh -NoProfile -File .claude/lib/ci-gate/Invoke-CiGateParser.ps1*)"
25
+ - "Bash(pwsh -NoProfile -File .claude/lib/ci-gate/Invoke-MergeConfirmationParser.ps1*)"
25
26
  - "Bash(dotnet tool restore*)"
26
27
  - "Bash(dotnet csharpier check *)"
27
28
  - "Bash(dotnet build *)"
@@ -287,6 +288,9 @@ In `closed` mode, do not report completion until:
287
288
 
288
289
  1. Every non-withdrawn item has `merge_status` of `merged` or `worktree_removed`, each durably
289
290
  confirmed by `git` and `gh` rather than by an in-memory notification.
291
+ A `merged` value is recorded only after the merge is confirmed by polling and ancestry check
292
+ (step 3 of the `parallel-orchestrate` skill's `## Per-Item Merge to Main (Merge-on-Green)`),
293
+ because under a merge queue `gh pr merge` exits 0 before the merge happens.
290
294
  2. `docs/features/parallel/<slug>/parallel-status.md` has been regenerated a final time and
291
295
  reflects the completed state.
292
296
  3. The parallel checkpoint passes validation with `require_complete` (the F3 completion gate for
@@ -9,7 +9,9 @@ tools:
9
9
  - WebFetch
10
10
  - "Write(/docs/features/**/research/**)"
11
11
  - "Write(/docs/research/**)"
12
+ skills:
12
13
  - evidence-and-timestamp-conventions
14
+ - execution-environment
13
15
  memory: project
14
16
  hooks:
15
17
  SubagentStop:
@@ -71,6 +73,12 @@ The orchestrator resolves which root to use from whether an active feature folde
71
73
 
72
74
  - Propose a test strategy consistent with repository policy without writing test code.
73
75
 
76
+ ### 6. Pre-Mortem
77
+
78
+ - Feature-associated research (a research path under `docs/features/`) MUST carry a `## Pre-Mortem` section with one bullet beginning with each of the seven labels `Review checklists`, `Coverage`, `Mirror parity`, `File size`, `Test isolation`, `CI-only behavior`, and `Evidence`. Each bullet states how the next review or CI run could fail on that dimension and the mitigation the plan must carry; a label that does not apply is written as "not applicable" followed by the reason.
79
+ - One-off research under `docs/research/` is exempt.
80
+ - `.claude/hooks/validate-task-researcher-output.ps1` blocks feature research without a complete section. The `execution-environment` skill states the CI shape, contract tests, and isolation checks the section accounts for.
81
+
74
82
  ## Constraints
75
83
 
76
84
  - Write only to the orchestrator-supplied research path under `docs/features/<feature>/research/` (feature-associated) or `docs/research/` (one-off). Do not modify source code or configurations.
@@ -0,0 +1,190 @@
1
+ <#
2
+ .SYNOPSIS
3
+ Scope predicate and pull-request selector reader for the epic merge gate (issue #952).
4
+ .DESCRIPTION
5
+ Dot-sourced by enforce-epic-merge-gate.ps1 on both runtimes; this file is byte-identical in
6
+ .claude/hooks and .codex/hooks. Under a merge queue every gh pr merge spelling (--merge, -m,
7
+ --squash, -s, --rebase, -r, --auto, --admin, or no method flag) adds the pull request to the
8
+ queue, and the gh api mutations enqueuePullRequest, enablePullRequestAutoMerge, and
9
+ mergePullRequest and the REST pulls/<n>/merge endpoint also reach the base branch. All of
10
+ them are in scope. A gh pr merge whose only action flag is --disable-auto is out of scope.
11
+
12
+ Pure string logic only: no disk, process, network, clock, or environment access. The file
13
+ dot-sources and imports nothing; it consumes Get-CommandLineInvocation and
14
+ Get-CommandLineStructuralMatch, which the gate loads from hook-command-invocation.ps1 first.
15
+
16
+ Known limitation (issue #952 code review CR-5): the gh api scope reads the command text
17
+ only. A GraphQL mutation supplied from a file (gh api graphql -F query=@mutation.graphql,
18
+ or gh api --input payload.json) is not visible to this text-based scope and is not gated.
19
+ #>
20
+
21
+ # gh pr merge flags that perform or schedule a merge. Any of them keeps a command in scope even
22
+ # when it also carries --disable-auto.
23
+ $script:EpicMergeGateActionFlags = @('--merge', '-m', '--squash', '-s', '--rebase', '-r', '--auto', '--admin')
24
+
25
+ # gh pr merge flags, including the inherited --repo, that consume the following token as a value.
26
+ $script:EpicMergeGateValueFlags = @('-b', '--body', '-F', '--body-file', '-t', '--subject', '-A', '--author-email', '--match-head-commit', '-R', '--repo')
27
+
28
+ # GraphQL mutations that merge, enqueue, or schedule the merge of a pull request.
29
+ $script:EpicMergeGateMutationNames = @('enqueuePullRequest', 'enablePullRequestAutoMerge', 'mergePullRequest')
30
+
31
+ # The REST merge endpoint path; group 1 is the pull-request number.
32
+ $script:EpicMergeGateRestMergePattern = '(?:^|/)pulls/(\d+)/merge(?:$|[^\w-])'
33
+
34
+ function Get-EpicMergeGateFlagName {
35
+ <#
36
+ .SYNOPSIS
37
+ Return the flag name of a dash-led token without any '=value' suffix.
38
+ .OUTPUTS
39
+ System.String
40
+ #>
41
+ [CmdletBinding()]
42
+ [OutputType([string])]
43
+ param([Parameter(Mandatory)][AllowEmptyString()][string] $Token)
44
+
45
+ $split = $Token.IndexOf('=')
46
+ if ($split -gt 0) { return $Token.Substring(0, $split) }
47
+ return $Token
48
+ }
49
+
50
+ function ConvertFrom-EpicMergeGateSelector {
51
+ <#
52
+ .SYNOPSIS
53
+ Convert a gh pr merge selector to a pull-request number, or $null.
54
+ .DESCRIPTION
55
+ Accepts an all-digit selector or a URL whose path ends in /pull/<n>. A branch-name
56
+ selector carries no number and yields $null.
57
+ .OUTPUTS
58
+ System.Nullable[int]
59
+ #>
60
+ [CmdletBinding()]
61
+ [OutputType([int])]
62
+ param([Parameter(Mandatory)][AllowEmptyString()][string] $Selector)
63
+
64
+ $digits = $null
65
+ if ($Selector -match '^\d+$') {
66
+ $digits = $Selector
67
+ } elseif ($Selector -match '/pull/(\d+)/?(?:[?#].*)?$') {
68
+ $digits = $Matches[1]
69
+ }
70
+ $number = 0
71
+ if ($null -ne $digits -and [int]::TryParse($digits, [ref] $number)) { return $number }
72
+ return $null
73
+ }
74
+
75
+ function Test-EpicMergeGateDisableAutoOnly {
76
+ <#
77
+ .SYNOPSIS
78
+ Report whether a Structural gh pr merge match carries --disable-auto and no action flag.
79
+ .OUTPUTS
80
+ System.Boolean
81
+ #>
82
+ [CmdletBinding()]
83
+ [OutputType([bool])]
84
+ param([Parameter(Mandatory)][pscustomobject] $Match)
85
+
86
+ $tokens = [string[]]@($Match.Segment.Tokens)
87
+ $hasDisableAuto = $false
88
+ for ($index = $Match.OperandIndex; $index -lt $tokens.Count; $index++) {
89
+ $name = Get-EpicMergeGateFlagName -Token $tokens[$index]
90
+ if ($script:EpicMergeGateActionFlags -ccontains $name) { return $false }
91
+ if ($name -ceq '--disable-auto') { $hasDisableAuto = $true }
92
+ }
93
+ return $hasDisableAuto
94
+ }
95
+
96
+ function Test-EpicMergeGateApiMutationText {
97
+ <#
98
+ .SYNOPSIS
99
+ Report whether gh api text names a merge or enqueue mutation or the REST merge endpoint.
100
+ .OUTPUTS
101
+ System.Boolean
102
+ #>
103
+ [CmdletBinding()]
104
+ [OutputType([bool])]
105
+ param([Parameter(Mandatory)][AllowEmptyString()][AllowNull()][string] $Text)
106
+
107
+ if ([string]::IsNullOrEmpty($Text)) { return $false }
108
+ foreach ($name in $script:EpicMergeGateMutationNames) {
109
+ if ($Text.IndexOf($name, [System.StringComparison]::OrdinalIgnoreCase) -ge 0) { return $true }
110
+ }
111
+ return [regex]::IsMatch($Text, $script:EpicMergeGateRestMergePattern)
112
+ }
113
+
114
+ function Test-EpicMergeGateCommandInScope {
115
+ <#
116
+ .SYNOPSIS
117
+ Report whether a command line is a gated merge or enqueue action (issue #952).
118
+ .DESCRIPTION
119
+ True for any gh pr merge match, Structural or Indeterminate, unless the match is Structural
120
+ and its only action flag is --disable-auto; true for any gh api match whose tokens name a
121
+ merge or enqueue mutation or a pulls/<n>/merge path. Text that only mentions these words
122
+ produces no match and stays out of scope. An Indeterminate gh pr merge match stays in scope
123
+ (fail closed).
124
+ .OUTPUTS
125
+ System.Boolean
126
+ #>
127
+ [CmdletBinding()]
128
+ [OutputType([bool])]
129
+ param([Parameter(Mandatory)][AllowEmptyString()][AllowNull()][string] $CommandText)
130
+
131
+ foreach ($match in @(Get-CommandLineInvocation -CommandText $CommandText -CommandWord 'gh' -SubcommandPath @('pr', 'merge'))) {
132
+ if ($match.Status -ne 'Structural' -or -not (Test-EpicMergeGateDisableAutoOnly -Match $match)) { return $true }
133
+ }
134
+ foreach ($match in @(Get-CommandLineInvocation -CommandText $CommandText -CommandWord 'gh' -SubcommandPath @('api'))) {
135
+ if (Test-EpicMergeGateApiMutationText -Text (@($match.Segment.Tokens) -join ' ')) { return $true }
136
+ }
137
+ return $false
138
+ }
139
+
140
+ function Get-EpicMergeGateSelectorPrNumber {
141
+ <#
142
+ .SYNOPSIS
143
+ Resolve the pull-request number of a gated merge or enqueue command, or $null.
144
+ .DESCRIPTION
145
+ For gh pr merge, only the first Structural match is read. Value flags are skipped with
146
+ their values, other flags are skipped, an equals-joined --merge=<digits> yields its
147
+ digits (an equals-joined value that is not a number is skipped like any other flag),
148
+ and the first non-flag token (or the token after a bare '--') is the selector.
149
+ For gh api, the number comes from a pulls/<n>/merge token. Any other command yields
150
+ $null, which the gate handles through its no-number branches.
151
+ .OUTPUTS
152
+ System.Nullable[int]
153
+ #>
154
+ [CmdletBinding()]
155
+ [OutputType([int])]
156
+ param([Parameter(Mandatory)][AllowEmptyString()][AllowNull()][string] $CommandText)
157
+
158
+ $match = Get-CommandLineStructuralMatch -CommandText $CommandText -CommandWord 'gh' -SubcommandPath @('pr', 'merge')
159
+ if ($null -ne $match) {
160
+ $tokens = [string[]]@($match.Segment.Tokens)
161
+ $index = $match.OperandIndex
162
+ while ($index -lt $tokens.Count) {
163
+ $token = $tokens[$index]
164
+ if ($token -ceq '--') {
165
+ if ($index + 1 -lt $tokens.Count) { return ConvertFrom-EpicMergeGateSelector -Selector $tokens[$index + 1] }
166
+ return $null
167
+ }
168
+ if ($token.StartsWith('-') -and $token.Length -gt 1) {
169
+ $name = Get-EpicMergeGateFlagName -Token $token
170
+ if ($name -ceq '--merge' -and $token.Length -gt $name.Length) {
171
+ $equalsNumber = ConvertFrom-EpicMergeGateSelector -Selector $token.Substring($name.Length + 1)
172
+ if ($null -ne $equalsNumber) { return $equalsNumber }
173
+ }
174
+ $index += if ($script:EpicMergeGateValueFlags -ccontains $token) { 2 } else { 1 }
175
+ continue
176
+ }
177
+ return ConvertFrom-EpicMergeGateSelector -Selector $token
178
+ }
179
+ return $null
180
+ }
181
+
182
+ foreach ($apiMatch in @(Get-CommandLineInvocation -CommandText $CommandText -CommandWord 'gh' -SubcommandPath @('api'))) {
183
+ foreach ($token in @($apiMatch.Segment.Tokens)) {
184
+ $rest = [regex]::Match([string]$token, $script:EpicMergeGateRestMergePattern)
185
+ $number = 0
186
+ if ($rest.Success -and [int]::TryParse($rest.Groups[1].Value, [ref] $number)) { return $number }
187
+ }
188
+ }
189
+ return $null
190
+ }