@xn-intenton-z2a/agentic-lib 7.1.51 → 7.1.52

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.
@@ -0,0 +1,702 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (C) 2025-2026 Polycode Limited
3
+ # .github/workflows/agentic-lib-workflow.yml
4
+ #
5
+ # Main autonomous pipeline: supervisor + maintain + review + dev + PR cleanup.
6
+ # Replaces agent-supervisor, agent-flow-transform, agent-flow-review,
7
+ # agent-flow-maintain, agent-flow-fix-code, and ci-automerge.
8
+
9
+ name: agentic-lib-workflow
10
+ run-name: "agentic-lib-workflow [${{ github.ref_name }}]"
11
+ concurrency:
12
+ group: agentic-lib-workflow
13
+ cancel-in-progress: false
14
+
15
+ on:
16
+ schedule:
17
+ - cron: "0 6 * * 1"
18
+ workflow_dispatch:
19
+ inputs:
20
+ model:
21
+ description: "Copilot SDK model to use"
22
+ type: choice
23
+ required: false
24
+ default: "gpt-5-mini"
25
+ options:
26
+ - gpt-5-mini
27
+ - claude-sonnet-4
28
+ - gpt-4.1
29
+ mode:
30
+ description: "Run mode"
31
+ type: choice
32
+ required: false
33
+ default: "full"
34
+ options:
35
+ - full
36
+ - dev-only
37
+ - maintain-only
38
+ - review-only
39
+ - pr-cleanup-only
40
+ message:
41
+ description: "Message from bot or human (context for supervisor)"
42
+ type: string
43
+ required: false
44
+ default: ""
45
+ issue-number:
46
+ description: "Target specific issue (dev-only mode)"
47
+ type: string
48
+ required: false
49
+ default: ""
50
+ schedule:
51
+ description: "Change schedule frequency after this run"
52
+ type: choice
53
+ required: false
54
+ default: ""
55
+ options:
56
+ - ""
57
+ - "off"
58
+ - "weekly"
59
+ - "daily"
60
+ - "hourly"
61
+ - "continuous"
62
+ pr-number:
63
+ description: "Target specific PR for fix"
64
+ type: string
65
+ required: false
66
+ default: ""
67
+
68
+ permissions:
69
+ actions: write
70
+ contents: write
71
+ issues: write
72
+ pull-requests: write
73
+ checks: read
74
+ discussions: write
75
+
76
+ env:
77
+ configPath: ".github/agentic-lib/agents/agentic-lib.yml"
78
+ maxFixAttempts: "3"
79
+ stalePrDays: "3"
80
+
81
+ jobs:
82
+ # ─── Normalise inputs ──────────────────────────────────────────────
83
+ params:
84
+ runs-on: ubuntu-latest
85
+ steps:
86
+ - name: Normalise params
87
+ id: normalise
88
+ shell: bash
89
+ run: |
90
+ MODEL='${{ inputs.model }}'
91
+ echo "model=${MODEL:-gpt-5-mini}" >> $GITHUB_OUTPUT
92
+ MODE='${{ inputs.mode }}'
93
+ echo "mode=${MODE:-full}" >> $GITHUB_OUTPUT
94
+ echo "message=${{ inputs.message }}" >> $GITHUB_OUTPUT
95
+ ISSUE='${{ inputs.issue-number }}'
96
+ echo "issue-number=${ISSUE}" >> $GITHUB_OUTPUT
97
+ SCHEDULE='${{ inputs.schedule }}'
98
+ echo "schedule=${SCHEDULE}" >> $GITHUB_OUTPUT
99
+ PR='${{ inputs.pr-number }}'
100
+ echo "pr-number=${PR}" >> $GITHUB_OUTPUT
101
+ outputs:
102
+ model: ${{ steps.normalise.outputs.model }}
103
+ mode: ${{ steps.normalise.outputs.mode }}
104
+ message: ${{ steps.normalise.outputs.message }}
105
+ issue-number: ${{ steps.normalise.outputs.issue-number }}
106
+ schedule: ${{ steps.normalise.outputs.schedule }}
107
+ pr-number: ${{ steps.normalise.outputs.pr-number }}
108
+
109
+ # ─── PR Cleanup: merge/close/delete stale PRs and branches ─────────
110
+ pr-cleanup:
111
+ needs: params
112
+ if: needs.params.outputs.mode == 'full' || needs.params.outputs.mode == 'pr-cleanup-only'
113
+ runs-on: ubuntu-latest
114
+ steps:
115
+ - name: PR cleanup
116
+ uses: actions/github-script@v7
117
+ with:
118
+ script: |
119
+ const owner = context.repo.owner;
120
+ const repo = context.repo.repo;
121
+ const stalePrDays = parseInt(process.env.stalePrDays) || 3;
122
+ const prCutoff = new Date(Date.now() - stalePrDays * 24 * 60 * 60 * 1000);
123
+ let mergedPRs = [];
124
+ let closedPRs = [];
125
+
126
+ // List open PRs with automerge label
127
+ const { data: openPRs } = await github.rest.pulls.list({
128
+ owner, repo, state: 'open', per_page: 20,
129
+ });
130
+
131
+ for (const pr of openPRs) {
132
+ const hasAutomerge = pr.labels.some(l => l.name === 'automerge');
133
+ if (!hasAutomerge) continue;
134
+
135
+ const { data: fullPr } = await github.rest.pulls.get({
136
+ owner, repo, pull_number: pr.number,
137
+ });
138
+
139
+ if (fullPr.mergeable && fullPr.mergeable_state === 'clean') {
140
+ // Merge it
141
+ try {
142
+ await github.rest.pulls.merge({
143
+ owner, repo, pull_number: pr.number, merge_method: 'squash',
144
+ });
145
+ core.info(`Merged PR #${pr.number}`);
146
+ // Delete branch
147
+ try {
148
+ await github.rest.git.deleteRef({ owner, repo, ref: `heads/${pr.head.ref}` });
149
+ } catch (e) { core.info(`Could not delete branch: ${e.message}`); }
150
+
151
+ // Label associated issue
152
+ const branchPrefix = 'agentic-lib-issue-';
153
+ if (pr.head.ref.startsWith(branchPrefix)) {
154
+ const issueNum = parseInt(pr.head.ref.replace(branchPrefix, ''));
155
+ if (issueNum) {
156
+ try {
157
+ await github.rest.issues.addLabels({ owner, repo, issue_number: issueNum, labels: ['merged'] });
158
+ await github.rest.issues.removeLabel({ owner, repo, issue_number: issueNum, name: 'in-progress' });
159
+ } catch (e) { /* label may not exist */ }
160
+ }
161
+ }
162
+ mergedPRs.push(pr.number);
163
+ } catch (e) {
164
+ core.warning(`Could not merge PR #${pr.number}: ${e.message}`);
165
+ }
166
+ } else if (fullPr.mergeable_state === 'dirty' || fullPr.mergeable === false) {
167
+ const isStale = new Date(pr.updated_at) < prCutoff;
168
+ if (isStale) {
169
+ // Close stale conflicting PR
170
+ core.info(`Closing stale conflicting PR #${pr.number}`);
171
+ await github.rest.pulls.update({ owner, repo, pull_number: pr.number, state: 'closed' });
172
+ try {
173
+ await github.rest.git.deleteRef({ owner, repo, ref: `heads/${pr.head.ref}` });
174
+ } catch (e) { /* branch may already be gone */ }
175
+ closedPRs.push(pr.number);
176
+ } else {
177
+ // Remove automerge label from conflicting but fresh PR
178
+ try {
179
+ await github.rest.issues.removeLabel({ owner, repo, issue_number: pr.number, name: 'automerge' });
180
+ await github.rest.issues.createComment({ owner, repo, issue_number: pr.number,
181
+ body: 'Automerge label removed — PR has conflicts. Resolve and re-add label to retry.' });
182
+ } catch (e) { /* label may not exist */ }
183
+ }
184
+ }
185
+ // If unstable/unknown: skip, next run handles
186
+ }
187
+ core.setOutput('merged-prs', JSON.stringify(mergedPRs));
188
+ core.setOutput('closed-prs', JSON.stringify(closedPRs));
189
+ id: cleanup
190
+ outputs:
191
+ merged-prs: ${{ steps.cleanup.outputs.merged-prs }}
192
+ closed-prs: ${{ steps.cleanup.outputs.closed-prs }}
193
+
194
+ # ─── Telemetry: gather repo state for supervisor ───────────────────
195
+ telemetry:
196
+ needs: params
197
+ if: needs.params.outputs.mode == 'full' || needs.params.outputs.mode == 'dev-only' || needs.params.outputs.mode == 'review-only'
198
+ runs-on: ubuntu-latest
199
+ steps:
200
+ - uses: actions/checkout@v4
201
+ - name: Gather telemetry
202
+ id: gather
203
+ uses: actions/github-script@v7
204
+ with:
205
+ script: |
206
+ const fs = require('fs');
207
+ const owner = context.repo.owner;
208
+ const repo = context.repo.repo;
209
+
210
+ // Open issues
211
+ const { data: issues } = await github.rest.issues.listForRepo({
212
+ owner, repo, state: 'open', per_page: 20,
213
+ labels: 'automated',
214
+ });
215
+ const issuesSummary = issues.map(i => ({
216
+ number: i.number, title: i.title,
217
+ labels: i.labels.map(l => l.name),
218
+ }));
219
+
220
+ // Open PRs
221
+ const { data: prs } = await github.rest.pulls.list({
222
+ owner, repo, state: 'open', per_page: 10,
223
+ });
224
+ const prsSummary = prs.map(p => ({
225
+ number: p.number, title: p.title, branch: p.head.ref,
226
+ labels: p.labels.map(l => l.name),
227
+ }));
228
+
229
+ // Recent workflow runs
230
+ const { data: runs } = await github.rest.actions.listWorkflowRunsForRepo({
231
+ owner, repo, per_page: 10,
232
+ });
233
+ const runsSummary = runs.workflow_runs.map(r => ({
234
+ name: r.name, status: r.status, conclusion: r.conclusion,
235
+ created: r.created_at,
236
+ }));
237
+
238
+ // Read local files
239
+ let mission = '';
240
+ try { mission = fs.readFileSync('MISSION.md', 'utf8').slice(0, 2000); } catch (e) {}
241
+ let features = [];
242
+ try {
243
+ const featDir = 'features/';
244
+ if (fs.existsSync(featDir)) {
245
+ features = fs.readdirSync(featDir).filter(f => f.endsWith('.md'));
246
+ }
247
+ } catch (e) {}
248
+
249
+ // Message from bot/human
250
+ const message = '${{ needs.params.outputs.message }}';
251
+
252
+ const telemetry = {
253
+ issues: issuesSummary,
254
+ prs: prsSummary,
255
+ recentRuns: runsSummary,
256
+ mission: mission.slice(0, 500),
257
+ featureFiles: features,
258
+ message: message || null,
259
+ };
260
+
261
+ // Write to file for downstream jobs
262
+ fs.writeFileSync('/tmp/telemetry.json', JSON.stringify(telemetry, null, 2));
263
+ // Set as output (truncated for GitHub output limits)
264
+ const summary = JSON.stringify(telemetry);
265
+ core.setOutput('telemetry', summary.slice(0, 60000));
266
+ outputs:
267
+ telemetry: ${{ steps.gather.outputs.telemetry }}
268
+
269
+ # ─── Supervisor: LLM decides what to do ─────────────────────────────
270
+ supervisor:
271
+ needs: [params, pr-cleanup, telemetry]
272
+ if: |
273
+ always() &&
274
+ (needs.params.outputs.mode == 'full' || needs.params.outputs.mode == 'dev-only') &&
275
+ needs.params.result == 'success'
276
+ runs-on: ubuntu-latest
277
+ steps:
278
+ - uses: actions/checkout@v4
279
+
280
+ - uses: actions/setup-node@v4
281
+ with:
282
+ node-version: "24"
283
+
284
+ - name: Install agentic-step dependencies
285
+ working-directory: .github/agentic-lib/actions/agentic-step
286
+ run: npm ci
287
+
288
+ - name: Run supervisor
289
+ uses: ./.github/agentic-lib/actions/agentic-step
290
+ env:
291
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
292
+ COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
293
+ with:
294
+ task: "supervise"
295
+ config: ${{ env.configPath }}
296
+ instructions: ".github/agentic-lib/agents/agent-supervisor.md"
297
+ model: ${{ needs.params.outputs.model }}
298
+
299
+ # ─── Maintain: features + library (push to main) ───────────────────
300
+ maintain:
301
+ needs: [params, supervisor]
302
+ if: |
303
+ always() &&
304
+ (needs.params.outputs.mode == 'full' || needs.params.outputs.mode == 'maintain-only') &&
305
+ needs.params.result == 'success'
306
+ runs-on: ubuntu-latest
307
+ steps:
308
+ - uses: actions/checkout@v4
309
+ with:
310
+ fetch-depth: 0
311
+
312
+ - uses: actions/setup-node@v4
313
+ with:
314
+ node-version: "24"
315
+
316
+ - name: Install agentic-step dependencies
317
+ working-directory: .github/agentic-lib/actions/agentic-step
318
+ run: npm ci
319
+
320
+ - name: Load config
321
+ id: config
322
+ run: |
323
+ CONFIG="${{ env.configPath }}"
324
+ FEATURES=$(yq -r '.paths.featuresPath.path // "features/"' "$CONFIG")
325
+ LIBRARY=$(yq -r '.paths.libraryDocumentsPath.path // "library/"' "$CONFIG")
326
+ SOURCES=$(yq -r '.paths.librarySourcesFilepath.path // "SOURCES.md"' "$CONFIG")
327
+ echo "featuresWritablePaths=${FEATURES}" >> $GITHUB_OUTPUT
328
+ echo "libraryWritablePaths=${LIBRARY};${SOURCES}" >> $GITHUB_OUTPUT
329
+
330
+ - name: Maintain features
331
+ uses: ./.github/agentic-lib/actions/agentic-step
332
+ env:
333
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
334
+ COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
335
+ with:
336
+ task: "maintain-features"
337
+ config: ${{ env.configPath }}
338
+ instructions: ".github/agentic-lib/agents/agent-maintain-features.md"
339
+ writable-paths: ${{ steps.config.outputs.featuresWritablePaths }}
340
+ model: ${{ needs.params.outputs.model }}
341
+
342
+ - name: Maintain library
343
+ uses: ./.github/agentic-lib/actions/agentic-step
344
+ env:
345
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
346
+ COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
347
+ with:
348
+ task: "maintain-library"
349
+ config: ${{ env.configPath }}
350
+ instructions: ".github/agentic-lib/agents/agent-maintain-library.md"
351
+ writable-paths: ${{ steps.config.outputs.libraryWritablePaths }}
352
+ model: ${{ needs.params.outputs.model }}
353
+
354
+ - name: Commit and push changes
355
+ uses: ./.github/agentic-lib/actions/commit-if-changed
356
+ with:
357
+ commit-message: "agentic-step: maintain features and library"
358
+ push-ref: ${{ github.ref_name }}
359
+
360
+ # ─── Fix stuck PRs with failing checks ─────────────────────────────
361
+ fix-stuck:
362
+ needs: [params, supervisor]
363
+ if: |
364
+ always() &&
365
+ needs.params.outputs.mode == 'full' &&
366
+ needs.params.result == 'success'
367
+ runs-on: ubuntu-latest
368
+ steps:
369
+ - uses: actions/checkout@v4
370
+ with:
371
+ fetch-depth: 0
372
+
373
+ - uses: actions/setup-node@v4
374
+ with:
375
+ node-version: "24"
376
+
377
+ - name: Install agentic-step dependencies
378
+ working-directory: .github/agentic-lib/actions/agentic-step
379
+ run: npm ci
380
+
381
+ - name: Find and fix stuck PRs
382
+ uses: actions/github-script@v7
383
+ env:
384
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
385
+ with:
386
+ script: |
387
+ const owner = context.repo.owner;
388
+ const repo = context.repo.repo;
389
+ const maxFixAttempts = parseInt(process.env.maxFixAttempts) || 3;
390
+ const prNumber = '${{ needs.params.outputs.pr-number }}';
391
+
392
+ // If specific PR requested, use that
393
+ if (prNumber) {
394
+ core.info(`Specific PR requested: #${prNumber}`);
395
+ core.exportVariable('FIX_PR_NUMBER', prNumber);
396
+ return;
397
+ }
398
+
399
+ // Find PRs with failing checks on agentic branches
400
+ const { data: openPRs } = await github.rest.pulls.list({
401
+ owner, repo, state: 'open', per_page: 10,
402
+ });
403
+
404
+ for (const pr of openPRs) {
405
+ const hasAutomerge = pr.labels.some(l => l.name === 'automerge');
406
+ if (!hasAutomerge) continue;
407
+ if (!pr.head.ref.startsWith('agentic-lib-') && !pr.head.ref.startsWith('copilot/')) continue;
408
+
409
+ // Check if checks are failing
410
+ const { data: checkRuns } = await github.rest.checks.listForRef({
411
+ owner, repo, ref: pr.head.sha,
412
+ });
413
+ const hasFailing = checkRuns.check_runs.some(c => c.conclusion === 'failure');
414
+ if (!hasFailing) continue;
415
+
416
+ // Check fix attempt count
417
+ const { data: fixRuns } = await github.rest.actions.listWorkflowRuns({
418
+ owner, repo, workflow_id: 'agentic-lib-workflow.yml',
419
+ branch: pr.head.ref, per_page: maxFixAttempts + 1,
420
+ });
421
+ if (fixRuns.total_count >= maxFixAttempts) {
422
+ core.info(`PR #${pr.number} exceeded fix attempts. Removing automerge.`);
423
+ try { await github.rest.issues.removeLabel({ owner, repo, issue_number: pr.number, name: 'automerge' }); } catch (e) {}
424
+ continue;
425
+ }
426
+
427
+ core.info(`Will attempt to fix PR #${pr.number}`);
428
+ core.exportVariable('FIX_PR_NUMBER', String(pr.number));
429
+ break;
430
+ }
431
+
432
+ - name: Checkout PR branch and fix
433
+ if: env.FIX_PR_NUMBER != ''
434
+ run: |
435
+ gh pr checkout ${{ env.FIX_PR_NUMBER }}
436
+ env:
437
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
438
+
439
+ - name: Fix failing code
440
+ if: env.FIX_PR_NUMBER != ''
441
+ uses: ./.github/agentic-lib/actions/agentic-step
442
+ env:
443
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
444
+ COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
445
+ with:
446
+ task: "fix-code"
447
+ config: ${{ env.configPath }}
448
+ instructions: ".github/agentic-lib/agents/agent-apply-fix.md"
449
+ pr-number: ${{ env.FIX_PR_NUMBER }}
450
+ test-command: "npm test"
451
+ model: ${{ needs.params.outputs.model }}
452
+
453
+ - name: Commit and push fixes
454
+ if: env.FIX_PR_NUMBER != ''
455
+ uses: ./.github/agentic-lib/actions/commit-if-changed
456
+ with:
457
+ commit-message: "agentic-step: fix failing tests"
458
+
459
+ # ─── Review: close resolved issues, enhance with criteria ──────────
460
+ review-features:
461
+ needs: [params, maintain]
462
+ if: |
463
+ always() &&
464
+ (needs.params.outputs.mode == 'full' || needs.params.outputs.mode == 'review-only') &&
465
+ needs.params.result == 'success'
466
+ runs-on: ubuntu-latest
467
+ steps:
468
+ - uses: actions/checkout@v4
469
+
470
+ - uses: actions/setup-node@v4
471
+ with:
472
+ node-version: "24"
473
+
474
+ - name: Install agentic-step dependencies
475
+ working-directory: .github/agentic-lib/actions/agentic-step
476
+ run: npm ci
477
+
478
+ - name: Review issues
479
+ uses: ./.github/agentic-lib/actions/agentic-step
480
+ env:
481
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
482
+ COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
483
+ with:
484
+ task: "review-issue"
485
+ config: ${{ env.configPath }}
486
+ instructions: ".github/agentic-lib/agents/agent-review-issue.md"
487
+ model: ${{ needs.params.outputs.model }}
488
+
489
+ - name: Enhance issues
490
+ uses: ./.github/agentic-lib/actions/agentic-step
491
+ env:
492
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
493
+ COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
494
+ with:
495
+ task: "enhance-issue"
496
+ config: ${{ env.configPath }}
497
+ instructions: ".github/agentic-lib/agents/agent-ready-issue.md"
498
+ model: ${{ needs.params.outputs.model }}
499
+
500
+ # ─── Dev: sequential issue resolution ──────────────────────────────
501
+ dev:
502
+ needs: [params, review-features]
503
+ if: |
504
+ always() &&
505
+ (needs.params.outputs.mode == 'full' || needs.params.outputs.mode == 'dev-only') &&
506
+ needs.params.result == 'success'
507
+ runs-on: ubuntu-latest
508
+ steps:
509
+ - uses: actions/checkout@v4
510
+ with:
511
+ fetch-depth: 0
512
+ token: ${{ secrets.GITHUB_TOKEN }}
513
+
514
+ - uses: actions/setup-node@v4
515
+ with:
516
+ node-version: "24"
517
+
518
+ - name: Install agentic-step dependencies
519
+ working-directory: .github/agentic-lib/actions/agentic-step
520
+ run: npm ci
521
+
522
+ - name: Load config
523
+ id: config
524
+ run: |
525
+ CONFIG="${{ env.configPath }}"
526
+ SOURCE=$(yq -r '.paths.targetSourcePath.path // "src/lib/"' "$CONFIG")
527
+ TESTS=$(yq -r '.paths.targetTestsPath.path // "tests/unit/"' "$CONFIG")
528
+ FEATURES=$(yq -r '.paths.featuresPath.path // "features/"' "$CONFIG")
529
+ DOCS=$(yq -r '.paths.documentationPath.path // "docs/"' "$CONFIG")
530
+ LIBRARY=$(yq -r '.paths.libraryDocumentsPath.path // "library/"' "$CONFIG")
531
+ SOURCES=$(yq -r '.paths.librarySourcesFilepath.path // "SOURCES.md"' "$CONFIG")
532
+ README=$(yq -r '.paths.readmeFilepath.path // "README.md"' "$CONFIG")
533
+ DEPS=$(yq -r '.paths.dependenciesFilepath.path // "package.json"' "$CONFIG")
534
+ echo "writablePaths=${SOURCE};${TESTS};${FEATURES};${DOCS};${LIBRARY};${SOURCES};${README};${DEPS}" >> $GITHUB_OUTPUT
535
+
536
+ - name: Find target issue
537
+ id: issue
538
+ uses: actions/github-script@v7
539
+ with:
540
+ script: |
541
+ const specificIssue = '${{ needs.params.outputs.issue-number }}';
542
+ if (specificIssue) {
543
+ core.setOutput('issue-number', specificIssue);
544
+ return;
545
+ }
546
+ // Find oldest open issue with 'ready' label
547
+ const { data: issues } = await github.rest.issues.listForRepo({
548
+ ...context.repo, state: 'open', labels: 'ready',
549
+ sort: 'created', direction: 'asc', per_page: 1,
550
+ });
551
+ if (issues.length > 0) {
552
+ core.setOutput('issue-number', String(issues[0].number));
553
+ core.info(`Targeting issue #${issues[0].number}: ${issues[0].title}`);
554
+ } else {
555
+ core.setOutput('issue-number', '');
556
+ core.info('No ready issues found');
557
+ }
558
+
559
+ - name: Create branch
560
+ if: steps.issue.outputs.issue-number != ''
561
+ id: branch
562
+ run: |
563
+ ISSUE_NUMBER="${{ steps.issue.outputs.issue-number }}"
564
+ BRANCH="agentic-lib-issue-${ISSUE_NUMBER}"
565
+ git checkout -b "${BRANCH}" 2>/dev/null || git checkout "${BRANCH}"
566
+ echo "branchName=${BRANCH}" >> $GITHUB_OUTPUT
567
+
568
+ - name: Run transformation
569
+ if: steps.issue.outputs.issue-number != ''
570
+ uses: ./.github/agentic-lib/actions/agentic-step
571
+ env:
572
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
573
+ COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
574
+ with:
575
+ task: "transform"
576
+ config: ${{ env.configPath }}
577
+ instructions: ".github/agentic-lib/agents/agent-issue-resolution.md"
578
+ test-command: "npm test"
579
+ model: ${{ needs.params.outputs.model }}
580
+ issue-number: ${{ steps.issue.outputs.issue-number }}
581
+ writable-paths: ${{ steps.config.outputs.writablePaths }}
582
+
583
+ - name: Commit and push
584
+ if: steps.issue.outputs.issue-number != ''
585
+ uses: ./.github/agentic-lib/actions/commit-if-changed
586
+ with:
587
+ commit-message: "agentic-step: transform issue #${{ steps.issue.outputs.issue-number }}"
588
+ push-ref: ${{ steps.branch.outputs.branchName }}
589
+
590
+ - name: Create PR and attempt merge
591
+ if: steps.issue.outputs.issue-number != ''
592
+ uses: actions/github-script@v7
593
+ with:
594
+ script: |
595
+ const owner = context.repo.owner;
596
+ const repo = context.repo.repo;
597
+ const branchName = '${{ steps.branch.outputs.branchName }}';
598
+ const issueNumber = '${{ steps.issue.outputs.issue-number }}';
599
+
600
+ if (!branchName) return;
601
+
602
+ // Check if branch has commits ahead of main
603
+ try {
604
+ const { data: comparison } = await github.rest.repos.compareCommitsWithBasehead({
605
+ owner, repo, basehead: `main...${branchName}`,
606
+ });
607
+ if (comparison.ahead_by === 0) {
608
+ core.info('Branch has no new commits — skipping');
609
+ return;
610
+ }
611
+ } catch (err) {
612
+ core.info(`Branch comparison failed: ${err.message}`);
613
+ return;
614
+ }
615
+
616
+ // Check if PR already exists
617
+ const { data: existingPRs } = await github.rest.pulls.list({
618
+ owner, repo, state: 'open',
619
+ head: `${owner}:${branchName}`, per_page: 1,
620
+ });
621
+
622
+ let prNumber;
623
+ if (existingPRs.length > 0) {
624
+ prNumber = existingPRs[0].number;
625
+ core.info(`PR already exists: #${prNumber}`);
626
+ } else {
627
+ const { data: pr } = await github.rest.pulls.create({
628
+ owner, repo,
629
+ title: `fix: resolve issue #${issueNumber}`,
630
+ body: `Closes #${issueNumber}\n\nAutomated transformation.`,
631
+ head: branchName, base: 'main',
632
+ });
633
+ prNumber = pr.number;
634
+ core.info(`Created PR #${prNumber}`);
635
+ await github.rest.issues.addLabels({
636
+ owner, repo, issue_number: prNumber, labels: ['automerge'],
637
+ });
638
+ }
639
+
640
+ // Wait briefly for checks to register
641
+ await new Promise(r => setTimeout(r, 10000));
642
+
643
+ // Attempt merge
644
+ for (let attempt = 0; attempt < 2; attempt++) {
645
+ const { data: fullPr } = await github.rest.pulls.get({
646
+ owner, repo, pull_number: prNumber,
647
+ });
648
+
649
+ if (fullPr.mergeable && fullPr.mergeable_state === 'clean') {
650
+ try {
651
+ await github.rest.pulls.merge({
652
+ owner, repo, pull_number: prNumber, merge_method: 'squash',
653
+ });
654
+ core.info(`Merged PR #${prNumber}`);
655
+ try {
656
+ await github.rest.git.deleteRef({ owner, repo, ref: `heads/${branchName}` });
657
+ } catch (e) {}
658
+ if (issueNumber) {
659
+ try {
660
+ await github.rest.issues.addLabels({ owner, repo, issue_number: parseInt(issueNumber), labels: ['merged'] });
661
+ await github.rest.issues.removeLabel({ owner, repo, issue_number: parseInt(issueNumber), name: 'in-progress' });
662
+ } catch (e) {}
663
+ }
664
+ return;
665
+ } catch (e) {
666
+ core.info(`Merge attempt ${attempt + 1} failed: ${e.message}`);
667
+ }
668
+ } else if (fullPr.mergeable_state === 'unstable' || fullPr.mergeable === null) {
669
+ core.info(`PR not ready yet (${fullPr.mergeable_state}), waiting...`);
670
+ await new Promise(r => setTimeout(r, 15000));
671
+ } else {
672
+ core.info(`PR not mergeable: ${fullPr.mergeable_state}`);
673
+ break;
674
+ }
675
+ }
676
+ core.info(`PR #${prNumber} left open for next run`);
677
+
678
+ # ─── Post-merge: stats, schedule, mission check ────────────────────
679
+ post-merge:
680
+ needs: [params, dev, pr-cleanup]
681
+ if: always() && needs.params.result == 'success'
682
+ runs-on: ubuntu-latest
683
+ steps:
684
+ - uses: actions/checkout@v4
685
+ with:
686
+ fetch-depth: 0
687
+
688
+ - name: Summary
689
+ run: |
690
+ echo "## agentic-lib-workflow run summary" >> $GITHUB_STEP_SUMMARY
691
+ echo "- Mode: ${{ needs.params.outputs.mode }}" >> $GITHUB_STEP_SUMMARY
692
+ echo "- Model: ${{ needs.params.outputs.model }}" >> $GITHUB_STEP_SUMMARY
693
+
694
+ # ─── Schedule change (if requested) ────────────────────────────────
695
+ update-schedule:
696
+ needs: [params, dev]
697
+ if: always() && needs.params.outputs.schedule != '' && needs.params.result == 'success'
698
+ uses: ./.github/workflows/agentic-lib-schedule.yml
699
+ with:
700
+ frequency: ${{ needs.params.outputs.schedule }}
701
+ model: ${{ needs.params.outputs.model }}
702
+ secrets: inherit