@xn-intenton-z2a/agentic-lib 7.4.23 → 7.4.25
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/agents/agent-issue-resolution.md +2 -0
- package/.github/agents/agent-supervisor.md +2 -2
- package/.github/workflows/agentic-lib-flow.yml +1 -1
- package/.github/workflows/agentic-lib-init.yml +1 -1
- package/.github/workflows/agentic-lib-schedule.yml +1 -1
- package/.github/workflows/agentic-lib-workflow.yml +228 -27
- package/agentic-lib.toml +37 -4
- package/package.json +1 -1
- package/src/actions/agentic-step/tasks/direct.js +52 -0
- package/src/actions/agentic-step/tasks/fix-code.js +7 -6
- package/src/actions/agentic-step/tasks/maintain-features.js +5 -4
- package/src/actions/agentic-step/tasks/maintain-library.js +5 -4
- package/src/actions/agentic-step/tasks/supervise.js +25 -0
- package/src/actions/agentic-step/tasks/transform.js +152 -27
- package/src/copilot/config.js +23 -1
- package/src/copilot/copilot-session.js +10 -5
- package/src/iterate.js +1 -1
- package/src/seeds/zero-package.json +1 -1
|
@@ -85,6 +85,8 @@ When writing both tests and implementation:
|
|
|
85
85
|
**Both unit tests AND behaviour tests must pass.** The project runs `npm test` (unit tests) and
|
|
86
86
|
`npm run test:behaviour` (Playwright). Both are gated — your changes must pass both.
|
|
87
87
|
|
|
88
|
+
**Code coverage:** Aim for the coverage thresholds stated in the Constraints section of the prompt. Write tests that exercise the code paths you're adding or modifying.
|
|
89
|
+
|
|
88
90
|
### Test philosophy
|
|
89
91
|
|
|
90
92
|
- **Unit tests** bind to the detail: exact return values, error types, edge cases, parameter validation.
|
|
@@ -68,7 +68,7 @@ If an **Implementation Review** section is present in the prompt, examine it car
|
|
|
68
68
|
|
|
69
69
|
## Priority Order
|
|
70
70
|
|
|
71
|
-
1. **Always strive to close gaps** — every action you take should aim to satisfy the remaining NOT MET metrics. If the code is already complete (see Source Exports and Recently Closed Issues), use `nop` and let the director evaluate. Otherwise,
|
|
71
|
+
1. **Always strive to close gaps** — every action you take should aim to satisfy the remaining NOT MET metrics. If the code is already complete (see Source Exports and Recently Closed Issues), use `nop` and let the director evaluate. Otherwise, assess the full gap between current state and mission, then create as many distinct issues as needed to cover the entire gap. Ideally one comprehensive issue covering the whole gap, but if the work is naturally separable (e.g. different features, different layers), create multiple focused issues. Create up to the WIP limit. Each issue should be self-contained and independently deliverable.
|
|
72
72
|
2. **Dispatch transform when ready issues exist** — transform is where code gets written. Always prefer it over maintain when there are open issues with the `ready` label.
|
|
73
73
|
3. **Dispatch review after transform** — when recent workflow runs show a transform completion, dispatch review to close resolved issues and add `ready` labels to new issues. This keeps the pipeline flowing.
|
|
74
74
|
4. **Fix failing PRs** — dispatch fix-code for any PR with failing checks (include pr-number).
|
|
@@ -78,7 +78,7 @@ If an **Implementation Review** section is present in the prompt, examine it car
|
|
|
78
78
|
|
|
79
79
|
1. **Check what's already in progress** — don't duplicate work. If the workflow is already running, don't dispatch another.
|
|
80
80
|
2. **Prioritise code generation** — the goal is working code. Prefer actions that produce code (dev-only, fix) over metadata (maintain, label).
|
|
81
|
-
3. **Right-size the work** — break the mission into chunks
|
|
81
|
+
3. **Right-size the work** — break the mission into the fewest chunks that can each be reliably delivered in a single transform. Create all the issues needed upfront rather than waiting for each to land before creating the next. Each issue should request maximum implementation in its scope.
|
|
82
82
|
4. **Respect limits** — don't create issues beyond the WIP limit shown in the context. Don't dispatch workflows that will fail due to missing prerequisites.
|
|
83
83
|
|
|
84
84
|
## When to use each action
|
|
@@ -68,7 +68,7 @@ on:
|
|
|
68
68
|
options:
|
|
69
69
|
- ""
|
|
70
70
|
- min
|
|
71
|
-
-
|
|
71
|
+
- med
|
|
72
72
|
- max
|
|
73
73
|
mode:
|
|
74
74
|
description: "Run mode"
|
|
@@ -140,7 +140,10 @@ jobs:
|
|
|
140
140
|
- uses: actions/checkout@v6
|
|
141
141
|
with:
|
|
142
142
|
ref: ${{ inputs.ref || github.sha }}
|
|
143
|
-
sparse-checkout:
|
|
143
|
+
sparse-checkout: |
|
|
144
|
+
${{ env.configPath }}
|
|
145
|
+
MISSION_COMPLETE.md
|
|
146
|
+
MISSION_FAILED.md
|
|
144
147
|
sparse-checkout-cone-mode: false
|
|
145
148
|
- name: Normalise params
|
|
146
149
|
id: normalise
|
|
@@ -190,6 +193,23 @@ jobs:
|
|
|
190
193
|
echo "log-prefix=${LOG_PREFIX:-agent-log-}" >> $GITHUB_OUTPUT
|
|
191
194
|
echo "log-branch=${LOG_BRANCH:-agentic-lib-logs}" >> $GITHUB_OUTPUT
|
|
192
195
|
echo "screenshot-file=${SCREENSHOT:-SCREENSHOT_INDEX.png}" >> $GITHUB_OUTPUT
|
|
196
|
+
- name: Check mission-complete signal (W4)
|
|
197
|
+
id: mission-check
|
|
198
|
+
shell: bash
|
|
199
|
+
run: |
|
|
200
|
+
SUPERVISOR=""
|
|
201
|
+
if [ -f "${{ env.configPath }}" ]; then
|
|
202
|
+
SUPERVISOR=$(grep '^\s*supervisor\s*=' "${{ env.configPath }}" 2>/dev/null | head -1 | sed 's/.*=\s*"\([^"]*\)".*/\1/' || true)
|
|
203
|
+
fi
|
|
204
|
+
if [ -f MISSION_COMPLETE.md ] && [ "$SUPERVISOR" != "maintenance" ]; then
|
|
205
|
+
echo "mission-complete=true" >> $GITHUB_OUTPUT
|
|
206
|
+
echo "::notice::Mission is complete — most jobs will be skipped"
|
|
207
|
+
elif [ -f MISSION_FAILED.md ]; then
|
|
208
|
+
echo "mission-complete=true" >> $GITHUB_OUTPUT
|
|
209
|
+
echo "::notice::Mission has failed — most jobs will be skipped"
|
|
210
|
+
else
|
|
211
|
+
echo "mission-complete=false" >> $GITHUB_OUTPUT
|
|
212
|
+
fi
|
|
193
213
|
outputs:
|
|
194
214
|
model: ${{ steps.normalise.outputs.model }}
|
|
195
215
|
profile: ${{ steps.normalise.outputs.profile }}
|
|
@@ -203,6 +223,7 @@ jobs:
|
|
|
203
223
|
log-prefix: ${{ steps.normalise.outputs.log-prefix }}
|
|
204
224
|
log-branch: ${{ steps.normalise.outputs.log-branch }}
|
|
205
225
|
screenshot-file: ${{ steps.normalise.outputs.screenshot-file }}
|
|
226
|
+
mission-complete: ${{ steps.mission-check.outputs.mission-complete }}
|
|
206
227
|
|
|
207
228
|
# ─── PR Cleanup: merge/close/delete stale PRs and branches ─────────
|
|
208
229
|
pr-cleanup:
|
|
@@ -280,7 +301,8 @@ jobs:
|
|
|
280
301
|
behaviour-telemetry:
|
|
281
302
|
needs: params
|
|
282
303
|
if: |
|
|
283
|
-
needs.params.outputs.
|
|
304
|
+
needs.params.outputs.mission-complete != 'true' &&
|
|
305
|
+
(needs.params.outputs.mode == 'full' || needs.params.outputs.mode == 'dev-only' || needs.params.outputs.mode == 'review-only')
|
|
284
306
|
runs-on: ubuntu-latest
|
|
285
307
|
container: mcr.microsoft.com/playwright:v1.58.2-noble
|
|
286
308
|
steps:
|
|
@@ -323,6 +345,7 @@ jobs:
|
|
|
323
345
|
needs: [params, behaviour-telemetry]
|
|
324
346
|
if: |
|
|
325
347
|
!cancelled() &&
|
|
348
|
+
needs.params.outputs.mission-complete != 'true' &&
|
|
326
349
|
(needs.params.outputs.mode == 'full' || needs.params.outputs.mode == 'dev-only' || needs.params.outputs.mode == 'review-only')
|
|
327
350
|
runs-on: ubuntu-latest
|
|
328
351
|
steps:
|
|
@@ -550,14 +573,25 @@ jobs:
|
|
|
550
573
|
if (fs.existsSync('agentic-lib.toml')) {
|
|
551
574
|
const toml = fs.readFileSync('agentic-lib.toml', 'utf8');
|
|
552
575
|
const profileMatch = toml.match(/^\s*profile\s*=\s*"(\w+)"/m);
|
|
553
|
-
const profile = profileMatch ? profileMatch[1] : '
|
|
554
|
-
const PROFILE_LIMITS = { min: 10000,
|
|
576
|
+
const profile = profileMatch ? profileMatch[1] : 'med';
|
|
577
|
+
const PROFILE_LIMITS = { min: 10000, med: 30000, max: 60000 };
|
|
555
578
|
maxTelemetryChars = PROFILE_LIMITS[profile] || 30000;
|
|
556
579
|
}
|
|
557
580
|
} catch (e) {}
|
|
558
581
|
const summary = JSON.stringify(telemetry);
|
|
559
582
|
core.setOutput('telemetry', summary.slice(0, maxTelemetryChars));
|
|
560
583
|
|
|
584
|
+
// W15: Output counts for downstream job gating
|
|
585
|
+
core.setOutput('open-issue-count', String(issuesSummary.length));
|
|
586
|
+
core.setOutput('open-pr-count', String(prsSummary.length));
|
|
587
|
+
|
|
588
|
+
// W19: Output unit test summary for transform prompt
|
|
589
|
+
const unitSummary = `exit=${telemetry.liveTests.unit.exitCode} pass=${telemetry.liveTests.unit.passCount} fail=${telemetry.liveTests.unit.failCount}`;
|
|
590
|
+
core.setOutput('unit-test-summary', unitSummary);
|
|
591
|
+
// Truncated unit test output for transform (first 4000 chars)
|
|
592
|
+
const unitOutputForTransform = (telemetry.liveTests?.unit?.output || '').substring(0, 4000);
|
|
593
|
+
core.setOutput('unit-test-output', unitOutputForTransform);
|
|
594
|
+
|
|
561
595
|
- name: Output telemetry summary
|
|
562
596
|
shell: bash
|
|
563
597
|
run: |
|
|
@@ -567,6 +601,10 @@ jobs:
|
|
|
567
601
|
|
|
568
602
|
outputs:
|
|
569
603
|
telemetry: ${{ steps.gather.outputs.telemetry }}
|
|
604
|
+
open-issue-count: ${{ steps.gather.outputs.open-issue-count }}
|
|
605
|
+
open-pr-count: ${{ steps.gather.outputs.open-pr-count }}
|
|
606
|
+
unit-test-summary: ${{ steps.gather.outputs.unit-test-summary }}
|
|
607
|
+
unit-test-output: ${{ steps.gather.outputs.unit-test-output }}
|
|
570
608
|
|
|
571
609
|
# ─── Maintain: features + library (push to main) ───────────────────
|
|
572
610
|
# Runs early (parallel with pr-cleanup/telemetry) so supervisor sees features.
|
|
@@ -574,6 +612,7 @@ jobs:
|
|
|
574
612
|
needs: [params]
|
|
575
613
|
if: |
|
|
576
614
|
!cancelled() &&
|
|
615
|
+
needs.params.outputs.mission-complete != 'true' &&
|
|
577
616
|
(needs.params.outputs.mode == 'full' || needs.params.outputs.mode == 'maintain-only') &&
|
|
578
617
|
needs.params.result == 'success' &&
|
|
579
618
|
inputs.skipMaintain != 'true' && inputs.skipMaintain != true
|
|
@@ -656,6 +695,7 @@ jobs:
|
|
|
656
695
|
- name: Maintain library
|
|
657
696
|
id: maintain-library
|
|
658
697
|
if: steps.mission-check.outputs.mission-complete != 'true'
|
|
698
|
+
timeout-minutes: 10
|
|
659
699
|
uses: ./.github/agentic-lib/actions/agentic-step
|
|
660
700
|
env:
|
|
661
701
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -670,6 +710,7 @@ jobs:
|
|
|
670
710
|
- name: Maintain features
|
|
671
711
|
id: maintain-features
|
|
672
712
|
if: steps.mission-check.outputs.mission-complete != 'true'
|
|
713
|
+
timeout-minutes: 10
|
|
673
714
|
uses: ./.github/agentic-lib/actions/agentic-step
|
|
674
715
|
env:
|
|
675
716
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -710,6 +751,7 @@ jobs:
|
|
|
710
751
|
needs: [params]
|
|
711
752
|
if: |
|
|
712
753
|
!cancelled() &&
|
|
754
|
+
needs.params.outputs.mission-complete != 'true' &&
|
|
713
755
|
(needs.params.outputs.mode == 'full' || needs.params.outputs.mode == 'maintain-only') &&
|
|
714
756
|
needs.params.result == 'success'
|
|
715
757
|
runs-on: ubuntu-latest
|
|
@@ -755,6 +797,7 @@ jobs:
|
|
|
755
797
|
- name: Run implementation review
|
|
756
798
|
id: review
|
|
757
799
|
if: github.repository != 'xn-intenton-z2a/agentic-lib'
|
|
800
|
+
timeout-minutes: 10
|
|
758
801
|
uses: ./.github/agentic-lib/actions/agentic-step
|
|
759
802
|
env:
|
|
760
803
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -776,6 +819,7 @@ jobs:
|
|
|
776
819
|
needs: [params, telemetry, maintain, implementation-review]
|
|
777
820
|
if: |
|
|
778
821
|
!cancelled() &&
|
|
822
|
+
needs.params.outputs.mission-complete != 'true' &&
|
|
779
823
|
(needs.params.outputs.mode == 'full' || needs.params.outputs.mode == 'dev-only') &&
|
|
780
824
|
needs.params.result == 'success'
|
|
781
825
|
runs-on: ubuntu-latest
|
|
@@ -816,6 +860,7 @@ jobs:
|
|
|
816
860
|
- name: Run director
|
|
817
861
|
id: director
|
|
818
862
|
if: github.repository != 'xn-intenton-z2a/agentic-lib'
|
|
863
|
+
timeout-minutes: 10
|
|
819
864
|
uses: ./.github/agentic-lib/actions/agentic-step
|
|
820
865
|
env:
|
|
821
866
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -903,6 +948,7 @@ jobs:
|
|
|
903
948
|
|
|
904
949
|
- name: Run supervisor
|
|
905
950
|
if: github.repository != 'xn-intenton-z2a/agentic-lib'
|
|
951
|
+
timeout-minutes: 10
|
|
906
952
|
uses: ./.github/agentic-lib/actions/agentic-step
|
|
907
953
|
env:
|
|
908
954
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -1179,6 +1225,7 @@ jobs:
|
|
|
1179
1225
|
env.FIX_PR_NUMBER != '' &&
|
|
1180
1226
|
steps.fix-mission-check.outputs.mission-complete != 'true' &&
|
|
1181
1227
|
env.FIX_REASON == 'requested'
|
|
1228
|
+
timeout-minutes: 10
|
|
1182
1229
|
uses: ./.github/agentic-lib/actions/agentic-step
|
|
1183
1230
|
env:
|
|
1184
1231
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -1196,6 +1243,7 @@ jobs:
|
|
|
1196
1243
|
if: |
|
|
1197
1244
|
env.FIX_MAIN_BUILD == 'true' &&
|
|
1198
1245
|
steps.fix-mission-check.outputs.mission-complete != 'true'
|
|
1246
|
+
timeout-minutes: 10
|
|
1199
1247
|
uses: ./.github/agentic-lib/actions/agentic-step
|
|
1200
1248
|
env:
|
|
1201
1249
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -1229,6 +1277,63 @@ jobs:
|
|
|
1229
1277
|
echo "No additional changes to push"
|
|
1230
1278
|
fi
|
|
1231
1279
|
|
|
1280
|
+
# W20: Immediately attempt merge after fix-stuck resolves conflicts
|
|
1281
|
+
- name: "W20: Immediate merge attempt after fix"
|
|
1282
|
+
if: |
|
|
1283
|
+
github.repository != 'xn-intenton-z2a/agentic-lib' &&
|
|
1284
|
+
env.FIX_PR_NUMBER != '' &&
|
|
1285
|
+
steps.fix-mission-check.outputs.mission-complete != 'true' &&
|
|
1286
|
+
needs.params.outputs.dry-run != 'true'
|
|
1287
|
+
uses: actions/github-script@v8
|
|
1288
|
+
with:
|
|
1289
|
+
script: |
|
|
1290
|
+
const owner = context.repo.owner;
|
|
1291
|
+
const repo = context.repo.repo;
|
|
1292
|
+
const prNumber = parseInt('${{ env.FIX_PR_NUMBER }}');
|
|
1293
|
+
if (!prNumber) return;
|
|
1294
|
+
|
|
1295
|
+
// Wait for checks to register
|
|
1296
|
+
await new Promise(r => setTimeout(r, 15000));
|
|
1297
|
+
|
|
1298
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
1299
|
+
const { data: pr } = await github.rest.pulls.get({
|
|
1300
|
+
owner, repo, pull_number: prNumber,
|
|
1301
|
+
});
|
|
1302
|
+
|
|
1303
|
+
if (pr.mergeable && pr.mergeable_state === 'clean') {
|
|
1304
|
+
try {
|
|
1305
|
+
await github.rest.pulls.merge({
|
|
1306
|
+
owner, repo, pull_number: prNumber, merge_method: 'squash',
|
|
1307
|
+
});
|
|
1308
|
+
core.info(`W20: Merged PR #${prNumber} immediately after fix`);
|
|
1309
|
+
try {
|
|
1310
|
+
await github.rest.git.deleteRef({ owner, repo, ref: `heads/${pr.head.ref}` });
|
|
1311
|
+
} catch (e) { /* branch may already be deleted */ }
|
|
1312
|
+
// Label associated issue
|
|
1313
|
+
const branchPrefix = 'agentic-lib-issue-';
|
|
1314
|
+
if (pr.head.ref.startsWith(branchPrefix)) {
|
|
1315
|
+
const issueNum = parseInt(pr.head.ref.replace(branchPrefix, ''));
|
|
1316
|
+
if (issueNum) {
|
|
1317
|
+
try {
|
|
1318
|
+
await github.rest.issues.addLabels({ owner, repo, issue_number: issueNum, labels: ['merged'] });
|
|
1319
|
+
await github.rest.issues.removeLabel({ owner, repo, issue_number: issueNum, name: 'in-progress' });
|
|
1320
|
+
} catch (e) { /* label may not exist */ }
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
return;
|
|
1324
|
+
} catch (e) {
|
|
1325
|
+
core.info(`W20: Merge attempt ${attempt + 1} failed: ${e.message}`);
|
|
1326
|
+
}
|
|
1327
|
+
} else if (pr.mergeable_state === 'unstable' || pr.mergeable === null) {
|
|
1328
|
+
core.info(`W20: PR not ready yet (${pr.mergeable_state}), waiting...`);
|
|
1329
|
+
await new Promise(r => setTimeout(r, 15000));
|
|
1330
|
+
} else {
|
|
1331
|
+
core.info(`W20: PR not mergeable (${pr.mergeable_state}), leaving for pr-cleanup`);
|
|
1332
|
+
break;
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
core.info(`W20: PR #${prNumber} left open for next cycle`);
|
|
1336
|
+
|
|
1232
1337
|
- name: Commit, push, and open PR for main build fix
|
|
1233
1338
|
if: github.repository != 'xn-intenton-z2a/agentic-lib' && env.FIX_MAIN_BUILD == 'true' && steps.fix-mission-check.outputs.mission-complete != 'true'
|
|
1234
1339
|
env:
|
|
@@ -1249,12 +1354,14 @@ jobs:
|
|
|
1249
1354
|
--label automerge
|
|
1250
1355
|
|
|
1251
1356
|
# ─── Review: close resolved issues, enhance with criteria ──────────
|
|
1357
|
+
# W15: Skip review when there are no open issues to review
|
|
1252
1358
|
review-features:
|
|
1253
|
-
needs: [params, maintain, supervisor]
|
|
1359
|
+
needs: [params, maintain, supervisor, telemetry]
|
|
1254
1360
|
if: |
|
|
1255
1361
|
!cancelled() &&
|
|
1256
1362
|
(needs.params.outputs.mode == 'full' || needs.params.outputs.mode == 'review-only') &&
|
|
1257
|
-
needs.params.result == 'success'
|
|
1363
|
+
needs.params.result == 'success' &&
|
|
1364
|
+
needs.telemetry.outputs.open-issue-count != '0'
|
|
1258
1365
|
runs-on: ubuntu-latest
|
|
1259
1366
|
steps:
|
|
1260
1367
|
- uses: actions/checkout@v6
|
|
@@ -1278,6 +1385,7 @@ jobs:
|
|
|
1278
1385
|
fi
|
|
1279
1386
|
|
|
1280
1387
|
- name: Review issues
|
|
1388
|
+
timeout-minutes: 10
|
|
1281
1389
|
uses: ./.github/agentic-lib/actions/agentic-step
|
|
1282
1390
|
env:
|
|
1283
1391
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -1289,6 +1397,7 @@ jobs:
|
|
|
1289
1397
|
model: ${{ needs.params.outputs.model }}
|
|
1290
1398
|
|
|
1291
1399
|
- name: Enhance issues
|
|
1400
|
+
timeout-minutes: 10
|
|
1292
1401
|
uses: ./.github/agentic-lib/actions/agentic-step
|
|
1293
1402
|
env:
|
|
1294
1403
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -1299,9 +1408,9 @@ jobs:
|
|
|
1299
1408
|
instructions: ".github/agents/agent-ready-issue.md"
|
|
1300
1409
|
model: ${{ needs.params.outputs.model }}
|
|
1301
1410
|
|
|
1302
|
-
# ─── Dev:
|
|
1411
|
+
# ─── Dev: issue resolution (W7: multiple issues per session) ──────
|
|
1303
1412
|
dev:
|
|
1304
|
-
needs: [params, maintain, review-features]
|
|
1413
|
+
needs: [params, maintain, review-features, telemetry, implementation-review]
|
|
1305
1414
|
if: |
|
|
1306
1415
|
!cancelled() &&
|
|
1307
1416
|
(needs.params.outputs.mode == 'full' || needs.params.outputs.mode == 'dev-only') &&
|
|
@@ -1383,25 +1492,32 @@ jobs:
|
|
|
1383
1492
|
core.setOutput('issue-number', specificIssue);
|
|
1384
1493
|
return;
|
|
1385
1494
|
}
|
|
1386
|
-
// W7:
|
|
1387
|
-
|
|
1495
|
+
// W7: Collect ALL ready issues for concurrent resolution in one session
|
|
1496
|
+
const collected = [];
|
|
1497
|
+
// Instability issues first (mechanical priority override)
|
|
1388
1498
|
const { data: instabilityIssues } = await github.rest.issues.listForRepo({
|
|
1389
1499
|
...context.repo, state: 'open', labels: 'instability',
|
|
1390
|
-
sort: 'created', direction: 'asc', per_page:
|
|
1500
|
+
sort: 'created', direction: 'asc', per_page: 10,
|
|
1391
1501
|
});
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
core.info(`Instability
|
|
1395
|
-
return;
|
|
1502
|
+
for (const i of instabilityIssues) {
|
|
1503
|
+
collected.push(i.number);
|
|
1504
|
+
core.info(`Instability issue: #${i.number}: ${i.title}`);
|
|
1396
1505
|
}
|
|
1397
|
-
//
|
|
1398
|
-
const { data:
|
|
1506
|
+
// Then ready issues
|
|
1507
|
+
const { data: readyIssues } = await github.rest.issues.listForRepo({
|
|
1399
1508
|
...context.repo, state: 'open', labels: 'ready',
|
|
1400
|
-
sort: 'created', direction: 'asc', per_page:
|
|
1509
|
+
sort: 'created', direction: 'asc', per_page: 10,
|
|
1401
1510
|
});
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1511
|
+
for (const i of readyIssues) {
|
|
1512
|
+
if (!collected.includes(i.number)) {
|
|
1513
|
+
collected.push(i.number);
|
|
1514
|
+
core.info(`Ready issue: #${i.number}: ${i.title}`);
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
if (collected.length > 0) {
|
|
1518
|
+
// W7: Pass all issues as comma-separated list
|
|
1519
|
+
core.setOutput('issue-number', collected.join(','));
|
|
1520
|
+
core.info(`Targeting ${collected.length} issue(s): ${collected.join(', ')}`);
|
|
1405
1521
|
} else {
|
|
1406
1522
|
core.setOutput('issue-number', '');
|
|
1407
1523
|
core.info('No ready issues found');
|
|
@@ -1411,17 +1527,24 @@ jobs:
|
|
|
1411
1527
|
if: steps.issue.outputs.issue-number != ''
|
|
1412
1528
|
id: branch
|
|
1413
1529
|
run: |
|
|
1414
|
-
|
|
1530
|
+
# W7: Use first issue number for branch name (may be comma-separated list)
|
|
1531
|
+
ISSUE_LIST="${{ steps.issue.outputs.issue-number }}"
|
|
1532
|
+
ISSUE_NUMBER="${ISSUE_LIST%%,*}"
|
|
1415
1533
|
BRANCH="agentic-lib-issue-${ISSUE_NUMBER}"
|
|
1416
1534
|
git checkout -b "${BRANCH}" 2>/dev/null || git checkout "${BRANCH}"
|
|
1417
1535
|
echo "branchName=${BRANCH}" >> $GITHUB_OUTPUT
|
|
1418
1536
|
|
|
1419
1537
|
- name: Run transformation
|
|
1420
1538
|
if: steps.issue.outputs.issue-number != ''
|
|
1539
|
+
timeout-minutes: 10
|
|
1421
1540
|
uses: ./.github/agentic-lib/actions/agentic-step
|
|
1422
1541
|
env:
|
|
1423
1542
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
1424
1543
|
COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
|
|
1544
|
+
REVIEW_ADVICE: ${{ needs.implementation-review.outputs.review-advice }}
|
|
1545
|
+
REVIEW_GAPS: ${{ needs.implementation-review.outputs.review-gaps }}
|
|
1546
|
+
TELEMETRY_UNIT_TEST_SUMMARY: ${{ needs.telemetry.outputs.unit-test-summary }}
|
|
1547
|
+
TELEMETRY_UNIT_TEST_OUTPUT: ${{ needs.telemetry.outputs.unit-test-output }}
|
|
1425
1548
|
with:
|
|
1426
1549
|
task: "transform"
|
|
1427
1550
|
config: ${{ needs.params.outputs.config-path }}
|
|
@@ -1501,7 +1624,10 @@ jobs:
|
|
|
1501
1624
|
const owner = context.repo.owner;
|
|
1502
1625
|
const repo = context.repo.repo;
|
|
1503
1626
|
const branchName = '${{ steps.branch.outputs.branchName }}';
|
|
1504
|
-
const
|
|
1627
|
+
const issueList = '${{ steps.issue.outputs.issue-number }}';
|
|
1628
|
+
// W7: issueNumber may be comma-separated list
|
|
1629
|
+
const issueNumbers = issueList.split(',').map(n => n.trim()).filter(Boolean);
|
|
1630
|
+
const issueNumber = issueNumbers[0] || '';
|
|
1505
1631
|
|
|
1506
1632
|
if (!branchName) return;
|
|
1507
1633
|
|
|
@@ -1525,6 +1651,12 @@ jobs:
|
|
|
1525
1651
|
head: `${owner}:${branchName}`, per_page: 1,
|
|
1526
1652
|
});
|
|
1527
1653
|
|
|
1654
|
+
// W7: Build PR body with Closes for all issues
|
|
1655
|
+
const closesLines = issueNumbers.map(n => `Closes #${n}`).join('\n');
|
|
1656
|
+
const prTitle = issueNumbers.length > 1
|
|
1657
|
+
? `fix: resolve issues ${issueNumbers.map(n => '#' + n).join(', ')}`
|
|
1658
|
+
: `fix: resolve issue #${issueNumber}`;
|
|
1659
|
+
|
|
1528
1660
|
let prNumber;
|
|
1529
1661
|
if (existingPRs.length > 0) {
|
|
1530
1662
|
prNumber = existingPRs[0].number;
|
|
@@ -1532,8 +1664,8 @@ jobs:
|
|
|
1532
1664
|
} else {
|
|
1533
1665
|
const { data: pr } = await github.rest.pulls.create({
|
|
1534
1666
|
owner, repo,
|
|
1535
|
-
title:
|
|
1536
|
-
body:
|
|
1667
|
+
title: prTitle,
|
|
1668
|
+
body: `${closesLines}\n\nAutomated transformation.`,
|
|
1537
1669
|
head: branchName, base: 'main',
|
|
1538
1670
|
});
|
|
1539
1671
|
prNumber = pr.number;
|
|
@@ -1583,7 +1715,7 @@ jobs:
|
|
|
1583
1715
|
|
|
1584
1716
|
# ─── Post-merge: stats, schedule, mission check ────────────────────
|
|
1585
1717
|
post-merge:
|
|
1586
|
-
needs: [params, maintain, dev, pr-cleanup]
|
|
1718
|
+
needs: [params, maintain, dev, pr-cleanup, implementation-review]
|
|
1587
1719
|
if: ${{ !cancelled() && needs.params.result == 'success' }}
|
|
1588
1720
|
runs-on: ubuntu-latest
|
|
1589
1721
|
steps:
|
|
@@ -1605,6 +1737,75 @@ jobs:
|
|
|
1605
1737
|
echo "- Dry-run: ${{ needs.params.outputs.dry-run }}" >> $GITHUB_STEP_SUMMARY
|
|
1606
1738
|
echo "- Website: [${SITE_URL}](${SITE_URL})" >> $GITHUB_STEP_SUMMARY
|
|
1607
1739
|
|
|
1740
|
+
# W14: Post-merge director check — re-evaluate mission status after dev/PR merges
|
|
1741
|
+
- name: Fetch log and screenshot from log branch (post-merge director)
|
|
1742
|
+
if: |
|
|
1743
|
+
needs.params.outputs.mission-complete != 'true' &&
|
|
1744
|
+
needs.params.outputs.dry-run != 'true' &&
|
|
1745
|
+
github.repository != 'xn-intenton-z2a/agentic-lib'
|
|
1746
|
+
env:
|
|
1747
|
+
LOG_BRANCH: ${{ needs.params.outputs.log-branch }}
|
|
1748
|
+
SCREENSHOT_FILE: ${{ needs.params.outputs.screenshot-file }}
|
|
1749
|
+
run: |
|
|
1750
|
+
git fetch origin "${LOG_BRANCH}" 2>/dev/null || true
|
|
1751
|
+
git show "origin/${LOG_BRANCH}:${SCREENSHOT_FILE}" > "${SCREENSHOT_FILE}" 2>/dev/null || true
|
|
1752
|
+
git show "origin/${LOG_BRANCH}:agentic-lib-state.toml" > "agentic-lib-state.toml" 2>/dev/null || true
|
|
1753
|
+
|
|
1754
|
+
- uses: actions/setup-node@v6
|
|
1755
|
+
if: |
|
|
1756
|
+
needs.params.outputs.mission-complete != 'true' &&
|
|
1757
|
+
needs.params.outputs.dry-run != 'true' &&
|
|
1758
|
+
github.repository != 'xn-intenton-z2a/agentic-lib'
|
|
1759
|
+
with:
|
|
1760
|
+
node-version: "24"
|
|
1761
|
+
|
|
1762
|
+
- name: Self-init (agentic-lib dev only)
|
|
1763
|
+
if: |
|
|
1764
|
+
needs.params.outputs.mission-complete != 'true' &&
|
|
1765
|
+
needs.params.outputs.dry-run != 'true' &&
|
|
1766
|
+
hashFiles('scripts/self-init.sh') != '' && hashFiles('.github/agentic-lib/actions/agentic-step/package.json') == ''
|
|
1767
|
+
run: bash scripts/self-init.sh
|
|
1768
|
+
|
|
1769
|
+
- name: Install agentic-step dependencies (post-merge director)
|
|
1770
|
+
if: |
|
|
1771
|
+
needs.params.outputs.mission-complete != 'true' &&
|
|
1772
|
+
needs.params.outputs.dry-run != 'true' &&
|
|
1773
|
+
github.repository != 'xn-intenton-z2a/agentic-lib'
|
|
1774
|
+
working-directory: .github/agentic-lib/actions/agentic-step
|
|
1775
|
+
run: |
|
|
1776
|
+
npm ci
|
|
1777
|
+
if [ -d "../../copilot" ]; then
|
|
1778
|
+
ln -sf "$(pwd)/node_modules" ../../copilot/node_modules
|
|
1779
|
+
fi
|
|
1780
|
+
|
|
1781
|
+
- name: "W14: Post-merge director evaluation"
|
|
1782
|
+
id: post-merge-director
|
|
1783
|
+
if: |
|
|
1784
|
+
needs.params.outputs.mission-complete != 'true' &&
|
|
1785
|
+
needs.params.outputs.dry-run != 'true' &&
|
|
1786
|
+
github.repository != 'xn-intenton-z2a/agentic-lib'
|
|
1787
|
+
timeout-minutes: 10
|
|
1788
|
+
uses: ./.github/agentic-lib/actions/agentic-step
|
|
1789
|
+
env:
|
|
1790
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
1791
|
+
COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
|
|
1792
|
+
REVIEW_ADVICE: ${{ needs.implementation-review.outputs.review-advice }}
|
|
1793
|
+
REVIEW_GAPS: ${{ needs.implementation-review.outputs.review-gaps }}
|
|
1794
|
+
with:
|
|
1795
|
+
task: "direct"
|
|
1796
|
+
config: ${{ needs.params.outputs.config-path }}
|
|
1797
|
+
instructions: ".github/agents/agent-director.md"
|
|
1798
|
+
model: ${{ needs.params.outputs.model }}
|
|
1799
|
+
|
|
1800
|
+
- name: Push log to log branch (post-merge director)
|
|
1801
|
+
if: |
|
|
1802
|
+
needs.params.outputs.mission-complete != 'true' &&
|
|
1803
|
+
needs.params.outputs.dry-run != 'true' &&
|
|
1804
|
+
github.repository != 'xn-intenton-z2a/agentic-lib'
|
|
1805
|
+
env:
|
|
1806
|
+
LOG_BRANCH: ${{ needs.params.outputs.log-branch }}
|
|
1807
|
+
run: bash .github/agentic-lib/scripts/push-to-logs.sh agent-log-*.md agentic-lib-state.toml
|
|
1808
|
+
|
|
1608
1809
|
# ─── Post-commit validation: call test workflow to verify branch health ───
|
|
1609
1810
|
post-commit-test:
|
|
1610
1811
|
needs: [params, maintain, dev, fix-stuck, post-merge]
|
package/agentic-lib.toml
CHANGED
|
@@ -36,9 +36,9 @@ test = "npm ci && npm test"
|
|
|
36
36
|
# library-limit = 32
|
|
37
37
|
|
|
38
38
|
[tuning]
|
|
39
|
-
# Profile sets defaults for all tuning and limit knobs: min |
|
|
39
|
+
# Profile sets defaults for all tuning and limit knobs: min | med | max
|
|
40
40
|
# Profile definitions live in [profiles.*] sections below.
|
|
41
|
-
profile = "min" #@dist "
|
|
41
|
+
profile = "min" #@dist "max"
|
|
42
42
|
#
|
|
43
43
|
# Model selection — each has different strengths:
|
|
44
44
|
# gpt-5-mini — Fast, cheap, supports reasoning-effort. Best for CI and iteration.
|
|
@@ -55,6 +55,13 @@ infinite-sessions = false # set to true for long sessions with compaction
|
|
|
55
55
|
# max-issues = 5
|
|
56
56
|
# stale-days = 14
|
|
57
57
|
# max-discussion-comments = 5
|
|
58
|
+
# session-timeout-ms = 480000 # LLM session timeout in ms (should be < workflow step timeout)
|
|
59
|
+
# max-tokens = 200000 # token budget — controls max tool calls (tokens / 5000)
|
|
60
|
+
# max-read-chars = 20000 # max chars per read_file result
|
|
61
|
+
# max-test-output = 4000 # max chars of test output in prompts
|
|
62
|
+
# max-file-listing = 30 # max files in directory listings (0 = unlimited)
|
|
63
|
+
# max-library-index = 2000 # max chars for library index summary
|
|
64
|
+
# max-fix-test-output = 8000 # max chars of failed run log in fix-code
|
|
58
65
|
|
|
59
66
|
# ─── Profile Definitions ────────────────────────────────────────────
|
|
60
67
|
# Each profile defines tuning and limits defaults. The active profile
|
|
@@ -75,9 +82,16 @@ max-attempts-per-branch = 2 # max transform attempts before abando
|
|
|
75
82
|
max-attempts-per-issue = 1 # max transform attempts before abandoning an issue
|
|
76
83
|
features-limit = 2 # max feature files in features/ directory
|
|
77
84
|
library-limit = 8 # max library entries in library/ directory
|
|
85
|
+
session-timeout-ms = 480000 # LLM session timeout in ms (8 min, below 10-min workflow step)
|
|
86
|
+
max-tokens = 200000 # token budget for tool-call cap calculation
|
|
87
|
+
max-read-chars = 20000 # max chars returned from read_file tool
|
|
88
|
+
max-test-output = 4000 # max chars of test output in prompts
|
|
89
|
+
max-file-listing = 30 # max files in directory listings (0 = unlimited)
|
|
90
|
+
max-library-index = 2000 # max chars for library index in prompts
|
|
91
|
+
max-fix-test-output = 8000 # max chars of failed run log in fix-code
|
|
78
92
|
|
|
79
|
-
[profiles.
|
|
80
|
-
# Balanced — good results,
|
|
93
|
+
[profiles.med]
|
|
94
|
+
# Balanced — good results, middle ground.
|
|
81
95
|
reasoning-effort = "medium" # low | medium | high | none
|
|
82
96
|
infinite-sessions = true # enable session compaction for long runs
|
|
83
97
|
transformation-budget = 32 # max code-changing cycles per run
|
|
@@ -90,6 +104,13 @@ max-attempts-per-branch = 3 # max transform attempts before abando
|
|
|
90
104
|
max-attempts-per-issue = 2 # max transform attempts before abandoning an issue
|
|
91
105
|
features-limit = 4 # max feature files in features/ directory
|
|
92
106
|
library-limit = 32 # max library entries in library/ directory
|
|
107
|
+
session-timeout-ms = 480000 # LLM session timeout in ms (8 min, below 10-min workflow step)
|
|
108
|
+
max-tokens = 200000 # token budget for tool-call cap calculation
|
|
109
|
+
max-read-chars = 50000 # max chars returned from read_file tool
|
|
110
|
+
max-test-output = 10000 # max chars of test output in prompts
|
|
111
|
+
max-file-listing = 100 # max files in directory listings (0 = unlimited)
|
|
112
|
+
max-library-index = 5000 # max chars for library index in prompts
|
|
113
|
+
max-fix-test-output = 15000 # max chars of failed run log in fix-code
|
|
93
114
|
|
|
94
115
|
[profiles.max]
|
|
95
116
|
# Thorough — maximum context for complex missions.
|
|
@@ -105,6 +126,18 @@ max-attempts-per-branch = 5 # max transform attempts before abando
|
|
|
105
126
|
max-attempts-per-issue = 4 # max transform attempts before abandoning an issue
|
|
106
127
|
features-limit = 8 # max feature files in features/ directory
|
|
107
128
|
library-limit = 64 # max library entries in library/ directory
|
|
129
|
+
session-timeout-ms = 480000 # LLM session timeout in ms (8 min, below 10-min workflow step)
|
|
130
|
+
max-tokens = 500000 # token budget for tool-call cap calculation
|
|
131
|
+
max-read-chars = 100000 # max chars returned from read_file tool
|
|
132
|
+
max-test-output = 20000 # max chars of test output in prompts
|
|
133
|
+
max-file-listing = 0 # max files in directory listings (0 = unlimited)
|
|
134
|
+
max-library-index = 10000 # max chars for library index in prompts
|
|
135
|
+
max-fix-test-output = 30000 # max chars of failed run log in fix-code
|
|
136
|
+
|
|
137
|
+
[goals]
|
|
138
|
+
# W12/W13: Code coverage thresholds — stated in all code-changing prompts
|
|
139
|
+
min-line-coverage = 50 # minimum % line coverage required
|
|
140
|
+
min-branch-coverage = 30 # minimum % branch coverage required
|
|
108
141
|
|
|
109
142
|
[mission-complete]
|
|
110
143
|
# Thresholds for deterministic mission-complete declaration.
|
package/package.json
CHANGED
|
@@ -213,6 +213,45 @@ async function executeMissionComplete(octokit, repo, reason) {
|
|
|
213
213
|
} catch (err) {
|
|
214
214
|
core.warning(`Could not commit MISSION_COMPLETE.md: ${err.message}`);
|
|
215
215
|
}
|
|
216
|
+
|
|
217
|
+
// W2: Update persistent state (Benchmark 011 FINDING-3)
|
|
218
|
+
try {
|
|
219
|
+
const { readState, writeState } = await import("../../../copilot/state.js");
|
|
220
|
+
const state = readState(".");
|
|
221
|
+
state.status["mission-complete"] = true;
|
|
222
|
+
state.schedule["auto-disabled"] = true;
|
|
223
|
+
state.schedule["auto-disabled-reason"] = "mission-complete";
|
|
224
|
+
writeState(".", state);
|
|
225
|
+
core.info("State updated: mission-complete, schedule auto-disabled");
|
|
226
|
+
} catch (err) {
|
|
227
|
+
core.warning(`Could not update state for mission-complete: ${err.message}`);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// W3: Disable schedule on mission-complete (Benchmark 011 FINDING-4)
|
|
231
|
+
try {
|
|
232
|
+
await octokit.rest.actions.createWorkflowDispatch({
|
|
233
|
+
...repo,
|
|
234
|
+
workflow_id: "agentic-lib-schedule.yml",
|
|
235
|
+
ref: "main",
|
|
236
|
+
inputs: { frequency: "off" },
|
|
237
|
+
});
|
|
238
|
+
core.info("Dispatched schedule change to off after mission-complete");
|
|
239
|
+
} catch (err) {
|
|
240
|
+
core.warning(`Could not dispatch schedule change: ${err.message}`);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// W16: Notify bot about mission-complete
|
|
244
|
+
try {
|
|
245
|
+
await octokit.rest.actions.createWorkflowDispatch({
|
|
246
|
+
...repo,
|
|
247
|
+
workflow_id: "agentic-lib-bot.yml",
|
|
248
|
+
ref: "main",
|
|
249
|
+
inputs: { message: `Mission complete: ${reason.substring(0, 200)}` },
|
|
250
|
+
});
|
|
251
|
+
core.info("Dispatched bot notification for mission-complete");
|
|
252
|
+
} catch (err) {
|
|
253
|
+
core.warning(`Could not dispatch bot notification: ${err.message}`);
|
|
254
|
+
}
|
|
216
255
|
}
|
|
217
256
|
|
|
218
257
|
/**
|
|
@@ -287,6 +326,19 @@ async function executeMissionFailed(octokit, repo, reason, metricAssessment) {
|
|
|
287
326
|
} catch (err) {
|
|
288
327
|
core.warning(`Could not dispatch schedule change: ${err.message}`);
|
|
289
328
|
}
|
|
329
|
+
|
|
330
|
+
// W16: Notify bot about mission-failed
|
|
331
|
+
try {
|
|
332
|
+
await octokit.rest.actions.createWorkflowDispatch({
|
|
333
|
+
...repo,
|
|
334
|
+
workflow_id: "agentic-lib-bot.yml",
|
|
335
|
+
ref: "main",
|
|
336
|
+
inputs: { message: `Mission failed: ${metricDetail.substring(0, 200)}` },
|
|
337
|
+
});
|
|
338
|
+
core.info("Dispatched bot notification for mission-failed");
|
|
339
|
+
} catch (err) {
|
|
340
|
+
core.warning(`Could not dispatch bot notification: ${err.message}`);
|
|
341
|
+
}
|
|
290
342
|
}
|
|
291
343
|
|
|
292
344
|
/**
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// failures, writes fixes, and runs tests via tools.
|
|
7
7
|
|
|
8
8
|
import * as core from "@actions/core";
|
|
9
|
-
import { readFileSync } from "fs";
|
|
9
|
+
import { readFileSync, existsSync, readdirSync } from "fs";
|
|
10
10
|
import { execSync } from "child_process";
|
|
11
11
|
import { formatPathsSection, extractNarrative, NARRATIVE_INSTRUCTION } from "../copilot.js";
|
|
12
12
|
import { runCopilotSession } from "../../../copilot/copilot-session.js";
|
|
@@ -25,14 +25,15 @@ function extractRunId(detailsUrl) {
|
|
|
25
25
|
/**
|
|
26
26
|
* Fetch actual test output from a GitHub Actions run log.
|
|
27
27
|
*/
|
|
28
|
-
|
|
28
|
+
// W22: maxChars configurable via profile
|
|
29
|
+
function fetchRunLog(runId, maxChars = 8000) {
|
|
29
30
|
try {
|
|
30
31
|
const output = execSync(`gh run view ${runId} --log-failed`, {
|
|
31
32
|
encoding: "utf8",
|
|
32
33
|
timeout: 30000,
|
|
33
34
|
env: { ...process.env },
|
|
34
35
|
});
|
|
35
|
-
return output.substring(0,
|
|
36
|
+
return output.substring(0, maxChars);
|
|
36
37
|
} catch (err) {
|
|
37
38
|
core.debug(`[fix-code] Could not fetch log for run ${runId}: ${err.message}`);
|
|
38
39
|
return null;
|
|
@@ -138,7 +139,8 @@ async function resolveConflicts({ config, pr, prNumber, instructions, model, wri
|
|
|
138
139
|
* Fix a broken main branch build.
|
|
139
140
|
*/
|
|
140
141
|
async function fixMainBuild({ config, runId, instructions, model, writablePaths, testCommand, octokit, repo, logFilePath, screenshotFilePath }) {
|
|
141
|
-
const
|
|
142
|
+
const t = config.tuning || {};
|
|
143
|
+
const logContent = fetchRunLog(runId, t.maxFixTestOutput || 8000);
|
|
142
144
|
if (!logContent) {
|
|
143
145
|
core.info(`Could not fetch log for run ${runId}. Returning nop.`);
|
|
144
146
|
return { outcome: "nop", details: `Could not fetch log for run ${runId}` };
|
|
@@ -169,7 +171,6 @@ async function fixMainBuild({ config, runId, instructions, model, writablePaths,
|
|
|
169
171
|
"- Do not introduce new features — focus on making the build green",
|
|
170
172
|
].join("\n");
|
|
171
173
|
|
|
172
|
-
const t = config.tuning || {};
|
|
173
174
|
const systemPrompt =
|
|
174
175
|
`You are an autonomous coding agent fixing a broken build on the main branch. The test/build workflow has failed. Analyze the error log and make minimal, targeted changes to fix it.` +
|
|
175
176
|
NARRATIVE_INSTRUCTION;
|
|
@@ -254,7 +255,7 @@ export async function fixCode(context) {
|
|
|
254
255
|
const runId = extractRunId(cr.details_url);
|
|
255
256
|
let logContent = null;
|
|
256
257
|
if (runId) {
|
|
257
|
-
logContent = fetchRunLog(runId);
|
|
258
|
+
logContent = fetchRunLog(runId, (config.tuning || {}).maxFixTestOutput || 8000);
|
|
258
259
|
}
|
|
259
260
|
const detail = logContent || cr.output?.summary || "Failed";
|
|
260
261
|
return `**${cr.name}:**\n${detail}`;
|
|
@@ -16,11 +16,12 @@ import { checkWipLimit } from "../safety.js";
|
|
|
16
16
|
/**
|
|
17
17
|
* Build a file listing summary (names + sizes, not content).
|
|
18
18
|
*/
|
|
19
|
-
|
|
19
|
+
// W22: maxFiles configurable via profile (0 = unlimited)
|
|
20
|
+
function buildFileListing(dirPath, extension, maxFiles = 30) {
|
|
20
21
|
if (!dirPath || !existsSync(dirPath)) return [];
|
|
21
22
|
try {
|
|
22
23
|
const files = readdirSync(dirPath, { recursive: true });
|
|
23
|
-
|
|
24
|
+
const filtered = files
|
|
24
25
|
.filter((f) => String(f).endsWith(extension))
|
|
25
26
|
.map((f) => {
|
|
26
27
|
const fullPath = join(dirPath, String(f));
|
|
@@ -30,8 +31,8 @@ function buildFileListing(dirPath, extension) {
|
|
|
30
31
|
} catch {
|
|
31
32
|
return String(f);
|
|
32
33
|
}
|
|
33
|
-
})
|
|
34
|
-
|
|
34
|
+
});
|
|
35
|
+
return maxFiles > 0 ? filtered.slice(0, maxFiles) : filtered;
|
|
35
36
|
} catch {
|
|
36
37
|
return [];
|
|
37
38
|
}
|
|
@@ -14,11 +14,12 @@ import { runCopilotSession } from "../../../copilot/copilot-session.js";
|
|
|
14
14
|
/**
|
|
15
15
|
* Build a file listing summary (names + sizes, not content).
|
|
16
16
|
*/
|
|
17
|
-
|
|
17
|
+
// W22: maxFiles configurable via profile (0 = unlimited)
|
|
18
|
+
function buildFileListing(dirPath, extension, maxFiles = 30) {
|
|
18
19
|
if (!dirPath || !existsSync(dirPath)) return [];
|
|
19
20
|
try {
|
|
20
21
|
const files = readdirSync(dirPath, { recursive: true });
|
|
21
|
-
|
|
22
|
+
const filtered = files
|
|
22
23
|
.filter((f) => String(f).endsWith(extension))
|
|
23
24
|
.map((f) => {
|
|
24
25
|
const fullPath = join(dirPath, String(f));
|
|
@@ -28,8 +29,8 @@ function buildFileListing(dirPath, extension) {
|
|
|
28
29
|
} catch {
|
|
29
30
|
return String(f);
|
|
30
31
|
}
|
|
31
|
-
})
|
|
32
|
-
|
|
32
|
+
});
|
|
33
|
+
return maxFiles > 0 ? filtered.slice(0, maxFiles) : filtered;
|
|
33
34
|
} catch {
|
|
34
35
|
return [];
|
|
35
36
|
}
|
|
@@ -651,6 +651,31 @@ async function executeCreateIssue(octokit, repo, params, ctx) {
|
|
|
651
651
|
}
|
|
652
652
|
const body = bodyParts.join("\n");
|
|
653
653
|
|
|
654
|
+
// W5: Dedup guard against open issues — skip if a similarly-titled issue already exists
|
|
655
|
+
try {
|
|
656
|
+
const { data: openIssues } = await octokit.rest.issues.listForRepo({
|
|
657
|
+
...repo,
|
|
658
|
+
state: "open",
|
|
659
|
+
labels: "automated",
|
|
660
|
+
sort: "created",
|
|
661
|
+
direction: "desc",
|
|
662
|
+
per_page: 20,
|
|
663
|
+
});
|
|
664
|
+
const titleLower = title.toLowerCase();
|
|
665
|
+
const titlePrefix = titleLower.substring(0, 30);
|
|
666
|
+
const openDuplicate = openIssues.find(
|
|
667
|
+
(i) =>
|
|
668
|
+
!i.pull_request &&
|
|
669
|
+
(i.title.toLowerCase().includes(titlePrefix) || titleLower.includes(i.title.toLowerCase().substring(0, 30))),
|
|
670
|
+
);
|
|
671
|
+
if (openDuplicate) {
|
|
672
|
+
core.info(`Skipping duplicate issue (similar to open #${openDuplicate.number}: "${openDuplicate.title}")`);
|
|
673
|
+
return `skipped:duplicate-open-#${openDuplicate.number}`;
|
|
674
|
+
}
|
|
675
|
+
} catch (err) {
|
|
676
|
+
core.warning(`Open issue dedup check failed: ${err.message}`);
|
|
677
|
+
}
|
|
678
|
+
|
|
654
679
|
// Dedup guard: skip if a similarly-titled issue was closed in the last hour
|
|
655
680
|
// Exclude issues closed before the init timestamp (cross-scenario protection)
|
|
656
681
|
try {
|
|
@@ -8,19 +8,21 @@
|
|
|
8
8
|
import * as core from "@actions/core";
|
|
9
9
|
import { existsSync, readFileSync, readdirSync, statSync } from "fs";
|
|
10
10
|
import { join, resolve } from "path";
|
|
11
|
+
import { execSync } from "child_process";
|
|
11
12
|
import { readOptionalFile, formatPathsSection, extractNarrative, NARRATIVE_INSTRUCTION } from "../copilot.js";
|
|
12
13
|
import { runCopilotSession } from "../../../copilot/copilot-session.js";
|
|
13
14
|
import { createGitHubTools, createGitTools } from "../../../copilot/github-tools.js";
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* Build a file listing summary (names + sizes, not content) for the lean prompt.
|
|
18
|
+
* W22: maxFiles configurable via profile (0 = unlimited).
|
|
17
19
|
*/
|
|
18
|
-
function buildFileListing(dirPath, extensions) {
|
|
20
|
+
function buildFileListing(dirPath, extensions, maxFiles = 30) {
|
|
19
21
|
if (!dirPath || !existsSync(dirPath)) return [];
|
|
20
22
|
const exts = Array.isArray(extensions) ? extensions : [extensions];
|
|
21
23
|
try {
|
|
22
24
|
const files = readdirSync(dirPath, { recursive: true });
|
|
23
|
-
|
|
25
|
+
const filtered = files
|
|
24
26
|
.filter((f) => exts.some((ext) => String(f).endsWith(ext)))
|
|
25
27
|
.map((f) => {
|
|
26
28
|
const fullPath = join(dirPath, String(f));
|
|
@@ -31,17 +33,18 @@ function buildFileListing(dirPath, extensions) {
|
|
|
31
33
|
} catch {
|
|
32
34
|
return String(f);
|
|
33
35
|
}
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
+
});
|
|
37
|
+
return maxFiles > 0 ? filtered.slice(0, maxFiles) : filtered;
|
|
36
38
|
} catch {
|
|
37
39
|
return [];
|
|
38
40
|
}
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
/**
|
|
42
|
-
* Build a library index: filename + first 2 lines of each library doc
|
|
44
|
+
* Build a library index: filename + first 2 lines of each library doc.
|
|
45
|
+
* W22: maxChars configurable via profile.
|
|
43
46
|
*/
|
|
44
|
-
function buildLibraryIndex(libraryPath) {
|
|
47
|
+
function buildLibraryIndex(libraryPath, maxChars = 2000) {
|
|
45
48
|
if (!libraryPath || !existsSync(libraryPath)) return "";
|
|
46
49
|
try {
|
|
47
50
|
const files = readdirSync(libraryPath).filter((f) => f.endsWith(".md")).sort();
|
|
@@ -54,7 +57,7 @@ function buildLibraryIndex(libraryPath) {
|
|
|
54
57
|
const content = readFileSync(fullPath, "utf8");
|
|
55
58
|
const lines = content.split("\n").slice(0, 2).join(" ").trim();
|
|
56
59
|
const entry = `- ${f}: ${lines}`;
|
|
57
|
-
if (totalLen + entry.length >
|
|
60
|
+
if (totalLen + entry.length > maxChars) break;
|
|
58
61
|
entries.push(entry);
|
|
59
62
|
totalLen += entry.length;
|
|
60
63
|
} catch {
|
|
@@ -67,6 +70,18 @@ function buildLibraryIndex(libraryPath) {
|
|
|
67
70
|
}
|
|
68
71
|
}
|
|
69
72
|
|
|
73
|
+
/**
|
|
74
|
+
* W9: Get worktree file listing via git ls-files.
|
|
75
|
+
*/
|
|
76
|
+
function getWorktreeFiles() {
|
|
77
|
+
try {
|
|
78
|
+
const gitFiles = execSync("git ls-files", { encoding: "utf8", timeout: 10000 }).trim();
|
|
79
|
+
return gitFiles.split("\n").filter(Boolean);
|
|
80
|
+
} catch {
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
70
85
|
/**
|
|
71
86
|
* Run the full transformation pipeline from mission to code.
|
|
72
87
|
*
|
|
@@ -76,6 +91,8 @@ function buildLibraryIndex(libraryPath) {
|
|
|
76
91
|
export async function transform(context) {
|
|
77
92
|
const { config, instructions, writablePaths, testCommand, model, octokit, repo, issueNumber, logFilePath, screenshotFilePath } = context;
|
|
78
93
|
const t = config.tuning || {};
|
|
94
|
+
const maxFileListing = t.maxFileListing ?? 30;
|
|
95
|
+
const maxLibraryIdx = t.maxLibraryIndex || 2000;
|
|
79
96
|
|
|
80
97
|
// Read mission (required)
|
|
81
98
|
const mission = readOptionalFile(config.paths.mission.path);
|
|
@@ -90,23 +107,24 @@ export async function transform(context) {
|
|
|
90
107
|
return { outcome: "nop", details: "Mission already complete (MISSION_COMPLETE.md signal)" };
|
|
91
108
|
}
|
|
92
109
|
|
|
93
|
-
// Fetch target
|
|
94
|
-
|
|
95
|
-
|
|
110
|
+
// W7: Fetch all target issues (supports comma-separated list)
|
|
111
|
+
const issueNumbers = issueNumber
|
|
112
|
+
? String(issueNumber).split(",").map((n) => n.trim()).filter(Boolean)
|
|
113
|
+
: [];
|
|
114
|
+
const targetIssueSections = [];
|
|
115
|
+
for (const num of issueNumbers) {
|
|
96
116
|
try {
|
|
97
117
|
const { data: issue } = await octokit.rest.issues.get({
|
|
98
118
|
...repo,
|
|
99
|
-
issue_number: Number(
|
|
119
|
+
issue_number: Number(num),
|
|
100
120
|
});
|
|
101
|
-
|
|
102
|
-
|
|
121
|
+
targetIssueSections.push([
|
|
122
|
+
`### Issue #${issue.number}: ${issue.title}`,
|
|
103
123
|
issue.body || "(no description)",
|
|
104
124
|
`Labels: ${issue.labels.map((l) => l.name).join(", ") || "none"}`,
|
|
105
|
-
|
|
106
|
-
"**Focus your transformation on resolving this specific issue.**",
|
|
107
|
-
].join("\n");
|
|
125
|
+
].join("\n"));
|
|
108
126
|
} catch (err) {
|
|
109
|
-
core.warning(`Could not fetch target issue #${
|
|
127
|
+
core.warning(`Could not fetch target issue #${num}: ${err.message}`);
|
|
110
128
|
}
|
|
111
129
|
}
|
|
112
130
|
|
|
@@ -114,17 +132,36 @@ export async function transform(context) {
|
|
|
114
132
|
instructions || "Transform the repository toward its mission by identifying the next best action.";
|
|
115
133
|
|
|
116
134
|
// ── Build lean prompt (structure + mission, not file contents) ──────
|
|
117
|
-
const sourceFiles = buildFileListing(config.paths.source.path, [".js", ".ts"]);
|
|
118
|
-
const testFiles = buildFileListing(config.paths.tests.path, [".js", ".ts"]);
|
|
119
|
-
const webFiles = buildFileListing(config.paths.web?.path || "src/web/", [".html", ".css", ".js"]);
|
|
120
|
-
const featureFiles = buildFileListing(config.paths.features.path, [".md"]);
|
|
121
|
-
const libraryIndex = buildLibraryIndex(config.paths.library?.path || "library/");
|
|
135
|
+
const sourceFiles = buildFileListing(config.paths.source.path, [".js", ".ts"], maxFileListing);
|
|
136
|
+
const testFiles = buildFileListing(config.paths.tests.path, [".js", ".ts"], maxFileListing);
|
|
137
|
+
const webFiles = buildFileListing(config.paths.web?.path || "src/web/", [".html", ".css", ".js"], maxFileListing);
|
|
138
|
+
const featureFiles = buildFileListing(config.paths.features.path, [".md"], maxFileListing);
|
|
139
|
+
const libraryIndex = buildLibraryIndex(config.paths.library?.path || "library/", maxLibraryIdx);
|
|
140
|
+
|
|
141
|
+
// W9: worktree file listing
|
|
142
|
+
const worktreeFiles = getWorktreeFiles();
|
|
143
|
+
|
|
144
|
+
// W17: Implementation review results from upstream
|
|
145
|
+
const reviewAdvice = process.env.REVIEW_ADVICE || "";
|
|
146
|
+
const reviewGapsRaw = process.env.REVIEW_GAPS || "";
|
|
147
|
+
|
|
148
|
+
// W19: Telemetry test output from upstream
|
|
149
|
+
const telemetryTestSummary = process.env.TELEMETRY_UNIT_TEST_SUMMARY || "";
|
|
150
|
+
const telemetryTestOutput = process.env.TELEMETRY_UNIT_TEST_OUTPUT || "";
|
|
122
151
|
|
|
123
152
|
const prompt = [
|
|
124
153
|
"## Instructions",
|
|
125
154
|
agentInstructions,
|
|
126
155
|
"",
|
|
127
|
-
|
|
156
|
+
// W7: Multiple target issues
|
|
157
|
+
...(targetIssueSections.length > 0 ? [
|
|
158
|
+
`## Target Issues (${targetIssueSections.length})`,
|
|
159
|
+
...targetIssueSections.map((s) => s + "\n"),
|
|
160
|
+
targetIssueSections.length > 1
|
|
161
|
+
? "**Resolve as many of these issues as you can in this session. Address them all if possible.**"
|
|
162
|
+
: "**Focus your transformation on resolving this specific issue.**",
|
|
163
|
+
"",
|
|
164
|
+
] : []),
|
|
128
165
|
"## Mission",
|
|
129
166
|
mission,
|
|
130
167
|
"",
|
|
@@ -143,12 +180,47 @@ export async function transform(context) {
|
|
|
143
180
|
"Reference documents available in `library/` (use read_file for full content):",
|
|
144
181
|
libraryIndex,
|
|
145
182
|
] : []),
|
|
183
|
+
// W9: worktree file listing
|
|
184
|
+
...(worktreeFiles.length > 0 ? [
|
|
185
|
+
"",
|
|
186
|
+
`## Worktree Files (${worktreeFiles.length} non-ignored files)`,
|
|
187
|
+
worktreeFiles.join("\n"),
|
|
188
|
+
] : []),
|
|
189
|
+
// W19: Current test state from telemetry
|
|
190
|
+
...(telemetryTestSummary ? [
|
|
191
|
+
"",
|
|
192
|
+
"## Current Test State (from telemetry)",
|
|
193
|
+
`Summary: ${telemetryTestSummary}`,
|
|
194
|
+
...(telemetryTestOutput ? [`\`\`\`\n${telemetryTestOutput}\n\`\`\``] : []),
|
|
195
|
+
] : []),
|
|
196
|
+
// W17: Implementation review
|
|
197
|
+
...(reviewAdvice ? [
|
|
198
|
+
"",
|
|
199
|
+
"## Implementation Review",
|
|
200
|
+
`**Completeness:** ${reviewAdvice}`,
|
|
201
|
+
...((() => {
|
|
202
|
+
try {
|
|
203
|
+
const gaps = JSON.parse(reviewGapsRaw || "[]");
|
|
204
|
+
if (gaps.length > 0) {
|
|
205
|
+
return [
|
|
206
|
+
"",
|
|
207
|
+
"**Gaps Found:**",
|
|
208
|
+
...gaps.map((g) => `- [${g.severity}] ${g.element}: ${g.description} (${g.gapType})`),
|
|
209
|
+
"",
|
|
210
|
+
"Address these gaps in your transformation if they fall within the target issues.",
|
|
211
|
+
];
|
|
212
|
+
}
|
|
213
|
+
} catch { /* ignore */ }
|
|
214
|
+
return [];
|
|
215
|
+
})()),
|
|
216
|
+
] : []),
|
|
146
217
|
"",
|
|
147
218
|
"## Your Task",
|
|
148
219
|
"Analyze the mission and open issues (use list_issues tool).",
|
|
149
220
|
"Read the source files you need (use read_file tool).",
|
|
150
|
-
|
|
151
|
-
|
|
221
|
+
issueNumbers.length > 1
|
|
222
|
+
? "Resolve all target issues listed above. Implement all changes, write tests, update the website, and run run_tests to verify."
|
|
223
|
+
: "Determine the single most impactful next step to transform this repository.\nThen implement that step, writing files and running run_tests to verify.",
|
|
152
224
|
"",
|
|
153
225
|
"## When NOT to make changes",
|
|
154
226
|
"If the existing code already satisfies all requirements in MISSION.md and all open issues have been addressed:",
|
|
@@ -162,6 +234,9 @@ export async function transform(context) {
|
|
|
162
234
|
`- Run \`${testCommand}\` via run_tests to validate your changes`,
|
|
163
235
|
"- Use list_issues to see open issues, get_issue for full details",
|
|
164
236
|
"- Use read_file to read source files you need (don't guess at contents)",
|
|
237
|
+
...(config.coverageGoals ? [
|
|
238
|
+
`- Required code coverage: ≥${config.coverageGoals.minLineCoverage}% lines, ≥${config.coverageGoals.minBranchCoverage}% branches`,
|
|
239
|
+
] : []),
|
|
165
240
|
].join("\n");
|
|
166
241
|
|
|
167
242
|
core.info(`Transform lean prompt length: ${prompt.length} chars`);
|
|
@@ -179,11 +254,61 @@ export async function transform(context) {
|
|
|
179
254
|
const systemPrompt =
|
|
180
255
|
"You are an autonomous code transformation agent. Your goal is to advance the repository toward its mission by making the most impactful change possible in a single step." + NARRATIVE_INSTRUCTION;
|
|
181
256
|
|
|
182
|
-
// ── Create custom tools (GitHub API + git)
|
|
257
|
+
// ── Create custom tools (GitHub API + git + W8 behaviour dry-run) ──
|
|
183
258
|
const createTools = (defineTool, _wp, logger) => {
|
|
184
259
|
const ghTools = createGitHubTools(octokit, repo, defineTool, logger);
|
|
185
260
|
const gitTools = createGitTools(defineTool, logger);
|
|
186
|
-
|
|
261
|
+
|
|
262
|
+
// W8: Dry-run behaviour test tool — reads test specs and source code,
|
|
263
|
+
// returns them to the LLM for reasoning about whether code would pass
|
|
264
|
+
const dryRunBehaviourTests = defineTool("dry_run_behaviour_tests", {
|
|
265
|
+
description: "Read behaviour test specifications and the source code they test, then return both for analysis. Use this to check if your code changes would pass behaviour tests without running Playwright. Call this after making code changes but before committing.",
|
|
266
|
+
parameters: { type: "object", properties: {}, required: [] },
|
|
267
|
+
handler: async () => {
|
|
268
|
+
const behaviourPath = config.paths.behaviour?.path || "tests/behaviour/";
|
|
269
|
+
const sourcePath = config.paths.source?.path || "src/lib/";
|
|
270
|
+
const webPath = config.paths.web?.path || "src/web/";
|
|
271
|
+
|
|
272
|
+
const readDir = (dir, exts) => {
|
|
273
|
+
if (!existsSync(dir)) return [];
|
|
274
|
+
try {
|
|
275
|
+
return readdirSync(dir)
|
|
276
|
+
.filter((f) => exts.some((e) => f.endsWith(e)))
|
|
277
|
+
.slice(0, 10)
|
|
278
|
+
.map((f) => {
|
|
279
|
+
try {
|
|
280
|
+
return { file: f, content: readFileSync(join(dir, f), "utf8") };
|
|
281
|
+
} catch { return { file: f, content: "(unreadable)" }; }
|
|
282
|
+
});
|
|
283
|
+
} catch { return []; }
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
const specs = readDir(behaviourPath, [".spec.js", ".spec.ts", ".test.js", ".test.ts"]);
|
|
287
|
+
const sources = readDir(sourcePath, [".js", ".ts"]);
|
|
288
|
+
const webFilesLocal = readDir(webPath, [".html", ".js"]);
|
|
289
|
+
|
|
290
|
+
if (specs.length === 0) {
|
|
291
|
+
return { textResultForLlm: "No behaviour test files found. Behaviour tests are not configured for this project." };
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const parts = [
|
|
295
|
+
"## Behaviour Test Specifications",
|
|
296
|
+
...specs.map((s) => `### ${s.file}\n\`\`\`\n${s.content}\n\`\`\``),
|
|
297
|
+
"",
|
|
298
|
+
"## Source Code Under Test",
|
|
299
|
+
...sources.map((s) => `### ${s.file}\n\`\`\`\n${s.content}\n\`\`\``),
|
|
300
|
+
];
|
|
301
|
+
if (webFilesLocal.length > 0) {
|
|
302
|
+
parts.push("", "## Website Files", ...webFilesLocal.map((s) => `### ${s.file}\n\`\`\`\n${s.content}\n\`\`\``));
|
|
303
|
+
}
|
|
304
|
+
parts.push("", "## Your Analysis", "Analyze whether the current source code and website would pass these behaviour tests. Report any gaps.");
|
|
305
|
+
|
|
306
|
+
logger.info(`[tool] dry_run_behaviour_tests: ${specs.length} specs, ${sources.length} sources, ${webFilesLocal.length} web files`);
|
|
307
|
+
return { textResultForLlm: parts.join("\n") };
|
|
308
|
+
},
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
return [...ghTools, ...gitTools, dryRunBehaviourTests];
|
|
187
312
|
};
|
|
188
313
|
|
|
189
314
|
// ── Run hybrid session ─────────────────────────────────────────────
|
package/src/copilot/config.js
CHANGED
|
@@ -92,6 +92,13 @@ function parseTuningProfile(profileSection) {
|
|
|
92
92
|
issuesScan: profileSection["max-issues"] || 20,
|
|
93
93
|
staleDays: profileSection["stale-days"] || 30,
|
|
94
94
|
discussionComments: profileSection["max-discussion-comments"] || 10,
|
|
95
|
+
sessionTimeoutMs: profileSection["session-timeout-ms"] || 480000,
|
|
96
|
+
maxTokens: profileSection["max-tokens"] || 200000,
|
|
97
|
+
maxReadChars: profileSection["max-read-chars"] || 20000,
|
|
98
|
+
maxTestOutput: profileSection["max-test-output"] || 4000,
|
|
99
|
+
maxFileListing: profileSection["max-file-listing"] ?? 30,
|
|
100
|
+
maxLibraryIndex: profileSection["max-library-index"] || 2000,
|
|
101
|
+
maxFixTestOutput: profileSection["max-fix-test-output"] || 8000,
|
|
95
102
|
};
|
|
96
103
|
}
|
|
97
104
|
|
|
@@ -132,7 +139,7 @@ function readPackageJson(tomlPath, depsRelPath) {
|
|
|
132
139
|
* @param {Object} [profilesSection] - The [profiles] section from TOML (source of truth)
|
|
133
140
|
*/
|
|
134
141
|
function resolveTuning(tuningSection, profilesSection) {
|
|
135
|
-
const profileName = tuningSection.profile || "
|
|
142
|
+
const profileName = tuningSection.profile || "med";
|
|
136
143
|
const tomlProfile = profilesSection?.[profileName];
|
|
137
144
|
const profile = parseTuningProfile(tomlProfile) || FALLBACK_TUNING;
|
|
138
145
|
const tuning = { ...profile, profileName };
|
|
@@ -149,6 +156,13 @@ function resolveTuning(tuningSection, profilesSection) {
|
|
|
149
156
|
"max-issues": "issuesScan",
|
|
150
157
|
"stale-days": "staleDays",
|
|
151
158
|
"max-discussion-comments": "discussionComments",
|
|
159
|
+
"session-timeout-ms": "sessionTimeoutMs",
|
|
160
|
+
"max-tokens": "maxTokens",
|
|
161
|
+
"max-read-chars": "maxReadChars",
|
|
162
|
+
"max-test-output": "maxTestOutput",
|
|
163
|
+
"max-file-listing": "maxFileListing",
|
|
164
|
+
"max-library-index": "maxLibraryIndex",
|
|
165
|
+
"max-fix-test-output": "maxFixTestOutput",
|
|
152
166
|
};
|
|
153
167
|
for (const [tomlKey, jsKey] of Object.entries(numericOverrides)) {
|
|
154
168
|
if (tuningSection[tomlKey] > 0) tuning[jsKey] = tuningSection[tomlKey];
|
|
@@ -239,6 +253,13 @@ export function loadConfig(configPath) {
|
|
|
239
253
|
const execution = toml.execution || {};
|
|
240
254
|
const bot = toml.bot || {};
|
|
241
255
|
|
|
256
|
+
// W13: Code coverage goals
|
|
257
|
+
const goals = toml.goals || {};
|
|
258
|
+
const coverageGoals = {
|
|
259
|
+
minLineCoverage: goals["min-line-coverage"] ?? 50,
|
|
260
|
+
minBranchCoverage: goals["min-branch-coverage"] ?? 30,
|
|
261
|
+
};
|
|
262
|
+
|
|
242
263
|
// Mission-complete thresholds (with safe defaults)
|
|
243
264
|
// C6: Removed minDedicatedTests and requireDedicatedTests
|
|
244
265
|
const mc = toml["mission-complete"] || {};
|
|
@@ -267,6 +288,7 @@ export function loadConfig(configPath) {
|
|
|
267
288
|
init: toml.init || null,
|
|
268
289
|
tdd: toml.tdd === true,
|
|
269
290
|
missionCompleteThresholds,
|
|
291
|
+
coverageGoals,
|
|
270
292
|
maxTokensPerMaintain: resolvedLimits.maxTokensPerMaintain || 200000,
|
|
271
293
|
writablePaths,
|
|
272
294
|
readOnlyPaths,
|
|
@@ -74,7 +74,7 @@ export async function runCopilotSession({
|
|
|
74
74
|
model = "gpt-5-mini",
|
|
75
75
|
githubToken,
|
|
76
76
|
tuning = {},
|
|
77
|
-
timeoutMs
|
|
77
|
+
timeoutMs,
|
|
78
78
|
agentPrompt,
|
|
79
79
|
userPrompt,
|
|
80
80
|
writablePaths,
|
|
@@ -94,6 +94,11 @@ export async function runCopilotSession({
|
|
|
94
94
|
|
|
95
95
|
const wsPath = resolve(workspacePath);
|
|
96
96
|
|
|
97
|
+
// W11: Session timeout — defaults to 480s (8 min), leaving 2 min headroom
|
|
98
|
+
// below the 10-min workflow step timeout for graceful shutdown.
|
|
99
|
+
// Callers can override via timeoutMs parameter or tuning.sessionTimeoutMs.
|
|
100
|
+
const effectiveTimeoutMs = timeoutMs || tuning.sessionTimeoutMs || 480000;
|
|
101
|
+
|
|
97
102
|
// ── Writable paths ──────────────────────────────────────────────────
|
|
98
103
|
// Default: entire workspace is writable (local CLI mode)
|
|
99
104
|
const effectiveWritablePaths = writablePaths || [wsPath + "/"];
|
|
@@ -154,7 +159,7 @@ export async function runCopilotSession({
|
|
|
154
159
|
const systemPrompt = basePrompt + NARRATIVE_INSTRUCTION;
|
|
155
160
|
|
|
156
161
|
// ── Session config ─────────────────────────────────────────────────
|
|
157
|
-
logger.info(`[agentic-lib] Creating session (model=${model}, workspace=${wsPath})`);
|
|
162
|
+
logger.info(`[agentic-lib] Creating session (model=${model}, workspace=${wsPath}, timeout=${Math.round(effectiveTimeoutMs / 1000)}s)`);
|
|
158
163
|
|
|
159
164
|
const client = new CopilotClient({
|
|
160
165
|
env: { ...process.env, GITHUB_TOKEN: copilotToken, GH_TOKEN: copilotToken },
|
|
@@ -192,7 +197,7 @@ export async function runCopilotSession({
|
|
|
192
197
|
// Truncate large read_file results to prevent context overflow
|
|
193
198
|
if (input.toolName === "read_file" || input.toolName === "view") {
|
|
194
199
|
const resultText = input.toolResult?.textResultForLlm || "";
|
|
195
|
-
const MAX_READ_CHARS = 20000;
|
|
200
|
+
const MAX_READ_CHARS = tuning.maxReadChars || 20000;
|
|
196
201
|
if (resultText.length > MAX_READ_CHARS) {
|
|
197
202
|
hookOutput.modifiedResult = {
|
|
198
203
|
...input.toolResult,
|
|
@@ -296,7 +301,7 @@ export async function runCopilotSession({
|
|
|
296
301
|
|
|
297
302
|
const prompt = userPrompt || [
|
|
298
303
|
`# Mission\n\n${missionText}`,
|
|
299
|
-
`# Current test state\n\n\`\`\`\n${initialTestOutput.substring(0, 4000)}\n\`\`\``,
|
|
304
|
+
`# Current test state\n\n\`\`\`\n${initialTestOutput.substring(0, tuning.maxTestOutput || 4000)}\n\`\`\``,
|
|
300
305
|
"",
|
|
301
306
|
"Implement this mission. Read the existing source code and tests,",
|
|
302
307
|
"make the required changes, run run_tests to verify, and iterate until all tests pass.",
|
|
@@ -315,7 +320,7 @@ export async function runCopilotSession({
|
|
|
315
320
|
}
|
|
316
321
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
317
322
|
try {
|
|
318
|
-
response = await session.sendAndWait(sendOptions,
|
|
323
|
+
response = await session.sendAndWait(sendOptions, effectiveTimeoutMs);
|
|
319
324
|
break;
|
|
320
325
|
} catch (err) {
|
|
321
326
|
if (isRateLimitError(err) && attempt < maxRetries) {
|
package/src/iterate.js
CHANGED
|
@@ -108,7 +108,7 @@ export function readTransformationCost(targetPath) {
|
|
|
108
108
|
|
|
109
109
|
/**
|
|
110
110
|
* Read transformation budget from agentic-lib.toml.
|
|
111
|
-
* Falls back to 8 (the "
|
|
111
|
+
* Falls back to 8 (the "med" profile default).
|
|
112
112
|
*/
|
|
113
113
|
export function readBudget(targetPath) {
|
|
114
114
|
const tomlPath = resolve(targetPath, "agentic-lib.toml");
|