@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.
Files changed (53) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +323 -0
  3. package/bin/agentic-lib.js +765 -0
  4. package/package.json +102 -0
  5. package/src/actions/agentic-step/action.yml +58 -0
  6. package/src/actions/agentic-step/config-loader.js +153 -0
  7. package/src/actions/agentic-step/copilot.js +170 -0
  8. package/src/actions/agentic-step/index.js +118 -0
  9. package/src/actions/agentic-step/logging.js +88 -0
  10. package/src/actions/agentic-step/package-lock.json +1891 -0
  11. package/src/actions/agentic-step/package.json +29 -0
  12. package/src/actions/agentic-step/safety.js +103 -0
  13. package/src/actions/agentic-step/tasks/discussions.js +141 -0
  14. package/src/actions/agentic-step/tasks/enhance-issue.js +102 -0
  15. package/src/actions/agentic-step/tasks/fix-code.js +71 -0
  16. package/src/actions/agentic-step/tasks/maintain-features.js +79 -0
  17. package/src/actions/agentic-step/tasks/maintain-library.js +67 -0
  18. package/src/actions/agentic-step/tasks/resolve-issue.js +98 -0
  19. package/src/actions/agentic-step/tasks/review-issue.js +121 -0
  20. package/src/actions/agentic-step/tasks/transform.js +213 -0
  21. package/src/actions/agentic-step/tools.js +142 -0
  22. package/src/actions/commit-if-changed/action.yml +39 -0
  23. package/src/actions/setup-npmrc/action.yml +38 -0
  24. package/src/agents/agent-apply-fix.md +13 -0
  25. package/src/agents/agent-discussion-bot.md +35 -0
  26. package/src/agents/agent-issue-resolution.md +13 -0
  27. package/src/agents/agent-maintain-features.md +29 -0
  28. package/src/agents/agent-maintain-library.md +31 -0
  29. package/src/agents/agent-ready-issue.md +13 -0
  30. package/src/agents/agent-review-issue.md +2 -0
  31. package/src/agents/agentic-lib.yml +68 -0
  32. package/src/scripts/accept-release.sh +29 -0
  33. package/src/scripts/activate-schedule.sh +41 -0
  34. package/src/scripts/clean.sh +21 -0
  35. package/src/scripts/generate-library-index.js +143 -0
  36. package/src/scripts/initialise.sh +39 -0
  37. package/src/scripts/md-to-html.js +77 -0
  38. package/src/scripts/update.sh +19 -0
  39. package/src/seeds/test.yml +33 -0
  40. package/src/seeds/zero-MISSION.md +7 -0
  41. package/src/seeds/zero-README.md +14 -0
  42. package/src/seeds/zero-agentic-lib.toml +32 -0
  43. package/src/seeds/zero-main.js +15 -0
  44. package/src/seeds/zero-main.test.js +11 -0
  45. package/src/seeds/zero-package.json +26 -0
  46. package/src/workflows/agent-discussions-bot.yml +78 -0
  47. package/src/workflows/agent-flow-fix-code.yml +98 -0
  48. package/src/workflows/agent-flow-maintain.yml +114 -0
  49. package/src/workflows/agent-flow-review.yml +99 -0
  50. package/src/workflows/agent-flow-transform.yml +82 -0
  51. package/src/workflows/agent-supervisor.yml +85 -0
  52. package/src/workflows/ci-automerge.yml +544 -0
  53. 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 }}