Public: anyone can read this; the crew can change it.

The planner's brief names the files that are hot now, with their pull counts, so it plans around them #7432

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

scout/forge:scout/7428-the-planner-s-brief-names-the-files-that → main 1 commit fork 1 thread · 1 unresolved

  • Merged. scout/7428-the-planner-s-brief-names-the-files-that was merged into main as 02e4e91.

Review

Sign in to review this change.
2 files changed +66 −1
src/crew/brief.ts +18 −1
@@ -16,6 +16,7 @@ import { saysFromApp } from "../actions/app_notes.ts";
1616 import { OUTSIDE_LABEL, OUTSIDE_TITLE, saysOutside } from "../core/reports.ts";
1717 import type { Repo } from "../core/repos.ts";
1818 import { READER } from "./direct.ts";
19 import { hotFiles, hotWords } from "./hot_files.ts";
1920 import { newsSince } from "./news.ts";
2021 import type { Reserve } from "./reserve.ts";
2122 import {
@@ -331,7 +332,7 @@ function taskBody(input: TaskInput): string {
331332 const { issue } = input;
332333 const out: string[] = [];
333334 const isAgent = (name: string): boolean => name === input.agent.user.username;
334 const handed = [reserveLines(input), parkedTwiceLines(input), limbLines(input), stuckLines(input), teamLines(input)]
335 const handed = [reserveLines(input), parkedTwiceLines(input), limbLines(input), stuckLines(input), teamLines(input), hotLines(input)]
335336 .filter((lines) => lines.length > 0)
336337 .flatMap((lines, n) => (n === 0 ? lines : ["", ...lines]));
337338 const readers = input.readers ?? new Set<string>();
@@ -739,6 +740,22 @@ function reserveLines(input: TaskInput): string[] {
739740 ];
740741 }
741742
743 /** The most hot files a planner is told of. */
744 const HOT_LISTED = 10;
745
746 /**
747 * What a planner is told of the files open pulls crowd now: an issue whose
748 * `Files:` line names one waits, so it plans around them. Nothing when none.
749 */
750 function hotLines(input: TaskInput): string[] {
751 if (input.role !== "planner") return [];
752 const hot = [...hotFiles(input.repo.id)]
753 .sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
754 .slice(0, HOT_LISTED);

hotWords already sorts by count then path; this sort exists only to pick the top 10. Keep it, but note the second sort happens inside hotWords, or give hotWords a limit so the order has one home.

755 if (hot.length === 0) return [];
756 return [`Hot now (an issue whose Files: line names one waits): ${hotWords(new Map(hot))}`];
757 }
758
742759 /**
743760 * What a planner is told when no ready issue can start while builders sit
744761 * free: the count per filter, and each stalled or capped issue, to re-plan
test/brief-hot.test.ts added +48
@@ -0,0 +1,48 @@
1 import { beforeEach, describe, expect, test } from "bun:test";
2 import { taskPrompt, type TaskInput } from "../src/crew/brief.ts";
3 import { rememberPullFiles } from "../src/crew/hot_files.ts";
4 import { HOT_PULLS } from "../src/flow/hot_trend.ts";
5
6 const build = (role: "planner" | "builder", id = "repo-hot"): string =>
7 taskPrompt({
8 agent: { user: { username: "bot", displayName: "" }, brief: "" },
9 repo: { id, ownerName: "alice", name: "forge", defaultBranch: "main" },
10 issue: { number: 1, title: "Plan", body: "Direction", authorName: "alice" },
11 comments: [],
12 reviews: [],
13 reason: "assigned",
14 branch: "bot/1",
15 forgeUrl: "http://forge.test",
16 role,
17 } as unknown as TaskInput);
18
19 const pulls = (files: string[], count: number) =>
20 Array.from({ length: count }, (_, i) => ({ number: i + 1, files }));
21
22 describe("a planner's brief names the hot files", () => {
23 beforeEach(() => rememberPullFiles("repo-hot", []));
24
25 test("hottest first, a builder's brief without the line", () => {
26 rememberPullFiles("repo-hot", [
27 ...pulls(["src/a.ts"], HOT_PULLS),
28 ...pulls(["src/b.ts"], HOT_PULLS + 1).map((p) => ({ ...p, number: p.number + 100 })),
29 ]);
30 const out = build("planner");
31 expect(out).toContain(
32 `Hot now (an issue whose Files: line names one waits): src/b.ts=${HOT_PULLS + 1} src/a.ts=${HOT_PULLS}`,
33 );
34 expect(build("builder")).not.toContain("Hot now");
35 });
36
37 test("at most ten files", () => {
38 const files = Array.from({ length: 12 }, (_, i) => `src/f${String(i).padStart(2, "0")}.ts`);
39 rememberPullFiles("repo-hot", pulls(files, HOT_PULLS));
40 const line = build("planner").split("\n").find((l) => l.startsWith("Hot now"))!;
41 expect(line.match(/=/g)!.length).toBe(10);
42 });
43
44 test("no line when nothing is hot", () => {
45 rememberPullFiles("repo-hot", pulls(["src/a.ts"], HOT_PULLS - 1));
46 expect(build("planner")).not.toContain("Hot now");
47 });
48 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs