@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.
Files changed (141) hide show
  1. package/LICENSE +0 -0
  2. package/README.md +7 -0
  3. package/apps/cli/bin/codeforge.cmd +2 -0
  4. package/apps/cli/src/bootstrap.js +115 -0
  5. package/apps/cli/src/cli.js +189 -0
  6. package/apps/cli/src/dev/git.js +17 -0
  7. package/apps/cli/src/dev/index.js +68 -0
  8. package/apps/cli/src/dev/provider.js +48 -0
  9. package/apps/cli/src/dev/scanner.js +14 -0
  10. package/apps/cli/src/index.js +31 -0
  11. package/bin/codeforge.js +2 -0
  12. package/package.json +63 -0
  13. package/packages/agent/contextBuilder.js +234 -0
  14. package/packages/agent/executor.js +1729 -0
  15. package/packages/agent/fallback.js +129 -0
  16. package/packages/agent/index.js +26 -0
  17. package/packages/agent/intent.js +73 -0
  18. package/packages/agent/modelSelector.js +469 -0
  19. package/packages/agent/planner.js +379 -0
  20. package/packages/agent/rag.js +412 -0
  21. package/packages/agent/ragSelector.js +570 -0
  22. package/packages/agent/router.js +121 -0
  23. package/packages/agent/thinking.js +54 -0
  24. package/packages/agent/toolCalls.js +258 -0
  25. package/packages/config/config.js +391 -0
  26. package/packages/config/defaults.js +72 -0
  27. package/packages/config/index.js +2 -0
  28. package/packages/core/approvalPolicy.js +140 -0
  29. package/packages/core/context.js +0 -0
  30. package/packages/core/message.js +0 -0
  31. package/packages/core/plan.js +0 -0
  32. package/packages/core/reasoning.js +0 -0
  33. package/packages/core/result.js +0 -0
  34. package/packages/core/retrieval.js +0 -0
  35. package/packages/core/tool.js +536 -0
  36. package/packages/git/branch.js +7 -0
  37. package/packages/git/commit.js +16 -0
  38. package/packages/git/diff.js +15 -0
  39. package/packages/git/git.js +35 -0
  40. package/packages/git/index.js +6 -0
  41. package/packages/git/remote.js +9 -0
  42. package/packages/git/status.js +11 -0
  43. package/packages/memory/index.js +0 -0
  44. package/packages/memory/project.js +0 -0
  45. package/packages/memory/session.js +0 -0
  46. package/packages/prompts/builder.js +43 -0
  47. package/packages/prompts/coder.js +38 -0
  48. package/packages/prompts/index.js +5 -0
  49. package/packages/prompts/planner.js +49 -0
  50. package/packages/prompts/reviewer.js +38 -0
  51. package/packages/prompts/system.js +94 -0
  52. package/packages/providers/index.js +31 -0
  53. package/packages/providers/openrouter.js +380 -0
  54. package/packages/reasoning/budget.js +0 -0
  55. package/packages/reasoning/chain.js +0 -0
  56. package/packages/reasoning/coder.js +0 -0
  57. package/packages/reasoning/evaluator.js +0 -0
  58. package/packages/reasoning/index.js +8 -0
  59. package/packages/reasoning/planner.js +0 -0
  60. package/packages/reasoning/reflector.js +0 -0
  61. package/packages/reasoning/reviewer.js +0 -0
  62. package/packages/reasoning/router.js +0 -0
  63. package/packages/retrieval/budget.js +92 -0
  64. package/packages/retrieval/cache.js +84 -0
  65. package/packages/retrieval/embeddings.js +147 -0
  66. package/packages/retrieval/graph.js +151 -0
  67. package/packages/retrieval/imports.js +74 -0
  68. package/packages/retrieval/index.js +9 -0
  69. package/packages/retrieval/parser/extractors/calls.js +43 -0
  70. package/packages/retrieval/parser/extractors/comments.js +49 -0
  71. package/packages/retrieval/parser/extractors/diagnostics.js +21 -0
  72. package/packages/retrieval/parser/extractors/exports.js +71 -0
  73. package/packages/retrieval/parser/extractors/imports.js +40 -0
  74. package/packages/retrieval/parser/extractors/index.js +12 -0
  75. package/packages/retrieval/parser/extractors/metrics.js +78 -0
  76. package/packages/retrieval/parser/extractors/symbols.js +51 -0
  77. package/packages/retrieval/parser/extractors/todos.js +53 -0
  78. package/packages/retrieval/parser/helpers.js +138 -0
  79. package/packages/retrieval/parser/index.js +10 -0
  80. package/packages/retrieval/parser/languages/c.js +14 -0
  81. package/packages/retrieval/parser/languages/cpp.js +21 -0
  82. package/packages/retrieval/parser/languages/csharp.js +14 -0
  83. package/packages/retrieval/parser/languages/go.js +14 -0
  84. package/packages/retrieval/parser/languages/index.js +12 -0
  85. package/packages/retrieval/parser/languages/java.js +14 -0
  86. package/packages/retrieval/parser/languages/javascript.js +14 -0
  87. package/packages/retrieval/parser/languages/php.js +14 -0
  88. package/packages/retrieval/parser/languages/python.js +14 -0
  89. package/packages/retrieval/parser/languages/ruby.js +14 -0
  90. package/packages/retrieval/parser/languages/rust.js +14 -0
  91. package/packages/retrieval/parser/languages/swift.js +14 -0
  92. package/packages/retrieval/parser/languages/typescript.js +14 -0
  93. package/packages/retrieval/parser/languages.js +218 -0
  94. package/packages/retrieval/parser/parseFile.js +79 -0
  95. package/packages/retrieval/parser/parseRepository.js +78 -0
  96. package/packages/retrieval/parser/queries/c.js +148 -0
  97. package/packages/retrieval/parser/queries/cpp.js +198 -0
  98. package/packages/retrieval/parser/queries/csharp.js +151 -0
  99. package/packages/retrieval/parser/queries/go.js +124 -0
  100. package/packages/retrieval/parser/queries/index.js +35 -0
  101. package/packages/retrieval/parser/queries/java.js +128 -0
  102. package/packages/retrieval/parser/queries/javascript.js +181 -0
  103. package/packages/retrieval/parser/queries/php.js +140 -0
  104. package/packages/retrieval/parser/queries/python.js +110 -0
  105. package/packages/retrieval/parser/queries/ruby.js +105 -0
  106. package/packages/retrieval/parser/queries/rust.js +175 -0
  107. package/packages/retrieval/parser/queries/swift.js +134 -0
  108. package/packages/retrieval/parser/queries/typescript.js +202 -0
  109. package/packages/retrieval/parser/registry.js +107 -0
  110. package/packages/retrieval/parser/traverse.js +82 -0
  111. package/packages/retrieval/parser/types.js +239 -0
  112. package/packages/retrieval/parser.js +78 -0
  113. package/packages/retrieval/ranker.js +73 -0
  114. package/packages/retrieval/retriever.js +910 -0
  115. package/packages/retrieval/symbols.js +145 -0
  116. package/packages/scanner/detect.js +69 -0
  117. package/packages/scanner/ignore.js +12 -0
  118. package/packages/scanner/index.js +2 -0
  119. package/packages/scanner/root.js +21 -0
  120. package/packages/scanner/scanner.js +35 -0
  121. package/packages/scanner/tree.js +45 -0
  122. package/packages/terminal/activity.js +514 -0
  123. package/packages/terminal/banner.js +34 -0
  124. package/packages/terminal/colors.js +16 -0
  125. package/packages/terminal/dashboard.js +94 -0
  126. package/packages/terminal/index.js +19 -0
  127. package/packages/terminal/logger.js +43 -0
  128. package/packages/terminal/prompt.js +11 -0
  129. package/packages/terminal/renderer.js +1209 -0
  130. package/packages/terminal/spinner.js +9 -0
  131. package/packages/tools/editFile.js +261 -0
  132. package/packages/tools/filesystem.js +82 -0
  133. package/packages/tools/index.js +24 -0
  134. package/packages/tools/readFile.js +101 -0
  135. package/packages/tools/registry.js +227 -0
  136. package/packages/tools/searchFiles.js +322 -0
  137. package/packages/tools/shell.js +352 -0
  138. package/packages/tools/writeFile.js +212 -0
  139. package/packages/utils/constants.js +0 -0
  140. package/packages/utils/helpers.js +0 -0
  141. package/packages/utils/logger.js +0 -0
@@ -0,0 +1,322 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import {
4
+ createToolError,
5
+ resolveProjectPath,
6
+ } from "./filesystem.js";
7
+ import {
8
+ defineTool,
9
+ } from "../core/tool.js";
10
+
11
+ const DEFAULT_IGNORE = new Set([
12
+ ".git",
13
+ "node_modules",
14
+ ".pnpm",
15
+ "dist",
16
+ "build",
17
+ "coverage",
18
+ ]);
19
+
20
+ const DEFAULT_MAX_RESULTS = 100;
21
+ const MAX_RESULTS_LIMIT = 500;
22
+
23
+
24
+
25
+ function normalizeMaxResults(value) {
26
+ if (value == null) {
27
+ return DEFAULT_MAX_RESULTS;
28
+ }
29
+
30
+ if (
31
+ !Number.isInteger(value) ||
32
+ value < 1 ||
33
+ value > MAX_RESULTS_LIMIT
34
+ ) {
35
+ throw new TypeError(
36
+ `maxResults must be an integer between 1 and ${MAX_RESULTS_LIMIT}.`
37
+ );
38
+ }
39
+
40
+ return value;
41
+ }
42
+
43
+ function normalizePath(filePath, root) {
44
+ return path
45
+ .relative(root, filePath)
46
+ .split(path.sep)
47
+ .join("/");
48
+ }
49
+
50
+ async function searchFile({
51
+ filePath,
52
+ root,
53
+ query,
54
+ caseSensitive,
55
+ results,
56
+ maxResults,
57
+ }) {
58
+ let content;
59
+
60
+ try {
61
+ content = await fs.readFile(
62
+ filePath,
63
+ "utf8"
64
+ );
65
+ } catch {
66
+ return;
67
+ }
68
+
69
+ const lines = content.split(/\r?\n/);
70
+
71
+ const needle = caseSensitive
72
+ ? query
73
+ : query.toLowerCase();
74
+
75
+ for (
76
+ let index = 0;
77
+ index < lines.length;
78
+ index += 1
79
+ ) {
80
+ const line = lines[index];
81
+
82
+ const haystack = caseSensitive
83
+ ? line
84
+ : line.toLowerCase();
85
+
86
+ if (!haystack.includes(needle)) {
87
+ continue;
88
+ }
89
+
90
+ results.push({
91
+ path: normalizePath(
92
+ filePath,
93
+ root
94
+ ),
95
+
96
+ line: index + 1,
97
+
98
+ text: line,
99
+ });
100
+
101
+ if (results.length >= maxResults) {
102
+ return;
103
+ }
104
+ }
105
+ }
106
+
107
+ async function walk({
108
+ directory,
109
+ root,
110
+ query,
111
+ caseSensitive,
112
+ results,
113
+ maxResults,
114
+ }) {
115
+ if (results.length >= maxResults) {
116
+ return;
117
+ }
118
+
119
+ let entries;
120
+
121
+ try {
122
+ entries = await fs.readdir(
123
+ directory,
124
+ {
125
+ withFileTypes: true,
126
+ }
127
+ );
128
+ } catch {
129
+ return;
130
+ }
131
+
132
+ for (const entry of entries) {
133
+ if (results.length >= maxResults) {
134
+ return;
135
+ }
136
+
137
+ if (DEFAULT_IGNORE.has(entry.name)) {
138
+ continue;
139
+ }
140
+
141
+ const fullPath = path.join(
142
+ directory,
143
+ entry.name
144
+ );
145
+
146
+ if (entry.isDirectory()) {
147
+ await walk({
148
+ directory: fullPath,
149
+ root,
150
+ query,
151
+ caseSensitive,
152
+ results,
153
+ maxResults,
154
+ });
155
+
156
+ continue;
157
+ }
158
+
159
+ if (!entry.isFile()) {
160
+ continue;
161
+ }
162
+
163
+ await searchFile({
164
+ filePath: fullPath,
165
+ root,
166
+ query,
167
+ caseSensitive,
168
+ results,
169
+ maxResults,
170
+ });
171
+ }
172
+ }
173
+
174
+ export const searchFilesTool = defineTool({
175
+ name: "search_files",
176
+
177
+ description:
178
+ "Search the contents of project files for literal text and return matching file paths, line numbers, and lines. This tool searches file contents, not file names or paths. If the exact path of a file is already known, use read_file instead.",
179
+ source: "builtin",
180
+
181
+ capabilities: [
182
+ "filesystem.read",
183
+ "filesystem.search",
184
+ ],
185
+
186
+ sideEffect: "none",
187
+
188
+ approval: "never",
189
+
190
+ inputSchema: {
191
+ type: "object",
192
+
193
+ properties: {
194
+ query: {
195
+ type: "string",
196
+ description:
197
+ "Literal text to search for.",
198
+ },
199
+
200
+ path: {
201
+ type: "string",
202
+ description:
203
+ "Optional project-relative file or directory to search. Defaults to the project root.",
204
+ },
205
+
206
+ caseSensitive: {
207
+ type: "boolean",
208
+ description:
209
+ "Whether matching should be case-sensitive. Defaults to false.",
210
+ },
211
+
212
+ maxResults: {
213
+ type: "integer",
214
+ minimum: 1,
215
+ maximum: MAX_RESULTS_LIMIT,
216
+ description:
217
+ "Maximum number of matches to return. Defaults to 100.",
218
+ },
219
+ },
220
+
221
+ required: [
222
+ "query",
223
+ ],
224
+
225
+ additionalProperties: false,
226
+ },
227
+
228
+ async execute(
229
+ input,
230
+ context = {}
231
+ ) {
232
+ if (
233
+ typeof input?.query !== "string" ||
234
+ input.query.length === 0
235
+ ) {
236
+ throw new TypeError(
237
+ "query must be a non-empty string."
238
+ );
239
+ }
240
+
241
+ const searchPath =
242
+ input.path ?? ".";
243
+
244
+ const caseSensitive =
245
+ input.caseSensitive ?? false;
246
+
247
+ if (
248
+ typeof caseSensitive !== "boolean"
249
+ ) {
250
+ throw new TypeError(
251
+ "caseSensitive must be a boolean."
252
+ );
253
+ }
254
+
255
+ const maxResults =
256
+ normalizeMaxResults(
257
+ input.maxResults
258
+ );
259
+
260
+ const {
261
+ root,
262
+ resolved,
263
+ } = resolveProjectPath(
264
+ context.projectRoot,
265
+ searchPath
266
+ );
267
+
268
+ let stats;
269
+
270
+ try {
271
+ stats = await fs.stat(
272
+ resolved
273
+ );
274
+ } catch (error) {
275
+ if (error?.code === "ENOENT") {
276
+ throw createToolError(
277
+ "path_not_found",
278
+ `Search path not found: ${searchPath}`
279
+ );
280
+ }
281
+
282
+ throw error;
283
+ }
284
+
285
+ const results = [];
286
+
287
+ if (stats.isFile()) {
288
+ await searchFile({
289
+ filePath: resolved,
290
+ root,
291
+ query: input.query,
292
+ caseSensitive,
293
+ results,
294
+ maxResults,
295
+ });
296
+ } else if (stats.isDirectory()) {
297
+ await walk({
298
+ directory: resolved,
299
+ root,
300
+ query: input.query,
301
+ caseSensitive,
302
+ results,
303
+ maxResults,
304
+ });
305
+ } else {
306
+ throw createToolError(
307
+ "invalid_search_path",
308
+ `Search path is not a file or directory: ${searchPath}`
309
+ );
310
+ }
311
+
312
+ return {
313
+ query: input.query,
314
+ path: searchPath,
315
+ caseSensitive,
316
+ matches: results,
317
+ count: results.length,
318
+ truncated:
319
+ results.length >= maxResults,
320
+ };
321
+ },
322
+ });
@@ -0,0 +1,352 @@
1
+ import {
2
+ spawn,
3
+ } from "node:child_process";
4
+
5
+ import {
6
+ defineTool,
7
+ } from "../core/tool.js";
8
+
9
+ function createShellError(
10
+ code,
11
+ message,
12
+ details = null
13
+ ) {
14
+ const error =
15
+ new Error(message);
16
+
17
+ error.code = code;
18
+
19
+ if (details !== null) {
20
+ error.details = details;
21
+ }
22
+
23
+ return error;
24
+ }
25
+
26
+ /**
27
+ * Execute a command in the project.
28
+ *
29
+ * Approval/capability authorization belongs
30
+ * to the agent/tool execution layer.
31
+ *
32
+ * @param {object} options
33
+ * @param {string} options.command
34
+ * @param {string} [options.cwd]
35
+ * @param {number} [options.timeoutMs=120000]
36
+ * @param {number} [options.maxOutputBytes=200000]
37
+ * @returns {Promise<object>}
38
+ */
39
+ export async function executeShell({
40
+ command,
41
+ cwd = process.cwd(),
42
+ timeoutMs = 120_000,
43
+ maxOutputBytes = 200_000,
44
+ } = {}) {
45
+ if (
46
+ typeof command !== "string" ||
47
+ command.trim().length === 0
48
+ ) {
49
+ throw new TypeError(
50
+ "Shell command must be a non-empty string."
51
+ );
52
+ }
53
+
54
+ if (
55
+ typeof cwd !== "string" ||
56
+ cwd.trim().length === 0
57
+ ) {
58
+ throw new TypeError(
59
+ "Shell cwd must be a non-empty string."
60
+ );
61
+ }
62
+
63
+ if (
64
+ !Number.isFinite(timeoutMs) ||
65
+ timeoutMs <= 0
66
+ ) {
67
+ throw new TypeError(
68
+ "Shell timeoutMs must be a positive number."
69
+ );
70
+ }
71
+
72
+ if (
73
+ !Number.isFinite(maxOutputBytes) ||
74
+ maxOutputBytes <= 0
75
+ ) {
76
+ throw new TypeError(
77
+ "Shell maxOutputBytes must be a positive number."
78
+ );
79
+ }
80
+
81
+ return new Promise((resolve) => {
82
+ let stdout = "";
83
+ let stderr = "";
84
+
85
+ let stdoutTruncated = false;
86
+ let stderrTruncated = false;
87
+
88
+ let timedOut = false;
89
+ let settled = false;
90
+
91
+ const appendOutput = (
92
+ current,
93
+ chunk
94
+ ) => {
95
+ const text =
96
+ chunk.toString();
97
+
98
+ const next =
99
+ current + text;
100
+
101
+ if (
102
+ Buffer.byteLength(
103
+ next,
104
+ "utf8"
105
+ ) <= maxOutputBytes
106
+ ) {
107
+ return {
108
+ value: next,
109
+ truncated: false,
110
+ };
111
+ }
112
+
113
+ const buffer =
114
+ Buffer.from(
115
+ next,
116
+ "utf8"
117
+ );
118
+
119
+ return {
120
+ value:
121
+ buffer
122
+ .subarray(
123
+ 0,
124
+ maxOutputBytes
125
+ )
126
+ .toString("utf8"),
127
+
128
+ truncated: true,
129
+ };
130
+ };
131
+
132
+ const child =
133
+ spawn(
134
+ command,
135
+ {
136
+ cwd,
137
+ shell: true,
138
+ windowsHide: true,
139
+
140
+ stdio: [
141
+ "ignore",
142
+ "pipe",
143
+ "pipe",
144
+ ],
145
+ }
146
+ );
147
+
148
+ const timer =
149
+ setTimeout(() => {
150
+ timedOut = true;
151
+
152
+ try {
153
+ child.kill();
154
+ } catch {
155
+ // Process may already have exited.
156
+ }
157
+ }, timeoutMs);
158
+
159
+ child.stdout.on(
160
+ "data",
161
+ (chunk) => {
162
+ const result =
163
+ appendOutput(
164
+ stdout,
165
+ chunk
166
+ );
167
+
168
+ stdout =
169
+ result.value;
170
+
171
+ stdoutTruncated =
172
+ stdoutTruncated ||
173
+ result.truncated;
174
+ }
175
+ );
176
+
177
+ child.stderr.on(
178
+ "data",
179
+ (chunk) => {
180
+ const result =
181
+ appendOutput(
182
+ stderr,
183
+ chunk
184
+ );
185
+
186
+ stderr =
187
+ result.value;
188
+
189
+ stderrTruncated =
190
+ stderrTruncated ||
191
+ result.truncated;
192
+ }
193
+ );
194
+
195
+ const finish = ({
196
+ code = null,
197
+ signal = null,
198
+ error = null,
199
+ } = {}) => {
200
+ if (settled) {
201
+ return;
202
+ }
203
+
204
+ settled = true;
205
+
206
+ clearTimeout(timer);
207
+
208
+ if (error) {
209
+ resolve({
210
+ success: false,
211
+
212
+ output: {
213
+ command,
214
+ cwd,
215
+ stdout,
216
+ stderr,
217
+ stdoutTruncated,
218
+ stderrTruncated,
219
+ exitCode: code,
220
+ signal,
221
+ timedOut,
222
+ },
223
+
224
+ error: {
225
+ code:
226
+ error?.code ??
227
+ "shell_execution_error",
228
+
229
+ message:
230
+ error?.message ??
231
+ "Shell execution failed.",
232
+ },
233
+ });
234
+
235
+ return;
236
+ }
237
+
238
+ resolve({
239
+ success:
240
+ !timedOut &&
241
+ code === 0,
242
+
243
+ output: {
244
+ command,
245
+ cwd,
246
+ stdout,
247
+ stderr,
248
+ stdoutTruncated,
249
+ stderrTruncated,
250
+ exitCode: code,
251
+ signal,
252
+ timedOut,
253
+ },
254
+ });
255
+ };
256
+
257
+ child.on(
258
+ "error",
259
+ (error) => {
260
+ finish({
261
+ error,
262
+ });
263
+ }
264
+ );
265
+
266
+ child.on(
267
+ "close",
268
+ (code, signal) => {
269
+ finish({
270
+ code,
271
+ signal,
272
+ });
273
+ }
274
+ );
275
+ });
276
+ };
277
+
278
+ /**
279
+ * CodeForge command-execution tool.
280
+ */
281
+ export const shellTool =
282
+ defineTool({
283
+ name: "execute_command",
284
+
285
+ description:
286
+ "Execute a shell command in the current project. Use this to run tests, builds, scripts, git commands, or other project commands. Execution requires explicit user approval.",
287
+
288
+ source: "builtin",
289
+
290
+ capabilities: [
291
+ "process.execute",
292
+ ],
293
+
294
+ sideEffect: "execute",
295
+
296
+ approval: "always",
297
+
298
+ inputSchema: {
299
+ type: "object",
300
+
301
+ properties: {
302
+ command: {
303
+ type: "string",
304
+ minLength: 1,
305
+ description:
306
+ "Shell command to execute.",
307
+ },
308
+ },
309
+
310
+ required: [
311
+ "command",
312
+ ],
313
+
314
+ additionalProperties: false,
315
+ },
316
+
317
+ async execute(
318
+ input,
319
+ context = {}
320
+ ) {
321
+ const command =
322
+ input?.command;
323
+
324
+ if (
325
+ typeof command !== "string" ||
326
+ command.trim().length === 0
327
+ ) {
328
+ throw createShellError(
329
+ "invalid_command",
330
+ "Command must be a non-empty string."
331
+ );
332
+ }
333
+
334
+ const projectRoot =
335
+ context.projectRoot;
336
+
337
+ if (
338
+ typeof projectRoot !== "string" ||
339
+ projectRoot.trim().length === 0
340
+ ) {
341
+ throw createShellError(
342
+ "project_root_required",
343
+ "A project root is required for command execution."
344
+ );
345
+ }
346
+
347
+ return executeShell({
348
+ command,
349
+ cwd: projectRoot,
350
+ });
351
+ },
352
+ });