@xn-intenton-z2a/agentic-lib 7.1.6
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/LICENSE +674 -0
- package/README.md +323 -0
- package/bin/agentic-lib.js +765 -0
- package/package.json +102 -0
- package/src/actions/agentic-step/action.yml +58 -0
- package/src/actions/agentic-step/config-loader.js +153 -0
- package/src/actions/agentic-step/copilot.js +170 -0
- package/src/actions/agentic-step/index.js +118 -0
- package/src/actions/agentic-step/logging.js +88 -0
- package/src/actions/agentic-step/package-lock.json +1891 -0
- package/src/actions/agentic-step/package.json +29 -0
- package/src/actions/agentic-step/safety.js +103 -0
- package/src/actions/agentic-step/tasks/discussions.js +141 -0
- package/src/actions/agentic-step/tasks/enhance-issue.js +102 -0
- package/src/actions/agentic-step/tasks/fix-code.js +71 -0
- package/src/actions/agentic-step/tasks/maintain-features.js +79 -0
- package/src/actions/agentic-step/tasks/maintain-library.js +67 -0
- package/src/actions/agentic-step/tasks/resolve-issue.js +98 -0
- package/src/actions/agentic-step/tasks/review-issue.js +121 -0
- package/src/actions/agentic-step/tasks/transform.js +213 -0
- package/src/actions/agentic-step/tools.js +142 -0
- package/src/actions/commit-if-changed/action.yml +39 -0
- package/src/actions/setup-npmrc/action.yml +38 -0
- package/src/agents/agent-apply-fix.md +13 -0
- package/src/agents/agent-discussion-bot.md +35 -0
- package/src/agents/agent-issue-resolution.md +13 -0
- package/src/agents/agent-maintain-features.md +29 -0
- package/src/agents/agent-maintain-library.md +31 -0
- package/src/agents/agent-ready-issue.md +13 -0
- package/src/agents/agent-review-issue.md +2 -0
- package/src/agents/agentic-lib.yml +68 -0
- package/src/scripts/accept-release.sh +29 -0
- package/src/scripts/activate-schedule.sh +41 -0
- package/src/scripts/clean.sh +21 -0
- package/src/scripts/generate-library-index.js +143 -0
- package/src/scripts/initialise.sh +39 -0
- package/src/scripts/md-to-html.js +77 -0
- package/src/scripts/update.sh +19 -0
- package/src/seeds/test.yml +33 -0
- package/src/seeds/zero-MISSION.md +7 -0
- package/src/seeds/zero-README.md +14 -0
- package/src/seeds/zero-agentic-lib.toml +32 -0
- package/src/seeds/zero-main.js +15 -0
- package/src/seeds/zero-main.test.js +11 -0
- package/src/seeds/zero-package.json +26 -0
- package/src/workflows/agent-discussions-bot.yml +78 -0
- package/src/workflows/agent-flow-fix-code.yml +98 -0
- package/src/workflows/agent-flow-maintain.yml +114 -0
- package/src/workflows/agent-flow-review.yml +99 -0
- package/src/workflows/agent-flow-transform.yml +82 -0
- package/src/workflows/agent-supervisor.yml +85 -0
- package/src/workflows/ci-automerge.yml +544 -0
- package/src/workflows/ci-init.yml +63 -0
|
@@ -0,0 +1,544 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (C) 2025-2026 Polycode Limited
|
|
3
|
+
# .github/workflows/ci-automerge.yml
|
|
4
|
+
#
|
|
5
|
+
# This file is part of the example suite for `agentic-lib` see: https://github.com/xn-intenton-z2a/agentic-lib
|
|
6
|
+
# This file is licensed under the MIT License. For details, see LICENSE-MIT
|
|
7
|
+
|
|
8
|
+
name: ci-automerge
|
|
9
|
+
concurrency: agentic-lib-merge-main
|
|
10
|
+
run-name: "ci-automerge [${{ github.ref_name }}]"
|
|
11
|
+
|
|
12
|
+
on:
|
|
13
|
+
pull_request:
|
|
14
|
+
check_suite:
|
|
15
|
+
workflow_dispatch:
|
|
16
|
+
workflow_call:
|
|
17
|
+
inputs:
|
|
18
|
+
workflow:
|
|
19
|
+
description: 'Was this workflow called by another workflow?, e.g. "true"'
|
|
20
|
+
type: string
|
|
21
|
+
required: false
|
|
22
|
+
default: "true"
|
|
23
|
+
schedule:
|
|
24
|
+
- cron: '53 */4 * * *'
|
|
25
|
+
|
|
26
|
+
env:
|
|
27
|
+
pullRequestLabel: "automerge"
|
|
28
|
+
branchPrefix: "agentic-lib-issue-"
|
|
29
|
+
copilotBranchPrefix: "copilot/"
|
|
30
|
+
|
|
31
|
+
jobs:
|
|
32
|
+
label:
|
|
33
|
+
runs-on: ubuntu-latest
|
|
34
|
+
steps:
|
|
35
|
+
- name: echo
|
|
36
|
+
shell: bash
|
|
37
|
+
run: |
|
|
38
|
+
echo "Label: ${{ env.pullRequestLabel }}"
|
|
39
|
+
outputs:
|
|
40
|
+
pullRequestLabel: ${{ env.pullRequestLabel }}
|
|
41
|
+
|
|
42
|
+
pr:
|
|
43
|
+
needs: label
|
|
44
|
+
if: github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, needs.label.outputs.pullRequestLabel)
|
|
45
|
+
runs-on: ubuntu-latest
|
|
46
|
+
steps:
|
|
47
|
+
- name: check-pr
|
|
48
|
+
id: check-pr
|
|
49
|
+
uses: actions/github-script@v7
|
|
50
|
+
with:
|
|
51
|
+
script: |
|
|
52
|
+
const pr = context.payload.pull_request;
|
|
53
|
+
const pullNumber = pr.number;
|
|
54
|
+
const owner = context.repo.owner;
|
|
55
|
+
const repo = context.repo.repo;
|
|
56
|
+
let shouldSkipMerge;
|
|
57
|
+
let prMerged;
|
|
58
|
+
|
|
59
|
+
const { data: pullRequest } = await github.rest.pulls.get({
|
|
60
|
+
owner,
|
|
61
|
+
repo,
|
|
62
|
+
pull_number: pullNumber,
|
|
63
|
+
});
|
|
64
|
+
core.info(`Found pull request #${pullRequest.number} with state: ${pullRequest.state}, mergeable: ${pullRequest.mergeable}`);
|
|
65
|
+
|
|
66
|
+
if (pullRequest.state === "closed") {
|
|
67
|
+
core.info(`PR #${pullNumber} is already closed.`);
|
|
68
|
+
shouldSkipMerge = 'true';
|
|
69
|
+
prMerged = 'true';
|
|
70
|
+
} else if (pullRequest.state !== "open") {
|
|
71
|
+
core.info(`PR #${pullNumber} is not open, it is ${pullRequest.state}.`);
|
|
72
|
+
shouldSkipMerge = 'true';
|
|
73
|
+
prMerged = 'false';
|
|
74
|
+
} else if (pullRequest.mergeable === true) {
|
|
75
|
+
core.info(`PR #${pullNumber} is mergeable.`);
|
|
76
|
+
shouldSkipMerge = 'false';
|
|
77
|
+
prMerged = 'false';
|
|
78
|
+
} else if (pullRequest.mergeable === false) {
|
|
79
|
+
core.info(`PR #${pullNumber} is not mergeable.`);
|
|
80
|
+
shouldSkipMerge = 'true';
|
|
81
|
+
prMerged = 'false';
|
|
82
|
+
} else {
|
|
83
|
+
core.info(`PR #${pullNumber} mergeability is ${pullRequest.mergeable}.`);
|
|
84
|
+
shouldSkipMerge = 'true';
|
|
85
|
+
prMerged = 'false';
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
core.setOutput('pullNumber', pullNumber ? pullNumber.toString() : '');
|
|
89
|
+
core.setOutput('shouldSkipMerge', shouldSkipMerge);
|
|
90
|
+
core.setOutput('prMerged', prMerged);
|
|
91
|
+
outputs:
|
|
92
|
+
pullNumber: ${{ steps.check-pr.outputs.pullNumber }}
|
|
93
|
+
shouldSkipMerge: ${{ steps.check-pr.outputs.shouldSkipMerge }}
|
|
94
|
+
prMerged: ${{ steps.check-pr.outputs.prMerged }}
|
|
95
|
+
|
|
96
|
+
cs:
|
|
97
|
+
if: github.event_name == 'check_suite' && github.event.check_suite.conclusion == 'success'
|
|
98
|
+
runs-on: ubuntu-latest
|
|
99
|
+
steps:
|
|
100
|
+
- name: find-pr
|
|
101
|
+
id: find-pr
|
|
102
|
+
uses: actions/github-script@v7
|
|
103
|
+
with:
|
|
104
|
+
script: |
|
|
105
|
+
const checkSuite = context.payload.check_suite;
|
|
106
|
+
const owner = context.repo.owner;
|
|
107
|
+
const repo = context.repo.repo;
|
|
108
|
+
const headSha = checkSuite.head_sha;
|
|
109
|
+
let pullNumber;
|
|
110
|
+
let shouldSkipMerge;
|
|
111
|
+
let prMerged;
|
|
112
|
+
|
|
113
|
+
const { data: prs } = await github.rest.repos.listPullRequestsAssociatedWithCommit({
|
|
114
|
+
owner,
|
|
115
|
+
repo,
|
|
116
|
+
commit_sha: headSha,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
if (!prs || prs.length === 0) {
|
|
120
|
+
core.info('No pull requests associated with this check suite.');
|
|
121
|
+
pullNumber = '';
|
|
122
|
+
shouldSkipMerge = 'true';
|
|
123
|
+
prMerged = 'false';
|
|
124
|
+
} else {
|
|
125
|
+
const openPRs = prs.filter(pr => pr.state === 'open');
|
|
126
|
+
const prWithAutomerge = openPRs.find(pr => pr.labels.some(label => label.name === 'automerge'));
|
|
127
|
+
|
|
128
|
+
if (!prWithAutomerge) {
|
|
129
|
+
core.info('No open pull requests with the "automerge" label.');
|
|
130
|
+
pullNumber = '';
|
|
131
|
+
shouldSkipMerge = 'true';
|
|
132
|
+
prMerged = 'false';
|
|
133
|
+
} else {
|
|
134
|
+
core.info(`Open pull request with "automerge" label: #${prWithAutomerge.number}`);
|
|
135
|
+
pullNumber = prWithAutomerge.number;
|
|
136
|
+
shouldSkipMerge = 'false';
|
|
137
|
+
prMerged = 'false';
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
core.setOutput('pullNumber', pullNumber ? pullNumber.toString() : '');
|
|
142
|
+
core.setOutput('shouldSkipMerge', shouldSkipMerge);
|
|
143
|
+
core.setOutput('prMerged', prMerged);
|
|
144
|
+
outputs:
|
|
145
|
+
pullNumber: ${{ steps.find-pr.outputs.pullNumber }}
|
|
146
|
+
shouldSkipMerge: ${{ steps.find-pr.outputs.shouldSkipMerge }}
|
|
147
|
+
prMerged: ${{ steps.find-pr.outputs.prMerged }}
|
|
148
|
+
|
|
149
|
+
ls:
|
|
150
|
+
needs: label
|
|
151
|
+
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || inputs.workflow == 'true' || github.event_name == 'workflow_call' || github.event_name == 'workflow_run'
|
|
152
|
+
runs-on: ubuntu-latest
|
|
153
|
+
steps:
|
|
154
|
+
- name: Determine pull request number
|
|
155
|
+
id: get-pull
|
|
156
|
+
uses: actions/github-script@v7
|
|
157
|
+
env:
|
|
158
|
+
pullRequestLabel: ${{ needs.label.outputs.pullRequestLabel }}
|
|
159
|
+
with:
|
|
160
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
161
|
+
script: |
|
|
162
|
+
const branchPrefix = process.env.branchPrefix;
|
|
163
|
+
const pullRequestLabel = process.env.pullRequestLabel;
|
|
164
|
+
let pullNumber = ''
|
|
165
|
+
let branchName = '';
|
|
166
|
+
let issueNumber = '';
|
|
167
|
+
const { data: pullRequests } = await github.rest.pulls.list({
|
|
168
|
+
owner: context.repo.owner,
|
|
169
|
+
repo: context.repo.repo,
|
|
170
|
+
state: 'open',
|
|
171
|
+
per_page: 1,
|
|
172
|
+
sort: 'created',
|
|
173
|
+
direction: 'asc'
|
|
174
|
+
});
|
|
175
|
+
if (pullRequests.length > 0) {
|
|
176
|
+
const filteredPRs = pullRequests
|
|
177
|
+
.filter(pr => pr.labels.some(label => label.name === pullRequestLabel ))
|
|
178
|
+
.filter(pr => pr.head.ref.startsWith(branchPrefix));
|
|
179
|
+
if (filteredPRs.length > 0) {
|
|
180
|
+
const pullRequest = filteredPRs[0];
|
|
181
|
+
pullNumber = pullRequest.number;
|
|
182
|
+
core.info(`Found open pull request with label ${pullRequestLabel}: #${pullRequest.number} and branch name ${pullRequest.head.ref}`);
|
|
183
|
+
core.info(JSON.stringify(pullRequest));
|
|
184
|
+
} else {
|
|
185
|
+
core.info(`No open pull request found with label ${pullRequestLabel}.`);
|
|
186
|
+
pullNumber = '';
|
|
187
|
+
}
|
|
188
|
+
} else {
|
|
189
|
+
pullNumber = '';
|
|
190
|
+
core.info('No open pull requests found.');
|
|
191
|
+
}
|
|
192
|
+
core.info(`pullNumber: ${pullNumber}`);
|
|
193
|
+
core.setOutput('pullNumber', pullNumber);
|
|
194
|
+
result-encoding: string
|
|
195
|
+
outputs:
|
|
196
|
+
pullNumber: ${{ steps.get-pull.outputs.pullNumber }}
|
|
197
|
+
|
|
198
|
+
merge-check:
|
|
199
|
+
if: ${{ !cancelled() }}
|
|
200
|
+
needs:
|
|
201
|
+
- pr
|
|
202
|
+
- cs
|
|
203
|
+
- ls
|
|
204
|
+
runs-on: ubuntu-latest
|
|
205
|
+
steps:
|
|
206
|
+
- name: get-pull
|
|
207
|
+
id: get-pull
|
|
208
|
+
uses: actions/github-script@v7
|
|
209
|
+
env:
|
|
210
|
+
prMerged: ${{ needs.pr.outputs.prMerged || 'false' }}
|
|
211
|
+
pullNumber: ${{ needs.pr.outputs.pullNumber || needs.cs.outputs.pullNumber || needs.ls.outputs.pullNumber || ''}}
|
|
212
|
+
shouldSkipMerge: ${{ needs.pr.outputs.shouldSkipMerge || 'false' }}
|
|
213
|
+
with:
|
|
214
|
+
script: |
|
|
215
|
+
// Merge outputs from pr-check, cs-check, and determine-ls.
|
|
216
|
+
// Only one of pr-check or cs-check should have run.
|
|
217
|
+
const prMerged = process.env.prMerged;
|
|
218
|
+
const pullNumber = process.env.pullNumber;
|
|
219
|
+
const branchPrefix = process.env.branchPrefix;
|
|
220
|
+
const shouldSkipMerge = process.env.shouldSkipMerge;
|
|
221
|
+
const owner = context.repo.owner;
|
|
222
|
+
const repo = context.repo.repo;
|
|
223
|
+
|
|
224
|
+
core.setOutput('prMerged', `${prMerged}`);
|
|
225
|
+
core.setOutput('pullNumber', `${pullNumber}`);
|
|
226
|
+
core.setOutput('shouldSkipMerge', `${shouldSkipMerge}`);
|
|
227
|
+
core.info(`prMerged: '${prMerged}'`);
|
|
228
|
+
core.info(`pullNumber: '${pullNumber}'`);
|
|
229
|
+
core.info(`shouldSkipMerge: '${shouldSkipMerge}'`);
|
|
230
|
+
core.info(`branchPrefix '${branchPrefix}'`);
|
|
231
|
+
|
|
232
|
+
let branchName = '';
|
|
233
|
+
let issueNumber = '';
|
|
234
|
+
if( pullNumber) {
|
|
235
|
+
const { data: pullRequest } = await github.rest.pulls.get({
|
|
236
|
+
owner,
|
|
237
|
+
repo,
|
|
238
|
+
pull_number: pullNumber
|
|
239
|
+
});
|
|
240
|
+
branchName = pullRequest.head.ref;
|
|
241
|
+
core.info(`branchName '${branchName}'`);
|
|
242
|
+
|
|
243
|
+
// Extract issue number from branch name (supports agentic-lib-issue-* and copilot/* prefixes)
|
|
244
|
+
let issueNumberMatch = '';
|
|
245
|
+
if (branchName.startsWith(branchPrefix)) {
|
|
246
|
+
issueNumberMatch = branchName.replace(branchPrefix, '');
|
|
247
|
+
} else if (branchName.startsWith('copilot/')) {
|
|
248
|
+
// Copilot branches may contain issue number in the name, e.g. copilot/fix-123
|
|
249
|
+
const match = branchName.match(/(\d+)/);
|
|
250
|
+
issueNumberMatch = match ? match[1] : '';
|
|
251
|
+
}
|
|
252
|
+
if (parseInt(issueNumberMatch)) {
|
|
253
|
+
issueNumber = `${parseInt(issueNumberMatch)}`;
|
|
254
|
+
} else {
|
|
255
|
+
issueNumber = '';
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
core.setOutput('branchName', branchName);
|
|
260
|
+
core.info(`branchName '${branchName}'`);
|
|
261
|
+
core.setOutput('issueNumber', issueNumber);
|
|
262
|
+
core.info(`issueNumber '${issueNumber}'`);
|
|
263
|
+
result-encoding: string
|
|
264
|
+
outputs:
|
|
265
|
+
prMerged: ${{ steps.get-pull.outputs.prMerged }}
|
|
266
|
+
pullNumber: ${{ steps.get-pull.outputs.pullNumber }}
|
|
267
|
+
shouldSkipMerge: ${{ steps.get-pull.outputs.shouldSkipMerge }}
|
|
268
|
+
branchName: ${{ steps.get-pull.outputs.branchName }}
|
|
269
|
+
issueNumber: ${{ steps.get-pull.outputs.issueNumber }}
|
|
270
|
+
|
|
271
|
+
automerge:
|
|
272
|
+
needs:
|
|
273
|
+
- merge-check
|
|
274
|
+
if: ${{ !cancelled() && needs.merge-check.outputs.shouldSkipMerge != 'true' && needs.merge-check.outputs.pullNumber != '' }}
|
|
275
|
+
permissions:
|
|
276
|
+
contents: write
|
|
277
|
+
pull-requests: write
|
|
278
|
+
checks: write
|
|
279
|
+
runs-on: ubuntu-latest
|
|
280
|
+
env:
|
|
281
|
+
pullNumber: ${{ needs.merge-check.outputs.pullNumber }}
|
|
282
|
+
steps:
|
|
283
|
+
- name: trigger-checks
|
|
284
|
+
id: trigger-checks
|
|
285
|
+
uses: actions/github-script@v7
|
|
286
|
+
with:
|
|
287
|
+
script: |
|
|
288
|
+
const pullNumber = parseInt(process.env.pullNumber);
|
|
289
|
+
if (!pullNumber) return;
|
|
290
|
+
const owner = context.repo.owner;
|
|
291
|
+
const repo = context.repo.repo;
|
|
292
|
+
|
|
293
|
+
const { data: pullRequest } = await github.rest.pulls.get({
|
|
294
|
+
owner, repo, pull_number: pullNumber,
|
|
295
|
+
});
|
|
296
|
+
core.info(`PR #${pullNumber} state: ${pullRequest.state}, mergeable: ${pullRequest.mergeable}`);
|
|
297
|
+
|
|
298
|
+
if (pullRequest.state === "closed") {
|
|
299
|
+
core.info(`PR #${pullNumber} is already closed.`);
|
|
300
|
+
} else if (pullRequest.mergeable === null) {
|
|
301
|
+
core.info(`PR #${pullNumber} mergeability unknown, triggering check re-runs.`);
|
|
302
|
+
const ref = pullRequest.head.sha;
|
|
303
|
+
const { data: checkSuites } = await github.rest.checks.listSuitesForRef({ owner, repo, ref });
|
|
304
|
+
for (const suite of checkSuites.check_suites) {
|
|
305
|
+
try {
|
|
306
|
+
await github.rest.checks.rerequestSuite({ owner, repo, check_suite_id: suite.id });
|
|
307
|
+
} catch (error) {
|
|
308
|
+
core.info(`Failed to re-request check suite: ${error.message}`);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
- name: Wait for checks
|
|
314
|
+
run: sleep 5
|
|
315
|
+
|
|
316
|
+
- name: auto-merge-pr
|
|
317
|
+
id: auto-merge-pr
|
|
318
|
+
uses: actions/github-script@v7
|
|
319
|
+
with:
|
|
320
|
+
script: |
|
|
321
|
+
const pullNumber = parseInt(process.env.pullNumber);
|
|
322
|
+
if (!pullNumber) { core.setOutput('prMerged', 'false'); core.setOutput('message', 'No PR number'); return; }
|
|
323
|
+
const owner = context.repo.owner;
|
|
324
|
+
const repo = context.repo.repo;
|
|
325
|
+
let prMerged;
|
|
326
|
+
let message = '';
|
|
327
|
+
|
|
328
|
+
const { data: pullRequest } = await github.rest.pulls.get({
|
|
329
|
+
owner, repo, pull_number: pullNumber,
|
|
330
|
+
});
|
|
331
|
+
core.info(`PR #${pullNumber} state: ${pullRequest.state}, mergeable_state: ${pullRequest.mergeable_state}`);
|
|
332
|
+
|
|
333
|
+
if (pullRequest.state === "closed") {
|
|
334
|
+
if (pullRequest.merged) {
|
|
335
|
+
message = `PR #${pullNumber} is closed and merged.`;
|
|
336
|
+
prMerged = 'true';
|
|
337
|
+
} else {
|
|
338
|
+
message = `PR #${pullNumber} is closed but not merged.`;
|
|
339
|
+
prMerged = 'false';
|
|
340
|
+
}
|
|
341
|
+
} else if (pullRequest.mergeable && pullRequest.mergeable_state === 'clean') {
|
|
342
|
+
await github.rest.pulls.merge({ owner, repo, pull_number: pullNumber, merge_method: 'squash' });
|
|
343
|
+
core.info(`PR #${pullNumber} merged successfully.`);
|
|
344
|
+
const branchRef = `heads/${pullRequest.head.ref}`;
|
|
345
|
+
await github.rest.git.deleteRef({ owner, repo, ref: branchRef });
|
|
346
|
+
message = `Branch '${pullRequest.head.ref}' deleted.`;
|
|
347
|
+
prMerged = 'true';
|
|
348
|
+
} else if (pullRequest.mergeable_state === 'dirty' || pullRequest.mergeable === false) {
|
|
349
|
+
message = `PR #${pullNumber} has conflicts. Closing.`;
|
|
350
|
+
await github.rest.issues.createComment({
|
|
351
|
+
owner, repo, issue_number: pullNumber,
|
|
352
|
+
body: `This pull request is being closed due to conflicts (mergeable_state: ${pullRequest.mergeable_state}, mergeable: ${pullRequest.mergeable}).`,
|
|
353
|
+
});
|
|
354
|
+
await github.rest.pulls.update({ owner, repo, pull_number: pullNumber, state: "closed" });
|
|
355
|
+
try {
|
|
356
|
+
await github.rest.git.deleteRef({ owner, repo, ref: `heads/${pullRequest.head.ref}` });
|
|
357
|
+
message += ` Branch '${pullRequest.head.ref}' deleted.`;
|
|
358
|
+
} catch (error) {
|
|
359
|
+
message += ` Failed to delete branch: ${error.message}`;
|
|
360
|
+
}
|
|
361
|
+
prMerged = 'false';
|
|
362
|
+
} else if (pullRequest.mergeable === null) {
|
|
363
|
+
message = `PR #${pullNumber} does not yet have a value for mergeability.`;
|
|
364
|
+
prMerged = 'false';
|
|
365
|
+
} else {
|
|
366
|
+
message = `PR #${pullNumber} is in an unexpected state: ${pullRequest.mergeable_state}.`;
|
|
367
|
+
prMerged = 'false';
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
core.setOutput('prMerged', prMerged);
|
|
371
|
+
core.setOutput('message', message);
|
|
372
|
+
outputs:
|
|
373
|
+
prMerged: ${{ steps.auto-merge-pr.outputs.prMerged }}
|
|
374
|
+
message: ${{ steps.auto-merge-pr.outputs.message }}
|
|
375
|
+
|
|
376
|
+
validate-issue-number:
|
|
377
|
+
needs:
|
|
378
|
+
- merge-check
|
|
379
|
+
if: ${{ !cancelled() }}
|
|
380
|
+
runs-on: ubuntu-latest
|
|
381
|
+
steps:
|
|
382
|
+
- id: validate-issue-number
|
|
383
|
+
run: |
|
|
384
|
+
issueNumber="${{ needs.merge-check.outputs.issueNumber }}"
|
|
385
|
+
if [[ -n "$issueNumber" && "$issueNumber" =~ [0-9] ]]; then
|
|
386
|
+
echo "[$issueNumber] is a valid issue number."
|
|
387
|
+
echo "isValid=true" >> $GITHUB_OUTPUT
|
|
388
|
+
else
|
|
389
|
+
echo "[$issueNumber] is not a valid issue number."
|
|
390
|
+
echo "isValid=false" >> $GITHUB_OUTPUT
|
|
391
|
+
fi
|
|
392
|
+
outputs:
|
|
393
|
+
isValid: ${{ steps.validate-issue-number.outputs.isValid }}
|
|
394
|
+
|
|
395
|
+
label-issue-after-automerge:
|
|
396
|
+
needs:
|
|
397
|
+
- merge-check
|
|
398
|
+
- automerge
|
|
399
|
+
- validate-issue-number
|
|
400
|
+
if: ${{ !cancelled() && ( needs.automerge.outputs.prMerged == 'true' && needs.merge-check.outputs.pullNumber != '' && needs.validate-issue-number.outputs.isValid == 'true' ) }}
|
|
401
|
+
permissions:
|
|
402
|
+
contents: write
|
|
403
|
+
issues: write
|
|
404
|
+
pull-requests: read
|
|
405
|
+
runs-on: ubuntu-latest
|
|
406
|
+
env:
|
|
407
|
+
pullNumber: ${{ needs.automerge.outputs.prMerged == 'true' && needs.merge-check.outputs.pullNumber || '' }}
|
|
408
|
+
issueNumber: ${{ needs.merge-check.outputs.issueNumber }}
|
|
409
|
+
steps:
|
|
410
|
+
- name: Label issue after merge
|
|
411
|
+
uses: actions/github-script@v7
|
|
412
|
+
with:
|
|
413
|
+
script: |
|
|
414
|
+
const pullNumber = parseInt(process.env.pullNumber);
|
|
415
|
+
const issueNumber = parseInt(process.env.issueNumber);
|
|
416
|
+
if (!pullNumber || !issueNumber) return;
|
|
417
|
+
const owner = context.repo.owner;
|
|
418
|
+
const repo = context.repo.repo;
|
|
419
|
+
|
|
420
|
+
const { data: pullRequest } = await github.rest.pulls.get({
|
|
421
|
+
owner, repo, pull_number: pullNumber,
|
|
422
|
+
});
|
|
423
|
+
const branchName = pullRequest.head.ref;
|
|
424
|
+
|
|
425
|
+
const { data: issue } = await github.rest.issues.get({
|
|
426
|
+
owner, repo, issue_number: issueNumber,
|
|
427
|
+
});
|
|
428
|
+
const hasMergedLabel = issue.labels.some(label => label.name === 'merged');
|
|
429
|
+
const hasInProgressLabel = issue.labels.some(label => label.name === 'in-progress');
|
|
430
|
+
|
|
431
|
+
if (!hasMergedLabel) {
|
|
432
|
+
core.info(`Adding "merged" label to issue #${issueNumber}.`);
|
|
433
|
+
await github.rest.issues.addLabels({
|
|
434
|
+
owner, repo, issue_number: issueNumber, labels: ['merged'],
|
|
435
|
+
});
|
|
436
|
+
await github.rest.issues.createComment({
|
|
437
|
+
owner, repo, issue_number: issueNumber,
|
|
438
|
+
body: `The feature branch has been merged: ${branchName}`,
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (hasInProgressLabel) {
|
|
443
|
+
core.info(`Removing "in-progress" label from issue #${issueNumber}.`);
|
|
444
|
+
await github.rest.issues.removeLabel({
|
|
445
|
+
owner, repo, issue_number: issueNumber, name: 'in-progress',
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
log-intention-activity-merge-pr:
|
|
450
|
+
needs:
|
|
451
|
+
- merge-check
|
|
452
|
+
- automerge
|
|
453
|
+
if: ${{ !cancelled() && needs.merge-check.outputs.shouldSkipMerge != 'true' && needs.merge-check.outputs.pullNumber != '' }}
|
|
454
|
+
runs-on: ubuntu-latest
|
|
455
|
+
env:
|
|
456
|
+
gitUserEmail: "action@github.com"
|
|
457
|
+
gitUserName: "GitHub Actions[bot]"
|
|
458
|
+
steps:
|
|
459
|
+
- uses: actions/checkout@v4
|
|
460
|
+
with:
|
|
461
|
+
fetch-depth: 0
|
|
462
|
+
ref: ${{ github.ref }}
|
|
463
|
+
|
|
464
|
+
- name: Load config
|
|
465
|
+
id: config
|
|
466
|
+
run: |
|
|
467
|
+
CONFIG="${{ vars.configPath || '.github/agentic-lib/agents/agentic-lib.yml' }}"
|
|
468
|
+
echo "intentionFilepath=$(yq -r '.intentionBot.intentionFilepath // "intentïon.md"' "$CONFIG")" >> $GITHUB_OUTPUT
|
|
469
|
+
|
|
470
|
+
- name: Get latest from remote
|
|
471
|
+
run: |
|
|
472
|
+
git config --local user.email '${{ env.gitUserEmail }}'
|
|
473
|
+
git config --local user.name '${{ env.gitUserName }}'
|
|
474
|
+
git config --local pull.ff false # never fast-forward
|
|
475
|
+
git config --local pull.rebase false # never rebase on pull
|
|
476
|
+
git fetch origin ${{ github.ref_name }}
|
|
477
|
+
git merge origin/${{ github.ref_name }} --no-ff --no-edit --strategy=recursive --strategy-option=ours
|
|
478
|
+
|
|
479
|
+
- name: log-intention-activity
|
|
480
|
+
id: log-intention-activity
|
|
481
|
+
uses: actions/github-script@v7
|
|
482
|
+
env:
|
|
483
|
+
pullNumber: ${{ needs.merge-check.outputs.pullNumber }}
|
|
484
|
+
shouldSkipMerge: ${{ needs.merge-check.outputs.shouldSkipMerge }}
|
|
485
|
+
branchName: ${{ needs.merge-check.outputs.branchName }}
|
|
486
|
+
issueNumber: ${{ needs.merge-check.outputs.issueNumber }}
|
|
487
|
+
prMerged: ${{ needs.automerge.outputs.prMerged }}
|
|
488
|
+
message: ${{ needs.automerge.outputs.message }}
|
|
489
|
+
outcome: ${{ needs.automerge.result }}
|
|
490
|
+
intentionFilepath: ${{ steps.config.outputs.intentionFilepath }}
|
|
491
|
+
with:
|
|
492
|
+
script: |
|
|
493
|
+
const pullNumber = process.env.pullNumber;
|
|
494
|
+
const shouldSkipMerge = process.env.shouldSkipMerge;
|
|
495
|
+
const branchName = process.env.branchName;
|
|
496
|
+
const issueNumber = process.env.issueNumber;
|
|
497
|
+
const prMerged = process.env.prMerged;
|
|
498
|
+
const message = process.env.message;
|
|
499
|
+
const outcome = process.env.outcome;
|
|
500
|
+
const intentionFilepath = process.env.intentionFilepath;
|
|
501
|
+
|
|
502
|
+
const activity = `When attempting to merge PR #${pullNumber} for branch name "${branchName}" to resolve issue number "${issueNumber}" the decision to skip merge was "${shouldSkipMerge}":
|
|
503
|
+
|
|
504
|
+
then the PR was merged "${prMerged}"
|
|
505
|
+
|
|
506
|
+
with message: "${message}"
|
|
507
|
+
|
|
508
|
+
with outcome "${outcome}".`;
|
|
509
|
+
|
|
510
|
+
core.info(`Activity: ${activity}`);
|
|
511
|
+
core.info(`Seed discussion filepath: ${intentionFilepath}`);
|
|
512
|
+
|
|
513
|
+
const fs = require('fs');
|
|
514
|
+
const path = require('path');
|
|
515
|
+
|
|
516
|
+
// Create trace file and the parent directory of intentionFilepath if it doesn't exist
|
|
517
|
+
if (!fs.existsSync(path.dirname(intentionFilepath))) {
|
|
518
|
+
fs.mkdirSync(path.dirname(intentionFilepath), { recursive: true });
|
|
519
|
+
}
|
|
520
|
+
const isoDate = new Date().toISOString();
|
|
521
|
+
const activityLogContent = `\n## Merge PR activity at ${isoDate}
|
|
522
|
+
|
|
523
|
+
${activity}
|
|
524
|
+
|
|
525
|
+
---
|
|
526
|
+
`;
|
|
527
|
+
if (fs.existsSync(intentionFilepath)) {
|
|
528
|
+
fs.appendFileSync(intentionFilepath, activityLogContent);
|
|
529
|
+
} else {
|
|
530
|
+
fs.writeFileSync(intentionFilepath, activityLogContent);
|
|
531
|
+
}
|
|
532
|
+
core.info(`Activity logged to ${intentionFilepath}`);
|
|
533
|
+
|
|
534
|
+
- name: Commit changes
|
|
535
|
+
run: |
|
|
536
|
+
git config --local user.email '${{ env.gitUserEmail }}'
|
|
537
|
+
git config --local user.name '${{ env.gitUserName }}'
|
|
538
|
+
git config --local pull.ff false # never fast-forward
|
|
539
|
+
git config --local pull.rebase false # never rebase on pull
|
|
540
|
+
git add ${{ steps.config.outputs.intentionFilepath }}
|
|
541
|
+
git commit -m "Activity logged by ci-automerge.yml" || echo "No changes to commit"
|
|
542
|
+
git fetch origin ${{ github.ref_name }}
|
|
543
|
+
git merge origin/${{ github.ref_name }} --no-ff --no-edit --strategy=recursive --strategy-option=ours
|
|
544
|
+
git push -v origin ${{ github.ref_name }}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (C) 2025-2026 Polycode Limited
|
|
3
|
+
# .github/workflows/ci-init.yml
|
|
4
|
+
#
|
|
5
|
+
# Pulls the latest agentic-lib infrastructure into this repository.
|
|
6
|
+
# Can optionally reset source files to the seed template state.
|
|
7
|
+
|
|
8
|
+
name: ci-init
|
|
9
|
+
run-name: "ci-init [${{ github.ref_name }}]"
|
|
10
|
+
|
|
11
|
+
on:
|
|
12
|
+
workflow_dispatch:
|
|
13
|
+
inputs:
|
|
14
|
+
purge:
|
|
15
|
+
description: "Reset source files to seed template state"
|
|
16
|
+
type: boolean
|
|
17
|
+
default: false
|
|
18
|
+
version:
|
|
19
|
+
description: "agentic-lib version to install (default: latest)"
|
|
20
|
+
type: string
|
|
21
|
+
default: "latest"
|
|
22
|
+
|
|
23
|
+
permissions:
|
|
24
|
+
contents: write
|
|
25
|
+
|
|
26
|
+
jobs:
|
|
27
|
+
init:
|
|
28
|
+
runs-on: ubuntu-latest
|
|
29
|
+
steps:
|
|
30
|
+
- uses: actions/checkout@v4
|
|
31
|
+
|
|
32
|
+
- uses: actions/setup-node@v4
|
|
33
|
+
with:
|
|
34
|
+
node-version: "24"
|
|
35
|
+
|
|
36
|
+
- name: Install agentic-lib
|
|
37
|
+
run: npm install @xn-intenton-z2a/agentic-lib@${{ inputs.version }}
|
|
38
|
+
|
|
39
|
+
- name: Run agentic-lib init
|
|
40
|
+
run: |
|
|
41
|
+
PURGE_FLAG=""
|
|
42
|
+
if [ "${{ inputs.purge }}" = "true" ]; then
|
|
43
|
+
PURGE_FLAG="--purge"
|
|
44
|
+
fi
|
|
45
|
+
npx agentic-lib init $PURGE_FLAG
|
|
46
|
+
|
|
47
|
+
- name: Install dependencies after init
|
|
48
|
+
if: inputs.purge == true
|
|
49
|
+
run: npm install
|
|
50
|
+
|
|
51
|
+
- name: Install agentic-step dependencies
|
|
52
|
+
run: |
|
|
53
|
+
cd .github/agentic-lib/actions/agentic-step
|
|
54
|
+
npm ci
|
|
55
|
+
|
|
56
|
+
- name: Verify tests pass
|
|
57
|
+
run: npm test
|
|
58
|
+
|
|
59
|
+
- name: Commit changes
|
|
60
|
+
uses: ./.github/agentic-lib/actions/commit-if-changed
|
|
61
|
+
with:
|
|
62
|
+
commit-message: "agentic-lib init${{ inputs.purge == true && ' --purge' || '' }} (v${{ inputs.version }})"
|
|
63
|
+
push-ref: ${{ github.ref_name }}
|