@xn-intenton-z2a/agentic-lib 7.1.56 → 7.1.58

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.
@@ -198,23 +198,8 @@ jobs:
198
198
  core.warning(`Could not merge PR #${pr.number}: ${e.message}`);
199
199
  }
200
200
  } else if (fullPr.mergeable_state === 'dirty' || fullPr.mergeable === false) {
201
- const isStale = new Date(pr.updated_at) < prCutoff;
202
- if (isStale) {
203
- // Close stale conflicting PR
204
- core.info(`Closing stale conflicting PR #${pr.number}`);
205
- await github.rest.pulls.update({ owner, repo, pull_number: pr.number, state: 'closed' });
206
- try {
207
- await github.rest.git.deleteRef({ owner, repo, ref: `heads/${pr.head.ref}` });
208
- } catch (e) { /* branch may already be gone */ }
209
- closedPRs.push(pr.number);
210
- } else {
211
- // Remove automerge label from conflicting but fresh PR
212
- try {
213
- await github.rest.issues.removeLabel({ owner, repo, issue_number: pr.number, name: 'automerge' });
214
- await github.rest.issues.createComment({ owner, repo, issue_number: pr.number,
215
- body: 'Automerge label removed — PR has conflicts. Resolve and re-add label to retry.' });
216
- } catch (e) { /* label may not exist */ }
217
- }
201
+ // Conflicting PR leave it for fix-stuck to resolve instead of deleting
202
+ core.info(`PR #${pr.number} has merge conflicts — fix-stuck job will attempt resolution`);
218
203
  }
219
204
  // If unstable/unknown: skip, next run handles
220
205
  }
@@ -450,7 +435,7 @@ jobs:
450
435
  return;
451
436
  }
452
437
 
453
- // Find PRs with failing checks on agentic branches
438
+ // Find PRs with failing checks or merge conflicts on agentic branches
454
439
  const { data: openPRs } = await github.rest.pulls.list({
455
440
  owner, repo, state: 'open', per_page: 10,
456
441
  });
@@ -460,12 +445,19 @@ jobs:
460
445
  if (!hasAutomerge) continue;
461
446
  if (!pr.head.ref.startsWith('agentic-lib-') && !pr.head.ref.startsWith('copilot/')) continue;
462
447
 
448
+ // Check for merge conflicts
449
+ const { data: fullPr } = await github.rest.pulls.get({
450
+ owner, repo, pull_number: pr.number,
451
+ });
452
+ const hasConflicts = fullPr.mergeable_state === 'dirty' || fullPr.mergeable === false;
453
+
463
454
  // Check if checks are failing
464
455
  const { data: checkRuns } = await github.rest.checks.listForRef({
465
456
  owner, repo, ref: pr.head.sha,
466
457
  });
467
458
  const hasFailing = checkRuns.check_runs.some(c => c.conclusion === 'failure');
468
- if (!hasFailing) continue;
459
+
460
+ if (!hasFailing && !hasConflicts) continue;
469
461
 
470
462
  // Check fix attempt count
471
463
  const { data: fixRuns } = await github.rest.actions.listWorkflowRuns({
@@ -478,24 +470,60 @@ jobs:
478
470
  continue;
479
471
  }
480
472
 
481
- core.info(`Will attempt to fix PR #${pr.number}`);
473
+ const reason = hasConflicts ? 'merge conflicts' : 'failing checks';
474
+ core.info(`Will attempt to fix PR #${pr.number} (${reason})`);
482
475
  core.exportVariable('FIX_PR_NUMBER', String(pr.number));
476
+ core.exportVariable('FIX_REASON', reason);
483
477
  break;
484
478
  }
485
479
 
486
- - name: Checkout PR branch and fix
480
+ - name: Checkout PR branch
487
481
  if: env.FIX_PR_NUMBER != ''
488
482
  run: |
489
483
  gh pr checkout ${{ env.FIX_PR_NUMBER }}
490
484
  env:
491
485
  GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
492
486
 
493
- - name: Fix failing code
494
- if: env.FIX_PR_NUMBER != ''
487
+ - name: "Tier 1: Auto-resolve trivial merge conflicts"
488
+ if: env.FIX_PR_NUMBER != '' && env.FIX_REASON == 'merge conflicts'
489
+ id: trivial-resolve
490
+ run: |
491
+ git fetch origin main
492
+ if git merge origin/main --no-edit 2>/dev/null; then
493
+ echo "resolved=clean" >> $GITHUB_OUTPUT
494
+ else
495
+ CONFLICTED=$(git diff --name-only --diff-filter=U)
496
+ TRIVIAL_PATTERN='intentïon\.md|intention\.md|package-lock\.json'
497
+ NON_TRIVIAL=$(echo "$CONFLICTED" | grep -vE "$TRIVIAL_PATTERN" || true)
498
+ if [ -z "$NON_TRIVIAL" ]; then
499
+ echo "All conflicts are trivial — auto-resolving"
500
+ for f in $CONFLICTED; do
501
+ git checkout --theirs "$f"
502
+ git add "$f"
503
+ done
504
+ npm install 2>/dev/null || true
505
+ git add package-lock.json 2>/dev/null || true
506
+ git commit --no-edit
507
+ echo "resolved=trivial" >> $GITHUB_OUTPUT
508
+ else
509
+ git merge --abort
510
+ echo "resolved=none" >> $GITHUB_OUTPUT
511
+ echo "non_trivial<<EOF" >> $GITHUB_OUTPUT
512
+ echo "$NON_TRIVIAL" >> $GITHUB_OUTPUT
513
+ echo "EOF" >> $GITHUB_OUTPUT
514
+ fi
515
+ fi
516
+
517
+ - name: "Tier 2: LLM fix (conflicts or failing checks)"
518
+ if: |
519
+ env.FIX_PR_NUMBER != '' &&
520
+ (env.FIX_REASON != 'merge conflicts' ||
521
+ steps.trivial-resolve.outputs.resolved == 'none')
495
522
  uses: ./.github/agentic-lib/actions/agentic-step
496
523
  env:
497
524
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
498
525
  COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
526
+ NON_TRIVIAL_FILES: ${{ steps.trivial-resolve.outputs.non_trivial }}
499
527
  with:
500
528
  task: "fix-code"
501
529
  config: ${{ needs.params.outputs.config-path }}
@@ -508,7 +536,7 @@ jobs:
508
536
  if: env.FIX_PR_NUMBER != ''
509
537
  uses: ./.github/agentic-lib/actions/commit-if-changed
510
538
  with:
511
- commit-message: "agentic-step: fix failing tests"
539
+ commit-message: "agentic-step: fix failing tests / resolve conflicts"
512
540
 
513
541
  # ─── Review: close resolved issues, enhance with criteria ──────────
514
542
  review-features:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xn-intenton-z2a/agentic-lib",
3
- "version": "7.1.56",
3
+ "version": "7.1.58",
4
4
  "description": "Agentic-lib Agentic Coding Systems SDK powering automated GitHub workflows.",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -96,7 +96,7 @@ async function run() {
96
96
  if (result.tokensUsed) core.setOutput("tokens-used", String(result.tokensUsed));
97
97
  if (result.model) core.setOutput("model", result.model);
98
98
 
99
- // Log to intentïon.md
99
+ // Log to intentïon.md (commit-if-changed excludes this on non-default branches)
100
100
  const intentionFilepath = config.intentionBot?.intentionFilepath;
101
101
  if (intentionFilepath) {
102
102
  logActivity({
@@ -1,11 +1,13 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-only
2
2
  // Copyright (C) 2025-2026 Polycode Limited
3
- // tasks/fix-code.js — Fix failing tests on a PR
3
+ // tasks/fix-code.js — Fix failing tests or resolve merge conflicts on a PR
4
4
  //
5
- // Given a PR number with failing tests, analyzes the test output,
6
- // generates fixes using the Copilot SDK, and pushes a commit.
5
+ // Given a PR number, detects merge conflicts or failing checks and resolves them.
6
+ // Conflict resolution: reads files with conflict markers, asks the LLM to resolve.
7
+ // Failing checks: analyzes test output, generates code fixes.
7
8
 
8
9
  import * as core from "@actions/core";
10
+ import { readFileSync } from "fs";
9
11
  import { execSync } from "child_process";
10
12
  import { runCopilotTask, formatPathsSection } from "../copilot.js";
11
13
 
@@ -37,7 +39,90 @@ function fetchRunLog(runId) {
37
39
  }
38
40
 
39
41
  /**
40
- * Fix failing code on a pull request.
42
+ * Resolve merge conflicts on a PR using the Copilot SDK.
43
+ * Called when the workflow has started a `git merge origin/main` that left
44
+ * conflict markers in non-trivial files (listed in NON_TRIVIAL_FILES env var).
45
+ *
46
+ * @param {Object} params
47
+ * @returns {Promise<Object>} Result with outcome, tokensUsed, model
48
+ */
49
+ async function resolveConflicts({ config, pr, prNumber, instructions, model, writablePaths, testCommand }) {
50
+ const nonTrivialEnv = process.env.NON_TRIVIAL_FILES || "";
51
+ const conflictedPaths = nonTrivialEnv
52
+ .split("\n")
53
+ .map((f) => f.trim())
54
+ .filter(Boolean);
55
+
56
+ if (conflictedPaths.length === 0) {
57
+ core.info(`PR #${prNumber} has conflicts but no non-trivial files listed. Returning nop.`);
58
+ return { outcome: "nop", details: "No non-trivial conflict files to resolve" };
59
+ }
60
+
61
+ core.info(`Resolving ${conflictedPaths.length} conflicted file(s) on PR #${prNumber}`);
62
+
63
+ const conflicts = conflictedPaths.map((f) => {
64
+ try {
65
+ return { name: f, content: readFileSync(f, "utf8") };
66
+ } catch (err) {
67
+ core.warning(`Could not read conflicted file ${f}: ${err.message}`);
68
+ return { name: f, content: "(could not read)" };
69
+ }
70
+ });
71
+
72
+ const agentInstructions = instructions || "Resolve the merge conflicts while preserving the PR's intended changes.";
73
+ const readOnlyPaths = config.readOnlyPaths;
74
+
75
+ const prompt = [
76
+ "## Instructions",
77
+ agentInstructions,
78
+ "",
79
+ `## Pull Request #${prNumber}: ${pr.title}`,
80
+ "",
81
+ pr.body || "(no description)",
82
+ "",
83
+ "## Task: Resolve Merge Conflicts",
84
+ "The PR branch has been merged with main but has conflicts in the files below.",
85
+ "Each file contains git conflict markers (<<<<<<< / ======= / >>>>>>>).",
86
+ "Resolve each conflict by keeping the PR's intended changes while incorporating",
87
+ "any non-conflicting updates from main.",
88
+ "",
89
+ `## Conflicted Files (${conflicts.length})`,
90
+ ...conflicts.map((f) => `### ${f.name}\n\`\`\`\n${f.content}\n\`\`\``),
91
+ "",
92
+ formatPathsSection(writablePaths, readOnlyPaths, config),
93
+ "",
94
+ "## Constraints",
95
+ "- Remove ALL conflict markers (<<<<<<, =======, >>>>>>>)",
96
+ "- Preserve the PR's feature/fix intent",
97
+ `- Run \`${testCommand}\` to validate your resolution`,
98
+ ].join("\n");
99
+
100
+ const t = config.tuning || {};
101
+ const { tokensUsed, inputTokens, outputTokens, cost } = await runCopilotTask({
102
+ model,
103
+ systemMessage: `You are resolving git merge conflicts on PR #${prNumber}. Write resolved versions of each conflicted file, removing all conflict markers. Preserve the PR's feature intent while incorporating main's updates.`,
104
+ prompt,
105
+ writablePaths,
106
+ tuning: t,
107
+ });
108
+
109
+ core.info(`Conflict resolution completed (${tokensUsed} tokens)`);
110
+
111
+ return {
112
+ outcome: "conflicts-resolved",
113
+ tokensUsed,
114
+ inputTokens,
115
+ outputTokens,
116
+ cost,
117
+ model,
118
+ details: `Resolved ${conflicts.length} conflicted file(s) on PR #${prNumber}`,
119
+ };
120
+ }
121
+
122
+ /**
123
+ * Fix failing code or resolve merge conflicts on a pull request.
124
+ *
125
+ * Priority: conflicts first (if NON_TRIVIAL_FILES env is set), then failing checks.
41
126
  *
42
127
  * @param {Object} context - Task context from index.js
43
128
  * @returns {Promise<Object>} Result with outcome, tokensUsed, model
@@ -49,8 +134,15 @@ export async function fixCode(context) {
49
134
  throw new Error("fix-code task requires pr-number input");
50
135
  }
51
136
 
52
- // Fetch the PR and check runs
137
+ // Fetch the PR
53
138
  const { data: pr } = await octokit.rest.pulls.get({ ...repo, pull_number: Number(prNumber) });
139
+
140
+ // If we have non-trivial conflict files from the workflow's Tier 1 step, resolve them
141
+ if (process.env.NON_TRIVIAL_FILES) {
142
+ return resolveConflicts({ config, pr, prNumber, instructions, model, writablePaths, testCommand });
143
+ }
144
+
145
+ // Otherwise, check for failing checks
54
146
  const { data: checkRuns } = await octokit.rest.checks.listForRef({ ...repo, ref: pr.head.sha, per_page: 10 });
55
147
 
56
148
  const failedChecks = checkRuns.check_runs.filter((cr) => cr.conclusion === "failure");
@@ -26,6 +26,11 @@ runs:
26
26
  git config --local user.email 'action@github.com'
27
27
  git config --local user.name 'GitHub Actions[bot]'
28
28
  git add -A
29
+ # Unstage log files on non-default branches to avoid merge conflicts
30
+ REF="${{ inputs.push-ref }}"
31
+ if [ -n "$REF" ] && [ "$REF" != "main" ] && [ "$REF" != "master" ]; then
32
+ git reset HEAD -- 'intentïon.md' 'intention.md' 2>/dev/null || true
33
+ fi
29
34
  if git diff --cached --quiet; then
30
35
  echo "No changes to commit"
31
36
  elif git diff --cached --name-only | grep -qvE '(intentïon\.md|intention\.md)$'; then
@@ -11,3 +11,15 @@ and maintain the integrity of the codebase's primary purpose.
11
11
  You may complete the implementation of a feature and/or bring the code output in line with the README
12
12
  or other documentation. Do as much as you can all at once so that the build runs (even with nothing
13
13
  to build) and the tests pass and the main at least doesn't output an error.
14
+
15
+ ## Merge Conflict Resolution
16
+
17
+ When resolving merge conflicts (files containing <<<<<<< / ======= / >>>>>>> markers):
18
+
19
+ 1. **Understand both sides**: The HEAD side (above =======) is the PR's changes. The incoming side
20
+ (below =======) is from main. Understand what each side intended before choosing.
21
+ 2. **Preserve PR intent**: The PR was created for a reason — keep its feature/fix changes.
22
+ 3. **Incorporate main's updates**: If main added new code that doesn't conflict with the PR's
23
+ purpose, include it.
24
+ 4. **Remove ALL markers**: Every <<<<<<, =======, and >>>>>>> line must be removed.
25
+ 5. **Run tests**: After resolving, run the test command to validate the resolution compiles and passes.
@@ -14,7 +14,7 @@
14
14
  "author": "",
15
15
  "license": "MIT",
16
16
  "dependencies": {
17
- "@xn-intenton-z2a/agentic-lib": "^7.1.56"
17
+ "@xn-intenton-z2a/agentic-lib": "^7.1.58"
18
18
  },
19
19
  "devDependencies": {
20
20
  "@vitest/coverage-v8": "^4.0.18",