@oisheek_c/codeforge 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +0 -0
- package/README.md +7 -0
- package/apps/cli/bin/codeforge.cmd +2 -0
- package/apps/cli/src/bootstrap.js +115 -0
- package/apps/cli/src/cli.js +189 -0
- package/apps/cli/src/dev/git.js +17 -0
- package/apps/cli/src/dev/index.js +68 -0
- package/apps/cli/src/dev/provider.js +48 -0
- package/apps/cli/src/dev/scanner.js +14 -0
- package/apps/cli/src/index.js +31 -0
- package/bin/codeforge.js +2 -0
- package/package.json +63 -0
- package/packages/agent/contextBuilder.js +234 -0
- package/packages/agent/executor.js +1729 -0
- package/packages/agent/fallback.js +129 -0
- package/packages/agent/index.js +26 -0
- package/packages/agent/intent.js +73 -0
- package/packages/agent/modelSelector.js +469 -0
- package/packages/agent/planner.js +379 -0
- package/packages/agent/rag.js +412 -0
- package/packages/agent/ragSelector.js +570 -0
- package/packages/agent/router.js +121 -0
- package/packages/agent/thinking.js +54 -0
- package/packages/agent/toolCalls.js +258 -0
- package/packages/config/config.js +391 -0
- package/packages/config/defaults.js +72 -0
- package/packages/config/index.js +2 -0
- package/packages/core/approvalPolicy.js +140 -0
- package/packages/core/context.js +0 -0
- package/packages/core/message.js +0 -0
- package/packages/core/plan.js +0 -0
- package/packages/core/reasoning.js +0 -0
- package/packages/core/result.js +0 -0
- package/packages/core/retrieval.js +0 -0
- package/packages/core/tool.js +536 -0
- package/packages/git/branch.js +7 -0
- package/packages/git/commit.js +16 -0
- package/packages/git/diff.js +15 -0
- package/packages/git/git.js +35 -0
- package/packages/git/index.js +6 -0
- package/packages/git/remote.js +9 -0
- package/packages/git/status.js +11 -0
- package/packages/memory/index.js +0 -0
- package/packages/memory/project.js +0 -0
- package/packages/memory/session.js +0 -0
- package/packages/prompts/builder.js +43 -0
- package/packages/prompts/coder.js +38 -0
- package/packages/prompts/index.js +5 -0
- package/packages/prompts/planner.js +49 -0
- package/packages/prompts/reviewer.js +38 -0
- package/packages/prompts/system.js +94 -0
- package/packages/providers/index.js +31 -0
- package/packages/providers/openrouter.js +380 -0
- package/packages/reasoning/budget.js +0 -0
- package/packages/reasoning/chain.js +0 -0
- package/packages/reasoning/coder.js +0 -0
- package/packages/reasoning/evaluator.js +0 -0
- package/packages/reasoning/index.js +8 -0
- package/packages/reasoning/planner.js +0 -0
- package/packages/reasoning/reflector.js +0 -0
- package/packages/reasoning/reviewer.js +0 -0
- package/packages/reasoning/router.js +0 -0
- package/packages/retrieval/budget.js +92 -0
- package/packages/retrieval/cache.js +84 -0
- package/packages/retrieval/embeddings.js +147 -0
- package/packages/retrieval/graph.js +151 -0
- package/packages/retrieval/imports.js +74 -0
- package/packages/retrieval/index.js +9 -0
- package/packages/retrieval/parser/extractors/calls.js +43 -0
- package/packages/retrieval/parser/extractors/comments.js +49 -0
- package/packages/retrieval/parser/extractors/diagnostics.js +21 -0
- package/packages/retrieval/parser/extractors/exports.js +71 -0
- package/packages/retrieval/parser/extractors/imports.js +40 -0
- package/packages/retrieval/parser/extractors/index.js +12 -0
- package/packages/retrieval/parser/extractors/metrics.js +78 -0
- package/packages/retrieval/parser/extractors/symbols.js +51 -0
- package/packages/retrieval/parser/extractors/todos.js +53 -0
- package/packages/retrieval/parser/helpers.js +138 -0
- package/packages/retrieval/parser/index.js +10 -0
- package/packages/retrieval/parser/languages/c.js +14 -0
- package/packages/retrieval/parser/languages/cpp.js +21 -0
- package/packages/retrieval/parser/languages/csharp.js +14 -0
- package/packages/retrieval/parser/languages/go.js +14 -0
- package/packages/retrieval/parser/languages/index.js +12 -0
- package/packages/retrieval/parser/languages/java.js +14 -0
- package/packages/retrieval/parser/languages/javascript.js +14 -0
- package/packages/retrieval/parser/languages/php.js +14 -0
- package/packages/retrieval/parser/languages/python.js +14 -0
- package/packages/retrieval/parser/languages/ruby.js +14 -0
- package/packages/retrieval/parser/languages/rust.js +14 -0
- package/packages/retrieval/parser/languages/swift.js +14 -0
- package/packages/retrieval/parser/languages/typescript.js +14 -0
- package/packages/retrieval/parser/languages.js +218 -0
- package/packages/retrieval/parser/parseFile.js +79 -0
- package/packages/retrieval/parser/parseRepository.js +78 -0
- package/packages/retrieval/parser/queries/c.js +148 -0
- package/packages/retrieval/parser/queries/cpp.js +198 -0
- package/packages/retrieval/parser/queries/csharp.js +151 -0
- package/packages/retrieval/parser/queries/go.js +124 -0
- package/packages/retrieval/parser/queries/index.js +35 -0
- package/packages/retrieval/parser/queries/java.js +128 -0
- package/packages/retrieval/parser/queries/javascript.js +181 -0
- package/packages/retrieval/parser/queries/php.js +140 -0
- package/packages/retrieval/parser/queries/python.js +110 -0
- package/packages/retrieval/parser/queries/ruby.js +105 -0
- package/packages/retrieval/parser/queries/rust.js +175 -0
- package/packages/retrieval/parser/queries/swift.js +134 -0
- package/packages/retrieval/parser/queries/typescript.js +202 -0
- package/packages/retrieval/parser/registry.js +107 -0
- package/packages/retrieval/parser/traverse.js +82 -0
- package/packages/retrieval/parser/types.js +239 -0
- package/packages/retrieval/parser.js +78 -0
- package/packages/retrieval/ranker.js +73 -0
- package/packages/retrieval/retriever.js +910 -0
- package/packages/retrieval/symbols.js +145 -0
- package/packages/scanner/detect.js +69 -0
- package/packages/scanner/ignore.js +12 -0
- package/packages/scanner/index.js +2 -0
- package/packages/scanner/root.js +21 -0
- package/packages/scanner/scanner.js +35 -0
- package/packages/scanner/tree.js +45 -0
- package/packages/terminal/activity.js +514 -0
- package/packages/terminal/banner.js +34 -0
- package/packages/terminal/colors.js +16 -0
- package/packages/terminal/dashboard.js +94 -0
- package/packages/terminal/index.js +19 -0
- package/packages/terminal/logger.js +43 -0
- package/packages/terminal/prompt.js +11 -0
- package/packages/terminal/renderer.js +1209 -0
- package/packages/terminal/spinner.js +9 -0
- package/packages/tools/editFile.js +261 -0
- package/packages/tools/filesystem.js +82 -0
- package/packages/tools/index.js +24 -0
- package/packages/tools/readFile.js +101 -0
- package/packages/tools/registry.js +227 -0
- package/packages/tools/searchFiles.js +322 -0
- package/packages/tools/shell.js +352 -0
- package/packages/tools/writeFile.js +212 -0
- package/packages/utils/constants.js +0 -0
- package/packages/utils/helpers.js +0 -0
- package/packages/utils/logger.js +0 -0
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
createToolError,
|
|
5
|
+
resolveProjectPath,
|
|
6
|
+
} from "./filesystem.js";
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
defineTool,
|
|
10
|
+
} from "../core/tool.js";
|
|
11
|
+
|
|
12
|
+
function countOccurrences(
|
|
13
|
+
content,
|
|
14
|
+
search
|
|
15
|
+
) {
|
|
16
|
+
let count = 0;
|
|
17
|
+
let position = 0;
|
|
18
|
+
|
|
19
|
+
while (true) {
|
|
20
|
+
const index =
|
|
21
|
+
content.indexOf(
|
|
22
|
+
search,
|
|
23
|
+
position
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
if (index === -1) {
|
|
27
|
+
return count;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
count += 1;
|
|
31
|
+
|
|
32
|
+
position =
|
|
33
|
+
index + search.length;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const editFileTool =
|
|
38
|
+
defineTool({
|
|
39
|
+
name: "edit_file",
|
|
40
|
+
|
|
41
|
+
description:
|
|
42
|
+
"Edit an existing UTF-8 text file inside the project by replacing exactly one occurrence of oldText with newText. Use this when the target file path and exact text to replace are known. The edit requires approval.",
|
|
43
|
+
|
|
44
|
+
source: "builtin",
|
|
45
|
+
|
|
46
|
+
capabilities: [
|
|
47
|
+
"filesystem.read",
|
|
48
|
+
"filesystem.write",
|
|
49
|
+
],
|
|
50
|
+
|
|
51
|
+
sideEffect: "write",
|
|
52
|
+
|
|
53
|
+
approval: "policy",
|
|
54
|
+
|
|
55
|
+
inputSchema: {
|
|
56
|
+
type: "object",
|
|
57
|
+
|
|
58
|
+
properties: {
|
|
59
|
+
path: {
|
|
60
|
+
type: "string",
|
|
61
|
+
minLength: 1,
|
|
62
|
+
description:
|
|
63
|
+
"Path to the existing file relative to the project root.",
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
oldText: {
|
|
67
|
+
type: "string",
|
|
68
|
+
minLength: 1,
|
|
69
|
+
description:
|
|
70
|
+
"Exact existing text to replace. It must occur exactly once in the file.",
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
newText: {
|
|
74
|
+
type: "string",
|
|
75
|
+
description:
|
|
76
|
+
"Replacement text.",
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
required: [
|
|
81
|
+
"path",
|
|
82
|
+
"oldText",
|
|
83
|
+
"newText",
|
|
84
|
+
],
|
|
85
|
+
|
|
86
|
+
additionalProperties: false,
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
async execute(
|
|
90
|
+
input,
|
|
91
|
+
context = {}
|
|
92
|
+
) {
|
|
93
|
+
const filePath =
|
|
94
|
+
input?.path;
|
|
95
|
+
|
|
96
|
+
const oldText =
|
|
97
|
+
input?.oldText;
|
|
98
|
+
|
|
99
|
+
const newText =
|
|
100
|
+
input?.newText;
|
|
101
|
+
|
|
102
|
+
if (
|
|
103
|
+
typeof oldText !== "string" ||
|
|
104
|
+
oldText.length === 0
|
|
105
|
+
) {
|
|
106
|
+
throw createToolError(
|
|
107
|
+
"invalid_old_text",
|
|
108
|
+
"oldText must be a non-empty string."
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (
|
|
113
|
+
typeof newText !== "string"
|
|
114
|
+
) {
|
|
115
|
+
throw createToolError(
|
|
116
|
+
"invalid_new_text",
|
|
117
|
+
"newText must be a string."
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const {
|
|
122
|
+
resolved,
|
|
123
|
+
relative,
|
|
124
|
+
} = resolveProjectPath(
|
|
125
|
+
context.projectRoot,
|
|
126
|
+
filePath
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
let stats;
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
stats =
|
|
133
|
+
await fs.stat(
|
|
134
|
+
resolved
|
|
135
|
+
);
|
|
136
|
+
} catch (error) {
|
|
137
|
+
if (
|
|
138
|
+
error?.code === "ENOENT"
|
|
139
|
+
) {
|
|
140
|
+
throw createToolError(
|
|
141
|
+
"file_not_found",
|
|
142
|
+
`File not found: ${relative}`
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
throw error;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (!stats.isFile()) {
|
|
150
|
+
throw createToolError(
|
|
151
|
+
"path_not_file",
|
|
152
|
+
`Path is not a file: ${relative}`
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
let content;
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
content =
|
|
160
|
+
await fs.readFile(
|
|
161
|
+
resolved,
|
|
162
|
+
"utf8"
|
|
163
|
+
);
|
|
164
|
+
} catch (error) {
|
|
165
|
+
throw createToolError(
|
|
166
|
+
"file_read_failed",
|
|
167
|
+
`Failed to read file: ${relative}`,
|
|
168
|
+
{
|
|
169
|
+
cause:
|
|
170
|
+
error?.message ??
|
|
171
|
+
String(error),
|
|
172
|
+
}
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const occurrences =
|
|
177
|
+
countOccurrences(
|
|
178
|
+
content,
|
|
179
|
+
oldText
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
if (occurrences === 0) {
|
|
183
|
+
throw createToolError(
|
|
184
|
+
"edit_target_not_found",
|
|
185
|
+
`oldText was not found in file: ${relative}`
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (occurrences > 1) {
|
|
190
|
+
throw createToolError(
|
|
191
|
+
"edit_target_ambiguous",
|
|
192
|
+
`oldText occurs more than once in file: ${relative}`,
|
|
193
|
+
{
|
|
194
|
+
occurrences,
|
|
195
|
+
}
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const updatedContent =
|
|
200
|
+
content.replace(
|
|
201
|
+
oldText,
|
|
202
|
+
newText
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
try {
|
|
206
|
+
await fs.writeFile(
|
|
207
|
+
resolved,
|
|
208
|
+
updatedContent,
|
|
209
|
+
{
|
|
210
|
+
encoding: "utf8",
|
|
211
|
+
flag: "w",
|
|
212
|
+
}
|
|
213
|
+
);
|
|
214
|
+
} catch (error) {
|
|
215
|
+
throw createToolError(
|
|
216
|
+
"file_write_failed",
|
|
217
|
+
`Failed to edit file: ${relative}`,
|
|
218
|
+
{
|
|
219
|
+
cause:
|
|
220
|
+
error?.message ??
|
|
221
|
+
String(error),
|
|
222
|
+
}
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
let after;
|
|
227
|
+
|
|
228
|
+
try {
|
|
229
|
+
after =
|
|
230
|
+
await fs.stat(
|
|
231
|
+
resolved
|
|
232
|
+
);
|
|
233
|
+
} catch (error) {
|
|
234
|
+
throw createToolError(
|
|
235
|
+
"file_verification_failed",
|
|
236
|
+
`Unable to verify edited file: ${relative}`,
|
|
237
|
+
{
|
|
238
|
+
cause:
|
|
239
|
+
error?.message ??
|
|
240
|
+
String(error),
|
|
241
|
+
}
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (!after.isFile()) {
|
|
246
|
+
throw createToolError(
|
|
247
|
+
"file_verification_failed",
|
|
248
|
+
`Edited path is not a file: ${relative}`
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return {
|
|
253
|
+
path: relative,
|
|
254
|
+
previousSize:
|
|
255
|
+
stats.size,
|
|
256
|
+
size:
|
|
257
|
+
after.size,
|
|
258
|
+
replacements: 1,
|
|
259
|
+
};
|
|
260
|
+
},
|
|
261
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
|
|
3
|
+
export function createToolError(
|
|
4
|
+
code,
|
|
5
|
+
message,
|
|
6
|
+
details = null
|
|
7
|
+
) {
|
|
8
|
+
const error = new Error(message);
|
|
9
|
+
|
|
10
|
+
error.code = code;
|
|
11
|
+
error.details = details;
|
|
12
|
+
|
|
13
|
+
return error;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function resolveProjectPath(
|
|
17
|
+
projectRoot,
|
|
18
|
+
targetPath
|
|
19
|
+
) {
|
|
20
|
+
if (
|
|
21
|
+
typeof projectRoot !== "string" ||
|
|
22
|
+
projectRoot.trim().length === 0
|
|
23
|
+
) {
|
|
24
|
+
throw createToolError(
|
|
25
|
+
"project_root_required",
|
|
26
|
+
"Project root is required."
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (
|
|
31
|
+
typeof targetPath !== "string" ||
|
|
32
|
+
targetPath.trim().length === 0
|
|
33
|
+
) {
|
|
34
|
+
throw createToolError(
|
|
35
|
+
"invalid_path",
|
|
36
|
+
"Path must be a non-empty string."
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const root =
|
|
41
|
+
path.resolve(projectRoot);
|
|
42
|
+
|
|
43
|
+
const resolved =
|
|
44
|
+
path.resolve(
|
|
45
|
+
root,
|
|
46
|
+
targetPath
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const relative =
|
|
50
|
+
path.relative(
|
|
51
|
+
root,
|
|
52
|
+
resolved
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
if (
|
|
56
|
+
relative === ".." ||
|
|
57
|
+
relative.startsWith(
|
|
58
|
+
`..${path.sep}`
|
|
59
|
+
) ||
|
|
60
|
+
path.isAbsolute(relative)
|
|
61
|
+
) {
|
|
62
|
+
throw createToolError(
|
|
63
|
+
"path_outside_project",
|
|
64
|
+
"Path is outside the project root."
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
root,
|
|
70
|
+
resolved,
|
|
71
|
+
relative:
|
|
72
|
+
relative || ".",
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function normalizeProjectPath(
|
|
77
|
+
filePath
|
|
78
|
+
) {
|
|
79
|
+
return filePath
|
|
80
|
+
.split(path.sep)
|
|
81
|
+
.join("/");
|
|
82
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export {
|
|
2
|
+
readFileTool,
|
|
3
|
+
} from "./readFile.js";
|
|
4
|
+
|
|
5
|
+
export {
|
|
6
|
+
searchFilesTool,
|
|
7
|
+
} from "./searchFiles.js";
|
|
8
|
+
|
|
9
|
+
export {
|
|
10
|
+
writeFileTool,
|
|
11
|
+
} from "./writeFile.js";
|
|
12
|
+
|
|
13
|
+
export {
|
|
14
|
+
ToolRegistry,
|
|
15
|
+
createToolRegistry,
|
|
16
|
+
} from "./registry.js";
|
|
17
|
+
|
|
18
|
+
export {
|
|
19
|
+
editFileTool,
|
|
20
|
+
} from "./editFile.js";
|
|
21
|
+
|
|
22
|
+
export {
|
|
23
|
+
shellTool,
|
|
24
|
+
} from "./shell.js";
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
defineTool,
|
|
5
|
+
} from "../core/tool.js";
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
createToolError,
|
|
9
|
+
normalizeProjectPath,
|
|
10
|
+
resolveProjectPath,
|
|
11
|
+
} from "./filesystem.js";
|
|
12
|
+
|
|
13
|
+
export const readFileTool = defineTool({
|
|
14
|
+
name: "read_file",
|
|
15
|
+
|
|
16
|
+
description:
|
|
17
|
+
"Read the contents of a UTF-8 text file inside the current project. Use this tool directly when the file path is already known; do not search for the filename with search_files first.",
|
|
18
|
+
|
|
19
|
+
source: "builtin",
|
|
20
|
+
|
|
21
|
+
capabilities: [
|
|
22
|
+
"filesystem.read",
|
|
23
|
+
],
|
|
24
|
+
|
|
25
|
+
sideEffect: "none",
|
|
26
|
+
|
|
27
|
+
approval: "never",
|
|
28
|
+
|
|
29
|
+
inputSchema: {
|
|
30
|
+
type: "object",
|
|
31
|
+
|
|
32
|
+
properties: {
|
|
33
|
+
path: {
|
|
34
|
+
type: "string",
|
|
35
|
+
description:
|
|
36
|
+
"Path to the file relative to the project root.",
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
required: [
|
|
41
|
+
"path",
|
|
42
|
+
],
|
|
43
|
+
|
|
44
|
+
additionalProperties: false,
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
async execute(
|
|
48
|
+
input,
|
|
49
|
+
context = {}
|
|
50
|
+
) {
|
|
51
|
+
const {
|
|
52
|
+
resolved,
|
|
53
|
+
relative,
|
|
54
|
+
} = resolveProjectPath(
|
|
55
|
+
context.projectRoot,
|
|
56
|
+
input?.path
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
let stats;
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
stats =
|
|
63
|
+
await fs.stat(
|
|
64
|
+
resolved
|
|
65
|
+
);
|
|
66
|
+
} catch (error) {
|
|
67
|
+
if (error?.code === "ENOENT") {
|
|
68
|
+
throw createToolError(
|
|
69
|
+
"file_not_found",
|
|
70
|
+
`File not found: ${input.path}`
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (!stats.isFile()) {
|
|
78
|
+
throw createToolError(
|
|
79
|
+
"not_a_file",
|
|
80
|
+
`Path is not a file: ${input.path}`
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const content =
|
|
85
|
+
await fs.readFile(
|
|
86
|
+
resolved,
|
|
87
|
+
"utf8"
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
path:
|
|
92
|
+
normalizeProjectPath(
|
|
93
|
+
relative
|
|
94
|
+
),
|
|
95
|
+
|
|
96
|
+
content,
|
|
97
|
+
|
|
98
|
+
size: stats.size,
|
|
99
|
+
};
|
|
100
|
+
},
|
|
101
|
+
});
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isTool,
|
|
3
|
+
} from "../core/tool.js";
|
|
4
|
+
|
|
5
|
+
export class ToolRegistry {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.tools = new Map();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Register a canonical CodeForge tool.
|
|
12
|
+
*/
|
|
13
|
+
register(tool) {
|
|
14
|
+
if (!isTool(tool)) {
|
|
15
|
+
throw new TypeError(
|
|
16
|
+
"Invalid tool. Tools must be created with defineTool()."
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (this.tools.has(tool.name)) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
`Tool already registered: ${tool.name}`
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
this.tools.set(tool.name, tool);
|
|
27
|
+
|
|
28
|
+
return tool;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Register multiple tools.
|
|
33
|
+
*/
|
|
34
|
+
registerMany(tools) {
|
|
35
|
+
if (!Array.isArray(tools)) {
|
|
36
|
+
throw new TypeError(
|
|
37
|
+
"Tools must be an array."
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
for (const tool of tools) {
|
|
42
|
+
this.register(tool);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return this;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Resolve a tool by its canonical name.
|
|
50
|
+
*/
|
|
51
|
+
get(name) {
|
|
52
|
+
if (typeof name !== "string") {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return this.tools.get(name) ?? null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Check whether a tool exists.
|
|
61
|
+
*/
|
|
62
|
+
has(name) {
|
|
63
|
+
return this.tools.has(name);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Remove a registered tool.
|
|
68
|
+
*/
|
|
69
|
+
unregister(name) {
|
|
70
|
+
return this.tools.delete(name);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Remove all registered tools.
|
|
75
|
+
*/
|
|
76
|
+
clear() {
|
|
77
|
+
this.tools.clear();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Return every registered tool.
|
|
82
|
+
*/
|
|
83
|
+
values() {
|
|
84
|
+
return [...this.tools.values()];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Return all registered tool names.
|
|
89
|
+
*/
|
|
90
|
+
names() {
|
|
91
|
+
return [...this.tools.keys()];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Number of registered tools.
|
|
96
|
+
*/
|
|
97
|
+
size() {
|
|
98
|
+
return this.tools.size;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Find tools exposing a capability.
|
|
103
|
+
*/
|
|
104
|
+
findByCapability(capability) {
|
|
105
|
+
if (typeof capability !== "string") {
|
|
106
|
+
return [];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return this.values().filter(
|
|
110
|
+
(tool) =>
|
|
111
|
+
tool.capabilities.includes(
|
|
112
|
+
capability
|
|
113
|
+
)
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Find tools originating from a source.
|
|
119
|
+
*
|
|
120
|
+
* Examples:
|
|
121
|
+
* builtin
|
|
122
|
+
* plugin
|
|
123
|
+
* mcp
|
|
124
|
+
*/
|
|
125
|
+
findBySource(source) {
|
|
126
|
+
if (typeof source !== "string") {
|
|
127
|
+
return [];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return this.values().filter(
|
|
131
|
+
(tool) => tool.source === source
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Create a restricted view of the registry.
|
|
137
|
+
*
|
|
138
|
+
* This becomes important for sub-agents,
|
|
139
|
+
* approval scopes, plugins, and autonomous
|
|
140
|
+
* workflows.
|
|
141
|
+
*/
|
|
142
|
+
select({
|
|
143
|
+
names = null,
|
|
144
|
+
capabilities = null,
|
|
145
|
+
sources = null,
|
|
146
|
+
} = {}) {
|
|
147
|
+
let tools = this.values();
|
|
148
|
+
|
|
149
|
+
if (Array.isArray(names)) {
|
|
150
|
+
const allowed =
|
|
151
|
+
new Set(names);
|
|
152
|
+
|
|
153
|
+
tools = tools.filter(
|
|
154
|
+
(tool) =>
|
|
155
|
+
allowed.has(tool.name)
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (Array.isArray(capabilities)) {
|
|
160
|
+
const required =
|
|
161
|
+
new Set(capabilities);
|
|
162
|
+
|
|
163
|
+
tools = tools.filter(
|
|
164
|
+
(tool) =>
|
|
165
|
+
[...required].every(
|
|
166
|
+
(capability) =>
|
|
167
|
+
tool.capabilities.includes(
|
|
168
|
+
capability
|
|
169
|
+
)
|
|
170
|
+
)
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (Array.isArray(sources)) {
|
|
175
|
+
const allowed =
|
|
176
|
+
new Set(sources);
|
|
177
|
+
|
|
178
|
+
tools = tools.filter(
|
|
179
|
+
(tool) =>
|
|
180
|
+
allowed.has(tool.source)
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return tools;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Metadata suitable for model/tool discovery.
|
|
189
|
+
*
|
|
190
|
+
* execute() is intentionally excluded.
|
|
191
|
+
*/
|
|
192
|
+
describe() {
|
|
193
|
+
return this.values().map(
|
|
194
|
+
(tool) => ({
|
|
195
|
+
name: tool.name,
|
|
196
|
+
description:
|
|
197
|
+
tool.description,
|
|
198
|
+
version:
|
|
199
|
+
tool.version,
|
|
200
|
+
source:
|
|
201
|
+
tool.source,
|
|
202
|
+
inputSchema:
|
|
203
|
+
tool.inputSchema,
|
|
204
|
+
capabilities:
|
|
205
|
+
[...tool.capabilities],
|
|
206
|
+
sideEffect:
|
|
207
|
+
tool.sideEffect,
|
|
208
|
+
approval:
|
|
209
|
+
tool.approval,
|
|
210
|
+
})
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Factory.
|
|
217
|
+
*/
|
|
218
|
+
export function createToolRegistry(
|
|
219
|
+
tools = []
|
|
220
|
+
) {
|
|
221
|
+
const registry =
|
|
222
|
+
new ToolRegistry();
|
|
223
|
+
|
|
224
|
+
registry.registerMany(tools);
|
|
225
|
+
|
|
226
|
+
return registry;
|
|
227
|
+
}
|