@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.
- package/bin/agentic-lib.js +20 -10
- package/package.json +1 -1
- package/src/actions/agentic-step/tasks/supervise.js +12 -13
- package/src/agents/agent-supervisor.md +11 -12
- package/src/seeds/{init.yml → agentic-lib-init.yml} +73 -36
- package/src/seeds/{test.yml → agentic-lib-test.yml} +6 -7
- package/src/seeds/zero-package.json +1 -1
- package/src/workflows/{agent-discussions-bot.yml → agentic-lib-bot.yml} +8 -6
- package/src/workflows/{agent-supervisor-schedule.yml → agentic-lib-schedule.yml} +14 -36
- package/src/workflows/agentic-lib-workflow.yml +702 -0
- package/src/workflows/agent-flow-fix-code.yml +0 -123
- package/src/workflows/agent-flow-maintain.yml +0 -115
- package/src/workflows/agent-flow-review.yml +0 -108
- package/src/workflows/agent-flow-transform.yml +0 -201
- package/src/workflows/agent-supervisor.yml +0 -266
- package/src/workflows/ci-automerge.yml +0 -457
|
@@ -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
|