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

A thought that says only a repository's name opens no issue: it waits as a note and the reply asks what should change #7740

Merged opened this

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

Merged

scout/forge:scout/7697-a-thought-that-says-only-a-repository-s → main 2 commits fork 2 threads · 2 unresolved

  • Merged. scout/7697-a-thought-that-says-only-a-repository-s was merged into main as 9eecb4b.

Review

Sign in to review this change.
2 files changed +36 −0
src/crew/triage.ts +11
@@ -199,6 +199,14 @@ export interface Thought {
199199 readonly text: string;
200200 }
201201
202 const SLUG = /^(?:https?:\/\/[^\s/]+\/)?[\w.-]+\/[\w.-]+(?:\.git)?\/?$/;
203
204 /** Nothing to act on: empty, or only a repository slug or a URL to one. */
205 export function isNote(text: string): boolean {
206 const t = text.trim();
207 return t === "" || SLUG.test(t);
208 }
outdated

Remove the split(/\s+/).length < 3 clause and return false here. A two-word ask is real work, and the issue names only a bare repository name. Anything unsure should go to the decider, which already asks a question when it is unsure.

209
202210 /** Place one thought. Always answers: a best placement, and a question when unsure. */
203211 export async function placeThought(
204212 app: App,
@@ -208,6 +216,9 @@ export async function placeThought(
208216 now = new Date(),
209217 ): Promise<Placement> {
210218 const slug = `${repo.ownerName}/${repo.name}`;
219 // A note has no kind of its own: "decision" by "rule" with an empty `placed` stands in for it.
220 if (isNote(thought.text))
221 return finish(app, thought, "decision", "rule", true, [], `Kept as a note: what should change in ${slug}?`, now);

The decision kind with decidedBy rule is a stand-in for a note. Add a short comment saying so, and confirm that src/tty.ts and the feed show the reply for an empty placed.

211222 const set = pillarsOf(app.db, repo);
212223 const direction = ensureDirectionIssue(app, by, repo);
213224 const open = openIssues(app, repo, set, direction);
test/triage.test.ts +25
@@ -98,6 +98,31 @@ describe("triage", () => {
9898 expect(placementOf(app.db, t.id)?.reply).toBe(p.reply);
9999 });
100100
101 test("a thought with nothing to act on opens nothing and is kept as a note", async () => {
102 app.config.decider = { name: "boom", async decide() { throw new Error("asked"); } } as never;
103 const count = () => app.db.query<{ c: number }, []>("SELECT count(*) c FROM issue").get()!.c;
104 const before = count();
105 const slug = await placeThought(app, fresh(), alice, think("trevato/forge"));
106 expect(slug.placed).toEqual([]);
107 expect(slug.reply).toBe("Kept as a note: what should change in alice/forge?");
108 const blank = await placeThought(app, fresh(), alice, { id: "blank-1", text: " " });
109 expect(blank.placed).toEqual([]);
110 const url = await placeThought(app, fresh(), alice, think("https://forge.open-platform.sh/trevato/forge"));
111 expect(url.placed).toEqual([]);
112 expect(count()).toBe(before);
113 });
114
115 test("a short ask reaches the decider, and #N and a plain sentence still place", async () => {
116 app.config.decider = stub({ fix: ["amendment", 0.9, `#${n}`], the: ["work", 0.9, "none", "P7"] });
117 const short = await placeThought(app, fresh(), alice, think("fix login"));
118 expect(short.decidedBy).toBe("stub");
119 expect(short.placed.length).toBe(1);
120 const a = await placeThought(app, fresh(), alice, think("fix #" + n));
121 expect(a.placed[0]!.number).toBe(n);
122 const w = await placeThought(app, fresh(), alice, think("the home page is slow"));
123 expect(w.placed[0]!.action).toBe("opened");
124 });
125
101126 test("an amendment comments on the open issue it concerns", async () => {
102127 app.config.decider = stub({ also: ["amendment", 0.9, `#${n}`] });
103128 const t = think("also show it on the phone");

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs