@thebattlearena/corner 0.10.1-beta.84 → 0.10.1-beta.85
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thebattlearena/corner",
|
|
3
|
-
"version": "0.10.1-beta.
|
|
3
|
+
"version": "0.10.1-beta.85",
|
|
4
4
|
"description": "Corner: tu esquina de Battle Arena en Claude Code (el plugin `battlearena`). Hooks de tracking, objetivo del turno, plan de batalla /battlearena:conquer, Ringside (el panel de tu día), el ring y la radio. Zero-dependency.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -872,11 +872,13 @@ export function readyForTitleDefense(pr, view) {
|
|
|
872
872
|
reason: `could not tell whether PR #${pr} is a draft (${error.message}); title-defense does not start`,
|
|
873
873
|
};
|
|
874
874
|
}
|
|
875
|
-
if (answer?.isDraft === false)
|
|
875
|
+
if (answer?.isDraft === false)
|
|
876
|
+
return { ok: true, reason: null, nodeId: answer.id ?? null };
|
|
876
877
|
if (answer?.isDraft === true)
|
|
877
878
|
return {
|
|
878
879
|
ok: false,
|
|
879
880
|
draft: true,
|
|
881
|
+
nodeId: answer.id ?? null,
|
|
880
882
|
reason: `PR #${pr} is still a draft; title-defense starts when you mark it ready for review`,
|
|
881
883
|
};
|
|
882
884
|
return { ok: false, reason: `gh did not say whether PR #${pr} is a draft` };
|
|
@@ -1005,7 +1007,7 @@ function titleDefense([pr, ...argv]) {
|
|
|
1005
1007
|
const gate = clear
|
|
1006
1008
|
? { ok: true }
|
|
1007
1009
|
: readyForTitleDefense(pr, () =>
|
|
1008
|
-
execFileSync('gh', ['pr', 'view', String(pr), '--json', 'isDraft'], {
|
|
1010
|
+
execFileSync('gh', ['pr', 'view', String(pr), '--json', 'isDraft,id'], {
|
|
1009
1011
|
cwd: state.worktree,
|
|
1010
1012
|
encoding: 'utf8',
|
|
1011
1013
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
@@ -1030,7 +1032,18 @@ function titleDefense([pr, ...argv]) {
|
|
|
1030
1032
|
throw new Error(
|
|
1031
1033
|
gate.draft ? `${gate.reason} (--keep-draft was passed)` : gate.reason,
|
|
1032
1034
|
);
|
|
1033
|
-
const
|
|
1035
|
+
const flagged = withTitleDefense(
|
|
1036
|
+
state,
|
|
1037
|
+
clear ? null : Number(argv[index + 1]),
|
|
1038
|
+
);
|
|
1039
|
+
// The PR's GraphQL node id: mask-guard ties a requestReviewsByLogin (`-F id=<node>`) to this bout.
|
|
1040
|
+
const next =
|
|
1041
|
+
clear || !gate.nodeId
|
|
1042
|
+
? flagged
|
|
1043
|
+
: {
|
|
1044
|
+
...flagged,
|
|
1045
|
+
titleDefense: { ...flagged.titleDefense, nodeId: gate.nodeId },
|
|
1046
|
+
};
|
|
1034
1047
|
writeState(dir, next);
|
|
1035
1048
|
console.log(
|
|
1036
1049
|
clear
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
import { boutArchGate } from './arch-gate.mjs';
|
|
14
14
|
import { boutFormatGate } from './format-gate.mjs';
|
|
15
15
|
import { boutLeases, commitLeaseRefusal, wrestlerBlocks } from './lanes.mjs';
|
|
16
|
+
import { reviewInputs, roundsAwaitingHuman } from './review-blocks.mjs';
|
|
16
17
|
import { execFileSync } from 'node:child_process';
|
|
17
18
|
import { existsSync, readFileSync, realpathSync } from 'node:fs';
|
|
18
19
|
import { homedir } from 'node:os';
|
|
@@ -955,6 +956,19 @@ const requestsCopilotByLogin = (gh) =>
|
|
|
955
956
|
/\brequestReviewsByLogin\b/.test(graphqlText(gh)) &&
|
|
956
957
|
/copilot-pull-request-reviewer/.test(graphqlText(gh));
|
|
957
958
|
|
|
959
|
+
/**
|
|
960
|
+
* The PR node id a GraphQL mutation targets: `-F id=<node>` / `-f id=…` or a `pullRequestId:
|
|
961
|
+
* "<node>"` inside the query. PURE.
|
|
962
|
+
*/
|
|
963
|
+
export function graphqlNodeId(gh) {
|
|
964
|
+
const text = graphqlText(gh);
|
|
965
|
+
return (
|
|
966
|
+
/(?:^|\s)-[fF]\s+(?:id|pullRequestId)=(\S+)/.exec(text)?.[1] ??
|
|
967
|
+
/pullRequestId:\s*"([^"]+)"/.exec(text)?.[1] ??
|
|
968
|
+
null
|
|
969
|
+
);
|
|
970
|
+
}
|
|
971
|
+
|
|
958
972
|
/** `node <…>/judges-card.mjs close <pr>`: the PR it closes, else null. */
|
|
959
973
|
function closesBout(words) {
|
|
960
974
|
const closing =
|
|
@@ -980,9 +994,13 @@ function chronicleBehind(ctx, where, pr) {
|
|
|
980
994
|
}
|
|
981
995
|
const [first] = stale;
|
|
982
996
|
if (!first) return null;
|
|
997
|
+
const unscoped =
|
|
998
|
+
pr === null
|
|
999
|
+
? ` This command could not be tied to one bout, so every active bout of the repo was checked: #${first.pr} is the one behind, not necessarily the PR this command is for.`
|
|
1000
|
+
: '';
|
|
983
1001
|
return {
|
|
984
1002
|
block: true,
|
|
985
|
-
reason: `The play-by-play is behind the bout (records changed after ${first.publishedAt ?? 'a publish that was never stamped'}). Run /ring:play-by-play ${first.pr} and publish to the same link first
|
|
1003
|
+
reason: `The play-by-play is behind the bout (records changed after ${first.publishedAt ?? 'a publish that was never stamped'}). Run /ring:play-by-play ${first.pr} and publish to the same link first.${unscoped}`,
|
|
986
1004
|
};
|
|
987
1005
|
}
|
|
988
1006
|
|
|
@@ -1376,8 +1394,19 @@ function judgeSegment(segment, words, cwd, ctx) {
|
|
|
1376
1394
|
const bell = gh && requestsCopilotByLogin(gh);
|
|
1377
1395
|
const where = { git, gh, dir: cwd };
|
|
1378
1396
|
if ((bell || closing !== null) && ctx.anyBout() && ctx.boutActive(where)) {
|
|
1379
|
-
|
|
1397
|
+
// A GraphQL re-request names its PR by node id: title-defense stored it when it set the flag.
|
|
1398
|
+
const bellPr = bell
|
|
1399
|
+
? (prNumberOf(gh) ?? ctx.prOfNode?.(where, graphqlNodeId(gh)) ?? null)
|
|
1400
|
+
: null;
|
|
1401
|
+
const behind = chronicleBehind(ctx, where, bell ? bellPr : closing);
|
|
1380
1402
|
if (behind) return behind;
|
|
1403
|
+
// Copilot gets no new round until the human validated the last one (its plan block).
|
|
1404
|
+
const unreviewed = bell ? ctx.roundsAwaitingHuman?.(where, bellPr) : [];
|
|
1405
|
+
if (unreviewed?.length)
|
|
1406
|
+
return {
|
|
1407
|
+
block: true,
|
|
1408
|
+
reason: `The human has not validated ${unreviewed.join(', ')} yet: Copilot gets the next round only after the human signs that round in PR Review Lab (a pass). Wait for the verdict (the lookout wakes you), apply it with judges-card.mjs summary <pr>, then re-request.`,
|
|
1409
|
+
};
|
|
1381
1410
|
}
|
|
1382
1411
|
const ambiguous =
|
|
1383
1412
|
git?.sub === 'commit' &&
|
|
@@ -1721,6 +1750,26 @@ function realContext(cwd, agentId = null) {
|
|
|
1721
1750
|
.filter((bout) => bout.stale)
|
|
1722
1751
|
.map(({ pr: stalePr, publishedAt }) => ({ pr: stalePr, publishedAt }));
|
|
1723
1752
|
},
|
|
1753
|
+
// The Copilot round blocks the human has not validated, of the bout of `pr` (every bout when null).
|
|
1754
|
+
roundsAwaitingHuman: (where, pr) => {
|
|
1755
|
+
const slug = slugWhere(where);
|
|
1756
|
+
if (!slug) return [];
|
|
1757
|
+
return boutsOf(slug)
|
|
1758
|
+
.filter(({ state }) => pr === null || state.pr === pr)
|
|
1759
|
+
.flatMap(({ dir }) => {
|
|
1760
|
+
const { plan, log, verdicts } = reviewInputs(dir);
|
|
1761
|
+
return roundsAwaitingHuman(plan, log, verdicts);
|
|
1762
|
+
});
|
|
1763
|
+
},
|
|
1764
|
+
// The bout whose title-defense flag carries this PR node id, or null.
|
|
1765
|
+
prOfNode: (where, nodeId) => {
|
|
1766
|
+
const slug = slugWhere(where);
|
|
1767
|
+
if (!slug || !nodeId) return null;
|
|
1768
|
+
return (
|
|
1769
|
+
boutsOf(slug).find(({ state }) => state.titleDefense?.nodeId === nodeId)
|
|
1770
|
+
?.state.pr ?? null
|
|
1771
|
+
);
|
|
1772
|
+
},
|
|
1724
1773
|
titleDefenseFlags: (where) => {
|
|
1725
1774
|
const slug = slugWhere(where);
|
|
1726
1775
|
if (!slug) return [];
|
|
@@ -287,6 +287,19 @@ export function lookaheadRefusal({ globs, waitingFiles, running }) {
|
|
|
287
287
|
return null;
|
|
288
288
|
}
|
|
289
289
|
|
|
290
|
+
/**
|
|
291
|
+
* The Copilot rounds of title-defense the human has not validated yet: a block «Copilot R<k>…»
|
|
292
|
+
* still «en curso», or waiting on the judge with no later verdict. Copilot is not asked for another
|
|
293
|
+
* round until they are empty. PURE.
|
|
294
|
+
*/
|
|
295
|
+
export function roundsAwaitingHuman(plan, log, verdicts) {
|
|
296
|
+
const isRound = (item) => /^Copilot R\d+/i.test(item.title);
|
|
297
|
+
const waiting = blocksAwaitingReview(plan, log, verdicts).filter(isRound);
|
|
298
|
+
return [...runningBlocks(plan).filter(isRound), ...waiting].map(
|
|
299
|
+
(item) => `B${item.n} «${item.title}»`,
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
|
|
290
303
|
/** The blocks «en curso» right now. PURE. */
|
|
291
304
|
export const runningBlocks = (plan) =>
|
|
292
305
|
parsePlan(plan)
|
|
@@ -94,7 +94,15 @@ list, judges card, `decisions.md` and `rebounds.md`, and the same `mask-guard` r
|
|
|
94
94
|
result is a new rebound; a fix is not done until the reproduction says so (PR26). If it cannot
|
|
95
95
|
run (environment down, scenario missing), the thread stays open and is listed for the human in
|
|
96
96
|
the judges card: never resolve it on unit tests alone.
|
|
97
|
-
6. **A round-bell for the fixes** (§2.6), one per concept.
|
|
97
|
+
6. **A round-bell for the fixes** (§2.6), one per concept. **The round is a block of the plan**,
|
|
98
|
+
so the human reviews it in PR Review Lab like any other: before the wrestler starts, `judges-card.mjs
|
|
99
|
+
plan <pr> add "Defensa del título" "Copilot R<k>: <one line of what it fixes>"`, then `plan <pr>
|
|
100
|
+
<n> "en curso" --minutes <m> --priority 1` (with `--files` when blocks run side by side), and
|
|
101
|
+
`plan <pr> <n> "espera al juez"` once its range is approved. The human's verdict applies as for
|
|
102
|
+
any block: a fail comes back as the rebound of the next round. A round with nothing to fix adds
|
|
103
|
+
no block. **Copilot gets the next round only after the human passed this one**: mask-guard
|
|
104
|
+
refuses the re-request while a «Copilot R<k>» block is running or waits on the judge; the lookout
|
|
105
|
+
wakes you when the verdict lands.
|
|
98
106
|
7. **CI red**: diagnose before touching code: `gh run view <id> --log-failed`. Tell a PR failure
|
|
99
107
|
from a gate failure (coverage of an untested file; a size ceiling, which goes to the human).
|
|
100
108
|
Never "fix" a gate by weakening it. A PR failure goes through steps 3 to 6 like a finding.
|