@xn-intenton-z2a/agentic-lib 7.1.101 → 7.1.103
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/.github/workflows/agentic-lib-init.yml +19 -11
- package/.github/workflows/agentic-lib-schedule.yml +35 -6
- package/.github/workflows/agentic-lib-test.yml +105 -2
- package/.github/workflows/agentic-lib-workflow.yml +191 -58
- package/package.json +1 -1
- package/src/actions/agentic-step/index.js +4 -4
- package/src/actions/agentic-step/tasks/maintain-features.js +2 -2
- package/src/actions/agentic-step/tasks/maintain-library.js +2 -2
- package/src/actions/agentic-step/tasks/supervise.js +62 -24
- package/src/actions/agentic-step/tasks/transform.js +2 -2
- package/src/agents/agent-supervisor.md +3 -0
- package/src/seeds/zero-package.json +1 -1
|
@@ -231,18 +231,26 @@ jobs:
|
|
|
231
231
|
const tomlPath = 'agentic-lib.toml';
|
|
232
232
|
if (!fs.existsSync(tomlPath)) return;
|
|
233
233
|
let toml = fs.readFileSync(tomlPath, 'utf8');
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
234
|
+
// Extract the [tuning] section, then replace within it.
|
|
235
|
+
// Previous regex [^\[]*? failed when comments contained '[' characters.
|
|
236
|
+
const tuningSectionRegex = /(\[tuning\])([\s\S]*?)(?=\n\[|$)/;
|
|
237
|
+
const tuningMatch = toml.match(tuningSectionRegex);
|
|
238
|
+
if (tuningMatch) {
|
|
239
|
+
let section = tuningMatch[0];
|
|
240
|
+
if (model) {
|
|
241
|
+
const updated = section.replace(/^(\s*model\s*=\s*)"[^"]*"/m, `$1"${model}"`);
|
|
242
|
+
if (updated !== section) {
|
|
243
|
+
toml = toml.replace(section, updated);
|
|
244
|
+
section = updated;
|
|
245
|
+
core.info(`Updated [tuning].model to: ${model}`);
|
|
246
|
+
}
|
|
239
247
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
248
|
+
if (profile) {
|
|
249
|
+
const updated = section.replace(/^(\s*profile\s*=\s*)"[^"]*"/m, `$1"${profile}"`);
|
|
250
|
+
if (updated !== section) {
|
|
251
|
+
toml = toml.replace(section, updated);
|
|
252
|
+
core.info(`Updated [tuning].profile to: ${profile}`);
|
|
253
|
+
}
|
|
246
254
|
}
|
|
247
255
|
}
|
|
248
256
|
fs.writeFileSync(tomlPath, toml);
|
|
@@ -35,11 +35,12 @@ on:
|
|
|
35
35
|
workflow_dispatch:
|
|
36
36
|
inputs:
|
|
37
37
|
frequency:
|
|
38
|
-
description: "How often the workflow should run"
|
|
38
|
+
description: "How often the workflow should run (maintenance = weekly + remove mission-complete + unlimited budget)"
|
|
39
39
|
required: true
|
|
40
40
|
type: choice
|
|
41
41
|
options:
|
|
42
42
|
- "off"
|
|
43
|
+
- "maintenance"
|
|
43
44
|
- "weekly"
|
|
44
45
|
- "daily"
|
|
45
46
|
- "hourly"
|
|
@@ -95,6 +96,9 @@ jobs:
|
|
|
95
96
|
const workflowPath = '.github/workflows/agentic-lib-workflow.yml';
|
|
96
97
|
const tomlPath = 'agentic-lib.toml';
|
|
97
98
|
|
|
99
|
+
const isMaintenance = frequency === 'maintenance';
|
|
100
|
+
const effectiveFrequency = isMaintenance ? 'weekly' : frequency;
|
|
101
|
+
|
|
98
102
|
const SCHEDULE_MAP = {
|
|
99
103
|
off: null,
|
|
100
104
|
weekly: '15 6 * * 1',
|
|
@@ -105,11 +109,11 @@ jobs:
|
|
|
105
109
|
|
|
106
110
|
// Update agentic-lib-workflow.yml schedule
|
|
107
111
|
let content = fs.readFileSync(workflowPath, 'utf8');
|
|
108
|
-
const cron = SCHEDULE_MAP[
|
|
112
|
+
const cron = SCHEDULE_MAP[effectiveFrequency];
|
|
109
113
|
|
|
110
|
-
// Check if the frequency is already set — skip if no-op
|
|
114
|
+
// Check if the frequency is already set — skip if no-op (but never skip maintenance)
|
|
111
115
|
const supervisorRegex2 = /^\s*supervisor\s*=\s*"([^"]*)"/m;
|
|
112
|
-
if (fs.existsSync(tomlPath)) {
|
|
116
|
+
if (!isMaintenance && fs.existsSync(tomlPath)) {
|
|
113
117
|
const currentToml = fs.readFileSync(tomlPath, 'utf8');
|
|
114
118
|
const currentMatch = currentToml.match(supervisorRegex2);
|
|
115
119
|
const currentFreq = currentMatch ? currentMatch[1] : '';
|
|
@@ -144,7 +148,17 @@ jobs:
|
|
|
144
148
|
}
|
|
145
149
|
|
|
146
150
|
fs.writeFileSync(workflowPath, content);
|
|
147
|
-
core.info(`Updated workflow schedule to: ${
|
|
151
|
+
core.info(`Updated workflow schedule to: ${effectiveFrequency} (cron: ${cron || 'none'})`);
|
|
152
|
+
|
|
153
|
+
// Maintenance mode: remove mission-complete/failed signals
|
|
154
|
+
if (isMaintenance) {
|
|
155
|
+
for (const f of ['MISSION_COMPLETE.md', 'MISSION_FAILED.md']) {
|
|
156
|
+
if (fs.existsSync(f)) {
|
|
157
|
+
fs.unlinkSync(f);
|
|
158
|
+
core.info(`Maintenance mode: removed ${f}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
148
162
|
|
|
149
163
|
// Update agentic-lib.toml with model and supervisor settings
|
|
150
164
|
if (fs.existsSync(tomlPath)) {
|
|
@@ -174,6 +188,15 @@ jobs:
|
|
|
174
188
|
}
|
|
175
189
|
}
|
|
176
190
|
|
|
191
|
+
// Maintenance mode: set transformation-budget to 0 (unlimited)
|
|
192
|
+
if (isMaintenance) {
|
|
193
|
+
const budgetRegex = /^(\s*transformation-budget\s*=\s*)\d+/m;
|
|
194
|
+
if (budgetRegex.test(toml)) {
|
|
195
|
+
toml = toml.replace(budgetRegex, '$10');
|
|
196
|
+
core.info('Maintenance mode: set transformation-budget = 0 (unlimited)');
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
177
200
|
fs.writeFileSync(tomlPath, toml);
|
|
178
201
|
core.info(`Updated agentic-lib.toml: model=${model}, supervisor=${frequency}${profile ? ', profile=' + profile : ''}`);
|
|
179
202
|
} else {
|
|
@@ -192,8 +215,14 @@ jobs:
|
|
|
192
215
|
FREQUENCY="${{ inputs.frequency }}"
|
|
193
216
|
MODEL="${{ inputs.model }}"
|
|
194
217
|
git add .github/workflows/agentic-lib-workflow.yml agentic-lib.toml
|
|
218
|
+
# Stage removed mission files if maintenance mode deleted them
|
|
219
|
+
git rm --ignore-unmatch MISSION_COMPLETE.md MISSION_FAILED.md 2>/dev/null || true
|
|
195
220
|
git diff --cached --quiet && echo "No changes to commit" && exit 0
|
|
196
|
-
|
|
221
|
+
if [ "$FREQUENCY" = "maintenance" ]; then
|
|
222
|
+
git commit -m "schedule: switch to maintenance mode (weekly, unlimited budget, mission reset)"
|
|
223
|
+
else
|
|
224
|
+
git commit -m "schedule: set to ${FREQUENCY}, model ${MODEL:-gpt-5-mini}"
|
|
225
|
+
fi
|
|
197
226
|
for attempt in 1 2 3; do
|
|
198
227
|
git push origin main && break
|
|
199
228
|
echo "Push failed (attempt $attempt) — pulling and retrying"
|
|
@@ -35,11 +35,23 @@ on:
|
|
|
35
35
|
#@dist - "**/*.yml"
|
|
36
36
|
#@dist - "**/*.sh"
|
|
37
37
|
workflow_call:
|
|
38
|
+
inputs:
|
|
39
|
+
push-screenshot:
|
|
40
|
+
type: string
|
|
41
|
+
required: false
|
|
42
|
+
default: "false"
|
|
38
43
|
workflow_dispatch:
|
|
44
|
+
inputs:
|
|
45
|
+
push-screenshot:
|
|
46
|
+
description: "Push screenshot to main (default: only on schedule)"
|
|
47
|
+
type: boolean
|
|
48
|
+
required: false
|
|
49
|
+
default: false
|
|
39
50
|
|
|
40
51
|
permissions:
|
|
41
52
|
contents: write
|
|
42
53
|
actions: write
|
|
54
|
+
issues: write
|
|
43
55
|
|
|
44
56
|
jobs:
|
|
45
57
|
test:
|
|
@@ -92,7 +104,9 @@ jobs:
|
|
|
92
104
|
if-no-files-found: ignore
|
|
93
105
|
|
|
94
106
|
- name: Push screenshot on main
|
|
95
|
-
if:
|
|
107
|
+
if: |
|
|
108
|
+
github.ref == 'refs/heads/main' &&
|
|
109
|
+
(github.event_name == 'schedule' || inputs.push-screenshot == 'true' || inputs.push-screenshot == true)
|
|
96
110
|
run: |
|
|
97
111
|
git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
|
98
112
|
git config user.name "github-actions[bot]"
|
|
@@ -119,7 +133,7 @@ jobs:
|
|
|
119
133
|
if: >-
|
|
120
134
|
!cancelled()
|
|
121
135
|
&& github.ref == 'refs/heads/main'
|
|
122
|
-
&& (github.event_name == 'push' || github.event_name == 'schedule')
|
|
136
|
+
&& (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_call')
|
|
123
137
|
&& github.repository != 'xn-intenton-z2a/agentic-lib'
|
|
124
138
|
&& (needs.test.result == 'failure' || needs.behaviour.result == 'failure')
|
|
125
139
|
runs-on: ubuntu-latest
|
|
@@ -154,3 +168,92 @@ jobs:
|
|
|
154
168
|
--repo "${{ github.repository }}" \
|
|
155
169
|
-f mode=full \
|
|
156
170
|
-f message="Build broken on main: agentic-lib-test run ${{ github.run_id }} failed. Please fix."
|
|
171
|
+
|
|
172
|
+
# ─── Report instability: create/update GitHub issue on test failure ──
|
|
173
|
+
report-instability:
|
|
174
|
+
needs: [test, behaviour]
|
|
175
|
+
if: >-
|
|
176
|
+
!cancelled()
|
|
177
|
+
&& github.ref == 'refs/heads/main'
|
|
178
|
+
&& github.repository != 'xn-intenton-z2a/agentic-lib'
|
|
179
|
+
&& (needs.test.result == 'failure' || needs.behaviour.result == 'failure')
|
|
180
|
+
runs-on: ubuntu-latest
|
|
181
|
+
steps:
|
|
182
|
+
- name: Determine failure type
|
|
183
|
+
id: failure-type
|
|
184
|
+
run: |
|
|
185
|
+
UNIT="${{ needs.test.result }}"
|
|
186
|
+
BEHAVIOUR="${{ needs.behaviour.result }}"
|
|
187
|
+
if [ "$UNIT" = "failure" ] && [ "$BEHAVIOUR" = "failure" ]; then
|
|
188
|
+
echo "type=both" >> $GITHUB_OUTPUT
|
|
189
|
+
elif [ "$UNIT" = "failure" ]; then
|
|
190
|
+
echo "type=unit" >> $GITHUB_OUTPUT
|
|
191
|
+
else
|
|
192
|
+
echo "type=behaviour" >> $GITHUB_OUTPUT
|
|
193
|
+
fi
|
|
194
|
+
|
|
195
|
+
- name: Collect failure logs
|
|
196
|
+
id: logs
|
|
197
|
+
env:
|
|
198
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
199
|
+
run: |
|
|
200
|
+
RUN_ID="${{ github.run_id }}"
|
|
201
|
+
# Get failed job logs via gh CLI
|
|
202
|
+
gh run view "$RUN_ID" --repo "${{ github.repository }}" --log-failed > /tmp/failed-logs.txt 2>&1 || true
|
|
203
|
+
# Trim to reasonable size (keep last 400 lines — generous context)
|
|
204
|
+
tail -400 /tmp/failed-logs.txt > /tmp/trimmed-logs.txt
|
|
205
|
+
|
|
206
|
+
- name: Create or update instability issue
|
|
207
|
+
uses: actions/github-script@v8
|
|
208
|
+
with:
|
|
209
|
+
script: |
|
|
210
|
+
const fs = require('fs');
|
|
211
|
+
const failureType = '${{ steps.failure-type.outputs.type }}';
|
|
212
|
+
const runId = '${{ github.run_id }}';
|
|
213
|
+
const runUrl = `https://github.com/${{ github.repository }}/actions/runs/${runId}`;
|
|
214
|
+
const logs = fs.readFileSync('/tmp/trimmed-logs.txt', 'utf8').slice(0, 60000);
|
|
215
|
+
|
|
216
|
+
const title = `instability: ${failureType} test failure on main`;
|
|
217
|
+
const body = [
|
|
218
|
+
`## Test Failure Report`,
|
|
219
|
+
``,
|
|
220
|
+
`**Type**: ${failureType}`,
|
|
221
|
+
`**Run**: [${runId}](${runUrl})`,
|
|
222
|
+
`**Trigger**: ${context.eventName}`,
|
|
223
|
+
`**Time**: ${new Date().toISOString()}`,
|
|
224
|
+
``,
|
|
225
|
+
`## Failure Logs`,
|
|
226
|
+
``,
|
|
227
|
+
'```',
|
|
228
|
+
logs,
|
|
229
|
+
'```',
|
|
230
|
+
].join('\n');
|
|
231
|
+
|
|
232
|
+
// Check for existing open instability issue of the same failure type
|
|
233
|
+
const { data: existing } = await github.rest.issues.listForRepo({
|
|
234
|
+
...context.repo, state: 'open', labels: 'instability', per_page: 10,
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
const match = existing.find(i => i.title.includes(failureType));
|
|
238
|
+
if (match) {
|
|
239
|
+
// Add a comment with the new failure logs instead of creating a duplicate
|
|
240
|
+
await github.rest.issues.createComment({
|
|
241
|
+
...context.repo, issue_number: match.number,
|
|
242
|
+
body: `## Recurrence — run [${runId}](${runUrl})\n\n**Trigger**: ${context.eventName}\n**Time**: ${new Date().toISOString()}\n\n` + '```\n' + logs.slice(0, 30000) + '\n```',
|
|
243
|
+
});
|
|
244
|
+
core.info(`Updated existing instability issue #${match.number}`);
|
|
245
|
+
} else {
|
|
246
|
+
// Ensure instability label exists
|
|
247
|
+
try {
|
|
248
|
+
await github.rest.issues.createLabel({
|
|
249
|
+
...context.repo, name: 'instability',
|
|
250
|
+
color: 'e11d48', description: 'Automated: test instability on main',
|
|
251
|
+
});
|
|
252
|
+
} catch (e) { /* label already exists */ }
|
|
253
|
+
|
|
254
|
+
const { data: issue } = await github.rest.issues.create({
|
|
255
|
+
...context.repo, title, body,
|
|
256
|
+
labels: ['instability', 'ready', 'automated'],
|
|
257
|
+
});
|
|
258
|
+
core.info(`Created instability issue #${issue.number}`);
|
|
259
|
+
}
|
|
@@ -36,6 +36,10 @@ on:
|
|
|
36
36
|
type: string
|
|
37
37
|
required: false
|
|
38
38
|
default: "true"
|
|
39
|
+
skipMaintain:
|
|
40
|
+
type: string
|
|
41
|
+
required: false
|
|
42
|
+
default: "false"
|
|
39
43
|
config-path:
|
|
40
44
|
type: string
|
|
41
45
|
required: false
|
|
@@ -99,6 +103,11 @@ on:
|
|
|
99
103
|
type: string
|
|
100
104
|
required: false
|
|
101
105
|
default: ""
|
|
106
|
+
skipMaintain:
|
|
107
|
+
description: "Skip maintain job (for testing fix-stuck in isolation)"
|
|
108
|
+
type: boolean
|
|
109
|
+
required: false
|
|
110
|
+
default: false
|
|
102
111
|
dry-run:
|
|
103
112
|
description: "Skip all push/PR/merge operations"
|
|
104
113
|
type: boolean
|
|
@@ -319,6 +328,16 @@ jobs:
|
|
|
319
328
|
labels: i.labels.map(l => l.name),
|
|
320
329
|
}));
|
|
321
330
|
|
|
331
|
+
// W7: Check for open instability issues (mechanical priority override)
|
|
332
|
+
const { data: instabilityIssues } = await github.rest.issues.listForRepo({
|
|
333
|
+
owner, repo, state: 'open', labels: 'instability',
|
|
334
|
+
sort: 'created', direction: 'asc', per_page: 10,
|
|
335
|
+
});
|
|
336
|
+
const instabilityNumbers = instabilityIssues.map(i => i.number);
|
|
337
|
+
if (instabilityNumbers.length > 0) {
|
|
338
|
+
core.info(`Found ${instabilityNumbers.length} instability issue(s): ${instabilityNumbers.join(', ')}`);
|
|
339
|
+
}
|
|
340
|
+
|
|
322
341
|
// Open PRs
|
|
323
342
|
const { data: prs } = await github.rest.pulls.list({
|
|
324
343
|
owner, repo, state: 'open', per_page: 10,
|
|
@@ -419,6 +438,7 @@ jobs:
|
|
|
419
438
|
|
|
420
439
|
const telemetry = {
|
|
421
440
|
issues: issuesSummary,
|
|
441
|
+
instabilityIssues: instabilityNumbers,
|
|
422
442
|
prs: prsSummary,
|
|
423
443
|
recentRuns: runsSummary,
|
|
424
444
|
mission: mission.slice(0, 500),
|
|
@@ -470,7 +490,8 @@ jobs:
|
|
|
470
490
|
if: |
|
|
471
491
|
!cancelled() &&
|
|
472
492
|
(needs.params.outputs.mode == 'full' || needs.params.outputs.mode == 'maintain-only') &&
|
|
473
|
-
needs.params.result == 'success'
|
|
493
|
+
needs.params.result == 'success' &&
|
|
494
|
+
inputs.skipMaintain != 'true' && inputs.skipMaintain != true
|
|
474
495
|
runs-on: ubuntu-latest
|
|
475
496
|
steps:
|
|
476
497
|
- uses: actions/checkout@v6
|
|
@@ -480,12 +501,16 @@ jobs:
|
|
|
480
501
|
- name: Check mission-complete signal
|
|
481
502
|
id: mission-check
|
|
482
503
|
run: |
|
|
483
|
-
|
|
504
|
+
SUPERVISOR=$(grep '^\s*supervisor\s*=' agentic-lib.toml 2>/dev/null | head -1 | sed 's/.*=\s*"\([^"]*\)".*/\1/')
|
|
505
|
+
if [ -f MISSION_COMPLETE.md ] && [ "$SUPERVISOR" != "maintenance" ]; then
|
|
484
506
|
echo "mission-complete=true" >> $GITHUB_OUTPUT
|
|
485
507
|
echo "Mission is complete — skipping budget-consuming tasks"
|
|
486
508
|
cat MISSION_COMPLETE.md
|
|
487
509
|
else
|
|
488
510
|
echo "mission-complete=false" >> $GITHUB_OUTPUT
|
|
511
|
+
if [ "$SUPERVISOR" = "maintenance" ] && [ -f MISSION_COMPLETE.md ]; then
|
|
512
|
+
echo "Maintenance mode — ignoring MISSION_COMPLETE.md"
|
|
513
|
+
fi
|
|
489
514
|
fi
|
|
490
515
|
|
|
491
516
|
- uses: actions/setup-node@v6
|
|
@@ -640,14 +665,15 @@ jobs:
|
|
|
640
665
|
- name: Check mission-complete signal
|
|
641
666
|
id: fix-mission-check
|
|
642
667
|
run: |
|
|
643
|
-
|
|
668
|
+
SUPERVISOR=$(grep '^\s*supervisor\s*=' agentic-lib.toml 2>/dev/null | head -1 | sed 's/.*=\s*"\([^"]*\)".*/\1/')
|
|
669
|
+
if [ -f MISSION_COMPLETE.md ] && [ "$SUPERVISOR" != "maintenance" ]; then
|
|
644
670
|
echo "mission-complete=true" >> $GITHUB_OUTPUT
|
|
645
671
|
echo "Mission is complete — skipping fix-stuck"
|
|
646
672
|
else
|
|
647
673
|
echo "mission-complete=false" >> $GITHUB_OUTPUT
|
|
648
674
|
fi
|
|
649
675
|
|
|
650
|
-
- name: Find
|
|
676
|
+
- name: Find stuck PRs or broken main build
|
|
651
677
|
if: steps.fix-mission-check.outputs.mission-complete != 'true'
|
|
652
678
|
uses: actions/github-script@v8
|
|
653
679
|
env:
|
|
@@ -663,10 +689,11 @@ jobs:
|
|
|
663
689
|
if (prNumber) {
|
|
664
690
|
core.info(`Specific PR requested: #${prNumber}`);
|
|
665
691
|
core.exportVariable('FIX_PR_NUMBER', prNumber);
|
|
692
|
+
core.exportVariable('FIX_REASON', 'requested');
|
|
666
693
|
return;
|
|
667
694
|
}
|
|
668
695
|
|
|
669
|
-
// Find PRs
|
|
696
|
+
// Find automerge PRs on agentic branches — always attempt to sync with main
|
|
670
697
|
const { data: openPRs } = await github.rest.pulls.list({
|
|
671
698
|
owner, repo, state: 'open', per_page: 10,
|
|
672
699
|
});
|
|
@@ -677,35 +704,20 @@ jobs:
|
|
|
677
704
|
if (!hasAutomerge) continue;
|
|
678
705
|
if (!pr.head.ref.startsWith('agentic-lib-') && !pr.head.ref.startsWith('copilot/')) continue;
|
|
679
706
|
|
|
680
|
-
// Check
|
|
681
|
-
const { data:
|
|
682
|
-
owner, repo,
|
|
683
|
-
});
|
|
684
|
-
const hasConflicts = fullPr.mergeable_state === 'dirty' || fullPr.mergeable === false;
|
|
685
|
-
|
|
686
|
-
// Check if checks are failing
|
|
687
|
-
const { data: checkRuns } = await github.rest.checks.listForRef({
|
|
688
|
-
owner, repo, ref: pr.head.sha,
|
|
689
|
-
});
|
|
690
|
-
const hasFailing = checkRuns.check_runs.some(c => c.conclusion === 'failure');
|
|
691
|
-
|
|
692
|
-
if (!hasFailing && !hasConflicts) continue;
|
|
693
|
-
|
|
694
|
-
// Check fix attempt count
|
|
695
|
-
const { data: fixRuns } = await github.rest.actions.listWorkflowRuns({
|
|
696
|
-
owner, repo, workflow_id: 'agentic-lib-workflow.yml',
|
|
697
|
-
branch: pr.head.ref, per_page: maxFixAttempts + 1,
|
|
707
|
+
// Check fix attempt count via PR comments with marker
|
|
708
|
+
const { data: comments } = await github.rest.issues.listComments({
|
|
709
|
+
owner, repo, issue_number: pr.number, per_page: 100,
|
|
698
710
|
});
|
|
699
|
-
|
|
700
|
-
|
|
711
|
+
const fixAttempts = comments.filter(c => c.body && c.body.includes('<!-- fix-stuck-attempt -->')).length;
|
|
712
|
+
if (fixAttempts >= maxFixAttempts) {
|
|
713
|
+
core.info(`PR #${pr.number} exceeded ${maxFixAttempts} fix attempts (${fixAttempts} markers). Removing automerge.`);
|
|
701
714
|
try { await github.rest.issues.removeLabel({ owner, repo, issue_number: pr.number, name: 'automerge' }); } catch (e) {}
|
|
702
715
|
continue;
|
|
703
716
|
}
|
|
704
717
|
|
|
705
|
-
|
|
706
|
-
core.info(`Will attempt to fix PR #${pr.number} (${reason})`);
|
|
718
|
+
core.info(`Will sync PR #${pr.number} with main (attempt ${fixAttempts + 1}/${maxFixAttempts})`);
|
|
707
719
|
core.exportVariable('FIX_PR_NUMBER', String(pr.number));
|
|
708
|
-
core.exportVariable('FIX_REASON',
|
|
720
|
+
core.exportVariable('FIX_REASON', 'sync');
|
|
709
721
|
foundPR = true;
|
|
710
722
|
break;
|
|
711
723
|
}
|
|
@@ -722,7 +734,6 @@ jobs:
|
|
|
722
734
|
if (testRuns.workflow_runs.length > 0) {
|
|
723
735
|
const latestTest = testRuns.workflow_runs[0];
|
|
724
736
|
if (latestTest.conclusion === 'failure') {
|
|
725
|
-
// Check we haven't already opened a fix branch for this
|
|
726
737
|
const { data: existingPRs } = await github.rest.pulls.list({
|
|
727
738
|
owner, repo, state: 'open', head: `${owner}:agentic-lib-fix-main-build`, per_page: 1,
|
|
728
739
|
});
|
|
@@ -749,47 +760,128 @@ jobs:
|
|
|
749
760
|
env:
|
|
750
761
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
751
762
|
|
|
763
|
+
- name: Record fix-stuck attempt on PR
|
|
764
|
+
if: env.FIX_PR_NUMBER != '' && steps.fix-mission-check.outputs.mission-complete != 'true'
|
|
765
|
+
env:
|
|
766
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
767
|
+
run: |
|
|
768
|
+
gh pr comment "${{ env.FIX_PR_NUMBER }}" --body "<!-- fix-stuck-attempt -->
|
|
769
|
+
**fix-stuck** attempting to resolve: ${{ env.FIX_REASON }} (run [${{ github.run_id }}](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}))"
|
|
770
|
+
|
|
752
771
|
- name: Create fix branch for broken main build
|
|
753
772
|
if: env.FIX_MAIN_BUILD == 'true' && steps.fix-mission-check.outputs.mission-complete != 'true'
|
|
754
773
|
run: |
|
|
755
774
|
git checkout -b agentic-lib-fix-main-build
|
|
756
775
|
|
|
757
|
-
- name: "
|
|
758
|
-
if: env.FIX_PR_NUMBER != '' &&
|
|
776
|
+
- name: "Sync PR branch with main (incremental conflict resolution)"
|
|
777
|
+
if: env.FIX_PR_NUMBER != '' && steps.fix-mission-check.outputs.mission-complete != 'true'
|
|
759
778
|
id: trivial-resolve
|
|
779
|
+
env:
|
|
780
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
760
781
|
run: |
|
|
782
|
+
set -e
|
|
783
|
+
git config user.email 'action@github.com'
|
|
784
|
+
git config user.name 'GitHub Actions[bot]'
|
|
761
785
|
git fetch origin main
|
|
762
|
-
|
|
763
|
-
|
|
786
|
+
BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
|
787
|
+
RESOLVED=false
|
|
788
|
+
|
|
789
|
+
# --- Tier 1: Simple merge (no conflicts) ---
|
|
790
|
+
echo "=== Tier 1: Simple merge ==="
|
|
791
|
+
if git merge origin/main --no-edit 2>&1; then
|
|
792
|
+
echo "Tier 1: merge clean, pushing..."
|
|
793
|
+
git push origin HEAD:"$BRANCH" 2>&1 && RESOLVED=true || echo "Tier 1: push failed"
|
|
764
794
|
else
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
795
|
+
echo "Tier 1: conflicts detected, aborting..."
|
|
796
|
+
git merge --abort 2>/dev/null || true
|
|
797
|
+
fi
|
|
798
|
+
|
|
799
|
+
# Check
|
|
800
|
+
if [ "$RESOLVED" = "true" ]; then
|
|
801
|
+
sleep 5
|
|
802
|
+
MERGEABLE=$(gh pr view "${{ env.FIX_PR_NUMBER }}" --json mergeable --jq '.mergeable')
|
|
803
|
+
echo "After Tier 1: mergeable=$MERGEABLE"
|
|
804
|
+
if [ "$MERGEABLE" = "CONFLICTING" ]; then
|
|
805
|
+
echo "Tier 1 pushed but PR still conflicting — continuing"
|
|
806
|
+
RESOLVED=false
|
|
807
|
+
fi
|
|
808
|
+
fi
|
|
809
|
+
|
|
810
|
+
# --- Tier 2: Merge preferring main for conflicts (-X theirs) ---
|
|
811
|
+
if [ "$RESOLVED" != "true" ]; then
|
|
812
|
+
echo "=== Tier 2: Merge with -X theirs ==="
|
|
813
|
+
git reset --hard HEAD 2>/dev/null || true
|
|
814
|
+
if git merge origin/main -X theirs --no-edit 2>&1; then
|
|
774
815
|
npm install 2>/dev/null || true
|
|
775
816
|
git add package-lock.json 2>/dev/null || true
|
|
776
|
-
git commit --no-edit
|
|
777
|
-
echo "
|
|
817
|
+
git diff --cached --quiet || git commit --amend --no-edit
|
|
818
|
+
echo "Tier 2: merge succeeded, pushing..."
|
|
819
|
+
git push origin HEAD:"$BRANCH" 2>&1 || git push --force-with-lease origin HEAD:"$BRANCH" 2>&1
|
|
820
|
+
RESOLVED=true
|
|
778
821
|
else
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
echo "non_trivial<<EOF" >> $GITHUB_OUTPUT
|
|
782
|
-
echo "$NON_TRIVIAL" >> $GITHUB_OUTPUT
|
|
783
|
-
echo "EOF" >> $GITHUB_OUTPUT
|
|
822
|
+
echo "Tier 2: merge -X theirs failed, aborting..."
|
|
823
|
+
git merge --abort 2>/dev/null || true
|
|
784
824
|
fi
|
|
785
825
|
fi
|
|
786
826
|
|
|
787
|
-
|
|
827
|
+
# Check
|
|
828
|
+
if [ "$RESOLVED" = "true" ]; then
|
|
829
|
+
sleep 5
|
|
830
|
+
MERGEABLE=$(gh pr view "${{ env.FIX_PR_NUMBER }}" --json mergeable --jq '.mergeable')
|
|
831
|
+
echo "After Tier 2: mergeable=$MERGEABLE"
|
|
832
|
+
if [ "$MERGEABLE" = "CONFLICTING" ]; then
|
|
833
|
+
echo "Tier 2 pushed but PR still conflicting — continuing"
|
|
834
|
+
RESOLVED=false
|
|
835
|
+
fi
|
|
836
|
+
fi
|
|
837
|
+
|
|
838
|
+
# --- Tier 3: Save source files, reset to main, copy back, force-push ---
|
|
839
|
+
if [ "$RESOLVED" != "true" ]; then
|
|
840
|
+
echo "=== Tier 3: Nuclear — save files, reset to main, copy back ==="
|
|
841
|
+
TMPDIR=$(mktemp -d)
|
|
842
|
+
for dir in src tests; do
|
|
843
|
+
[ -d "$dir" ] && cp -r "$dir" "$TMPDIR/" || true
|
|
844
|
+
done
|
|
845
|
+
cp package.json "$TMPDIR/" 2>/dev/null || true
|
|
846
|
+
|
|
847
|
+
git reset --hard origin/main
|
|
848
|
+
for dir in src tests; do
|
|
849
|
+
[ -d "$TMPDIR/$dir" ] && cp -r "$TMPDIR/$dir" ./ || true
|
|
850
|
+
done
|
|
851
|
+
cp "$TMPDIR/package.json" . 2>/dev/null || true
|
|
852
|
+
npm install 2>/dev/null || true
|
|
853
|
+
|
|
854
|
+
git add -A
|
|
855
|
+
if ! git diff --cached --quiet; then
|
|
856
|
+
git commit -m "fix-stuck: sync with main (resolve conflicts)"
|
|
857
|
+
git push --force-with-lease origin HEAD:"$BRANCH" 2>&1 && RESOLVED=true
|
|
858
|
+
else
|
|
859
|
+
echo "Tier 3: no differences after reset — branch identical to main"
|
|
860
|
+
RESOLVED=true
|
|
861
|
+
fi
|
|
862
|
+
rm -rf "$TMPDIR"
|
|
863
|
+
fi
|
|
864
|
+
|
|
865
|
+
# --- Final check: HARD FAIL if still not resolved ---
|
|
866
|
+
sleep 5
|
|
867
|
+
MERGEABLE=$(gh pr view "${{ env.FIX_PR_NUMBER }}" --json mergeable --jq '.mergeable')
|
|
868
|
+
echo "Final PR mergeable status: $MERGEABLE"
|
|
869
|
+
if [ "$MERGEABLE" = "CONFLICTING" ]; then
|
|
870
|
+
echo "::error::PR #${{ env.FIX_PR_NUMBER }} is STILL CONFLICTING after all 3 resolution tiers"
|
|
871
|
+
exit 1
|
|
872
|
+
fi
|
|
873
|
+
if [ "$RESOLVED" != "true" ]; then
|
|
874
|
+
echo "::error::All 3 tiers failed to resolve PR #${{ env.FIX_PR_NUMBER }}"
|
|
875
|
+
exit 1
|
|
876
|
+
fi
|
|
877
|
+
echo "PR #${{ env.FIX_PR_NUMBER }} successfully synced with main"
|
|
878
|
+
echo "resolved=true" >> $GITHUB_OUTPUT
|
|
879
|
+
|
|
880
|
+
- name: "Tier 2: LLM fix (only for explicitly requested PRs with failing checks)"
|
|
788
881
|
if: |
|
|
789
882
|
env.FIX_PR_NUMBER != '' &&
|
|
790
883
|
steps.fix-mission-check.outputs.mission-complete != 'true' &&
|
|
791
|
-
|
|
792
|
-
steps.trivial-resolve.outputs.resolved == 'none')
|
|
884
|
+
env.FIX_REASON == 'requested'
|
|
793
885
|
uses: ./.github/agentic-lib/actions/agentic-step
|
|
794
886
|
env:
|
|
795
887
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -828,9 +920,17 @@ jobs:
|
|
|
828
920
|
|
|
829
921
|
- name: Commit and push fixes
|
|
830
922
|
if: github.repository != 'xn-intenton-z2a/agentic-lib' && env.FIX_PR_NUMBER != '' && steps.fix-mission-check.outputs.mission-complete != 'true'
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
923
|
+
run: |
|
|
924
|
+
git config user.email 'action@github.com'
|
|
925
|
+
git config user.name 'GitHub Actions[bot]'
|
|
926
|
+
BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
|
927
|
+
git add -A
|
|
928
|
+
if ! git diff --cached --quiet; then
|
|
929
|
+
git commit -m "agentic-step: fix failing tests / resolve conflicts"
|
|
930
|
+
git push origin HEAD:"$BRANCH" 2>&1 || git push --force-with-lease origin HEAD:"$BRANCH" 2>&1
|
|
931
|
+
else
|
|
932
|
+
echo "No additional changes to push"
|
|
933
|
+
fi
|
|
834
934
|
|
|
835
935
|
- name: Commit, push, and open PR for main build fix
|
|
836
936
|
if: github.repository != 'xn-intenton-z2a/agentic-lib' && env.FIX_MAIN_BUILD == 'true' && steps.fix-mission-check.outputs.mission-complete != 'true'
|
|
@@ -943,12 +1043,16 @@ jobs:
|
|
|
943
1043
|
- name: Check mission-complete signal
|
|
944
1044
|
id: dev-mission-check
|
|
945
1045
|
run: |
|
|
946
|
-
|
|
1046
|
+
SUPERVISOR=$(grep '^\s*supervisor\s*=' agentic-lib.toml 2>/dev/null | head -1 | sed 's/.*=\s*"\([^"]*\)".*/\1/')
|
|
1047
|
+
if [ -f MISSION_COMPLETE.md ] && [ "$SUPERVISOR" != "maintenance" ]; then
|
|
947
1048
|
echo "mission-complete=true" >> $GITHUB_OUTPUT
|
|
948
1049
|
echo "Mission is complete — skipping dev transformation"
|
|
949
1050
|
cat MISSION_COMPLETE.md
|
|
950
1051
|
else
|
|
951
1052
|
echo "mission-complete=false" >> $GITHUB_OUTPUT
|
|
1053
|
+
if [ "$SUPERVISOR" = "maintenance" ] && [ -f MISSION_COMPLETE.md ]; then
|
|
1054
|
+
echo "Maintenance mode — ignoring MISSION_COMPLETE.md"
|
|
1055
|
+
fi
|
|
952
1056
|
fi
|
|
953
1057
|
|
|
954
1058
|
- name: Find target issue
|
|
@@ -962,6 +1066,17 @@ jobs:
|
|
|
962
1066
|
core.setOutput('issue-number', specificIssue);
|
|
963
1067
|
return;
|
|
964
1068
|
}
|
|
1069
|
+
// W7: Mechanical instability override — prioritise instability issues
|
|
1070
|
+
// before any other ready issues, regardless of supervisor decisions
|
|
1071
|
+
const { data: instabilityIssues } = await github.rest.issues.listForRepo({
|
|
1072
|
+
...context.repo, state: 'open', labels: 'instability',
|
|
1073
|
+
sort: 'created', direction: 'asc', per_page: 1,
|
|
1074
|
+
});
|
|
1075
|
+
if (instabilityIssues.length > 0) {
|
|
1076
|
+
core.setOutput('issue-number', String(instabilityIssues[0].number));
|
|
1077
|
+
core.info(`Instability override: targeting issue #${instabilityIssues[0].number}: ${instabilityIssues[0].title}`);
|
|
1078
|
+
return;
|
|
1079
|
+
}
|
|
965
1080
|
// Find oldest open issue with 'ready' label
|
|
966
1081
|
const { data: issues } = await github.rest.issues.listForRepo({
|
|
967
1082
|
...context.repo, state: 'open', labels: 'ready',
|
|
@@ -1024,15 +1139,33 @@ jobs:
|
|
|
1024
1139
|
echo "tests-passed=true" >> $GITHUB_OUTPUT
|
|
1025
1140
|
echo "All tests passed"
|
|
1026
1141
|
|
|
1142
|
+
- name: Run behaviour tests before committing
|
|
1143
|
+
id: pre-commit-behaviour-test
|
|
1144
|
+
if: steps.issue.outputs.issue-number != '' && steps.pre-commit-test.outputs.tests-passed == 'true' && (hashFiles('playwright.config.js') != '' || hashFiles('playwright.config.ts') != '')
|
|
1145
|
+
run: |
|
|
1146
|
+
npx playwright install --with-deps chromium 2>/dev/null || true
|
|
1147
|
+
npm run build:web 2>/dev/null || true
|
|
1148
|
+
set +e
|
|
1149
|
+
npm run --if-present test:behaviour 2>&1 | tail -30
|
|
1150
|
+
EXIT_CODE=$?
|
|
1151
|
+
set -e
|
|
1152
|
+
if [ $EXIT_CODE -ne 0 ]; then
|
|
1153
|
+
echo "tests-passed=false" >> $GITHUB_OUTPUT
|
|
1154
|
+
echo "WARNING: Behaviour tests failed (exit $EXIT_CODE) — skipping commit and PR"
|
|
1155
|
+
exit 0
|
|
1156
|
+
fi
|
|
1157
|
+
echo "tests-passed=true" >> $GITHUB_OUTPUT
|
|
1158
|
+
echo "Behaviour tests passed"
|
|
1159
|
+
|
|
1027
1160
|
- name: Commit and push
|
|
1028
|
-
if: github.repository != 'xn-intenton-z2a/agentic-lib' && steps.issue.outputs.issue-number != '' && needs.params.outputs.dry-run != 'true' && steps.pre-commit-test.outputs.tests-passed == 'true'
|
|
1161
|
+
if: github.repository != 'xn-intenton-z2a/agentic-lib' && steps.issue.outputs.issue-number != '' && needs.params.outputs.dry-run != 'true' && steps.pre-commit-test.outputs.tests-passed == 'true' && steps.pre-commit-behaviour-test.outputs.tests-passed != 'false'
|
|
1029
1162
|
uses: ./.github/agentic-lib/actions/commit-if-changed
|
|
1030
1163
|
with:
|
|
1031
1164
|
commit-message: "agentic-step: transform issue #${{ steps.issue.outputs.issue-number }}"
|
|
1032
1165
|
push-ref: ${{ steps.branch.outputs.branchName }}
|
|
1033
1166
|
|
|
1034
1167
|
- name: Create PR and attempt merge
|
|
1035
|
-
if: github.repository != 'xn-intenton-z2a/agentic-lib' && steps.issue.outputs.issue-number != '' && needs.params.outputs.dry-run != 'true' && steps.pre-commit-test.outputs.tests-passed == 'true'
|
|
1168
|
+
if: github.repository != 'xn-intenton-z2a/agentic-lib' && steps.issue.outputs.issue-number != '' && needs.params.outputs.dry-run != 'true' && steps.pre-commit-test.outputs.tests-passed == 'true' && steps.pre-commit-behaviour-test.outputs.tests-passed != 'false'
|
|
1036
1169
|
uses: actions/github-script@v8
|
|
1037
1170
|
with:
|
|
1038
1171
|
script: |
|
package/package.json
CHANGED
|
@@ -50,7 +50,7 @@ function buildMissionMetrics(config, result, limitsStatus, cumulativeCost, featu
|
|
|
50
50
|
const metrics = [
|
|
51
51
|
{ metric: "Open issues", value: String(openIssues), target: "0", status: openIssues === 0 ? "MET" : "NOT MET" },
|
|
52
52
|
{ metric: "Open PRs", value: String(openPrs), target: "0", status: openPrs === 0 ? "MET" : "NOT MET" },
|
|
53
|
-
{ metric: "Issues
|
|
53
|
+
{ metric: "Issues resolved (review or PR merge)", value: String(resolvedCount), target: ">= 1", status: resolvedCount >= 1 ? "MET" : "NOT MET" },
|
|
54
54
|
{ metric: "Transformation budget used", value: `${cumulativeCost}/${budgetCap}`, target: budgetCap > 0 ? `< ${budgetCap}` : "unlimited", status: budgetCap > 0 && cumulativeCost >= budgetCap ? "EXHAUSTED" : "OK" },
|
|
55
55
|
{ metric: "Cumulative transforms", value: String(cumulativeCost), target: ">= 1", status: cumulativeCost >= 1 ? "MET" : "NOT MET" },
|
|
56
56
|
{ metric: "Mission complete declared", value: missionComplete ? "YES" : "NO", target: "—", status: "—" },
|
|
@@ -66,7 +66,7 @@ function buildMissionMetrics(config, result, limitsStatus, cumulativeCost, featu
|
|
|
66
66
|
function buildMissionReadiness(metrics) {
|
|
67
67
|
const openIssues = parseInt(metrics.find((m) => m.metric === "Open issues")?.value || "0", 10);
|
|
68
68
|
const openPrs = parseInt(metrics.find((m) => m.metric === "Open PRs")?.value || "0", 10);
|
|
69
|
-
const resolved = parseInt(metrics.find((m) => m.metric === "Issues
|
|
69
|
+
const resolved = parseInt(metrics.find((m) => m.metric === "Issues resolved (review or PR merge)")?.value || "0", 10);
|
|
70
70
|
const missionComplete = metrics.find((m) => m.metric === "Mission complete declared")?.value === "YES";
|
|
71
71
|
const missionFailed = metrics.find((m) => m.metric === "Mission failed declared")?.value === "YES";
|
|
72
72
|
|
|
@@ -82,12 +82,12 @@ function buildMissionReadiness(metrics) {
|
|
|
82
82
|
|
|
83
83
|
if (conditionsMet) {
|
|
84
84
|
parts.push("Mission complete conditions ARE met.");
|
|
85
|
-
parts.push(`0 open issues, 0 open PRs, ${resolved} issue(s)
|
|
85
|
+
parts.push(`0 open issues, 0 open PRs, ${resolved} issue(s) resolved.`);
|
|
86
86
|
} else {
|
|
87
87
|
parts.push("Mission complete conditions are NOT met.");
|
|
88
88
|
if (openIssues > 0) parts.push(`${openIssues} open issue(s) remain.`);
|
|
89
89
|
if (openPrs > 0) parts.push(`${openPrs} open PR(s) remain.`);
|
|
90
|
-
if (resolved < 1) parts.push("No issues have been
|
|
90
|
+
if (resolved < 1) parts.push("No issues have been resolved yet.");
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
return parts.join(" ");
|
|
@@ -19,8 +19,8 @@ export async function maintainFeatures(context) {
|
|
|
19
19
|
const { config, instructions, writablePaths, model, octokit, repo } = context;
|
|
20
20
|
const t = config.tuning || {};
|
|
21
21
|
|
|
22
|
-
// Check mission-complete signal
|
|
23
|
-
if (existsSync("MISSION_COMPLETE.md")) {
|
|
22
|
+
// Check mission-complete signal (skip in maintenance mode)
|
|
23
|
+
if (existsSync("MISSION_COMPLETE.md") && config.supervisor !== "maintenance") {
|
|
24
24
|
return { outcome: "nop", details: "Mission already complete (MISSION_COMPLETE.md signal)" };
|
|
25
25
|
}
|
|
26
26
|
|
|
@@ -19,8 +19,8 @@ export async function maintainLibrary(context) {
|
|
|
19
19
|
const { config, instructions, writablePaths, model } = context;
|
|
20
20
|
const t = config.tuning || {};
|
|
21
21
|
|
|
22
|
-
// Check mission-complete signal
|
|
23
|
-
if (existsSync("MISSION_COMPLETE.md")) {
|
|
22
|
+
// Check mission-complete signal (skip in maintenance mode)
|
|
23
|
+
if (existsSync("MISSION_COMPLETE.md") && config.supervisor !== "maintenance") {
|
|
24
24
|
core.info("Mission is complete — skipping library maintenance");
|
|
25
25
|
return { outcome: "nop", details: "Mission already complete (MISSION_COMPLETE.md signal)" };
|
|
26
26
|
}
|
|
@@ -184,7 +184,8 @@ async function gatherContext(octokit, repo, config, t) {
|
|
|
184
184
|
const { data: closedIssuesRaw } = await octokit.rest.issues.listForRepo({
|
|
185
185
|
...repo,
|
|
186
186
|
state: "closed",
|
|
187
|
-
|
|
187
|
+
labels: "automated",
|
|
188
|
+
per_page: 10,
|
|
188
189
|
sort: "updated",
|
|
189
190
|
direction: "desc",
|
|
190
191
|
});
|
|
@@ -195,15 +196,27 @@ async function gatherContext(octokit, repo, config, t) {
|
|
|
195
196
|
for (const ci of closedIssuesFiltered) {
|
|
196
197
|
let closeReason = "closed";
|
|
197
198
|
try {
|
|
199
|
+
// Check for review-closed (Automated Review Result comment)
|
|
198
200
|
const { data: comments } = await octokit.rest.issues.listComments({
|
|
199
201
|
...repo,
|
|
200
202
|
issue_number: ci.number,
|
|
201
|
-
per_page:
|
|
203
|
+
per_page: 5,
|
|
202
204
|
sort: "created",
|
|
203
205
|
direction: "desc",
|
|
204
206
|
});
|
|
205
|
-
if (comments.
|
|
206
|
-
closeReason = "
|
|
207
|
+
if (comments.some((c) => c.body?.includes("Automated Review Result"))) {
|
|
208
|
+
closeReason = "RESOLVED";
|
|
209
|
+
} else {
|
|
210
|
+
// Check for PR-linked closure (GitHub auto-closes via "Closes #N")
|
|
211
|
+
const { data: events } = await octokit.rest.issues.listEvents({
|
|
212
|
+
...repo,
|
|
213
|
+
issue_number: ci.number,
|
|
214
|
+
per_page: 10,
|
|
215
|
+
});
|
|
216
|
+
const closedByPR = events.some((e) => e.event === "closed" && e.commit_id);
|
|
217
|
+
if (closedByPR) {
|
|
218
|
+
closeReason = "RESOLVED";
|
|
219
|
+
}
|
|
207
220
|
}
|
|
208
221
|
} catch (_) { /* ignore */ }
|
|
209
222
|
recentlyClosedSummary.push(`#${ci.number}: ${ci.title} — ${closeReason}`);
|
|
@@ -631,15 +644,27 @@ async function executeMissionComplete(octokit, repo, params, ctx) {
|
|
|
631
644
|
}
|
|
632
645
|
|
|
633
646
|
if (process.env.GITHUB_REPOSITORY !== "xn-intenton-z2a/agentic-lib") {
|
|
647
|
+
// Only turn off schedule if it's not already off or in maintenance mode
|
|
648
|
+
let currentSupervisor = "";
|
|
634
649
|
try {
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
650
|
+
const tomlContent = readFileSync("agentic-lib.toml", "utf8");
|
|
651
|
+
const match = tomlContent.match(/^\s*supervisor\s*=\s*"([^"]*)"/m);
|
|
652
|
+
if (match) currentSupervisor = match[1];
|
|
653
|
+
} catch { /* ignore */ }
|
|
654
|
+
|
|
655
|
+
if (currentSupervisor === "off" || currentSupervisor === "maintenance") {
|
|
656
|
+
core.info(`Schedule already "${currentSupervisor}" — not changing on mission-complete`);
|
|
657
|
+
} else {
|
|
658
|
+
try {
|
|
659
|
+
await octokit.rest.actions.createWorkflowDispatch({
|
|
660
|
+
...repo,
|
|
661
|
+
workflow_id: "agentic-lib-schedule.yml",
|
|
662
|
+
ref: "main",
|
|
663
|
+
inputs: { frequency: "off" },
|
|
664
|
+
});
|
|
665
|
+
} catch (err) {
|
|
666
|
+
core.warning(`Could not set schedule to off: ${err.message}`);
|
|
667
|
+
}
|
|
643
668
|
}
|
|
644
669
|
|
|
645
670
|
// Announce mission complete via bot
|
|
@@ -688,15 +713,27 @@ async function executeMissionFailed(octokit, repo, params, ctx) {
|
|
|
688
713
|
}
|
|
689
714
|
|
|
690
715
|
if (process.env.GITHUB_REPOSITORY !== "xn-intenton-z2a/agentic-lib") {
|
|
716
|
+
// Only turn off schedule if it's not already off or in maintenance mode
|
|
717
|
+
let currentSupervisor = "";
|
|
691
718
|
try {
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
719
|
+
const tomlContent = readFileSync("agentic-lib.toml", "utf8");
|
|
720
|
+
const match = tomlContent.match(/^\s*supervisor\s*=\s*"([^"]*)"/m);
|
|
721
|
+
if (match) currentSupervisor = match[1];
|
|
722
|
+
} catch { /* ignore */ }
|
|
723
|
+
|
|
724
|
+
if (currentSupervisor === "off" || currentSupervisor === "maintenance") {
|
|
725
|
+
core.info(`Schedule already "${currentSupervisor}" — not changing on mission-failed`);
|
|
726
|
+
} else {
|
|
727
|
+
try {
|
|
728
|
+
await octokit.rest.actions.createWorkflowDispatch({
|
|
729
|
+
...repo,
|
|
730
|
+
workflow_id: "agentic-lib-schedule.yml",
|
|
731
|
+
ref: "main",
|
|
732
|
+
inputs: { frequency: "off" },
|
|
733
|
+
});
|
|
734
|
+
} catch (err) {
|
|
735
|
+
core.warning(`Could not set schedule to off: ${err.message}`);
|
|
736
|
+
}
|
|
700
737
|
}
|
|
701
738
|
|
|
702
739
|
// Announce mission failed via bot
|
|
@@ -810,17 +847,18 @@ export async function supervise(context) {
|
|
|
810
847
|
|
|
811
848
|
// Strategy A: Deterministic mission-complete fallback
|
|
812
849
|
// If the LLM didn't choose mission-complete but conditions are clearly met, auto-execute it.
|
|
813
|
-
|
|
850
|
+
// Skip in maintenance mode — maintenance keeps running regardless of mission status.
|
|
851
|
+
if (!ctx.missionComplete && !ctx.missionFailed && config.supervisor !== "maintenance") {
|
|
814
852
|
const llmChoseMissionComplete = results.some((r) => r.startsWith("mission-complete:"));
|
|
815
853
|
if (!llmChoseMissionComplete) {
|
|
816
|
-
const resolvedCount = ctx.recentlyClosedSummary.filter((s) => s.includes("
|
|
854
|
+
const resolvedCount = ctx.recentlyClosedSummary.filter((s) => s.includes("RESOLVED")).length;
|
|
817
855
|
const hasNoOpenIssues = ctx.issuesSummary.length === 0;
|
|
818
856
|
const hasNoOpenPRs = ctx.prsSummary.length === 0;
|
|
819
|
-
if (hasNoOpenIssues && hasNoOpenPRs && resolvedCount >=
|
|
857
|
+
if (hasNoOpenIssues && hasNoOpenPRs && resolvedCount >= 1) {
|
|
820
858
|
core.info(`Deterministic mission-complete: 0 open issues, 0 open PRs, ${resolvedCount} recently resolved — LLM did not detect completion`);
|
|
821
859
|
try {
|
|
822
860
|
const autoResult = await executeMissionComplete(octokit, repo,
|
|
823
|
-
{ reason: `All acceptance criteria satisfied (${resolvedCount} issues
|
|
861
|
+
{ reason: `All acceptance criteria satisfied (${resolvedCount} issues resolved, 0 open issues, 0 open PRs)` },
|
|
824
862
|
ctx);
|
|
825
863
|
results.push(autoResult);
|
|
826
864
|
} catch (err) {
|
|
@@ -26,8 +26,8 @@ export async function transform(context) {
|
|
|
26
26
|
return { outcome: "nop", details: "No mission file found" };
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
// Check mission-complete signal
|
|
30
|
-
if (existsSync("MISSION_COMPLETE.md")) {
|
|
29
|
+
// Check mission-complete signal (skip in maintenance mode)
|
|
30
|
+
if (existsSync("MISSION_COMPLETE.md") && config.supervisor !== "maintenance") {
|
|
31
31
|
core.info("Mission is complete — skipping transformation (MISSION_COMPLETE.md exists)");
|
|
32
32
|
return { outcome: "nop", details: "Mission already complete (MISSION_COMPLETE.md signal)" };
|
|
33
33
|
}
|
|
@@ -106,6 +106,9 @@ Check the Recent Activity log and Recently Closed Issues for patterns:
|
|
|
106
106
|
- If the last 3+ cycles show the pattern: create issue → review closes as resolved → no transform → create identical issue, the pipeline is stuck. Check if acceptance criteria are truly met (use `mission-complete`) or if review is wrong (create a more specific issue). If neither works, use `mission-failed`.
|
|
107
107
|
- Look for `transform: nop` or `transform: transformed` patterns in the activity log to distinguish productive iterations from idle ones.
|
|
108
108
|
|
|
109
|
+
**Dedup deadlock recovery:**
|
|
110
|
+
- If your issue creation is blocked by the dedup guard (similar to a recently closed issue), do NOT retry the same issue title. Instead, create an issue with a different scope — e.g. "fix: resolve failing tests on main" or "fix: align code and test expectations" — with both the `instability` and `ready` labels. This sidesteps the dedup guard and directs the dev job to the actual problem. The `instability` label gives the issue mechanical priority over other `ready` issues.
|
|
111
|
+
|
|
109
112
|
## Discussions Awareness
|
|
110
113
|
|
|
111
114
|
Check the Recent Activity log for discussion bot referrals (lines containing `discussion-request-supervisor`). These indicate a user asked the bot something that requires supervisor action. **Prioritise responding to these referrals.**
|