@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,9 @@
1
+ import ora from "ora";
2
+
3
+ export function createSpinner(text = "Loading...") {
4
+ return ora({
5
+ text,
6
+ spinner: "dots",
7
+ discardStdin: false
8
+ });
9
+ }
@@ -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
+ }