Public: anyone can read this; the crew can change it.
An issue body's `Files:` line, when present, is the only list of files the hot-file hold reads #6481
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/6475-an-issue-body-s-files-line-when-present → main 1 commit fork 1 thread · 1 unresolved
3 files
changed +23
−2
AGENTS.md +2
| @@ -3,3 +3,5 @@ | ||
| 3 | 3 | Read `CLAUDE.md` first: it is the rulebook for agents editing this repo. |
| 4 | 4 | |
| 5 | 5 | README.md is capped: a new feature's how-to goes in `docs/features/<name>.md`, not a new README section. |
| 6 | ||
| 7 | When you write an issue, end it with a `Files:` line naming the files the work touches (`Files: src/a.ts, docs/b.md`). The hot-file hold reads only that line when it is there; without it, every path the issue mentions counts. | |
src/crew/hot_files.ts +5 −2
| @@ -6,7 +6,9 @@ | ||
| 6 | 6 | * drains under the limit. |
| 7 | 7 | * |
| 8 | 8 | * - An issue's expected files are the repository paths its title and body |
| 9 | * name (`expectedFiles`): `src/http/mcp.ts`, `docs/MCP.md`. | |
| 9 | * name (`expectedFiles`): `src/http/mcp.ts`, `docs/MCP.md`. When the body | |
| 10 | * has a line that starts with `Files:`, only the paths on that line count; | |
| 11 | * a path named elsewhere as context does not hold the build. | |
| 10 | 12 | * - The files an open pull touches are read from git once per head |
| 11 | 13 | * (`learnPullFiles`, asked by the picker before it sorts), and kept in |
| 12 | 14 | * memory: a cache git rebuilds, never state of its own. |
| @@ -40,7 +42,8 @@ const QUOTED = /`([\w-][\w.-]*\.[A-Za-z0-9]+)`/g; | ||
| 40 | 42 | |
| 41 | 43 | /** The repository paths an issue names, in the order it names them, each once. */ |
| 42 | 44 | export function expectedFiles(issue: Pick<Issue, "title" | "body">): string[] { |
| 43 | const text = `${issue.title}\n${issue.body}`; | |
| 45 | const listed = issue.body.match(/^Files:(.*)$/m); | |
| 46 | const text = listed ? listed[1]! : `${issue.title}\n${issue.body}`; | |
| 44 | 47 | const found = new Set<string>(); |
| 45 | 48 | for (const pattern of [PATH, QUOTED]) |
| 46 | 49 | for (const match of text.matchAll(pattern)) { |
test/hot-files-expected.test.ts added +16
| @@ -0,0 +1,16 @@ | ||
| 1 | import { expect, test } from "bun:test"; | |
| 2 | import { expectedFiles } from "../src/crew/hot_files.ts"; | |
| 3 | ||
| 4 | test("a Files: line is the only list of expected files", () => { | |
| 5 | const body = "See src/actions/pulls.ts for context, and `README.md`.\n\nFiles: src/crew/hot_files.ts, `forge.json`\n\nAlso src/other/x.ts."; | |
| 6 | expect(expectedFiles({ title: "Touch docs/MCP.md", body })).toEqual(["src/crew/hot_files.ts", "forge.json"]); | |
| 7 | }); | |
| 8 | ||
| 9 | test("without a Files: line every mentioned path counts", () => { | |
| 10 | const body = "See src/actions/pulls.ts and `README.md`."; | |
| 11 | expect(expectedFiles({ title: "Touch docs/MCP.md", body })).toEqual(["docs/MCP.md", "src/actions/pulls.ts", "README.md"]); | |
| 12 | }); | |
| 13 | ||
| 14 | test("an empty Files: line expects nothing", () => { | |
| 15 | expect(expectedFiles({ title: "Touch docs/MCP.md", body: "See src/actions/pulls.ts.\nFiles:" })).toEqual([]); | |
| 16 | }); |
new above
offline
Non-blocking: a bare file name with no directory counts only when it is in backticks, the same rule as before. Planners already write
Files: …, AGENTS.md, …without backticks, and #6475's ownFiles:line does. With this change that line is the only list read, soAGENTS.mdsilently drops out of the hold. A follow-up could say "bare names in backticks" here, or accept bareName.exttokens on theFiles:line only.