@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.
@@ -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[frequency];
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: ${frequency} (cron: ${cron || 'none'})`);
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
- git commit -m "schedule: set to ${FREQUENCY}, model ${MODEL:-gpt-5-mini}"
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: github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
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
- if [ -f MISSION_COMPLETE.md ]; then
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
- if [ -f MISSION_COMPLETE.md ]; then
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 and fix stuck PRs or broken main build
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 with failing checks or merge conflicts on agentic branches
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 for merge conflicts
692
- const { data: fullPr } = await github.rest.pulls.get({
693
- owner, repo, pull_number: pr.number,
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
- if (fixRuns.total_count >= maxFixAttempts) {
711
- core.info(`PR #${pr.number} exceeded fix attempts. Removing automerge.`);
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
- const reason = hasConflicts ? 'merge conflicts' : 'failing checks';
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', 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: "Tier 1: Auto-resolve trivial merge conflicts"
769
- if: env.FIX_PR_NUMBER != '' && env.FIX_REASON == 'merge conflicts' && steps.fix-mission-check.outputs.mission-complete != 'true'
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
- if git merge origin/main --no-edit 2>/dev/null; then
774
- echo "resolved=clean" >> $GITHUB_OUTPUT
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
- CONFLICTED=$(git diff --name-only --diff-filter=U)
777
- TRIVIAL_PATTERN='intentïon\.md|intention\.md|package-lock\.json'
778
- NON_TRIVIAL=$(echo "$CONFLICTED" | grep -vE "$TRIVIAL_PATTERN" || true)
779
- if [ -z "$NON_TRIVIAL" ]; then
780
- echo "All conflicts are trivial — auto-resolving"
781
- for f in $CONFLICTED; do
782
- git checkout --theirs "$f"
783
- git add "$f"
784
- done
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 "resolved=trivial" >> $GITHUB_OUTPUT
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
- git merge --abort
791
- echo "resolved=none" >> $GITHUB_OUTPUT
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 (conflicts or failing checks)"
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
- (env.FIX_REASON != 'merge conflicts' ||
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
- uses: ./.github/agentic-lib/actions/commit-if-changed
843
- with:
844
- commit-message: "agentic-step: fix failing tests / resolve conflicts"
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
- if [ -f MISSION_COMPLETE.md ]; then
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xn-intenton-z2a/agentic-lib",
3
- "version": "7.1.102",
3
+ "version": "7.1.103",
4
4
  "description": "Agentic-lib Agentic Coding Systems SDK powering automated GitHub workflows.",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -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 closed by review (RESOLVED)", value: String(resolvedCount), target: ">= 1", status: resolvedCount >= 1 ? "MET" : "NOT MET" },
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 closed by review (RESOLVED)")?.value || "0", 10);
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) closed by review as RESOLVED.`);
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 closed by review as RESOLVED yet.");
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
- per_page: 5,
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: 1,
203
+ per_page: 5,
202
204
  sort: "created",
203
205
  direction: "desc",
204
206
  });
205
- if (comments.length > 0 && comments[0].body?.includes("Automated Review Result")) {
206
- closeReason = "closed by review as RESOLVED";
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
- await octokit.rest.actions.createWorkflowDispatch({
636
- ...repo,
637
- workflow_id: "agentic-lib-schedule.yml",
638
- ref: "main",
639
- inputs: { frequency: "off" },
640
- });
641
- } catch (err) {
642
- core.warning(`Could not set schedule to off: ${err.message}`);
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
- await octokit.rest.actions.createWorkflowDispatch({
693
- ...repo,
694
- workflow_id: "agentic-lib-schedule.yml",
695
- ref: "main",
696
- inputs: { frequency: "off" },
697
- });
698
- } catch (err) {
699
- core.warning(`Could not set schedule to off: ${err.message}`);
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
- if (!ctx.missionComplete && !ctx.missionFailed) {
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("closed by review as RESOLVED")).length;
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 >= 2) {
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 closed by review as RESOLVED, 0 open issues, 0 open PRs)` },
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
  }
@@ -17,7 +17,7 @@
17
17
  "author": "",
18
18
  "license": "MIT",
19
19
  "dependencies": {
20
- "@xn-intenton-z2a/agentic-lib": "^7.1.102"
20
+ "@xn-intenton-z2a/agentic-lib": "^7.1.103"
21
21
  },
22
22
  "devDependencies": {
23
23
  "@playwright/test": "^1.58.0",