codex-orchestrator 2.0.5 → 2.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -0
- package/README.md +1 -0
- package/dist/src/v2/acceptance-proof.d.ts +7 -0
- package/dist/src/v2/acceptance-proof.d.ts.map +1 -1
- package/dist/src/v2/acceptance-proof.js +111 -5
- package/dist/src/v2/acceptance-proof.js.map +1 -1
- package/dist/src/v2/adapters/command.d.ts +2 -0
- package/dist/src/v2/adapters/command.d.ts.map +1 -1
- package/dist/src/v2/adapters/command.js +67 -8
- package/dist/src/v2/adapters/command.js.map +1 -1
- package/dist/src/v2/adapters/gh-issue-adapter.js +16 -5
- package/dist/src/v2/adapters/gh-issue-adapter.js.map +1 -1
- package/dist/src/v2/android-proof-runner.d.ts +93 -0
- package/dist/src/v2/android-proof-runner.d.ts.map +1 -0
- package/dist/src/v2/android-proof-runner.js +921 -0
- package/dist/src/v2/android-proof-runner.js.map +1 -0
- package/dist/src/v2/config.d.ts +13 -0
- package/dist/src/v2/config.d.ts.map +1 -1
- package/dist/src/v2/config.js +57 -1
- package/dist/src/v2/config.js.map +1 -1
- package/dist/src/v2/immutable-workflow-publisher.js +6 -1
- package/dist/src/v2/immutable-workflow-publisher.js.map +1 -1
- package/dist/src/v2/mobile-lease.d.ts +9 -0
- package/dist/src/v2/mobile-lease.d.ts.map +1 -1
- package/dist/src/v2/mobile-lease.js +27 -3
- package/dist/src/v2/mobile-lease.js.map +1 -1
- package/dist/src/v2/run-issue.d.ts +4 -0
- package/dist/src/v2/run-issue.d.ts.map +1 -1
- package/dist/src/v2/run-issue.js +156 -44
- package/dist/src/v2/run-issue.js.map +1 -1
- package/dist/src/v2/run-store.d.ts +3 -0
- package/dist/src/v2/run-store.d.ts.map +1 -1
- package/dist/src/v2/run-store.js +12 -1
- package/dist/src/v2/run-store.js.map +1 -1
- package/dist/src/v2/runtime.d.ts.map +1 -1
- package/dist/src/v2/runtime.js +63 -10
- package/dist/src/v2/runtime.js.map +1 -1
- package/docs/deep-dive.md +10 -2
- package/internal-workflow/manifest.json +1 -1
- package/internal-workflow/skills/acceptance-proof/SKILL.md +1 -1
- package/internal-workflow/skills/acceptance-proof/references/android.md +6 -6
- package/internal-workflow/skills/implementation-spec-maker/SKILL.md +10 -8
- package/internal-workflow/skills/implementation-spec-review/SKILL.md +17 -3
- package/internal-workflow/skills/implementation-spec-review/evals/evals.json +30 -0
- package/internal-workflow/skills/implementation-spec-review/references/review-loop.md +30 -6
- package/package.json +1 -1
- package/internal-workflow/skills/acceptance-proof/tools/android-lease.mjs +0 -280
package/dist/src/v2/run-issue.js
CHANGED
|
@@ -104,7 +104,7 @@ export class RunIssue {
|
|
|
104
104
|
return await this.publicationDiverged(active, 'commit-tree-diverged');
|
|
105
105
|
if (this.signal.aborted)
|
|
106
106
|
return await this.terminal(await this.clearIntent(active), { status: 'cancelled' });
|
|
107
|
-
if (!await this.authorized(
|
|
107
|
+
if (!await this.authorized(active, config))
|
|
108
108
|
return await this.revoked(active);
|
|
109
109
|
try {
|
|
110
110
|
commitSha = await this.dependencies.git.commit({ worktreePath, message: intent.message });
|
|
@@ -136,7 +136,7 @@ export class RunIssue {
|
|
|
136
136
|
if (!remoteSha) {
|
|
137
137
|
if (this.signal.aborted)
|
|
138
138
|
return await this.terminal(await this.clearIntent(active), { status: 'cancelled' });
|
|
139
|
-
if (!await this.authorized(
|
|
139
|
+
if (!await this.authorized(active, config))
|
|
140
140
|
return await this.revoked(active);
|
|
141
141
|
try {
|
|
142
142
|
await this.dependencies.git.push({ worktreePath, branchName });
|
|
@@ -170,7 +170,7 @@ export class RunIssue {
|
|
|
170
170
|
if (!observed) {
|
|
171
171
|
if (this.signal.aborted)
|
|
172
172
|
return await this.terminal(await this.clearIntent(active), { status: 'cancelled' });
|
|
173
|
-
if (!await this.authorized(
|
|
173
|
+
if (!await this.authorized(active, config))
|
|
174
174
|
return await this.revoked(active);
|
|
175
175
|
try {
|
|
176
176
|
await this.dependencies.pullRequests.createDraft({
|
|
@@ -210,7 +210,7 @@ export class RunIssue {
|
|
|
210
210
|
if (matching.length === 0) {
|
|
211
211
|
if (this.signal.aborted)
|
|
212
212
|
return await this.terminal(await this.clearIntent(active), { status: 'cancelled' });
|
|
213
|
-
if (!await this.authorized(
|
|
213
|
+
if (!await this.authorized(active, config))
|
|
214
214
|
return await this.revoked(active);
|
|
215
215
|
try {
|
|
216
216
|
await this.dependencies.issues.postComment(issueNumber, handoffBody);
|
|
@@ -236,7 +236,7 @@ export class RunIssue {
|
|
|
236
236
|
if (!sameStrings(observation.labels, terminalLabels)) {
|
|
237
237
|
if (this.signal.aborted)
|
|
238
238
|
return await this.terminal(await this.clearIntent(active), { status: 'cancelled' });
|
|
239
|
-
if (!await this.authorized(
|
|
239
|
+
if (!await this.authorized(active, config))
|
|
240
240
|
return await this.revoked(active);
|
|
241
241
|
try {
|
|
242
242
|
await this.dependencies.issues.setLabels(issueNumber, terminalLabels);
|
|
@@ -304,7 +304,7 @@ export class RunIssue {
|
|
|
304
304
|
}
|
|
305
305
|
head = await this.dependencies.git.getHead(active.record.worktreePath);
|
|
306
306
|
if (head === intent.parentSha) {
|
|
307
|
-
if (!await this.authorized(
|
|
307
|
+
if (!await this.authorized(active, config)) {
|
|
308
308
|
return this.blockReviewFeedback(active, 'safety', 'review-feedback-publication-authority-revoked');
|
|
309
309
|
}
|
|
310
310
|
const validation = await coordinator.revalidate({ batch, epoch: 'pre-update', expectedHeadSha: oldHead });
|
|
@@ -350,7 +350,7 @@ export class RunIssue {
|
|
|
350
350
|
}
|
|
351
351
|
remote = await this.dependencies.git.getRemoteBranchSha(active.record.worktreePath, active.record.branchName);
|
|
352
352
|
if (remote === oldHead) {
|
|
353
|
-
if (!await this.authorized(
|
|
353
|
+
if (!await this.authorized(active, config)) {
|
|
354
354
|
return this.blockReviewFeedback(active, 'safety', 'review-feedback-publication-authority-revoked');
|
|
355
355
|
}
|
|
356
356
|
const validation = await coordinator.revalidate({ batch, epoch: 'pre-update', expectedHeadSha: oldHead });
|
|
@@ -410,7 +410,7 @@ export class RunIssue {
|
|
|
410
410
|
return this.blockReviewFeedback(active, 'safety', 'review-feedback-summary-diverged');
|
|
411
411
|
}
|
|
412
412
|
if (matches.length === 0) {
|
|
413
|
-
if (!await this.authorized(
|
|
413
|
+
if (!await this.authorized(active, config)) {
|
|
414
414
|
return this.blockReviewFeedback(active, 'safety', 'review-feedback-publication-authority-revoked');
|
|
415
415
|
}
|
|
416
416
|
try {
|
|
@@ -459,7 +459,7 @@ export class RunIssue {
|
|
|
459
459
|
return validationFailure;
|
|
460
460
|
let issue = await this.readIssue(issueNumber);
|
|
461
461
|
if (!issue || !sameStrings(issue.labels, finalLabels)) {
|
|
462
|
-
if (!await this.authorized(
|
|
462
|
+
if (!await this.authorized(active, config)) {
|
|
463
463
|
return this.blockReviewFeedback(active, 'safety', 'review-feedback-publication-authority-revoked');
|
|
464
464
|
}
|
|
465
465
|
try {
|
|
@@ -546,7 +546,7 @@ export class RunIssue {
|
|
|
546
546
|
}
|
|
547
547
|
if (feedback.phase !== 'idle' || observed.status === 'none')
|
|
548
548
|
return { result: publicOutcome(terminal) };
|
|
549
|
-
if (!await this.authorizedForExactLabels(starting
|
|
549
|
+
if (!await this.authorizedForExactLabels(starting, [config.github.labels.review.name])) {
|
|
550
550
|
return { result: publicOutcome(terminal) };
|
|
551
551
|
}
|
|
552
552
|
const projected = projectReviewFeedbackBatch(observed.batch, starting.record.directReview.targetRevision);
|
|
@@ -595,9 +595,9 @@ export class RunIssue {
|
|
|
595
595
|
}
|
|
596
596
|
const issue = await this.readIssue(active.record.issueNumber);
|
|
597
597
|
const labelsAreRunning = !!issue && issue.state === 'OPEN' && sameStrings(issue.labels, runningLabels)
|
|
598
|
-
&& this.hasTrustedClaim(issue, active.record
|
|
598
|
+
&& this.hasTrustedClaim(issue, active.record);
|
|
599
599
|
const labelsAreReview = !!issue && issue.state === 'OPEN' && sameStrings(issue.labels, reviewLabels)
|
|
600
|
-
&& this.hasTrustedClaim(issue, active.record
|
|
600
|
+
&& this.hasTrustedClaim(issue, active.record);
|
|
601
601
|
if (!labelsAreRunning && !labelsAreReview) {
|
|
602
602
|
return { result: await this.blockReviewFeedback(active, 'safety', 'review-feedback-activation-authority-revoked') };
|
|
603
603
|
}
|
|
@@ -616,7 +616,7 @@ export class RunIssue {
|
|
|
616
616
|
}
|
|
617
617
|
const observed = await this.readIssue(active.record.issueNumber);
|
|
618
618
|
if (!observed || !sameStrings(observed.labels, runningLabels)
|
|
619
|
-
|| !this.hasTrustedClaim(observed, active.record
|
|
619
|
+
|| !this.hasTrustedClaim(observed, active.record)) {
|
|
620
620
|
return { result: await this.blockReviewFeedback(active, 'safety', 'review-feedback-activation-labels-diverged') };
|
|
621
621
|
}
|
|
622
622
|
}
|
|
@@ -779,15 +779,22 @@ export class RunIssue {
|
|
|
779
779
|
if ('result' in claim)
|
|
780
780
|
return claim.result;
|
|
781
781
|
active = claim.active;
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
782
|
+
let worktree;
|
|
783
|
+
try {
|
|
784
|
+
worktree = await this.dependencies.git.inspectWorktree({ worktreePath, branchName, baseSha });
|
|
785
|
+
}
|
|
786
|
+
catch (error) {
|
|
787
|
+
return await this.invokedFailure(active, 'local-git-worktree-inspection-failed', claimedWorktreeInspectionFailureSummary(error));
|
|
788
|
+
}
|
|
789
|
+
if (worktree === 'diverged') {
|
|
790
|
+
return await this.invokedFailure(active, 'local-git-worktree-diverged', 'The claimed worktree path is present but does not match the expected branch and base. Correct or preserve the local artifact, then retry the same run.');
|
|
791
|
+
}
|
|
785
792
|
if (worktree === 'absent') {
|
|
786
793
|
try {
|
|
787
794
|
await this.dependencies.git.createWorktree({ targetRoot, worktreePath, branchName, baseBranch: config.github.baseBranch, baseSha });
|
|
788
795
|
}
|
|
789
|
-
catch {
|
|
790
|
-
return await this.
|
|
796
|
+
catch (error) {
|
|
797
|
+
return await this.invokedFailure(active, 'local-git-worktree-creation-failed', worktreeCreationFailureSummary(error));
|
|
791
798
|
}
|
|
792
799
|
}
|
|
793
800
|
active = await this.initializeClaimedRun(active, issue);
|
|
@@ -815,7 +822,7 @@ export class RunIssue {
|
|
|
815
822
|
frozenCriteria = structuredClone(active.record.frozenCriteria);
|
|
816
823
|
}
|
|
817
824
|
if (existing.lifecycle === 'spec-authoring') {
|
|
818
|
-
if (!await this.authorized(
|
|
825
|
+
if (!await this.authorized(active, config))
|
|
819
826
|
return await this.revoked(active);
|
|
820
827
|
return await this.continueSpecRequired(active);
|
|
821
828
|
}
|
|
@@ -853,7 +860,7 @@ export class RunIssue {
|
|
|
853
860
|
if (!['triaging', 'routed', 'implementing', 'reworking', 'checking', 'proving'].includes(active.record.lifecycle)) {
|
|
854
861
|
return await this.terminal(active, { status: 'internal-error', code: 'resume-phase-not-reconciled' });
|
|
855
862
|
}
|
|
856
|
-
if (!await this.authorized(
|
|
863
|
+
if (!await this.authorized(active, config))
|
|
857
864
|
return await this.revoked(active);
|
|
858
865
|
if (active.record.lifecycle !== 'triaging' && active.record.lifecycle !== 'routed') {
|
|
859
866
|
if (!active.record.routeExecution || !active.record.routeReceipt)
|
|
@@ -902,8 +909,8 @@ export class RunIssue {
|
|
|
902
909
|
try {
|
|
903
910
|
await this.dependencies.git.createWorktree({ targetRoot, worktreePath, branchName, baseBranch: config.github.baseBranch, baseSha });
|
|
904
911
|
}
|
|
905
|
-
catch {
|
|
906
|
-
return await this.
|
|
912
|
+
catch (error) {
|
|
913
|
+
return await this.invokedFailure(active, 'local-git-worktree-creation-failed', worktreeCreationFailureSummary(error));
|
|
907
914
|
}
|
|
908
915
|
active = await this.initializeClaimedRun(active, issue);
|
|
909
916
|
issueSnapshot = structuredClone(active.record.issueSnapshot);
|
|
@@ -921,7 +928,7 @@ export class RunIssue {
|
|
|
921
928
|
return await this.terminal(active, { status: 'internal-error', code: 'direct-review-state-missing' });
|
|
922
929
|
}
|
|
923
930
|
attemptLoop: while (true) {
|
|
924
|
-
if (!await this.authorized(
|
|
931
|
+
if (!await this.authorized(active, config)) {
|
|
925
932
|
return await this.terminal(active, { status: 'blocked', kind: 'safety', resumable: true });
|
|
926
933
|
}
|
|
927
934
|
if (this.signal.aborted)
|
|
@@ -1443,12 +1450,31 @@ export class RunIssue {
|
|
|
1443
1450
|
catch {
|
|
1444
1451
|
throw new RouteInitializationUnrecoverableError();
|
|
1445
1452
|
}
|
|
1446
|
-
const issueSnapshot = issue?.state === 'OPEN'
|
|
1453
|
+
const issueSnapshot = issue?.state === 'OPEN'
|
|
1454
|
+
? refreshClaimedIssueSnapshot(active.record.issueSnapshot, issue)
|
|
1455
|
+
: active.record.issueSnapshot;
|
|
1447
1456
|
return this.persist(active, { lifecycle: 'triaging', routeExecution: initialRouteExecution(), issueSnapshot });
|
|
1448
1457
|
}
|
|
1449
1458
|
async routeRun(starting, issue, frozenCriteria, worktreePath, config, issueNumber, branchName) {
|
|
1450
1459
|
let active = starting;
|
|
1451
1460
|
while (active.record.lifecycle === 'triaging') {
|
|
1461
|
+
let currentIssue;
|
|
1462
|
+
try {
|
|
1463
|
+
currentIssue = await this.readIssue(active.record.issueNumber);
|
|
1464
|
+
}
|
|
1465
|
+
catch (error) {
|
|
1466
|
+
if (error instanceof TransportReadError) {
|
|
1467
|
+
return { result: await this.invokedFailure(active, 'authorization-read-failed') };
|
|
1468
|
+
}
|
|
1469
|
+
throw error;
|
|
1470
|
+
}
|
|
1471
|
+
if (!currentIssue || !this.isAuthorizedIssue(currentIssue, active.record, config)) {
|
|
1472
|
+
return { result: await this.revoked(active) };
|
|
1473
|
+
}
|
|
1474
|
+
active = await this.persist(active, {
|
|
1475
|
+
issueSnapshot: refreshClaimedIssueSnapshot(active.record.issueSnapshot, currentIssue),
|
|
1476
|
+
});
|
|
1477
|
+
issue = structuredClone(active.record.issueSnapshot);
|
|
1452
1478
|
const state = {
|
|
1453
1479
|
read: async () => requireRouteExecution(active.record.routeExecution),
|
|
1454
1480
|
compareAndSwap: async (expected, next) => {
|
|
@@ -1550,7 +1576,7 @@ export class RunIssue {
|
|
|
1550
1576
|
return { result: await this.terminal(active, { status: 'internal-error', code: 'route-state-not-dispatchable' }) };
|
|
1551
1577
|
}
|
|
1552
1578
|
try {
|
|
1553
|
-
if (!await this.authorized(
|
|
1579
|
+
if (!await this.authorized(active, config)) {
|
|
1554
1580
|
return { result: await this.revoked(active) };
|
|
1555
1581
|
}
|
|
1556
1582
|
}
|
|
@@ -1575,7 +1601,7 @@ export class RunIssue {
|
|
|
1575
1601
|
if (receipt.route !== 'awaiting-user')
|
|
1576
1602
|
active = await this.persist(active, { lifecycle });
|
|
1577
1603
|
try {
|
|
1578
|
-
if (!await this.authorized(
|
|
1604
|
+
if (!await this.authorized(active, config)) {
|
|
1579
1605
|
return { result: await this.revoked(active) };
|
|
1580
1606
|
}
|
|
1581
1607
|
}
|
|
@@ -1808,33 +1834,47 @@ export class RunIssue {
|
|
|
1808
1834
|
throw new PostEffectStateError(active);
|
|
1809
1835
|
}
|
|
1810
1836
|
}
|
|
1811
|
-
async authorized(
|
|
1812
|
-
const issue = await this.readIssue(issueNumber);
|
|
1813
|
-
|
|
1837
|
+
async authorized(active, config) {
|
|
1838
|
+
const issue = await this.readIssue(active.record.issueNumber);
|
|
1839
|
+
return !!issue && this.isAuthorizedIssue(issue, active.record, config);
|
|
1840
|
+
}
|
|
1841
|
+
isAuthorizedIssue(issue, record, config) {
|
|
1842
|
+
if (issue.state !== 'OPEN')
|
|
1814
1843
|
return false;
|
|
1815
1844
|
const labels = new Set(issue.labels);
|
|
1816
1845
|
if (!labels.has(config.github.labels.auto.name) || !labels.has(config.github.labels.running.name))
|
|
1817
1846
|
return false;
|
|
1818
|
-
return this.hasTrustedClaim(issue,
|
|
1847
|
+
return this.hasTrustedClaim(issue, record);
|
|
1819
1848
|
}
|
|
1820
|
-
async authorizedForExactLabels(
|
|
1821
|
-
const issue = await this.readIssue(issueNumber);
|
|
1849
|
+
async authorizedForExactLabels(active, expectedLabels) {
|
|
1850
|
+
const issue = await this.readIssue(active.record.issueNumber);
|
|
1822
1851
|
return !!issue && issue.state === 'OPEN' && sameStrings(issue.labels, expectedLabels)
|
|
1823
|
-
&& this.hasTrustedClaim(issue,
|
|
1852
|
+
&& this.hasTrustedClaim(issue, active.record);
|
|
1824
1853
|
}
|
|
1825
|
-
hasTrustedClaim(issue,
|
|
1826
|
-
const exactBody = claimComment(runId, issueNumber, branchName);
|
|
1827
|
-
const
|
|
1828
|
-
const markers = issue.comments.filter((comment) => markerPattern.test(comment.body.split('\n')[0] ?? ''));
|
|
1854
|
+
hasTrustedClaim(issue, record) {
|
|
1855
|
+
const exactBody = claimComment(record.runId, record.issueNumber, record.branchName);
|
|
1856
|
+
const markers = issue.comments.filter(isClaimMarkerComment);
|
|
1829
1857
|
if (issue.comments.some((comment) => {
|
|
1830
1858
|
const firstLine = comment.body.split('\n')[0] ?? '';
|
|
1831
|
-
return firstLine.startsWith(`<!-- codex-orchestrator:run:${runId}:claim`) && !
|
|
1859
|
+
return firstLine.startsWith(`<!-- codex-orchestrator:run:${record.runId}:claim`) && !claimMarkerPattern.test(firstLine);
|
|
1832
1860
|
}))
|
|
1833
1861
|
return false;
|
|
1834
|
-
|
|
1862
|
+
const currentMarkers = markers.filter((comment) => claimRunId(comment) === record.runId);
|
|
1863
|
+
if (currentMarkers.some((comment) => comment.body !== exactBody
|
|
1864
|
+
|| !['OWNER', 'MEMBER', 'COLLABORATOR'].includes(comment.authorAssociation)))
|
|
1835
1865
|
return false;
|
|
1836
|
-
const
|
|
1837
|
-
|
|
1866
|
+
const historicalClaims = historicalClaimCounts(record);
|
|
1867
|
+
for (const comment of markers) {
|
|
1868
|
+
const markerRunId = claimRunId(comment);
|
|
1869
|
+
if (markerRunId === record.runId)
|
|
1870
|
+
continue;
|
|
1871
|
+
const key = observedHistoricalClaimKeys(comment, record)
|
|
1872
|
+
.find((candidate) => (historicalClaims.get(candidate) ?? 0) > 0);
|
|
1873
|
+
if (!key)
|
|
1874
|
+
return false;
|
|
1875
|
+
historicalClaims.set(key, historicalClaims.get(key) - 1);
|
|
1876
|
+
}
|
|
1877
|
+
return currentMarkers.length === 1;
|
|
1838
1878
|
}
|
|
1839
1879
|
async readIssue(issueNumber) {
|
|
1840
1880
|
try {
|
|
@@ -1848,7 +1888,7 @@ export class RunIssue {
|
|
|
1848
1888
|
const batch = active.record.reviewFeedback?.activeBatch;
|
|
1849
1889
|
if (!batch)
|
|
1850
1890
|
return undefined;
|
|
1851
|
-
if (!await this.authorized(
|
|
1891
|
+
if (!await this.authorized(active, config)) {
|
|
1852
1892
|
return this.blockReviewFeedback(active, 'safety', 'review-feedback-worker-authority-revoked');
|
|
1853
1893
|
}
|
|
1854
1894
|
if (!this.dependencies.reviewFeedback) {
|
|
@@ -2204,8 +2244,8 @@ export class RunIssue {
|
|
|
2204
2244
|
async revoked(active) {
|
|
2205
2245
|
return this.terminal(await this.clearIntent(active), { status: 'blocked', kind: 'safety', resumable: true });
|
|
2206
2246
|
}
|
|
2207
|
-
async invokedFailure(active, code) {
|
|
2208
|
-
const evidence = await this.dependencies.writeEvidence({ runId: active.record.runId, code, summary
|
|
2247
|
+
async invokedFailure(active, code, summary = 'Publication delivery requires reconciliation.') {
|
|
2248
|
+
const evidence = await this.dependencies.writeEvidence({ runId: active.record.runId, code, summary });
|
|
2209
2249
|
return { status: 'transport-failed', resumable: true, evidencePath: evidence.path };
|
|
2210
2250
|
}
|
|
2211
2251
|
async terminal(active, outcome, evidenceCode = outcome.status, retainIntent = false) {
|
|
@@ -2285,6 +2325,63 @@ class PostEffectStateError extends Error {
|
|
|
2285
2325
|
this.active = active;
|
|
2286
2326
|
}
|
|
2287
2327
|
}
|
|
2328
|
+
const claimMarkerPattern = /^<!-- codex-orchestrator:run:([0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}):claim -->$/u;
|
|
2329
|
+
function isClaimMarkerComment(comment) {
|
|
2330
|
+
return claimMarkerPattern.test(comment.body.split('\n')[0] ?? '');
|
|
2331
|
+
}
|
|
2332
|
+
function claimRunId(comment) {
|
|
2333
|
+
return (comment.body.split('\n')[0] ?? '').match(claimMarkerPattern)?.[1];
|
|
2334
|
+
}
|
|
2335
|
+
function trustedHistoricalClaimBodyKey(comment, issueNumber, branchName) {
|
|
2336
|
+
const runId = claimRunId(comment);
|
|
2337
|
+
if (!runId || !['OWNER', 'MEMBER', 'COLLABORATOR'].includes(comment.authorAssociation))
|
|
2338
|
+
return undefined;
|
|
2339
|
+
if (comment.body !== claimComment(runId, issueNumber, branchName))
|
|
2340
|
+
return undefined;
|
|
2341
|
+
return `${comment.authorAssociation}\u0000${comment.body}`;
|
|
2342
|
+
}
|
|
2343
|
+
function frozenHistoricalClaimKey(comment, issueNumber, branchName) {
|
|
2344
|
+
const bodyKey = trustedHistoricalClaimBodyKey(comment, issueNumber, branchName);
|
|
2345
|
+
if (!bodyKey)
|
|
2346
|
+
return undefined;
|
|
2347
|
+
return comment.id ? `id\u0000${comment.id}\u0000${bodyKey}` : `legacy\u0000${bodyKey}`;
|
|
2348
|
+
}
|
|
2349
|
+
function observedHistoricalClaimKeys(comment, record) {
|
|
2350
|
+
const bodyKey = trustedHistoricalClaimBodyKey(comment, record.issueNumber, record.branchName);
|
|
2351
|
+
if (!bodyKey)
|
|
2352
|
+
return [];
|
|
2353
|
+
const keys = comment.id ? [`id\u0000${comment.id}\u0000${bodyKey}`] : [];
|
|
2354
|
+
if (timestampAtOrBefore(comment.createdAt, record.createdAt)
|
|
2355
|
+
&& timestampAtOrBefore(comment.updatedAt, record.createdAt)) {
|
|
2356
|
+
keys.push(`legacy\u0000${bodyKey}`);
|
|
2357
|
+
}
|
|
2358
|
+
return keys;
|
|
2359
|
+
}
|
|
2360
|
+
function timestampAtOrBefore(value, cutoff) {
|
|
2361
|
+
if (!value)
|
|
2362
|
+
return false;
|
|
2363
|
+
const timestamp = Date.parse(value);
|
|
2364
|
+
return !Number.isNaN(timestamp) && timestamp <= Date.parse(cutoff);
|
|
2365
|
+
}
|
|
2366
|
+
function historicalClaimCounts(record) {
|
|
2367
|
+
const counts = new Map();
|
|
2368
|
+
for (const comment of record.issueSnapshot.comments ?? []) {
|
|
2369
|
+
const key = frozenHistoricalClaimKey(comment, record.issueNumber, record.branchName);
|
|
2370
|
+
if (!key || claimRunId(comment) === record.runId)
|
|
2371
|
+
continue;
|
|
2372
|
+
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
2373
|
+
}
|
|
2374
|
+
return counts;
|
|
2375
|
+
}
|
|
2376
|
+
function refreshClaimedIssueSnapshot(baseline, issue) {
|
|
2377
|
+
return {
|
|
2378
|
+
...structuredClone(baseline),
|
|
2379
|
+
comments: [
|
|
2380
|
+
...(baseline.comments ?? []).filter(isClaimMarkerComment),
|
|
2381
|
+
...issue.comments.filter((comment) => !isClaimMarkerComment(comment)),
|
|
2382
|
+
],
|
|
2383
|
+
};
|
|
2384
|
+
}
|
|
2288
2385
|
function snapshotIssue(issue) {
|
|
2289
2386
|
if (issue.state !== 'OPEN')
|
|
2290
2387
|
throw new Error('cannot snapshot a closed issue');
|
|
@@ -2422,6 +2519,21 @@ function commentsWithMarker(issue, marker) {
|
|
|
2422
2519
|
function sortedUnique(values) {
|
|
2423
2520
|
return [...new Set(values)].sort();
|
|
2424
2521
|
}
|
|
2522
|
+
function worktreeCreationFailureSummary(error) {
|
|
2523
|
+
return boundedLocalGitFailureSummary('Local Git worktree creation failed; correct the local Git state and retry the same run.', error);
|
|
2524
|
+
}
|
|
2525
|
+
function claimedWorktreeInspectionFailureSummary(error) {
|
|
2526
|
+
return boundedLocalGitFailureSummary('The claimed worktree could not be inspected; correct or preserve the local artifact and retry the same run.', error);
|
|
2527
|
+
}
|
|
2528
|
+
function boundedLocalGitFailureSummary(prefix, error) {
|
|
2529
|
+
const raw = error instanceof Error ? error.message : String(error);
|
|
2530
|
+
const diagnostic = raw
|
|
2531
|
+
.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/gu, ' ')
|
|
2532
|
+
.trim();
|
|
2533
|
+
if (diagnostic.length === 0)
|
|
2534
|
+
return prefix;
|
|
2535
|
+
return `${prefix}\n${diagnostic.slice(0, 4 * 1024 - prefix.length - 1)}`;
|
|
2536
|
+
}
|
|
2425
2537
|
function assertUuid(value) {
|
|
2426
2538
|
if (typeof value !== 'string' || !/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u.test(value))
|
|
2427
2539
|
throw new Error('runId is invalid');
|