Skip to content

Commit 14ff4af

Browse files
refactor(utils): extract and centralize trigger detection and context extraction functions
1 parent 4a137a4 commit 14ff4af

File tree

2 files changed

+16
-122
lines changed

2 files changed

+16
-122
lines changed

codex-cli/src/hooks/use-watch-mode.ts

Lines changed: 4 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -3,78 +3,16 @@ import type { ResponseItem } from "openai/resources/responses/responses";
33

44
import { createInputItem } from "../utils/input-utils";
55
import { isLoggingEnabled, log } from "../utils/logger/log";
6+
import {
7+
findAllTriggers,
8+
extractContextAroundTrigger
9+
} from "../utils/watch-mode-utils";
610
import chokidar from "chokidar";
711
import fs from "fs";
812
import ignore from "ignore";
913
import path from "path";
1014
import { useEffect } from "react";
1115

12-
/**
13-
* Pattern to match various "AI!" style trigger comments with possible instructions
14-
* This will match patterns like:
15-
* - "// what does this function do, AI?"
16-
* - "// change this variable name to something more precise, AI!"
17-
* - "# fix this code, AI!"
18-
*/
19-
const TRIGGER_PATTERN =
20-
/\/\/\s*(.*),?\s*AI[!?]|#\s*(.*),?\s*AI[!?]|\/\*\s*(.*),?\s*AI[!?]\s*\*\//;
21-
22-
/**
23-
* Function to find all AI trigger matches in a file content
24-
*/
25-
function findAllTriggers(content: string): Array<RegExpMatchArray> {
26-
const matches: Array<RegExpMatchArray> = [];
27-
const regex = new RegExp(TRIGGER_PATTERN, "g");
28-
29-
let match;
30-
while ((match = regex.exec(content)) != null) {
31-
matches.push(match);
32-
}
33-
34-
return matches;
35-
}
36-
37-
/**
38-
* Function to extract context around an AI trigger
39-
*/
40-
function extractContextAroundTrigger(
41-
content: string,
42-
triggerMatch: RegExpMatchArray,
43-
): { context: string; instruction: string } {
44-
// Default context size (number of lines before and after the trigger)
45-
const contextSize = 15;
46-
47-
// Get the lines of the file
48-
const lines = content.split("\n");
49-
50-
// Find the line number of the trigger
51-
const triggerPos =
52-
content.substring(0, triggerMatch.index).split("\n").length - 1;
53-
54-
// Calculate start and end lines for context
55-
const startLine = Math.max(0, triggerPos - contextSize);
56-
const endLine = Math.min(lines.length - 1, triggerPos + contextSize);
57-
58-
// Extract the context lines
59-
const contextLines = lines.slice(startLine, endLine + 1);
60-
61-
// Join the context lines back together
62-
const context = contextLines.join("\n");
63-
64-
// Extract the instruction from the capture groups
65-
// The regex has 3 capture groups for different comment styles:
66-
// Group 1: // instruction AI!
67-
// Group 2: # instruction AI!
68-
// Group 3: /* instruction AI! */
69-
const instruction =
70-
triggerMatch[1] ||
71-
triggerMatch[2] ||
72-
triggerMatch[3] ||
73-
"fix or improve this code";
74-
75-
return { context, instruction };
76-
}
77-
7816
/**
7917
* A custom hook that sets up file watching for AI trigger comments
8018
*/

codex-cli/tests/watch-mode-trigger-extraction.test.ts

Lines changed: 12 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -2,60 +2,13 @@
22
// used in the useWatchMode hook
33

44
import { describe, it, expect } from "vitest";
5+
import {
6+
findAllTriggers,
7+
extractContextAroundTrigger,
8+
} from "../src/utils/watch-mode-utils";
59

6-
// These functions are private in the hook, so we need to recreate them for testing
7-
const TRIGGER_PATTERN =
8-
/\/\/\s*(.*),?\s*AI[!?]|#\s*(.*),?\s*AI[!?]|\/\*\s*(.*),?\s*AI[!?]\s*\*\//;
9-
10-
function findAllTriggers(content: string): Array<RegExpMatchArray> {
11-
const matches: Array<RegExpMatchArray> = [];
12-
const regex = new RegExp(TRIGGER_PATTERN, "g");
13-
14-
let match;
15-
while ((match = regex.exec(content)) != null) {
16-
matches.push(match);
17-
}
18-
19-
return matches;
20-
}
21-
22-
function extractContextAroundTrigger(
23-
content: string,
24-
triggerMatch: RegExpMatchArray,
25-
): { context: string; instruction: string } {
26-
// Default context size (number of lines before and after the trigger)
27-
const contextSize = 20;
28-
29-
// Get the lines of the file
30-
const lines = content.split("\n");
31-
32-
// Find the line number of the trigger
33-
const triggerPos =
34-
content.substring(0, triggerMatch.index).split("\n").length - 1;
35-
36-
// Calculate start and end lines for context
37-
const startLine = Math.max(0, triggerPos - contextSize);
38-
const endLine = Math.min(lines.length - 1, triggerPos + contextSize);
39-
40-
// Extract the context lines
41-
const contextLines = lines.slice(startLine, endLine + 1);
42-
43-
// Join the context lines back together
44-
const context = contextLines.join("\n");
45-
46-
// Extract the instruction from the capture groups
47-
// The regex has 3 capture groups for different comment styles:
48-
// Group 1: // instruction AI!
49-
// Group 2: # instruction AI!
50-
// Group 3: /* instruction AI! */
51-
const instruction =
52-
triggerMatch[1] ||
53-
triggerMatch[2] ||
54-
triggerMatch[3] ||
55-
"fix or improve this code";
56-
57-
return { context, instruction };
58-
}
10+
// For testing, we'll use a larger context size
11+
const TEST_CONTEXT_SIZE = 20;
5912

6013
describe("Watch mode trigger pattern matching", () => {
6114
it("should detect double-slash (JS-style) AI triggers", () => {
@@ -213,6 +166,7 @@ export default Counter;`;
213166
const { context, instruction } = extractContextAroundTrigger(
214167
content,
215168
matches[0]!,
169+
TEST_CONTEXT_SIZE,
216170
);
217171

218172
// Should include appropriate context around the trigger (the entire file in this case)
@@ -235,12 +189,13 @@ export default Counter;`;
235189
const { context, instruction } = extractContextAroundTrigger(
236190
content,
237191
matches[0]!,
192+
TEST_CONTEXT_SIZE,
238193
);
239194

240-
// Should only include the default number of lines around the trigger (15 before, 15 after)
195+
// Should only include the default number of lines around the trigger (20 before, 20 after)
241196
const contextLines = context.split("\n");
242197
expect(contextLines.length).toBeLessThan(50); // Less than the full 100 lines
243-
expect(contextLines.length).toBeGreaterThanOrEqual(31); // At least the trigger line + 15 before + 15 after
198+
expect(contextLines.length).toBeGreaterThanOrEqual(41); // At least the trigger line + 20 before + 20 after
244199

245200
// Should include the trigger line
246201
expect(context).toContain("// Optimize this code, AI!");
@@ -260,6 +215,7 @@ function complexFunction() {
260215
const { context, instruction } = extractContextAroundTrigger(
261216
content,
262217
matches[0]!,
218+
TEST_CONTEXT_SIZE,
263219
);
264220

265221
// Should include the entire short file
@@ -280,6 +236,7 @@ function complexFunction() {
280236
const { context, instruction } = extractContextAroundTrigger(
281237
content,
282238
matches[0]!,
239+
TEST_CONTEXT_SIZE,
283240
);
284241

285242
// Should include the entire short file
@@ -289,4 +246,3 @@ function complexFunction() {
289246
expect(instruction).toBe("Explain this code, ");
290247
});
291248
});
292-

0 commit comments

Comments
 (0)