diff --git a/.github/codex/prompts/fix_merge_conflicts.md b/.github/codex/prompts/fix_merge_conflicts.md new file mode 100644 index 00000000..cb28042b --- /dev/null +++ b/.github/codex/prompts/fix_merge_conflicts.md @@ -0,0 +1,103 @@ +# Fix Merge Conflicts + +This PR has **merge conflicts** that must be resolved before CI can run or the PR can be merged. + +## Your Task + +Resolve all merge conflicts by integrating changes from the base branch with this PR's changes. + +## Conflict Detection + +{{#if conflict_files}} +**Potentially conflicting files:** +{{#each conflict_files}} +- `{{this}}` +{{/each}} +{{else}} +Check `git status` to identify files with conflicts. +{{/if}} + +## Resolution Steps + +1. **Fetch latest base branch:** + ```bash + git fetch origin {{base_branch}} + ``` + > Note: Replace `{{base_branch}}` with the actual base branch name (e.g., `main` or `master`) + +2. **Attempt merge:** + ```bash + git merge origin/{{base_branch}} + ``` + +3. **For each conflicting file:** + - Look for conflict markers: `<<<<<<<`, `=======`, `>>>>>>>` + - Understand what each side (HEAD vs incoming) intended + - Combine the changes intelligently: + - If changes are to different parts: keep both + - If changes conflict: prefer the newer/more complete version + - If changes are incompatible: adapt the PR's code to work with new base + - Remove all conflict markers + +4. **Verify resolution:** + ```bash + # Check no conflict markers remain + git diff --check + + # Run the project's test suite (language-specific) + # For Python: pytest + # For JavaScript: npm test + # For other: check the project's README or CI config + ``` + +5. **Commit the resolution:** + ```bash + git add . + git commit -m "fix: resolve merge conflicts with {{base_branch}}" + ``` + +## Resolution Guidelines + +### When to prefer PR changes: +- PR adds new functionality not in main +- PR fixes a bug that main doesn't address +- PR has more complete implementation + +### When to prefer main changes: +- Base branch has breaking API changes PR must adapt to +- Base branch has bug fixes PR should incorporate +- Base branch renamed/moved files PR still references + +### When to combine: +- Both sides add different functions/methods +- Both sides add different imports +- Both sides modify different parts of the same function + +## Common Conflict Patterns + +### Import conflicts (Python example): +```python +<<<<<<< HEAD +from module import foo, bar +======= +from module import foo, baz +>>>>>>> origin/{{base_branch}} +``` +**Resolution:** Combine imports: `from module import foo, bar, baz` + +### Function modification conflicts: +Keep the more complete/correct version, or merge logic if both changes are needed. + +### Test file conflicts: +Usually keep both sets of tests unless they're duplicates. + +## Exit Criteria + +- All conflict markers removed from all files +- Code compiles/parses without syntax errors +- Tests pass (at least the ones that were passing before) +- Changes committed with descriptive message + +--- + +**Focus solely on resolving conflicts. Do not add new features or refactor code beyond what's needed for resolution.** diff --git a/.github/scripts/conflict_detector.js b/.github/scripts/conflict_detector.js new file mode 100644 index 00000000..95bb8e3b --- /dev/null +++ b/.github/scripts/conflict_detector.js @@ -0,0 +1,365 @@ +'use strict'; + +/** + * Conflict detector module for keepalive pipeline. + * Detects merge conflicts on PRs to trigger conflict-specific prompts. + */ + +const CONFLICT_PATTERNS = [ + /merge conflict/i, + /CONFLICT \(content\)/i, + /Automatic merge failed/i, + /fix conflicts and then commit/i, + /Merge branch .* into .* failed/i, + /<<<<<<< HEAD/, + /=======\n/, + />>>>>>> /, +]; + +/** + * Check if a PR has merge conflicts via GitHub API. + * @param {object} github - Octokit instance + * @param {object} context - GitHub Actions context + * @param {number} prNumber - PR number to check + * @returns {Promise<{hasConflict: boolean, source: string, files: string[]}>} + */ +async function checkGitHubMergeability(github, context, prNumber) { + try { + const { data: pr } = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: prNumber, + }); + + // mergeable_state can be: 'clean', 'dirty', 'unstable', 'blocked', 'behind', 'unknown' + // 'dirty' indicates merge conflicts + if (pr.mergeable_state === 'dirty' || pr.mergeable === false) { + // Try to get conflict files from the PR + const files = await getConflictFiles(github, context, prNumber); + return { + hasConflict: true, + source: 'github-api', + mergeableState: pr.mergeable_state, + files, + }; + } + + return { + hasConflict: false, + source: 'github-api', + mergeableState: pr.mergeable_state, + files: [], + }; + } catch (error) { + console.error(`Error checking PR mergeability: ${error.message}`); + return { + hasConflict: false, + source: 'error', + error: error.message, + files: [], + }; + } +} + +/** + * Get list of files that might have conflicts. + * Note: GitHub doesn't directly expose conflict files, so we check changed files. + * @param {object} github - Octokit instance + * @param {object} context - GitHub Actions context + * @param {number} prNumber - PR number + * @returns {Promise} + */ +async function getConflictFiles(github, context, prNumber) { + try { + const { data: files } = await github.rest.pulls.listFiles({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: prNumber, + per_page: 100, + }); + + // Return all changed files - actual conflicts will be subset + return files.map((f) => f.filename); + } catch (error) { + console.error(`Error getting PR files: ${error.message}`); + return []; + } +} + +/** + * Check CI logs for conflict indicators. + * @param {object} github - Octokit instance + * @param {object} context - GitHub Actions context + * @param {number} prNumber - PR number + * @param {string} headSha - Head commit SHA + * @returns {Promise<{hasConflict: boolean, source: string, matchedPatterns: string[]}>} + */ +async function checkCILogsForConflicts(github, context, prNumber, headSha) { + try { + // Get recent workflow runs for this PR's head SHA + const { data: runs } = await github.rest.actions.listWorkflowRunsForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + head_sha: headSha, + per_page: 10, + }); + + const failedRuns = runs.workflow_runs.filter( + (run) => run.conclusion === 'failure' + ); + + if (failedRuns.length === 0) { + return { hasConflict: false, source: 'ci-logs', matchedPatterns: [] }; + } + + // Check job logs for conflict patterns + for (const run of failedRuns.slice(0, 3)) { + // Limit to 3 most recent + try { + const { data: jobs } = await github.rest.actions.listJobsForWorkflowRun( + { + owner: context.repo.owner, + repo: context.repo.repo, + run_id: run.id, + } + ); + + for (const job of jobs.jobs.filter((j) => j.conclusion === 'failure')) { + // Get job logs + try { + const { data: logs } = + await github.rest.actions.downloadJobLogsForWorkflowRun({ + owner: context.repo.owner, + repo: context.repo.repo, + job_id: job.id, + }); + + const logText = typeof logs === 'string' ? logs : String(logs); + const matchedPatterns = []; + + for (const pattern of CONFLICT_PATTERNS) { + if (pattern.test(logText)) { + matchedPatterns.push(pattern.source || pattern.toString()); + } + } + + if (matchedPatterns.length > 0) { + return { + hasConflict: true, + source: 'ci-logs', + workflowRun: run.name, + job: job.name, + matchedPatterns, + }; + } + } catch (logError) { + // Log download might fail for old runs, continue + console.debug(`Could not download logs for job ${job.id}: ${logError.message}`); + continue; + } + } + } catch (jobError) { + console.debug(`Could not list jobs for run ${run.id}: ${jobError.message}`); + continue; + } + } + + return { hasConflict: false, source: 'ci-logs', matchedPatterns: [] }; + } catch (error) { + console.error(`Error checking CI logs: ${error.message}`); + return { hasConflict: false, source: 'error', error: error.message }; + } +} + +/** + * Check PR comments for conflict mentions. + * @param {object} github - Octokit instance + * @param {object} context - GitHub Actions context + * @param {number} prNumber - PR number + * @returns {Promise<{hasConflict: boolean, source: string}>} + */ +async function checkCommentsForConflicts(github, context, prNumber) { + try { + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + per_page: 20, + }); + + // Check recent comments (last 10) + const recentComments = comments.slice(-10); + + for (const comment of recentComments) { + for (const pattern of CONFLICT_PATTERNS) { + if (pattern.test(comment.body)) { + return { + hasConflict: true, + source: 'pr-comments', + commentId: comment.id, + commentAuthor: comment.user.login, + }; + } + } + } + + return { hasConflict: false, source: 'pr-comments' }; + } catch (error) { + console.error(`Error checking PR comments: ${error.message}`); + return { hasConflict: false, source: 'error', error: error.message }; + } +} + +/** + * Main conflict detection function. + * Checks multiple sources for merge conflict indicators. + * @param {object} github - Octokit instance + * @param {object} context - GitHub Actions context + * @param {number} prNumber - PR number to check + * @param {string} [headSha] - Optional head SHA for CI log check + * @returns {Promise} Conflict detection result + */ +async function detectConflicts(github, context, prNumber, headSha) { + const results = { + hasConflict: false, + detectionSources: [], + files: [], + details: {}, + }; + + // Method 1: Check GitHub mergeability (most reliable) + const githubResult = await checkGitHubMergeability( + github, + context, + prNumber + ); + results.detectionSources.push({ + source: 'github-api', + result: githubResult, + }); + + if (githubResult.hasConflict) { + results.hasConflict = true; + results.files = githubResult.files; + results.primarySource = 'github-api'; + results.details.mergeableState = githubResult.mergeableState; + } + + // Method 2: Check CI logs (if head SHA provided) + if (headSha) { + const ciResult = await checkCILogsForConflicts( + github, + context, + prNumber, + headSha + ); + results.detectionSources.push({ + source: 'ci-logs', + result: ciResult, + }); + + if (ciResult.hasConflict && !results.hasConflict) { + results.hasConflict = true; + results.primarySource = 'ci-logs'; + results.details.matchedPatterns = ciResult.matchedPatterns; + } + } + + // Method 3: Check PR comments + const commentResult = await checkCommentsForConflicts( + github, + context, + prNumber + ); + results.detectionSources.push({ + source: 'pr-comments', + result: commentResult, + }); + + if (commentResult.hasConflict && !results.hasConflict) { + results.hasConflict = true; + results.primarySource = 'pr-comments'; + } + + return results; +} + +/** + * Post a conflict detection comment on the PR. + * @param {object} github - Octokit instance + * @param {object} context - GitHub Actions context + * @param {number} prNumber - PR number + * @param {object} conflictResult - Result from detectConflicts + * @returns {Promise} + */ +async function postConflictComment(github, context, prNumber, conflictResult) { + if (!conflictResult.hasConflict) { + return; + } + + const files = conflictResult.files.slice(0, 10); // Limit to 10 files + const fileList = + files.length > 0 + ? `\n\n**Potentially affected files:**\n${files.map((f) => `- \`${f}\``).join('\n')}` + : ''; + + const body = `### ⚠️ Merge Conflict Detected + +This PR has merge conflicts that need to be resolved before it can be merged. + +**Detection source:** ${conflictResult.primarySource}${fileList} + +
+How to resolve + +1. Fetch the latest changes from the base branch +2. Merge or rebase your branch +3. Resolve any conflicts in affected files +4. Commit and push the resolved changes + +Or wait for the agent to attempt automatic resolution. +
+ +--- +*Auto-detected by conflict detector*`; + + // Check for existing conflict comment + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + per_page: 30, + }); + + const existingComment = comments.find( + (c) => + c.body.includes('### ⚠️ Merge Conflict Detected') && c.user.type === 'Bot' + ); + + if (existingComment) { + // Update existing comment + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existingComment.id, + body, + }); + } else { + // Create new comment + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + body, + }); + } +} + +module.exports = { + detectConflicts, + checkGitHubMergeability, + checkCILogsForConflicts, + checkCommentsForConflicts, + postConflictComment, + CONFLICT_PATTERNS, +}; diff --git a/docs/LABELS.md b/docs/LABELS.md index fcde4278..724303c4 100644 --- a/docs/LABELS.md +++ b/docs/LABELS.md @@ -11,17 +11,12 @@ This document describes all labels that trigger automated workflows or affect CI | `agent:codex` | Issue labeled | Triggers Codex agent assignment | | `agent:codex-invite` | Issue labeled | Invites Codex agent to participate | | `agent:needs-attention` | Auto-applied | Indicates agent needs human intervention | +| `agents:format` | Issue labeled | Formats raw issue into AGENT_ISSUE_TEMPLATE | +| `agents:formatted` | Auto-applied | Issue has been formatted by LangChain | | `status:ready` | Issue labeled | Marks issue as ready for agent processing | -| `agents:format` | Issue labeled | Direct issue formatting | -| `agents:formatted` | Auto-applied | Indicates issue has been formatted | -| `agents:optimize` | Issue labeled | Analyzes issue and posts suggestions | -| `agents:apply-suggestions` | Issue labeled | Applies optimization suggestions | -| `agents:paused` | PR labeled | Pauses keepalive loop on PR | -| `agents:keepalive` | PR labeled | Enables keepalive loop on PR | | `verify:checkbox` | PR labeled | Runs verifier checkbox mode after merge | | `verify:evaluate` | PR labeled | Runs verifier evaluation mode after merge | | `verify:compare` | PR labeled | Runs verifier comparison mode after merge | -| `verify:create-issue` | PR labeled | Creates follow-up issue from verification | --- @@ -111,62 +106,59 @@ This document describes all labels that trigger automated workflows or affect CI --- -### `agent:needs-attention` - -**Applies to:** Issues and Pull Requests - -**Trigger:** Automatically applied by workflows - -**Effect:** -1. Signals that human intervention is required -2. Agent processing is paused until addressed -3. Typically applied when: - - Agent encounters an error - - CI checks fail repeatedly - - Agent requests clarification - -**Action Required:** Review the issue/PR, address concerns, and remove the label to resume agent processing. - -**Workflow:** Multiple workflows may apply this label - ---- - -### `status:ready` +### `agents:format` **Applies to:** Issues -**Trigger:** When applied to an issue with agent labels +**Trigger:** When applied to an issue with unstructured content **Effect:** -1. Marks the issue as ready for agent processing -2. Used in conjunction with `agent:codex` to signal readiness -3. May trigger the next step in the agent automation pipeline +1. Triggers the LangChain Issue Formatter workflow (`agents-issue-optimizer.yml`) +2. Parses the raw issue body using LLM (with regex fallback) +3. Rewrites the issue into the structured AGENT_ISSUE_TEMPLATE format: + - **Why** - Purpose/motivation + - **Scope** - Boundaries of work + - **Non-Goals** - Explicitly out of scope + - **Tasks** - Actionable checkboxes + - **Acceptance Criteria** - Testable success conditions + - **Implementation Notes** - Technical hints (if any) +4. Preserves the original issue body in a collapsed `
` block +5. Removes `agents:format` and applies `agents:formatted` on success -**Workflow:** `agents-70-orchestrator.yml` (Agents 70 Orchestrator) +**Prerequisites:** +- Issue must have non-empty body text +- Repository must have `OPENAI_API_KEY` secret for LLM mode (optional) ---- +**Workflow:** `agents-issue-optimizer.yml` (LangChain Issue Optimizer) -## Issue Formatting Labels (LangChain Enhancement) +**Example:** -These labels control the LangChain-powered issue formatting pipeline introduced in #484. +Before (raw issue): +``` +The TripPlan contract in models.py is confusing - it has different shapes in +different places. Let's pick one canonical form and update all the converters. +``` -### `agents:format` +After (formatted): +```markdown +## Why +To establish a single canonical TripPlan contract and eliminate inconsistencies... -**Applies to:** Issues +## Scope +- Review all TripPlan definitions in models.py +- Choose canonical form +- Update converters -**Trigger:** When applied to an issue - -**Effect:** -1. Automatically formats the raw issue body into the AGENT_ISSUE_TEMPLATE structure -2. Uses LLM (GitHub Models API) with fallback to regex-based formatting -3. Adds proper sections: Why, Scope, Non-Goals, Tasks, Acceptance Criteria, Implementation Notes -4. Converts task items to checkboxes -5. Replaces the issue body with formatted version -6. Removes `agents:format` label and adds `agents:formatted` +## Non-Goals +- Changing the underlying data model +- Modifying external API contracts -**Use Case:** Quick, one-step formatting without review. Best for issues that are already well-structured but need template compliance. - -**Workflow:** `agents-issue-optimizer.yml` +## Tasks +- [ ] Audit all TripPlan shapes in codebase +- [ ] Select canonical form +- [ ] Update converters to use canonical form +... +``` --- @@ -174,59 +166,49 @@ These labels control the LangChain-powered issue formatting pipeline introduced **Applies to:** Issues -**Trigger:** Automatically applied after formatting completes +**Trigger:** Automatically applied by `agents-issue-optimizer.yml` **Effect:** -1. Indicates the issue has been formatted to AGENT_ISSUE_TEMPLATE -2. Signals the issue is ready for agent processing -3. Prevents re-formatting (workflows skip issues with this label) - -**Note:** This is a result label, not a trigger label. Do not apply manually. +1. Indicates the issue has been processed by the LangChain formatter +2. Replaces the `agents:format` label +3. No additional workflow triggers (safe terminal state) -**Workflow:** Applied by `agents-issue-optimizer.yml` +**Note:** This is a status label, not a trigger label. Do not apply manually. --- -### `agents:optimize` +### `agent:needs-attention` -**Applies to:** Issues +**Applies to:** Issues and Pull Requests -**Trigger:** When applied to an issue +**Trigger:** Automatically applied by workflows **Effect:** -1. Analyzes the issue for agent compatibility and formatting quality -2. Posts a comment with suggestions including: - - Tasks that are too broad (should be split) - - Tasks the agent cannot complete (with reasons) - - Subjective acceptance criteria (with objective alternatives) - - Missing sections or formatting issues -3. Includes embedded JSON with structured suggestions -4. Prompts user to add `agents:apply-suggestions` to apply changes +1. Signals that human intervention is required +2. Agent processing is paused until addressed +3. Typically applied when: + - Agent encounters an error + - CI checks fail repeatedly + - Agent requests clarification -**Use Case:** Two-step formatting with human review. Best for issues needing significant restructuring. +**Action Required:** Review the issue/PR, address concerns, and remove the label to resume agent processing. -**Workflow:** `agents-issue-optimizer.yml` +**Workflow:** Multiple workflows may apply this label --- -### `agents:apply-suggestions` +### `status:ready` **Applies to:** Issues -**Trigger:** When applied to an issue that has received optimization suggestions - -**Prerequisites:** -- Issue must have a comment with optimization suggestions (from `agents:optimize`) -- The suggestions comment must contain valid JSON in `` marker +**Trigger:** When applied to an issue with agent labels **Effect:** -1. Extracts approved suggestions from the analysis comment -2. Applies all suggestions to reformat the issue body -3. Moves blocked tasks to "## Deferred Tasks (Requires Human)" section -4. Removes both `agents:optimize` and `agents:apply-suggestions` labels -5. Adds `agents:formatted` label +1. Marks the issue as ready for agent processing +2. Used in conjunction with `agent:codex` to signal readiness +3. May trigger the next step in the agent automation pipeline -**Workflow:** `agents-issue-optimizer.yml` +**Workflow:** `agents-70-orchestrator.yml` (Agents 70 Orchestrator) --- @@ -270,95 +252,6 @@ These labels trigger the post-merge verifier workflow on a merged PR. --- -### `verify:create-issue` - -**Applies to:** Pull Requests - -**Trigger:** When applied to a merged PR that has verification feedback - -**Prerequisites:** -- PR must be merged -- PR must have a verification comment (from `verify:evaluate` or `verify:compare`) - -**Effect:** -1. Extracts concerns and low scores from verification feedback -2. Creates a new follow-up issue with: - - Link to original PR - - Extracted concerns from verification - - Scores below 7/10 - - Suggested tasks for addressing issues -3. Posts comment on original PR linking to new issue -4. Removes the `verify:create-issue` label after completion -5. Adds `agents:optimize` label to new issue for agent formatting - -**Use Case:** User-triggered creation of follow-up work from verification feedback. Replaces automatic issue creation which was too aggressive. - -**Workflow:** `agents-verify-to-issue.yml` - ---- - -## Keepalive Control Labels - -### `agents:paused` - -**Applies to:** Pull Requests - -**Trigger:** When applied to a PR with active keepalive - -**Effect:** -1. Pauses all keepalive activity on the PR -2. Agent will not be dispatched until label is removed -3. Useful for manual intervention or debugging - -**To Resume:** Remove the `agents:paused` label. - -**Workflow:** `agents-keepalive-loop.yml` - ---- - -### `agents:keepalive` - -**Applies to:** Pull Requests - -**Trigger:** When applied to a PR - -**Effect:** -1. Enables the keepalive loop for the PR -2. Agent continues working until all tasks are complete -3. Tracks progress and updates PR status - -**Prerequisites:** -- PR must have an `agent:*` label -- Gate workflow must pass - -**Workflow:** `agents-keepalive-loop.yml` - ---- - -## Informational Labels - -These labels are used for categorization but do not trigger workflows. - -### `follow-up` - -**Applies to:** Issues - -**Effect:** Indicates this issue was created as follow-up to another issue or PR. - -**Applied by:** `agents-verify-to-issue.yml` workflow - ---- - -### `needs-formatting` - -**Applies to:** Issues - -**Effect:** Indicates the issue needs formatting to AGENT_ISSUE_TEMPLATE structure. - -**Applied by:** Issue lint workflow (when enabled) - ---- - ## CI/Build Labels ### `skip-ci` (if configured) @@ -379,10 +272,9 @@ These labels are used for categorization but do not trigger workflows. | `agent:codex` | `agent:codex-invite` | Sends agent invitation | | `agent:codex` | `status:ready` | Agent begins processing | | `agent:needs-attention` | (removed) | Agent resumes processing | -| (none) | `agents:format` | Direct formatting | -| (none) | `agents:optimize` | Analyzes and posts suggestions | -| `agents:optimize` | `agents:apply-suggestions` | Applies suggestions, adds `agents:formatted` | -| `agents:formatted` | `agent:codex` | Issue ready for agent processing | +| (none) | `agents:format` | Triggers LangChain formatter | +| `agents:format` | (auto-removed) | Label replaced by `agents:formatted` | +| `agents:formatted` | `agent:codex` | Ready for agent assignment | --- diff --git a/tools/llm_provider.py b/tools/llm_provider.py index ddc08616..2db6c931 100644 --- a/tools/llm_provider.py +++ b/tools/llm_provider.py @@ -11,6 +11,12 @@ provider = get_llm_provider() result = provider.analyze_completion(session_text, tasks) + +LangSmith Tracing: + Set these environment variables to enable LangSmith tracing: + - LANGSMITH_API_KEY: Your LangSmith API key + - LANGCHAIN_TRACING_V2: Set to "true" to enable tracing + - LANGCHAIN_PROJECT: Project name (default: "workflows-agents") """ from __future__ import annotations @@ -28,6 +34,32 @@ DEFAULT_MODEL = "gpt-4o-mini" +def _setup_langsmith_tracing() -> bool: + """ + Configure LangSmith tracing if API key is available. + + Returns True if tracing is enabled, False otherwise. + """ + api_key = os.environ.get("LANGSMITH_API_KEY") + if not api_key: + return False + + # Enable LangChain tracing v2 + os.environ.setdefault("LANGCHAIN_TRACING_V2", "true") + os.environ.setdefault("LANGCHAIN_PROJECT", "workflows-agents") + # LangSmith uses LANGSMITH_API_KEY directly, but LangChain expects LANGCHAIN_API_KEY + os.environ.setdefault("LANGSMITH_API_KEY", api_key) + + project = os.environ.get("LANGCHAIN_PROJECT") + logger.info(f"LangSmith tracing enabled for project: {project}") + return True + + +# Initialize tracing on module load. +# This flag can be used to conditionally enable LangSmith-specific features. +LANGSMITH_ENABLED = _setup_langsmith_tracing() + + @dataclass class CompletionAnalysis: """Result of task completion analysis."""