Files
gh-duongdev-ccpm/commands/_shared-decision-helpers.md
2025-11-29 18:24:24 +08:00

25 KiB

Shared Decision Helper Functions (Always-Ask Policy Implementation)

This file provides reusable decision-making utilities for implementing the Always-Ask Policy across all CCPM commands. These helpers enable consistent, confidence-based decision making with automatic user interaction when confidence is below thresholds.

Overview

The Always-Ask Policy states: When confidence < 80%, explicitly ask the user rather than making assumptions.

These helper functions provide:

  • Confidence calculation - Score decisions 0-100 based on context
  • Ask/proceed logic - Automatically ask user when confidence is low
  • User question formatting - Standardized question templates
  • Fuzzy matching - Intelligent string matching with thresholds
  • Validation - Pre/during/post command validation

Usage in commands: Reference this file at the start of command execution:

READ: commands/_shared-decision-helpers.md

Then use the functions as described below.


Core Functions

1. calculateConfidence

Calculates confidence score (0-100) for a decision based on multiple signals.

/**
 * Calculate confidence score for a decision
 * @param {Object} context - Decision context
 * @param {any} context.input - User input to evaluate
 * @param {any} context.expected - Expected value/pattern
 * @param {Object} context.signals - Additional confidence signals
 * @param {number} context.signals.patternMatch - Pattern match confidence (0-100)
 * @param {number} context.signals.contextMatch - Context match confidence (0-100)
 * @param {number} context.signals.historicalSuccess - Historical success rate (0-100)
 * @param {number} context.signals.userPreference - User preference strength (0-100)
 * @returns {Object} Confidence result with score and reasoning
 */
function calculateConfidence(context) {
  let confidence = 0;
  const reasoning = [];

  // Signal 1: Pattern Match (weight: 50%)
  if (context.signals?.patternMatch !== undefined) {
    const patternScore = context.signals.patternMatch * 0.5;
    confidence += patternScore;
    reasoning.push(`Pattern match: ${context.signals.patternMatch}% (weighted: ${patternScore.toFixed(0)})`);
  }

  // Signal 2: Context Match (weight: 30%)
  if (context.signals?.contextMatch !== undefined) {
    const contextScore = context.signals.contextMatch * 0.3;
    confidence += contextScore;
    reasoning.push(`Context match: ${context.signals.contextMatch}% (weighted: ${contextScore.toFixed(0)})`);
  }

  // Signal 3: Historical Success (weight: 20%)
  if (context.signals?.historicalSuccess !== undefined) {
    const historyScore = context.signals.historicalSuccess * 0.2;
    confidence += historyScore;
    reasoning.push(`Historical success: ${context.signals.historicalSuccess}% (weighted: ${historyScore.toFixed(0)})`);
  }

  // Signal 4: User Preference (bonus: +10)
  if (context.signals?.userPreference !== undefined && context.signals.userPreference > 70) {
    const preferenceBonus = 10;
    confidence += preferenceBonus;
    reasoning.push(`User preference bonus: +${preferenceBonus}`);
  }

  // Cap at 100
  confidence = Math.min(confidence, 100);

  return {
    confidence: Math.round(confidence),
    reasoning: reasoning.join(', '),
    shouldAsk: confidence < 80,
    level: getConfidenceLevel(confidence)
  };
}

function getConfidenceLevel(confidence) {
  if (confidence >= 95) return 'CERTAIN';
  if (confidence >= 80) return 'HIGH';
  if (confidence >= 50) return 'MEDIUM';
  return 'LOW';
}

Usage Example:

// Calculate confidence for issue ID pattern
const result = calculateConfidence({
  input: "PSN-29",
  expected: /^[A-Z]+-\d+$/,
  signals: {
    patternMatch: 100,  // Exact regex match
    contextMatch: 90,    // Branch name contains PSN-29
    historicalSuccess: 95 // 95% past success rate
  }
});

// Result:
// {
//   confidence: 93,
//   reasoning: "Pattern match: 100% (weighted: 50), Context match: 90% (weighted: 27), Historical success: 95% (weighted: 19)",
//   shouldAsk: false,
//   level: "HIGH"
// }

2. shouldAsk

Determines if user should be asked based on confidence level and policy.

/**
 * Determine if user should be asked for confirmation
 * @param {number} confidence - Confidence score (0-100)
 * @param {Object} options - Additional options
 * @param {boolean} options.alwaysAsk - Force asking regardless of confidence
 * @param {boolean} options.neverAsk - Never ask (for certain operations)
 * @param {number} options.threshold - Custom confidence threshold (default: 80)
 * @returns {Object} Decision result
 */
function shouldAsk(confidence, options = {}) {
  // Override: Always ask (for safety-critical operations)
  if (options.alwaysAsk) {
    return {
      shouldAsk: true,
      reason: 'Safety-critical operation requires confirmation',
      displayMode: 'CONFIRM_REQUIRED'
    };
  }

  // Override: Never ask (for validation operations)
  if (options.neverAsk) {
    return {
      shouldAsk: false,
      reason: 'Validation operation, no user input needed',
      displayMode: 'AUTO_PROCEED'
    };
  }

  // Default threshold: 80%
  const threshold = options.threshold || 80;

  if (confidence >= 95) {
    return {
      shouldAsk: false,
      reason: 'Certain - proceeding automatically',
      displayMode: 'AUTO_PROCEED_SILENT'
    };
  } else if (confidence >= threshold) {
    return {
      shouldAsk: false,
      reason: `High confidence (${confidence}%) - proceeding with display`,
      displayMode: 'AUTO_PROCEED_WITH_DISPLAY'
    };
  } else if (confidence >= 50) {
    return {
      shouldAsk: true,
      reason: `Medium confidence (${confidence}%) - suggesting with confirmation`,
      displayMode: 'SUGGEST_AND_CONFIRM'
    };
  } else {
    return {
      shouldAsk: true,
      reason: `Low confidence (${confidence}%) - asking user`,
      displayMode: 'ASK_WITHOUT_SUGGESTION'
    };
  }
}

Usage Example:

// Check if we should ask
const decision = shouldAsk(65);
// Result: { shouldAsk: true, reason: "Medium confidence (65%) - suggesting with confirmation", displayMode: "SUGGEST_AND_CONFIRM" }

// Force asking for external writes
const externalWrite = shouldAsk(95, { alwaysAsk: true });
// Result: { shouldAsk: true, reason: "Safety-critical operation requires confirmation", displayMode: "CONFIRM_REQUIRED" }

3. askUserForClarification

Wrapper around AskUserQuestion tool with standardized formatting.

/**
 * Ask user for clarification using AskUserQuestion tool
 * @param {Object} questionConfig - Question configuration
 * @param {string} questionConfig.question - The question to ask
 * @param {string} questionConfig.header - Short header (max 12 chars)
 * @param {Array} questionConfig.options - Array of options
 * @param {any} questionConfig.options[].label - Option label
 * @param {string} questionConfig.options[].description - Option description
 * @param {any} questionConfig.options[].value - Option value (optional, defaults to label)
 * @param {boolean} questionConfig.multiSelect - Allow multiple selections (default: false)
 * @param {any} questionConfig.suggestion - Suggested value (for pre-selection)
 * @param {number} questionConfig.confidence - Confidence in suggestion (for display)
 * @returns {Promise<any>} User's selected value(s)
 */
async function askUserForClarification(questionConfig) {
  const {
    question,
    header,
    options,
    multiSelect = false,
    suggestion = null,
    confidence = null
  } = questionConfig;

  // Format options with suggestion highlighting
  const formattedOptions = options.map(opt => {
    const isSuggestion = suggestion && (opt.value === suggestion || opt.label === suggestion);

    return {
      label: isSuggestion ? `${opt.label} ⭐ (suggested)` : opt.label,
      description: isSuggestion && confidence
        ? `${opt.description} (${confidence}% confidence)`
        : opt.description
    };
  });

  // Display question context if medium/low confidence
  if (confidence !== null && confidence < 80) {
    console.log(`\n💡 AI Confidence: ${confidence}%`);
    if (suggestion) {
      console.log(`   Suggested: ${suggestion}`);
    }
    console.log('');
  }

  // Ask user
  const answer = await AskUserQuestion({
    questions: [{
      question,
      header,
      multiSelect,
      options: formattedOptions
    }]
  });

  // Extract answer (remove suggestion marker if present)
  const rawAnswer = answer[header];
  const cleanedAnswer = Array.isArray(rawAnswer)
    ? rawAnswer.map(a => a.replace(' ⭐ (suggested)', ''))
    : rawAnswer.replace(' ⭐ (suggested)', '');

  return cleanedAnswer;
}

Usage Example:

// Ask with suggestion (medium confidence)
const mode = await askUserForClarification({
  question: "What would you like to do?",
  header: "Mode",
  options: [
    { label: "Create new", description: "Start from scratch" },
    { label: "Plan existing", description: "Add plan to existing issue" },
    { label: "Update plan", description: "Modify existing plan" }
  ],
  suggestion: "Plan existing",
  confidence: 65
});

// Ask without suggestion (low confidence)
const commitType = await askUserForClarification({
  question: "Select commit type:",
  header: "Type",
  options: [
    { label: "feat", description: "New feature" },
    { label: "fix", description: "Bug fix" },
    { label: "docs", description: "Documentation" }
  ]
});

4. displayOptionsAndConfirm

Display what will happen and ask for confirmation.

/**
 * Display proposed action and ask for confirmation
 * @param {string} action - Action description
 * @param {Object} details - Action details to display
 * @param {Object} options - Display options
 * @param {string} options.title - Section title (default: "Proposed Action")
 * @param {string} options.emoji - Title emoji (default: "📋")
 * @param {boolean} options.requireExplicitYes - Require "yes" instead of any confirmation (default: false)
 * @returns {Promise<boolean>} True if confirmed, false otherwise
 */
async function displayOptionsAndConfirm(action, details, options = {}) {
  const {
    title = 'Proposed Action',
    emoji = '📋',
    requireExplicitYes = false
  } = options;

  console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
  console.log(`${emoji} ${title}`);
  console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
  console.log(action);
  console.log('');

  // Display details
  Object.entries(details).forEach(([key, value]) => {
    if (Array.isArray(value)) {
      console.log(`${key}:`);
      value.forEach(item => console.log(`  • ${item}`));
    } else {
      console.log(`${key}: ${value}`);
    }
  });

  console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');

  // Ask for confirmation
  const answer = await askUserForClarification({
    question: "Proceed with this action?",
    header: "Confirm",
    options: [
      { label: "Yes, proceed", description: "Execute the action shown above" },
      { label: "No, cancel", description: "Cancel and return" }
    ]
  });

  const confirmed = answer.toLowerCase().includes('yes');

  if (!confirmed) {
    console.log('❌ Cancelled\n');
  }

  return confirmed;
}

Usage Example:

// Display and confirm external write
const confirmed = await displayOptionsAndConfirm(
  "Update Jira ticket TRAIN-456",
  {
    "Status": "In Progress → Done",
    "Comment": "Completed via /ccpm:done",
    "Labels": ["ccpm", "completed"]
  },
  {
    title: "External System Write",
    emoji: "🚨",
    requireExplicitYes: true
  }
);

if (confirmed) {
  // Proceed with write
}

5. fuzzyMatch

Intelligent fuzzy matching with confidence scoring.

/**
 * Fuzzy match input against options with confidence scoring
 * @param {string} input - User input
 * @param {Array} options - Array of valid options (strings or objects with 'name' property)
 * @param {Object} config - Matching configuration
 * @param {number} config.threshold - Minimum similarity threshold (0-100, default: 60)
 * @param {boolean} config.caseSensitive - Case sensitive matching (default: false)
 * @param {Array} config.aliases - Map of aliases to canonical values
 * @returns {Object} Match result
 */
function fuzzyMatch(input, options, config = {}) {
  const {
    threshold = 60,
    caseSensitive = false,
    aliases = {}
  } = config;

  // Normalize input
  const normalizedInput = caseSensitive ? input : input.toLowerCase().trim();

  // Check aliases first
  if (aliases[normalizedInput]) {
    return {
      match: aliases[normalizedInput],
      confidence: 100,
      exactMatch: true,
      reason: 'Alias match'
    };
  }

  // Extract option strings
  const optionStrings = options.map(opt =>
    typeof opt === 'string' ? opt : opt.name || opt.label || String(opt)
  );

  // Normalize options
  const normalizedOptions = optionStrings.map(opt =>
    caseSensitive ? opt : opt.toLowerCase().trim()
  );

  // Strategy 1: Exact match
  const exactIndex = normalizedOptions.findIndex(opt => opt === normalizedInput);
  if (exactIndex !== -1) {
    return {
      match: optionStrings[exactIndex],
      confidence: 100,
      exactMatch: true,
      reason: 'Exact match'
    };
  }

  // Strategy 2: Starts with
  const startsWithIndex = normalizedOptions.findIndex(opt => opt.startsWith(normalizedInput));
  if (startsWithIndex !== -1) {
    return {
      match: optionStrings[startsWithIndex],
      confidence: 85,
      exactMatch: false,
      reason: 'Starts with match'
    };
  }

  // Strategy 3: Contains
  const containsIndex = normalizedOptions.findIndex(opt => opt.includes(normalizedInput));
  if (containsIndex !== -1) {
    return {
      match: optionStrings[containsIndex],
      confidence: 70,
      exactMatch: false,
      reason: 'Contains match'
    };
  }

  // Strategy 4: Levenshtein distance (simplified)
  const distances = normalizedOptions.map(opt => ({
    option: opt,
    distance: levenshteinDistance(normalizedInput, opt)
  }));

  distances.sort((a, b) => a.distance - b.distance);
  const closest = distances[0];

  // Calculate similarity percentage
  const maxLen = Math.max(normalizedInput.length, closest.option.length);
  const similarity = ((maxLen - closest.distance) / maxLen) * 100;

  if (similarity >= threshold) {
    const matchIndex = normalizedOptions.indexOf(closest.option);
    return {
      match: optionStrings[matchIndex],
      confidence: Math.round(similarity),
      exactMatch: false,
      reason: `Fuzzy match (${closest.distance} edits)`
    };
  }

  // No match found
  return {
    match: null,
    confidence: 0,
    exactMatch: false,
    reason: 'No match found',
    suggestions: optionStrings.slice(0, 3)
  };
}

// Simplified Levenshtein distance
function levenshteinDistance(str1, str2) {
  const len1 = str1.length;
  const len2 = str2.length;
  const matrix = Array(len1 + 1).fill(null).map(() => Array(len2 + 1).fill(0));

  for (let i = 0; i <= len1; i++) matrix[i][0] = i;
  for (let j = 0; j <= len2; j++) matrix[0][j] = j;

  for (let i = 1; i <= len1; i++) {
    for (let j = 1; j <= len2; j++) {
      const cost = str1[i - 1] === str2[j - 1] ? 0 : 1;
      matrix[i][j] = Math.min(
        matrix[i - 1][j] + 1,      // deletion
        matrix[i][j - 1] + 1,      // insertion
        matrix[i - 1][j - 1] + cost // substitution
      );
    }
  }

  return matrix[len1][len2];
}

Usage Example:

// Fuzzy match state name
const result = fuzzyMatch("in prog", [
  "Backlog",
  "In Progress",
  "In Review",
  "Done"
]);
// Result: { match: "In Progress", confidence: 70, exactMatch: false, reason: "Contains match" }

// Fuzzy match with aliases
const typeResult = fuzzyMatch("bugfix", ["feat", "fix", "docs"], {
  aliases: {
    "bugfix": "fix",
    "feature": "feat",
    "documentation": "docs"
  }
});
// Result: { match: "fix", confidence: 100, exactMatch: true, reason: "Alias match" }

Validation Functions

6. validateTransition

Validate workflow state transitions.

/**
 * Validate if state transition is allowed
 * @param {string} fromState - Current state
 * @param {string} toState - Target state
 * @param {Object} stateMachine - State machine definition
 * @returns {Object} Validation result
 */
function validateTransition(fromState, toState, stateMachine) {
  const currentStateConfig = stateMachine[fromState];

  if (!currentStateConfig) {
    return {
      valid: false,
      confidence: 0,
      error: `Unknown state: ${fromState}`,
      suggestions: Object.keys(stateMachine)
    };
  }

  const allowedNextStates = currentStateConfig.next_states || [];

  if (!allowedNextStates.includes(toState)) {
    return {
      valid: false,
      confidence: 0,
      error: `Cannot transition from ${fromState} to ${toState}`,
      allowedStates: allowedNextStates,
      suggestions: allowedNextStates.map(s => `Try: ${s}`)
    };
  }

  return {
    valid: true,
    confidence: currentStateConfig.confidence_to_transition || 90
  };
}

Pattern Matching Functions

7. Common Patterns

// Issue ID pattern (PROJECT-NUMBER)
const ISSUE_ID_PATTERN = /^[A-Z]+-\d+$/;

function isIssueId(input) {
  return ISSUE_ID_PATTERN.test(input);
}

function detectIssueIdConfidence(input) {
  if (ISSUE_ID_PATTERN.test(input)) {
    return { confidence: 95, match: true };
  }

  // Partial match (e.g., "PSN" or "123")
  if (/^[A-Z]+$/.test(input) || /^\d+$/.test(input)) {
    return { confidence: 30, match: false, suggestion: 'Provide full issue ID (e.g., PSN-29)' };
  }

  return { confidence: 0, match: false };
}

// Quoted string pattern (for titles)
const QUOTED_STRING_PATTERN = /^["'].*["']$/;

function isQuotedString(input) {
  return QUOTED_STRING_PATTERN.test(input);
}

// Detect change type from update text
function detectChangeType(text) {
  const lower = text.toLowerCase();

  const patterns = {
    scope_change: /(add|also|include|plus|additionally|extra)/i,
    approach_change: /(instead|different|change|use.*not|replace.*with)/i,
    simplification: /(remove|don't need|skip|simpler|drop|omit)/i,
    blocker: /(blocked|can't|cannot|doesn't work|issue|problem|error)/i,
    clarification: /.*/  // Default
  };

  for (const [type, pattern] of Object.entries(patterns)) {
    if (pattern.test(lower) && type !== 'clarification') {
      return {
        type,
        confidence: 75,
        keywords: lower.match(pattern)
      };
    }
  }

  return {
    type: 'clarification',
    confidence: 50,
    keywords: []
  };
}

Display Helpers

8. Confidence Display

/**
 * Display confidence level with appropriate emoji and color
 * @param {number} confidence - Confidence score (0-100)
 * @param {string} message - Message to display
 */
function displayWithConfidence(confidence, message) {
  let emoji, prefix;

  if (confidence >= 95) {
    emoji = '✅';
    prefix = 'CERTAIN';
  } else if (confidence >= 80) {
    emoji = '✅';
    prefix = `HIGH (${confidence}%)`;
  } else if (confidence >= 50) {
    emoji = '⚠️';
    prefix = `MEDIUM (${confidence}%)`;
  } else {
    emoji = '❓';
    prefix = `LOW (${confidence}%)`;
  }

  console.log(`${emoji} ${prefix}: ${message}`);
}

/**
 * Display decision summary
 * @param {Object} decision - Decision result
 */
function displayDecision(decision) {
  console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
  console.log('🎯 Decision Summary');
  console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');

  displayWithConfidence(decision.confidence, decision.suggestion || decision.action);

  if (decision.reasoning) {
    console.log(`\n📊 Reasoning: ${decision.reasoning}`);
  }

  if (decision.shouldAsk) {
    console.log(`\n👤 User input required`);
  } else {
    console.log(`\n🤖 Proceeding automatically`);
  }

  console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
}

Error Handling

9. Structured Error Messages

/**
 * Create structured error with suggestions
 * @param {string} message - Error message
 * @param {Object} details - Error details
 * @param {Array} details.suggestions - Actionable suggestions
 * @param {Array} details.availableOptions - Available options
 * @returns {Error} Enhanced error object
 */
function createStructuredError(message, details = {}) {
  const error = new Error(message);
  error.details = details;

  // Format error message with suggestions
  let fullMessage = message;

  if (details.availableOptions && details.availableOptions.length > 0) {
    fullMessage += '\n\nAvailable options:';
    details.availableOptions.forEach(opt => {
      fullMessage += `\n  • ${opt}`;
    });
  }

  if (details.suggestions && details.suggestions.length > 0) {
    fullMessage += '\n\nSuggestions:';
    details.suggestions.forEach(suggestion => {
      fullMessage += `\n  • ${suggestion}`;
    });
  }

  error.message = fullMessage;
  return error;
}

Integration Example

Complete example showing all helpers working together:

// Example: Command routing with Always-Ask Policy
async function executeSmartPlan(args) {
  const arg1 = args[0];
  const arg2 = args[1];

  // Step 1: Detect mode with confidence
  const issueIdCheck = detectIssueIdConfidence(arg1);

  let modeDecision;
  if (issueIdCheck.match) {
    // High confidence: Issue ID pattern
    if (arg2) {
      modeDecision = {
        mode: 'UPDATE',
        confidence: 95,
        reasoning: 'Issue ID with update text provided'
      };
    } else {
      modeDecision = {
        mode: 'PLAN',
        confidence: 95,
        reasoning: 'Issue ID without update text'
      };
    }
  } else if (isQuotedString(arg1)) {
    // High confidence: Quoted title
    modeDecision = {
      mode: 'CREATE',
      confidence: 90,
      reasoning: 'Quoted string indicates new task title'
    };
  } else {
    // Low confidence: Ambiguous
    modeDecision = {
      mode: null,
      confidence: 30,
      reasoning: 'Input format is ambiguous'
    };
  }

  // Step 2: Check if we should ask
  const askDecision = shouldAsk(modeDecision.confidence);

  // Step 3: Ask user if needed
  if (askDecision.shouldAsk) {
    displayDecision({
      ...modeDecision,
      shouldAsk: true,
      action: `Detected input: "${arg1}"`
    });

    const mode = await askUserForClarification({
      question: "What would you like to do?",
      header: "Mode",
      options: [
        { label: "Create new", description: "Create new task with this title" },
        { label: "Plan existing", description: "Plan existing issue" },
        { label: "Update plan", description: "Update existing plan" }
      ],
      suggestion: modeDecision.mode,
      confidence: modeDecision.confidence
    });

    modeDecision.mode = mode.replace(' new', '').replace(' existing', '').replace(' plan', '');
  } else {
    // High confidence: Display and proceed
    displayWithConfidence(modeDecision.confidence, `Mode: ${modeDecision.mode}`);
  }

  // Step 4: Execute mode
  switch (modeDecision.mode.toUpperCase()) {
    case 'CREATE':
      await executeCreate(arg1, arg2);
      break;
    case 'PLAN':
      await executePlan(arg1);
      break;
    case 'UPDATE':
      await executeUpdate(arg1, arg2);
      break;
  }
}

Best Practices

  1. Always calculate confidence - Don't guess, use signals
  2. Display confidence scores - Be transparent with users
  3. Provide reasoning - Explain why confidence is at a level
  4. Ask with suggestions - Pre-select high confidence options
  5. Validate early - Catch errors before execution
  6. Use fuzzy matching - Be forgiving with user input
  7. Log decisions - Track accuracy over time
  8. Fail gracefully - Provide actionable error messages

Testing Helpers

// Test confidence calculation
const testConfidence = calculateConfidence({
  input: "PSN-29",
  signals: {
    patternMatch: 100,
    contextMatch: 90,
    historicalSuccess: 95
  }
});
console.log('Confidence test:', testConfidence);

// Test fuzzy matching
const testMatch = fuzzyMatch("in prog", ["Backlog", "In Progress", "Done"]);
console.log('Fuzzy match test:', testMatch);

// Test should ask
const testAsk = shouldAsk(65);
console.log('Should ask test:', testAsk);

Performance Characteristics

Operation Avg Time Notes
calculateConfidence <1ms Synchronous calculation
shouldAsk <1ms Simple threshold check
askUserForClarification Varies Waits for user input
fuzzyMatch 1-5ms Depends on options count
displayOptionsAndConfirm Varies Waits for user input

Migration Guide

Old Pattern (no confidence tracking):

if (ISSUE_ID_PATTERN.test(arg1)) {
  mode = 'PLAN';
} else {
  mode = 'CREATE';
}

New Pattern (with Always-Ask Policy):

const issueIdCheck = detectIssueIdConfidence(arg1);
const decision = shouldAsk(issueIdCheck.confidence);

if (decision.shouldAsk) {
  mode = await askUserForClarification({ ... });
} else {
  displayWithConfidence(issueIdCheck.confidence, `Mode: ${mode}`);
}