Skip to content

Commit fc37b78

Browse files
committed
feat: refactor command loading and enhance command definitions with frontmatter parsing
1 parent ced8a14 commit fc37b78

File tree

5 files changed

+124
-45
lines changed

5 files changed

+124
-45
lines changed

commands/find.md

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,24 @@
22
description: Find code using hybrid approach (semantic + grep)
33
---
44

5-
Find code related to: $ARGUMENTS
5+
Find code using both semantic search and grep.
6+
7+
User input: $ARGUMENTS
68

79
Strategy:
8-
1. First use `codebase_search` to find semantically related code
9-
2. From the results, identify specific function/class names
10+
1. Use `codebase_search` to find semantically related code
11+
2. Identify specific function/class/variable names from results
1012
3. Use grep to find all occurrences of those identifiers
11-
4. Combine findings into a comprehensive answer
13+
4. Combine into a comprehensive answer
14+
15+
Parse optional parameters from input:
16+
- `limit=N` → limit semantic results
17+
- `type=X` or "functions"/"classes" → filter chunk type
18+
- `dir=X` → filter directory
19+
20+
Examples:
21+
- `/find error handling middleware`
22+
- `/find payment validation type=function`
23+
- `/find user auth in src/services`
1224

13-
If the semantic index doesn't exist, run `index_codebase` first.
25+
If no index exists, run `index_codebase` first.

commands/index.md

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,20 @@
22
description: Index the codebase for semantic search
33
---
44

5-
Run the `index_codebase` tool to create or update the semantic search index.
5+
Run the `index_codebase` tool with these settings:
66

7-
Show progress and final statistics including:
8-
- Number of files processed
9-
- Number of chunks indexed
10-
- Tokens used
11-
- Duration
7+
User input: $ARGUMENTS
8+
9+
Parse the input and set tool arguments:
10+
- force=true if input contains "force"
11+
- estimateOnly=true if input contains "estimate"
12+
- verbose=true (always, for detailed output)
13+
14+
Examples:
15+
- `/index` → force=false, estimateOnly=false, verbose=true
16+
- `/index force` → force=true, estimateOnly=false, verbose=true
17+
- `/index estimate` → force=false, estimateOnly=true, verbose=true
18+
19+
IMPORTANT: You MUST pass the parsed arguments to `index_codebase`. Do not ignore them.
20+
21+
Show final statistics including files processed, chunks indexed, tokens used, and duration.

commands/search.md

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,23 @@
22
description: Search codebase by meaning using semantic search
33
---
44

5-
Use the `codebase_search` tool to find code related to: $ARGUMENTS
5+
Search the codebase using semantic search.
66

7-
If the index doesn't exist yet, run `index_codebase` first.
7+
User input: $ARGUMENTS
88

9-
Return the most relevant results with file paths and line numbers.
9+
The first part is the search query. Look for optional parameters:
10+
- `limit=N` or "top N" or "first N" → set limit
11+
- `type=X` or mentions "functions"/"classes"/"methods" → set chunkType
12+
- `dir=X` or "in folder X" → set directory filter
13+
- File extensions like ".ts", "typescript", ".py" → set fileType
14+
15+
Call `codebase_search` with the parsed arguments.
16+
17+
Examples:
18+
- `/search authentication logic` → query="authentication logic"
19+
- `/search error handling limit=5` → query="error handling", limit=5
20+
- `/search validation functions` → query="validation", chunkType="function"
21+
22+
If the index doesn't exist, run `index_codebase` first.
23+
24+
Return results with file paths and line numbers.

src/commands/loader.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { existsSync, readdirSync, readFileSync } from "fs";
2+
import * as path from "path";
3+
4+
export interface CommandDefinition {
5+
description: string;
6+
template: string;
7+
}
8+
9+
function parseFrontmatter(content: string): { frontmatter: Record<string, string>; body: string } {
10+
const frontmatterRegex = /^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/;
11+
const match = content.match(frontmatterRegex);
12+
13+
if (!match) {
14+
return { frontmatter: {}, body: content.trim() };
15+
}
16+
17+
const frontmatterLines = match[1].split("\n");
18+
const frontmatter: Record<string, string> = {};
19+
20+
for (const line of frontmatterLines) {
21+
const colonIndex = line.indexOf(":");
22+
if (colonIndex > 0) {
23+
const key = line.slice(0, colonIndex).trim();
24+
const value = line.slice(colonIndex + 1).trim();
25+
frontmatter[key] = value;
26+
}
27+
}
28+
29+
return { frontmatter, body: match[2].trim() };
30+
}
31+
32+
export function loadCommandsFromDirectory(commandsDir: string): Map<string, CommandDefinition> {
33+
const commands = new Map<string, CommandDefinition>();
34+
35+
if (!existsSync(commandsDir)) {
36+
return commands;
37+
}
38+
39+
const files = readdirSync(commandsDir).filter((f) => f.endsWith(".md"));
40+
41+
for (const file of files) {
42+
const filePath = path.join(commandsDir, file);
43+
const content = readFileSync(filePath, "utf-8");
44+
const { frontmatter, body } = parseFrontmatter(content);
45+
46+
const name = path.basename(file, ".md");
47+
const description = frontmatter.description || `Run the ${name} command`;
48+
49+
commands.set(name, {
50+
description,
51+
template: body,
52+
});
53+
}
54+
55+
return commands;
56+
}

src/index.ts

Lines changed: 17 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { Plugin } from "@opencode-ai/plugin";
22
import { existsSync, readFileSync } from "fs";
33
import * as path from "path";
44
import * as os from "os";
5+
import { fileURLToPath } from "url";
56

67
import { parseConfig } from "./config/schema.js";
78
import { Indexer } from "./indexer/index.js";
@@ -13,6 +14,17 @@ import {
1314
index_health_check,
1415
initializeTools,
1516
} from "./tools/index.js";
17+
import { loadCommandsFromDirectory } from "./commands/loader.js";
18+
19+
function getCommandsDir(): string {
20+
let currentDir = process.cwd();
21+
22+
if (typeof import.meta !== "undefined" && import.meta.url) {
23+
currentDir = path.dirname(fileURLToPath(import.meta.url));
24+
}
25+
26+
return path.join(currentDir, "..", "commands");
27+
}
1628

1729
function loadJsonFile(filePath: string): unknown {
1830
try {
@@ -69,38 +81,12 @@ const plugin: Plugin = async ({ directory }) => {
6981
async config(cfg) {
7082
cfg.command = cfg.command ?? {};
7183

72-
cfg.command["search"] = {
73-
description: "Search codebase by meaning using semantic search",
74-
template: `Use the \`codebase_search\` tool to find code related to: $ARGUMENTS
75-
76-
If the index doesn't exist yet, run \`index_codebase\` first.
77-
78-
Return the most relevant results with file paths and line numbers.`,
79-
};
80-
81-
cfg.command["find"] = {
82-
description: "Find code using hybrid approach (semantic + grep)",
83-
template: `Find code related to: $ARGUMENTS
84-
85-
Strategy:
86-
1. First use \`codebase_search\` to find semantically related code
87-
2. From the results, identify specific function/class names
88-
3. Use grep to find all occurrences of those identifiers
89-
4. Combine findings into a comprehensive answer
90-
91-
If the semantic index doesn't exist, run \`index_codebase\` first.`,
92-
};
93-
94-
cfg.command["index"] = {
95-
description: "Index the codebase for semantic search",
96-
template: `Run the \`index_codebase\` tool to create or update the semantic search index.
84+
const commandsDir = getCommandsDir();
85+
const commands = loadCommandsFromDirectory(commandsDir);
9786

98-
Show progress and final statistics including:
99-
- Number of files processed
100-
- Number of chunks indexed
101-
- Tokens used
102-
- Duration`,
103-
};
87+
for (const [name, definition] of commands) {
88+
cfg.command[name] = definition;
89+
}
10490
},
10591
};
10692
};

0 commit comments

Comments
 (0)