@xn-intenton-z2a/agentic-lib 7.1.102 → 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-schedule.yml +35 -6
- package/.github/workflows/agentic-lib-test.yml +14 -1
- package/.github/workflows/agentic-lib-workflow.yml +149 -56
- 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/seeds/zero-package.json +1 -1
|
@@ -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,7 +35,18 @@ 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
|
|
@@ -93,7 +104,9 @@ jobs:
|
|
|
93
104
|
if-no-files-found: ignore
|
|
94
105
|
|
|
95
106
|
- name: Push screenshot on main
|
|
96
|
-
if:
|
|
107
|
+
if: |
|
|
108
|
+
github.ref == 'refs/heads/main' &&
|
|
109
|
+
(github.event_name == 'schedule' || inputs.push-screenshot == 'true' || inputs.push-screenshot == true)
|
|
97
110
|
run: |
|
|
98
111
|
git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
|
99
112
|
git config user.name "github-actions[bot]"
|
|
@@ -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
|
|
@@ -481,7 +490,8 @@ jobs:
|
|
|
481
490
|
if: |
|
|
482
491
|
!cancelled() &&
|
|
483
492
|
(needs.params.outputs.mode == 'full' || needs.params.outputs.mode == 'maintain-only') &&
|
|
484
|
-
needs.params.result == 'success'
|
|
493
|
+
needs.params.result == 'success' &&
|
|
494
|
+
inputs.skipMaintain != 'true' && inputs.skipMaintain != true
|
|
485
495
|
runs-on: ubuntu-latest
|
|
486
496
|
steps:
|
|
487
497
|
- uses: actions/checkout@v6
|
|
@@ -491,12 +501,16 @@ jobs:
|
|
|
491
501
|
- name: Check mission-complete signal
|
|
492
502
|
id: mission-check
|
|
493
503
|
run: |
|
|
494
|
-
|
|
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
|
|
495
506
|
echo "mission-complete=true" >> $GITHUB_OUTPUT
|
|
496
507
|
echo "Mission is complete — skipping budget-consuming tasks"
|
|
497
508
|
cat MISSION_COMPLETE.md
|
|
498
509
|
else
|
|
499
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
|
|
500
514
|
fi
|
|
501
515
|
|
|
502
516
|
- uses: actions/setup-node@v6
|
|
@@ -651,14 +665,15 @@ jobs:
|
|
|
651
665
|
- name: Check mission-complete signal
|
|
652
666
|
id: fix-mission-check
|
|
653
667
|
run: |
|
|
654
|
-
|
|
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
|
|
655
670
|
echo "mission-complete=true" >> $GITHUB_OUTPUT
|
|
656
671
|
echo "Mission is complete — skipping fix-stuck"
|
|
657
672
|
else
|
|
658
673
|
echo "mission-complete=false" >> $GITHUB_OUTPUT
|
|
659
674
|
fi
|
|
660
675
|
|
|
661
|
-
- name: Find
|
|
676
|
+
- name: Find stuck PRs or broken main build
|
|
662
677
|
if: steps.fix-mission-check.outputs.mission-complete != 'true'
|
|
663
678
|
uses: actions/github-script@v8
|
|
664
679
|
env:
|
|
@@ -674,10 +689,11 @@ jobs:
|
|
|
674
689
|
if (prNumber) {
|
|
675
690
|
core.info(`Specific PR requested: #${prNumber}`);
|
|
676
691
|
core.exportVariable('FIX_PR_NUMBER', prNumber);
|
|
692
|
+
core.exportVariable('FIX_REASON', 'requested');
|
|
677
693
|
return;
|
|
678
694
|
}
|
|
679
695
|
|
|
680
|
-
// Find PRs
|
|
696
|
+
// Find automerge PRs on agentic branches — always attempt to sync with main
|
|
681
697
|
const { data: openPRs } = await github.rest.pulls.list({
|
|
682
698
|
owner, repo, state: 'open', per_page: 10,
|
|
683
699
|
});
|
|
@@ -688,35 +704,20 @@ jobs:
|
|
|
688
704
|
if (!hasAutomerge) continue;
|
|
689
705
|
if (!pr.head.ref.startsWith('agentic-lib-') && !pr.head.ref.startsWith('copilot/')) continue;
|
|
690
706
|
|
|
691
|
-
// Check
|
|
692
|
-
const { data:
|
|
693
|
-
owner, repo,
|
|
694
|
-
});
|
|
695
|
-
const hasConflicts = fullPr.mergeable_state === 'dirty' || fullPr.mergeable === false;
|
|
696
|
-
|
|
697
|
-
// Check if checks are failing
|
|
698
|
-
const { data: checkRuns } = await github.rest.checks.listForRef({
|
|
699
|
-
owner, repo, ref: pr.head.sha,
|
|
700
|
-
});
|
|
701
|
-
const hasFailing = checkRuns.check_runs.some(c => c.conclusion === 'failure');
|
|
702
|
-
|
|
703
|
-
if (!hasFailing && !hasConflicts) continue;
|
|
704
|
-
|
|
705
|
-
// Check fix attempt count
|
|
706
|
-
const { data: fixRuns } = await github.rest.actions.listWorkflowRuns({
|
|
707
|
-
owner, repo, workflow_id: 'agentic-lib-workflow.yml',
|
|
708
|
-
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,
|
|
709
710
|
});
|
|
710
|
-
|
|
711
|
-
|
|
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.`);
|
|
712
714
|
try { await github.rest.issues.removeLabel({ owner, repo, issue_number: pr.number, name: 'automerge' }); } catch (e) {}
|
|
713
715
|
continue;
|
|
714
716
|
}
|
|
715
717
|
|
|
716
|
-
|
|
717
|
-
core.info(`Will attempt to fix PR #${pr.number} (${reason})`);
|
|
718
|
+
core.info(`Will sync PR #${pr.number} with main (attempt ${fixAttempts + 1}/${maxFixAttempts})`);
|
|
718
719
|
core.exportVariable('FIX_PR_NUMBER', String(pr.number));
|
|
719
|
-
core.exportVariable('FIX_REASON',
|
|
720
|
+
core.exportVariable('FIX_REASON', 'sync');
|
|
720
721
|
foundPR = true;
|
|
721
722
|
break;
|
|
722
723
|
}
|
|
@@ -733,7 +734,6 @@ jobs:
|
|
|
733
734
|
if (testRuns.workflow_runs.length > 0) {
|
|
734
735
|
const latestTest = testRuns.workflow_runs[0];
|
|
735
736
|
if (latestTest.conclusion === 'failure') {
|
|
736
|
-
// Check we haven't already opened a fix branch for this
|
|
737
737
|
const { data: existingPRs } = await github.rest.pulls.list({
|
|
738
738
|
owner, repo, state: 'open', head: `${owner}:agentic-lib-fix-main-build`, per_page: 1,
|
|
739
739
|
});
|
|
@@ -760,47 +760,128 @@ jobs:
|
|
|
760
760
|
env:
|
|
761
761
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
762
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
|
+
|
|
763
771
|
- name: Create fix branch for broken main build
|
|
764
772
|
if: env.FIX_MAIN_BUILD == 'true' && steps.fix-mission-check.outputs.mission-complete != 'true'
|
|
765
773
|
run: |
|
|
766
774
|
git checkout -b agentic-lib-fix-main-build
|
|
767
775
|
|
|
768
|
-
- name: "
|
|
769
|
-
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'
|
|
770
778
|
id: trivial-resolve
|
|
779
|
+
env:
|
|
780
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
771
781
|
run: |
|
|
782
|
+
set -e
|
|
783
|
+
git config user.email 'action@github.com'
|
|
784
|
+
git config user.name 'GitHub Actions[bot]'
|
|
772
785
|
git fetch origin main
|
|
773
|
-
|
|
774
|
-
|
|
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"
|
|
775
794
|
else
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
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
|
|
785
815
|
npm install 2>/dev/null || true
|
|
786
816
|
git add package-lock.json 2>/dev/null || true
|
|
787
|
-
git commit --no-edit
|
|
788
|
-
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
|
|
821
|
+
else
|
|
822
|
+
echo "Tier 2: merge -X theirs failed, aborting..."
|
|
823
|
+
git merge --abort 2>/dev/null || true
|
|
824
|
+
fi
|
|
825
|
+
fi
|
|
826
|
+
|
|
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
|
|
789
858
|
else
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
echo "non_trivial<<EOF" >> $GITHUB_OUTPUT
|
|
793
|
-
echo "$NON_TRIVIAL" >> $GITHUB_OUTPUT
|
|
794
|
-
echo "EOF" >> $GITHUB_OUTPUT
|
|
859
|
+
echo "Tier 3: no differences after reset — branch identical to main"
|
|
860
|
+
RESOLVED=true
|
|
795
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
|
|
796
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
|
|
797
879
|
|
|
798
|
-
- name: "Tier 2: LLM fix (
|
|
880
|
+
- name: "Tier 2: LLM fix (only for explicitly requested PRs with failing checks)"
|
|
799
881
|
if: |
|
|
800
882
|
env.FIX_PR_NUMBER != '' &&
|
|
801
883
|
steps.fix-mission-check.outputs.mission-complete != 'true' &&
|
|
802
|
-
|
|
803
|
-
steps.trivial-resolve.outputs.resolved == 'none')
|
|
884
|
+
env.FIX_REASON == 'requested'
|
|
804
885
|
uses: ./.github/agentic-lib/actions/agentic-step
|
|
805
886
|
env:
|
|
806
887
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -839,9 +920,17 @@ jobs:
|
|
|
839
920
|
|
|
840
921
|
- name: Commit and push fixes
|
|
841
922
|
if: github.repository != 'xn-intenton-z2a/agentic-lib' && env.FIX_PR_NUMBER != '' && steps.fix-mission-check.outputs.mission-complete != 'true'
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
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
|
|
845
934
|
|
|
846
935
|
- name: Commit, push, and open PR for main build fix
|
|
847
936
|
if: github.repository != 'xn-intenton-z2a/agentic-lib' && env.FIX_MAIN_BUILD == 'true' && steps.fix-mission-check.outputs.mission-complete != 'true'
|
|
@@ -954,12 +1043,16 @@ jobs:
|
|
|
954
1043
|
- name: Check mission-complete signal
|
|
955
1044
|
id: dev-mission-check
|
|
956
1045
|
run: |
|
|
957
|
-
|
|
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
|
|
958
1048
|
echo "mission-complete=true" >> $GITHUB_OUTPUT
|
|
959
1049
|
echo "Mission is complete — skipping dev transformation"
|
|
960
1050
|
cat MISSION_COMPLETE.md
|
|
961
1051
|
else
|
|
962
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
|
|
963
1056
|
fi
|
|
964
1057
|
|
|
965
1058
|
- name: Find target issue
|
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
|
}
|