headlesscode 1.0.2

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 (232) hide show
  1. package/ATTRIBUTION.md +53 -0
  2. package/CODE_OF_CONDUCT.md +130 -0
  3. package/CONTRIBUTING.md +107 -0
  4. package/LICENSE +202 -0
  5. package/README.md +486 -0
  6. package/SECURITY.md +211 -0
  7. package/bin/headlesscode.mjs +83 -0
  8. package/package.json +63 -0
  9. package/shared/prompts/review-mode-prompt-short.md +93 -0
  10. package/shared/prompts/review-mode-prompt.md +281 -0
  11. package/shared/rules-code/rules.md +22 -0
  12. package/shared/stacks/cpp/rules.md +30 -0
  13. package/shared/stacks/fastapi/rules.md +30 -0
  14. package/shared/stacks/javascript/rules.md +37 -0
  15. package/shared/stacks/postgresql/rules.md +31 -0
  16. package/shared/stacks/python/rules.md +35 -0
  17. package/shared/stacks/react/rules.md +11 -0
  18. package/shared/stacks/typescript/rules.md +10 -0
  19. package/src/budget/budget.ts +221 -0
  20. package/src/budget/concurrency.ts +126 -0
  21. package/src/budget/cost.ts +309 -0
  22. package/src/budget/index.ts +8 -0
  23. package/src/checkpoints/cli.ts +256 -0
  24. package/src/checkpoints/service.ts +227 -0
  25. package/src/cli.ts +1535 -0
  26. package/src/cloud/docker-provider.ts +334 -0
  27. package/src/cloud/provider.ts +300 -0
  28. package/src/codeintel/call-graph.ts +78 -0
  29. package/src/codeintel/find-references.ts +123 -0
  30. package/src/codeintel/go-to-definition.ts +193 -0
  31. package/src/codeintel/handlers.ts +190 -0
  32. package/src/codeintel/import-graph.ts +173 -0
  33. package/src/codeintel/outline.ts +180 -0
  34. package/src/codeintel/position.ts +77 -0
  35. package/src/codeintel/program.ts +350 -0
  36. package/src/codeintel/rename-symbol.ts +213 -0
  37. package/src/codeintel/tools.ts +280 -0
  38. package/src/codemap/build.ts +135 -0
  39. package/src/codemap/cli.ts +190 -0
  40. package/src/codemap/extract.ts +339 -0
  41. package/src/codemap/files.ts +236 -0
  42. package/src/codemap/fingerprint.ts +65 -0
  43. package/src/codemap/flows.ts +62 -0
  44. package/src/codemap/html.ts +451 -0
  45. package/src/codemap/lock.ts +80 -0
  46. package/src/codemap/types.ts +101 -0
  47. package/src/codesearch/airunner-embedder.ts +185 -0
  48. package/src/codesearch/chunk.ts +339 -0
  49. package/src/codesearch/cli.ts +223 -0
  50. package/src/codesearch/embedder.ts +332 -0
  51. package/src/codesearch/files.ts +280 -0
  52. package/src/codesearch/index.ts +469 -0
  53. package/src/codesearch/ollama-embedder.ts +205 -0
  54. package/src/codesearch/search.ts +141 -0
  55. package/src/codesearch/types.ts +100 -0
  56. package/src/config/mode-models.ts +218 -0
  57. package/src/dashboard/aggregate.ts +364 -0
  58. package/src/dashboard/chat-thread.ts +141 -0
  59. package/src/dashboard/checkpoints.ts +124 -0
  60. package/src/dashboard/cli.ts +193 -0
  61. package/src/dashboard/codemap.ts +44 -0
  62. package/src/dashboard/files.ts +121 -0
  63. package/src/dashboard/page.ts +2803 -0
  64. package/src/dashboard/self-improvement-metrics.ts +282 -0
  65. package/src/dashboard/server.ts +1103 -0
  66. package/src/dashboard/session-launch.ts +310 -0
  67. package/src/dashboard/timeline.ts +273 -0
  68. package/src/dashboard/tool-exec.ts +107 -0
  69. package/src/dashboard/trend-cli.ts +141 -0
  70. package/src/dashboard/trend.ts +413 -0
  71. package/src/decision-proxy/cli.ts +261 -0
  72. package/src/decision-proxy/proxy.ts +569 -0
  73. package/src/deploy/gate-cli.ts +147 -0
  74. package/src/deploy/gate.ts +254 -0
  75. package/src/engine/condense.ts +512 -0
  76. package/src/engine/events.ts +428 -0
  77. package/src/engine/handoff.ts +71 -0
  78. package/src/engine/lazy-tools.ts +160 -0
  79. package/src/engine/local-explore.ts +653 -0
  80. package/src/engine/logger.ts +96 -0
  81. package/src/engine/loop.ts +5517 -0
  82. package/src/engine/parser.ts +347 -0
  83. package/src/engine/prompt.ts +860 -0
  84. package/src/engine/reports.ts +47 -0
  85. package/src/engine/stacks.ts +448 -0
  86. package/src/engine/types.ts +291 -0
  87. package/src/engine/usage.ts +186 -0
  88. package/src/github/app-auth.ts +161 -0
  89. package/src/github/cli.ts +448 -0
  90. package/src/github/installations.ts +133 -0
  91. package/src/github/pr.ts +321 -0
  92. package/src/github/provision.ts +118 -0
  93. package/src/github/push.ts +122 -0
  94. package/src/index-util.ts +50 -0
  95. package/src/index.ts +81 -0
  96. package/src/init/cli.ts +248 -0
  97. package/src/init/gitignore.ts +74 -0
  98. package/src/llm/ollama.ts +308 -0
  99. package/src/llm/openrouter.ts +868 -0
  100. package/src/llm/preflight.ts +367 -0
  101. package/src/llm/transcript-capture.ts +84 -0
  102. package/src/memory/embed.ts +110 -0
  103. package/src/memory/index.ts +22 -0
  104. package/src/memory/local.ts +259 -0
  105. package/src/memory/summarizer.ts +283 -0
  106. package/src/memory/types.ts +153 -0
  107. package/src/memory/uwuchat.ts +157 -0
  108. package/src/migrate/cli.ts +115 -0
  109. package/src/orchestrator/analyze-cli.ts +104 -0
  110. package/src/orchestrator/auto-split.ts +206 -0
  111. package/src/orchestrator/cleanup.ts +1003 -0
  112. package/src/orchestrator/cli.ts +3571 -0
  113. package/src/orchestrator/cost-estimate.ts +564 -0
  114. package/src/orchestrator/cost-history-cli.ts +242 -0
  115. package/src/orchestrator/cost-history.ts +397 -0
  116. package/src/orchestrator/git-sync.ts +250 -0
  117. package/src/orchestrator/index.ts +153 -0
  118. package/src/orchestrator/log-analysis.ts +0 -0
  119. package/src/orchestrator/merge-check.ts +108 -0
  120. package/src/orchestrator/pipeline.ts +411 -0
  121. package/src/orchestrator/resume.ts +1940 -0
  122. package/src/orchestrator/reviewer.ts +503 -0
  123. package/src/orchestrator/split.ts +296 -0
  124. package/src/orchestrator/state.ts +542 -0
  125. package/src/orchestrator/status.ts +697 -0
  126. package/src/orchestrator/verification-gate.ts +134 -0
  127. package/src/orchestrator/watch.ts +898 -0
  128. package/src/permissions/commands.ts +1083 -0
  129. package/src/permissions/config.ts +241 -0
  130. package/src/permissions/index.ts +12 -0
  131. package/src/permissions/protected-files.ts +96 -0
  132. package/src/permissions/store-protection.ts +272 -0
  133. package/src/project-store.ts +648 -0
  134. package/src/projects/cli.ts +382 -0
  135. package/src/qa/qa.ts +487 -0
  136. package/src/tools/browser/handler.ts +346 -0
  137. package/src/tools/browser/service.ts +406 -0
  138. package/src/tools/browser/smoke.ts +78 -0
  139. package/src/tools/browser/tool.ts +99 -0
  140. package/src/tools/executor.ts +2575 -0
  141. package/src/tools/language-detect.ts +183 -0
  142. package/src/tools/output-summarizer.ts +369 -0
  143. package/src/tools/run-tests.ts +302 -0
  144. package/src/tools/set-indentation-tool.ts +49 -0
  145. package/src/tools/test-selection.ts +160 -0
  146. package/src/vendor/tests/smoke.ts +103 -0
  147. package/src/vendor/zoo-code/VENDOR-NOTES.md +213 -0
  148. package/src/vendor/zoo-code/shim/anthropic.ts +71 -0
  149. package/src/vendor/zoo-code/shim/openai.d.ts +60 -0
  150. package/src/vendor/zoo-code/shim/os-name.ts +18 -0
  151. package/src/vendor/zoo-code/shim/strip-bom.ts +14 -0
  152. package/src/vendor/zoo-code/shim/vscode.ts +76 -0
  153. package/src/vendor/zoo-code/src/core/config/CustomModesManager.ts +1015 -0
  154. package/src/vendor/zoo-code/src/core/diff/strategies/multi-search-replace.ts +670 -0
  155. package/src/vendor/zoo-code/src/core/prompts/sections/capabilities.ts +46 -0
  156. package/src/vendor/zoo-code/src/core/prompts/sections/custom-instructions.ts +559 -0
  157. package/src/vendor/zoo-code/src/core/prompts/sections/index.ts +10 -0
  158. package/src/vendor/zoo-code/src/core/prompts/sections/markdown-formatting.ts +7 -0
  159. package/src/vendor/zoo-code/src/core/prompts/sections/modes.ts +35 -0
  160. package/src/vendor/zoo-code/src/core/prompts/sections/objective.ts +13 -0
  161. package/src/vendor/zoo-code/src/core/prompts/sections/rules.ts +95 -0
  162. package/src/vendor/zoo-code/src/core/prompts/sections/skills.ts +105 -0
  163. package/src/vendor/zoo-code/src/core/prompts/sections/system-info.ts +30 -0
  164. package/src/vendor/zoo-code/src/core/prompts/sections/tool-use-guidelines.ts +9 -0
  165. package/src/vendor/zoo-code/src/core/prompts/sections/tool-use.ts +7 -0
  166. package/src/vendor/zoo-code/src/core/prompts/system.ts +176 -0
  167. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/access_mcp_resource.ts +41 -0
  168. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/apply_diff.ts +40 -0
  169. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/apply_patch.ts +61 -0
  170. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/ask_followup_question.ts +62 -0
  171. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/attempt_completion.ts +33 -0
  172. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/codebase_search.ts +43 -0
  173. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/converters.ts +109 -0
  174. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/edit.ts +48 -0
  175. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/edit_file.ts +72 -0
  176. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/execute_command.ts +54 -0
  177. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/generate_image.ts +51 -0
  178. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/index.ts +75 -0
  179. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/list_files.ts +41 -0
  180. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/mcp_server.ts +75 -0
  181. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/new_task.ts +39 -0
  182. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/read_command_output.ts +81 -0
  183. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/read_file.ts +169 -0
  184. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/run_slash_command.ts +31 -0
  185. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/search_files.ts +50 -0
  186. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/search_replace.ts +51 -0
  187. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/skill.ts +33 -0
  188. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/switch_mode.ts +31 -0
  189. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/update_todo_list.ts +54 -0
  190. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/write_to_file.ts +40 -0
  191. package/src/vendor/zoo-code/src/core/prompts/types.ts +12 -0
  192. package/src/vendor/zoo-code/src/i18n/index.ts +19 -0
  193. package/src/vendor/zoo-code/src/integrations/misc/extract-text.ts +81 -0
  194. package/src/vendor/zoo-code/src/services/checkpoints/RepoPerTaskCheckpointService.ts +15 -0
  195. package/src/vendor/zoo-code/src/services/checkpoints/ShadowCheckpointService.ts +553 -0
  196. package/src/vendor/zoo-code/src/services/checkpoints/excludes.ts +212 -0
  197. package/src/vendor/zoo-code/src/services/checkpoints/index.ts +3 -0
  198. package/src/vendor/zoo-code/src/services/checkpoints/types.ts +35 -0
  199. package/src/vendor/zoo-code/src/services/code-index/manager.ts +19 -0
  200. package/src/vendor/zoo-code/src/services/mcp/McpHub.ts +36 -0
  201. package/src/vendor/zoo-code/src/services/roo-config/index.ts +441 -0
  202. package/src/vendor/zoo-code/src/services/search/file-search.ts +143 -0
  203. package/src/vendor/zoo-code/src/services/skills/SkillsManager.ts +20 -0
  204. package/src/vendor/zoo-code/src/shared/globalFileNames.ts +9 -0
  205. package/src/vendor/zoo-code/src/shared/language.ts +43 -0
  206. package/src/vendor/zoo-code/src/shared/modes.ts +257 -0
  207. package/src/vendor/zoo-code/src/shared/tools.ts +385 -0
  208. package/src/vendor/zoo-code/src/utils/fs.ts +39 -0
  209. package/src/vendor/zoo-code/src/utils/globalContext.ts +22 -0
  210. package/src/vendor/zoo-code/src/utils/json-schema.ts +16 -0
  211. package/src/vendor/zoo-code/src/utils/logging.ts +21 -0
  212. package/src/vendor/zoo-code/src/utils/mcp-name.ts +190 -0
  213. package/src/vendor/zoo-code/src/utils/object.ts +18 -0
  214. package/src/vendor/zoo-code/src/utils/path.ts +94 -0
  215. package/src/vendor/zoo-code/src/utils/shell.ts +376 -0
  216. package/src/vendor/zoo-code/src/utils/text-normalization.ts +99 -0
  217. package/src/vendor/zoo-code/types/global-settings.ts +19 -0
  218. package/src/vendor/zoo-code/types/index.ts +22 -0
  219. package/src/vendor/zoo-code/types/message.ts +375 -0
  220. package/src/vendor/zoo-code/types/mode.ts +241 -0
  221. package/src/vendor/zoo-code/types/todo.ts +19 -0
  222. package/src/vendor/zoo-code/types/tool-params.ts +116 -0
  223. package/src/vendor/zoo-code/types/tool.ts +67 -0
  224. package/src/vendor/zoo-code/types/vscode.ts +84 -0
  225. package/src/vision/describe.ts +242 -0
  226. package/src/vision/tool.ts +91 -0
  227. package/src/watcher/cli.ts +369 -0
  228. package/src/watcher/github.ts +304 -0
  229. package/src/watcher/index.ts +59 -0
  230. package/src/watcher/state.ts +254 -0
  231. package/src/watcher/watch.ts +562 -0
  232. package/tsconfig.json +18 -0
@@ -0,0 +1,46 @@
1
+ import { McpHub } from "../../../services/mcp/McpHub"
2
+
3
+ /**
4
+ * Builds the CAPABILITIES section of the system prompt.
5
+ *
6
+ * The MCP availability line is only emitted when at least one MCP server is actually
7
+ * exposed to the current mode. When `allowedMcpServers` is provided, the hub's server
8
+ * list is filtered by that allowlist BEFORE deciding whether to advertise MCP, so the
9
+ * capability text matches the per-mode tool exposure:
10
+ * - `undefined` allowlist → all connected servers count (backward compatible)
11
+ * - empty `[]` allowlist → no servers count ⇒ MCP line omitted
12
+ * - populated allowlist → only listed servers count
13
+ *
14
+ * @param cwd Current working directory used in the prompt text.
15
+ * @param mcpHub Optional MCP hub. When omitted, the MCP line is never emitted.
16
+ * @param allowedMcpServers Optional per-mode allowlist of MCP server names. When provided,
17
+ * the hub's servers are filtered to this set before determining MCP availability.
18
+ */
19
+ export function getCapabilitiesSection(cwd: string, mcpHub?: McpHub, allowedMcpServers?: string[]): string {
20
+ // Determine whether any MCP server is actually available to the current mode.
21
+ // Filtering the hub's servers by the allowlist (when provided) keeps the capability
22
+ // text consistent with the tools that are exposed for the mode.
23
+ let hasMcpServers = false
24
+ if (mcpHub) {
25
+ let servers = mcpHub.getServers()
26
+ if (allowedMcpServers) {
27
+ const allowSet = new Set(allowedMcpServers)
28
+ servers = servers.filter((server) => allowSet.has(server.name))
29
+ }
30
+ hasMcpServers = servers.length > 0
31
+ }
32
+
33
+ return `====
34
+
35
+ CAPABILITIES
36
+
37
+ - You have access to tools that let you execute CLI commands on the user's computer, list files, view source code definitions, regex search, read and write files, and ask follow-up questions. These tools help you effectively accomplish a wide range of tasks, such as writing code, making edits or improvements to existing files, understanding the current state of a project, performing system operations, and much more.
38
+ - When the user initially gives you a task, a recursive list of all filepaths in the current workspace directory ('${cwd}') will be included in environment_details. This provides an overview of the project's file structure, offering key insights into the project from directory/file names (how developers conceptualize and organize their code) and file extensions (the language used). This can also guide decision-making on which files to explore further. If you need to further explore directories such as outside the current workspace directory, you can use the list_files tool. If you pass 'true' for the recursive parameter, it will list files recursively. Otherwise, it will list files at the top level, which is better suited for generic directories where you don't necessarily need the nested structure, like the Desktop.
39
+ - You can use the execute_command tool to run commands on the user's computer whenever you feel it can help accomplish the user's task. When you need to execute a CLI command, you must provide a clear explanation of what the command does. Prefer to execute complex CLI commands over creating executable scripts, since they are more flexible and easier to run. Interactive and long-running commands are allowed, since the commands are run in the user's VSCode terminal. The user may keep commands running in the background and you will be kept updated on their status along the way. Each command you execute is run in a new terminal instance.${
40
+ hasMcpServers
41
+ ? `
42
+ - You have access to MCP servers that may provide additional tools and resources. Each server may provide different capabilities that you can use to accomplish tasks more effectively.
43
+ `
44
+ : ""
45
+ }`
46
+ }
@@ -0,0 +1,559 @@
1
+ import fs from "fs/promises"
2
+ import path from "path"
3
+ import * as os from "os"
4
+ import { Dirent } from "fs"
5
+
6
+ import { isLanguage } from "../../../../types"
7
+
8
+ import type { SystemPromptSettings } from "../types"
9
+
10
+ import { LANGUAGES } from "../../../shared/language"
11
+ import {
12
+ getRooDirectoriesForCwd,
13
+ getAllRooDirectoriesForCwd,
14
+ getAgentsDirectoriesForCwd,
15
+ getGlobalRooDirectory,
16
+ } from "../../../services/roo-config"
17
+
18
+ /**
19
+ * Safely read a file and return its trimmed content
20
+ */
21
+ async function safeReadFile(filePath: string): Promise<string> {
22
+ try {
23
+ const content = await fs.readFile(filePath, "utf-8")
24
+ return content.trim()
25
+ } catch (err) {
26
+ const errorCode = (err as NodeJS.ErrnoException).code
27
+ if (!errorCode || !["ENOENT", "EISDIR"].includes(errorCode)) {
28
+ throw err
29
+ }
30
+ return ""
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Check if a directory exists
36
+ */
37
+ async function directoryExists(dirPath: string): Promise<boolean> {
38
+ try {
39
+ const stats = await fs.stat(dirPath)
40
+ return stats.isDirectory()
41
+ } catch (err) {
42
+ return false
43
+ }
44
+ }
45
+
46
+ const MAX_DEPTH = 5
47
+
48
+ /**
49
+ * Recursively resolve directory entries and collect file paths
50
+ */
51
+ async function resolveDirectoryEntry(
52
+ entry: Dirent,
53
+ dirPath: string,
54
+ fileInfo: Array<{ originalPath: string; resolvedPath: string }>,
55
+ depth: number,
56
+ ): Promise<void> {
57
+ // Avoid cyclic symlinks
58
+ if (depth > MAX_DEPTH) {
59
+ return
60
+ }
61
+
62
+ const fullPath = path.resolve(entry.parentPath || dirPath, entry.name)
63
+ if (entry.isFile()) {
64
+ // Regular file - both original and resolved paths are the same
65
+ fileInfo.push({ originalPath: fullPath, resolvedPath: fullPath })
66
+ } else if (entry.isSymbolicLink()) {
67
+ // Await the resolution of the symbolic link
68
+ await resolveSymLink(fullPath, fileInfo, depth + 1)
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Recursively resolve a symbolic link and collect file paths
74
+ */
75
+ async function resolveSymLink(
76
+ symlinkPath: string,
77
+ fileInfo: Array<{ originalPath: string; resolvedPath: string }>,
78
+ depth: number,
79
+ ): Promise<void> {
80
+ // Avoid cyclic symlinks
81
+ if (depth > MAX_DEPTH) {
82
+ return
83
+ }
84
+ try {
85
+ // Get the symlink target
86
+ const linkTarget = await fs.readlink(symlinkPath)
87
+ // Resolve the target path relative to the symlink's real parent directory.
88
+ // We must use realpath on the parent directory because the symlink itself
89
+ // may be inside a symlinked directory. Relative symlink targets are resolved
90
+ // by the filesystem relative to the symlink's actual location, not the path
91
+ // used to access it.
92
+ const symlinkDir = path.dirname(symlinkPath)
93
+ let realSymlinkDir: string
94
+ try {
95
+ realSymlinkDir = await fs.realpath(symlinkDir)
96
+ } catch {
97
+ realSymlinkDir = symlinkDir
98
+ }
99
+ const resolvedTarget = path.resolve(realSymlinkDir, linkTarget)
100
+
101
+ // Check if the target is a file
102
+ const stats = await fs.stat(resolvedTarget)
103
+ if (stats.isFile()) {
104
+ // For symlinks to files, store the symlink path as original and target as resolved
105
+ fileInfo.push({
106
+ originalPath: symlinkPath,
107
+ resolvedPath: resolvedTarget,
108
+ })
109
+ } else if (stats.isDirectory()) {
110
+ const anotherEntries = await fs.readdir(resolvedTarget, {
111
+ withFileTypes: true,
112
+ recursive: true,
113
+ })
114
+ // Collect promises for recursive calls within the directory
115
+ const directoryPromises: Promise<void>[] = []
116
+ for (const anotherEntry of anotherEntries) {
117
+ directoryPromises.push(resolveDirectoryEntry(anotherEntry, resolvedTarget, fileInfo, depth + 1))
118
+ }
119
+ // Wait for all entries in the resolved directory to be processed
120
+ await Promise.all(directoryPromises)
121
+ } else if (stats.isSymbolicLink()) {
122
+ // Handle nested symlinks by awaiting the recursive call
123
+ await resolveSymLink(resolvedTarget, fileInfo, depth + 1)
124
+ }
125
+ } catch (err) {
126
+ // Skip invalid symlinks
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Read all text files from a directory in alphabetical order
132
+ */
133
+ async function readTextFilesFromDirectory(dirPath: string): Promise<Array<{ filename: string; content: string }>> {
134
+ try {
135
+ const entries = await fs.readdir(dirPath, {
136
+ withFileTypes: true,
137
+ recursive: true,
138
+ })
139
+
140
+ // Process all entries - regular files and symlinks that might point to files
141
+ // Store both original path (for sorting) and resolved path (for reading)
142
+ const fileInfo: Array<{ originalPath: string; resolvedPath: string }> = []
143
+ // Collect promises for the initial resolution calls
144
+ const initialPromises: Promise<void>[] = []
145
+
146
+ for (const entry of entries) {
147
+ initialPromises.push(resolveDirectoryEntry(entry, dirPath, fileInfo, 0))
148
+ }
149
+
150
+ // Wait for all asynchronous operations (including recursive ones) to complete
151
+ await Promise.all(initialPromises)
152
+
153
+ const fileContents = await Promise.all(
154
+ fileInfo.map(async ({ originalPath, resolvedPath }) => {
155
+ try {
156
+ // Check if it's a file (not a directory)
157
+ const stats = await fs.stat(resolvedPath)
158
+ if (stats.isFile()) {
159
+ // Filter out cache files and system files that shouldn't be in rules
160
+ if (!shouldIncludeRuleFile(resolvedPath)) {
161
+ return null
162
+ }
163
+ const content = await safeReadFile(resolvedPath)
164
+ // Use resolvedPath for display to maintain existing behavior
165
+ return { filename: resolvedPath, content, sortKey: originalPath }
166
+ }
167
+ return null
168
+ } catch (err) {
169
+ return null
170
+ }
171
+ }),
172
+ )
173
+
174
+ // Filter out null values (directories, failed reads, or excluded files)
175
+ const filteredFiles = fileContents.filter(
176
+ (item): item is { filename: string; content: string; sortKey: string } => item !== null,
177
+ )
178
+
179
+ // Sort files alphabetically by the original filename (case-insensitive) to ensure consistent order
180
+ // For symlinks, this will use the symlink name, not the target name
181
+ return filteredFiles
182
+ .sort((a, b) => {
183
+ const filenameA = path.basename(a.sortKey).toLowerCase()
184
+ const filenameB = path.basename(b.sortKey).toLowerCase()
185
+ return filenameA.localeCompare(filenameB)
186
+ })
187
+ .map(({ filename, content }) => ({ filename, content }))
188
+ } catch (err) {
189
+ return []
190
+ }
191
+ }
192
+
193
+ /**
194
+ * Format content from multiple files with filenames as headers
195
+ * @param files - Array of files with filename (absolute path) and content
196
+ * @param cwd - Current working directory for computing relative paths
197
+ */
198
+ function formatDirectoryContent(files: Array<{ filename: string; content: string }>, cwd: string): string {
199
+ if (files.length === 0) return ""
200
+
201
+ return files
202
+ .map((file) => {
203
+ // Compute relative path for display
204
+ const displayPath = path.relative(cwd, file.filename)
205
+ return `# Rules from ${displayPath}:\n${file.content}`
206
+ })
207
+ .join("\n\n")
208
+ }
209
+
210
+ /**
211
+ * Load rule files from global, project-local, and optionally subfolder directories
212
+ * Rules are loaded in order: global first, then project-local, then subfolders (alphabetically)
213
+ *
214
+ * @param cwd - Current working directory (project root)
215
+ * @param enableSubfolderRules - Whether to include rules from subdirectories (default: false)
216
+ */
217
+ export async function loadRuleFiles(cwd: string, enableSubfolderRules: boolean = false): Promise<string> {
218
+ const rules: string[] = []
219
+ // Use recursive discovery only if enableSubfolderRules is true
220
+ const rooDirectories = enableSubfolderRules ? await getAllRooDirectoriesForCwd(cwd) : getRooDirectoriesForCwd(cwd)
221
+
222
+ // Check for .roo/rules/ directories in order (global, project-local, and optionally subfolders)
223
+ for (const rooDir of rooDirectories) {
224
+ const rulesDir = path.join(rooDir, "rules")
225
+ if (await directoryExists(rulesDir)) {
226
+ const files = await readTextFilesFromDirectory(rulesDir)
227
+ if (files.length > 0) {
228
+ const content = formatDirectoryContent(files, cwd)
229
+ rules.push(content)
230
+ }
231
+ }
232
+ }
233
+
234
+ // If we found rules in .roo/rules/ directories, return them
235
+ if (rules.length > 0) {
236
+ return "\n# Rules from .roo directories:\n\n" + rules.join("\n\n")
237
+ }
238
+
239
+ // Fall back to existing behavior for legacy .roorules/.clinerules files
240
+ const ruleFiles = [".roorules", ".clinerules"]
241
+
242
+ for (const file of ruleFiles) {
243
+ const content = await safeReadFile(path.join(cwd, file))
244
+ if (content) {
245
+ return `\n# Rules from ${file}:\n${content}\n`
246
+ }
247
+ }
248
+
249
+ return ""
250
+ }
251
+
252
+ /**
253
+ * Read content from an agent rules file (AGENTS.md, AGENT.md, etc.)
254
+ * Handles symlink resolution.
255
+ *
256
+ * @param filePath - Full path to the agent rules file
257
+ * @returns File content or empty string if file doesn't exist
258
+ */
259
+ async function readAgentRulesFile(filePath: string): Promise<string> {
260
+ let resolvedPath = filePath
261
+
262
+ // Check if file exists and handle symlinks
263
+ try {
264
+ const stats = await fs.lstat(filePath)
265
+ if (stats.isSymbolicLink()) {
266
+ // Create a temporary fileInfo array to use with resolveSymLink
267
+ const fileInfo: Array<{
268
+ originalPath: string
269
+ resolvedPath: string
270
+ }> = []
271
+
272
+ // Use the existing resolveSymLink function to handle symlink resolution
273
+ await resolveSymLink(filePath, fileInfo, 0)
274
+
275
+ // Extract the resolved path from fileInfo
276
+ if (fileInfo.length > 0) {
277
+ resolvedPath = fileInfo[0].resolvedPath
278
+ }
279
+ }
280
+ } catch (err) {
281
+ // If lstat fails (file doesn't exist), return empty
282
+ return ""
283
+ }
284
+
285
+ // Read the content from the resolved path
286
+ return safeReadFile(resolvedPath)
287
+ }
288
+
289
+ /**
290
+ * Load AGENTS.md or AGENT.md file from a specific directory
291
+ * Checks for both AGENTS.md (standard) and AGENT.md (alternative) for compatibility
292
+ * Also loads AGENTS.local.md for personal overrides (not checked in to version control)
293
+ * AGENTS.local.md can be loaded even if AGENTS.md doesn't exist
294
+ *
295
+ * @param directory - Directory to check for AGENTS.md
296
+ * @param showPath - Whether to include the directory path in the header
297
+ * @param cwd - Current working directory for computing relative paths (optional)
298
+ */
299
+ async function loadAgentRulesFileFromDirectory(
300
+ directory: string,
301
+ showPath: boolean = false,
302
+ cwd?: string,
303
+ ): Promise<string> {
304
+ // Try both filenames - AGENTS.md (standard) first, then AGENT.md (alternative)
305
+ const filenames = ["AGENTS.md", "AGENT.md"]
306
+ const results: string[] = []
307
+ const displayPath = cwd ? path.relative(cwd, directory) : directory
308
+
309
+ for (const filename of filenames) {
310
+ try {
311
+ const agentPath = path.join(directory, filename)
312
+ const content = await readAgentRulesFile(agentPath)
313
+
314
+ if (content) {
315
+ // Compute relative path for display if cwd is provided
316
+ const header = showPath
317
+ ? `# Agent Rules Standard (${filename}) from ${displayPath}:`
318
+ : `# Agent Rules Standard (${filename}):`
319
+ results.push(`${header}\n${content}`)
320
+
321
+ // Found a standard file, don't check alternative
322
+ break
323
+ }
324
+ } catch (err) {
325
+ // Silently ignore errors - agent rules files are optional
326
+ }
327
+ }
328
+
329
+ // Always try to load AGENTS.local.md for personal overrides (even if AGENTS.md doesn't exist)
330
+ try {
331
+ const localFilename = "AGENTS.local.md"
332
+ const localPath = path.join(directory, localFilename)
333
+ const localContent = await readAgentRulesFile(localPath)
334
+
335
+ if (localContent) {
336
+ const localHeader = showPath
337
+ ? `# Agent Rules Local (${localFilename}) from ${displayPath}:`
338
+ : `# Agent Rules Local (${localFilename}):`
339
+ results.push(`${localHeader}\n${localContent}`)
340
+ }
341
+ } catch (err) {
342
+ // Silently ignore errors - local agent rules file is optional
343
+ }
344
+
345
+ return results.join("\n\n")
346
+ }
347
+
348
+ /**
349
+ * Load AGENTS.md or AGENT.md file from the project root if it exists
350
+ * Checks for both AGENTS.md (standard) and AGENT.md (alternative) for compatibility
351
+ *
352
+ * @deprecated Use loadAllAgentRulesFiles for loading from all directories
353
+ */
354
+ async function loadAgentRulesFile(cwd: string): Promise<string> {
355
+ return loadAgentRulesFileFromDirectory(cwd, false, cwd)
356
+ }
357
+
358
+ /**
359
+ * Load all AGENTS.md files from project root and optionally subdirectories with .roo folders
360
+ * Returns combined content with clear path headers for each file
361
+ *
362
+ * @param cwd - Current working directory (project root)
363
+ * @param enableSubfolderRules - Whether to include AGENTS.md from subdirectories (default: false)
364
+ * @returns Combined AGENTS.md content from all locations
365
+ */
366
+ async function loadAllAgentRulesFiles(cwd: string, enableSubfolderRules: boolean = false): Promise<string> {
367
+ const agentRules: string[] = []
368
+
369
+ // When subfolder rules are disabled, only load from root
370
+ if (!enableSubfolderRules) {
371
+ const content = await loadAgentRulesFileFromDirectory(cwd, false, cwd)
372
+ if (content && content.trim()) {
373
+ agentRules.push(content.trim())
374
+ }
375
+ return agentRules.join("\n\n")
376
+ }
377
+
378
+ // When enabled, load from root and all subdirectories with .roo folders
379
+ const directories = await getAgentsDirectoriesForCwd(cwd)
380
+
381
+ for (const directory of directories) {
382
+ // Show path for all directories except the root
383
+ const showPath = directory !== cwd
384
+ const content = await loadAgentRulesFileFromDirectory(directory, showPath, cwd)
385
+ if (content && content.trim()) {
386
+ agentRules.push(content.trim())
387
+ }
388
+ }
389
+
390
+ return agentRules.join("\n\n")
391
+ }
392
+
393
+ export async function addCustomInstructions(
394
+ modeCustomInstructions: string,
395
+ globalCustomInstructions: string,
396
+ cwd: string,
397
+ mode: string,
398
+ options: {
399
+ language?: string
400
+ rooIgnoreInstructions?: string
401
+ settings?: SystemPromptSettings
402
+ } = {},
403
+ ): Promise<string> {
404
+ const sections = []
405
+
406
+ // Get the enableSubfolderRules setting (default: false)
407
+ const enableSubfolderRules = options.settings?.enableSubfolderRules ?? false
408
+
409
+ // Load mode-specific rules if mode is provided
410
+ let modeRuleContent = ""
411
+ let usedRuleFile = ""
412
+
413
+ if (mode) {
414
+ const modeRules: string[] = []
415
+ // Use recursive discovery only if enableSubfolderRules is true
416
+ const rooDirectories = enableSubfolderRules
417
+ ? await getAllRooDirectoriesForCwd(cwd)
418
+ : getRooDirectoriesForCwd(cwd)
419
+
420
+ // Check for .roo/rules-${mode}/ directories in order (global, project-local, and optionally subfolders)
421
+ for (const rooDir of rooDirectories) {
422
+ const modeRulesDir = path.join(rooDir, `rules-${mode}`)
423
+ if (await directoryExists(modeRulesDir)) {
424
+ const files = await readTextFilesFromDirectory(modeRulesDir)
425
+ if (files.length > 0) {
426
+ const content = formatDirectoryContent(files, cwd)
427
+ modeRules.push(content)
428
+ }
429
+ }
430
+ }
431
+
432
+ // If we found mode-specific rules in .roo/rules-${mode}/ directories, use them
433
+ if (modeRules.length > 0) {
434
+ modeRuleContent = "\n" + modeRules.join("\n\n")
435
+ usedRuleFile = `rules-${mode} directories`
436
+ } else {
437
+ // Fall back to existing behavior for legacy files
438
+ const rooModeRuleFile = `.roorules-${mode}`
439
+ modeRuleContent = await safeReadFile(path.join(cwd, rooModeRuleFile))
440
+ if (modeRuleContent) {
441
+ usedRuleFile = rooModeRuleFile
442
+ } else {
443
+ const clineModeRuleFile = `.clinerules-${mode}`
444
+ modeRuleContent = await safeReadFile(path.join(cwd, clineModeRuleFile))
445
+ if (modeRuleContent) {
446
+ usedRuleFile = clineModeRuleFile
447
+ }
448
+ }
449
+ }
450
+ }
451
+
452
+ // Add language preference if provided
453
+ if (options.language) {
454
+ const languageName = isLanguage(options.language) ? LANGUAGES[options.language] : options.language
455
+ sections.push(
456
+ `Language Preference:\nYou should always speak and think in the "${languageName}" (${options.language}) language unless the user gives you instructions below to do otherwise.`,
457
+ )
458
+ }
459
+
460
+ // Add global instructions first
461
+ if (typeof globalCustomInstructions === "string" && globalCustomInstructions.trim()) {
462
+ sections.push(`Global Instructions:\n${globalCustomInstructions.trim()}`)
463
+ }
464
+
465
+ // Add mode-specific instructions after
466
+ if (typeof modeCustomInstructions === "string" && modeCustomInstructions.trim()) {
467
+ sections.push(`Mode-specific Instructions:\n${modeCustomInstructions.trim()}`)
468
+ }
469
+
470
+ // Add rules - include both mode-specific and generic rules if they exist
471
+ const rules = []
472
+
473
+ // Add mode-specific rules first if they exist
474
+ if (modeRuleContent && modeRuleContent.trim()) {
475
+ if (usedRuleFile.includes(path.join(".roo", `rules-${mode}`))) {
476
+ rules.push(modeRuleContent.trim())
477
+ } else {
478
+ rules.push(`# Rules from ${usedRuleFile}:\n${modeRuleContent}`)
479
+ }
480
+ }
481
+
482
+ if (options.rooIgnoreInstructions) {
483
+ rules.push(options.rooIgnoreInstructions)
484
+ }
485
+
486
+ // Add AGENTS.md content if enabled (default: true)
487
+ // Load from root and optionally subdirectories with .roo folders based on enableSubfolderRules setting
488
+ if (options.settings?.useAgentRules !== false) {
489
+ const agentRulesContent = await loadAllAgentRulesFiles(cwd, enableSubfolderRules)
490
+ if (agentRulesContent && agentRulesContent.trim()) {
491
+ rules.push(agentRulesContent.trim())
492
+ }
493
+ }
494
+
495
+ // Add generic rules
496
+ const genericRuleContent = await loadRuleFiles(cwd, enableSubfolderRules)
497
+ if (genericRuleContent && genericRuleContent.trim()) {
498
+ rules.push(genericRuleContent.trim())
499
+ }
500
+
501
+ if (rules.length > 0) {
502
+ sections.push(`Rules:\n\n${rules.join("\n\n")}`)
503
+ }
504
+
505
+ const joinedSections = sections.join("\n\n")
506
+
507
+ return joinedSections
508
+ ? `
509
+ ====
510
+
511
+ USER'S CUSTOM INSTRUCTIONS
512
+
513
+ The following additional instructions are provided by the user, and should be followed to the best of your ability.
514
+
515
+ ${joinedSections}
516
+ `
517
+ : ""
518
+ }
519
+
520
+ /**
521
+ * Check if a file should be included in rule compilation.
522
+ * Excludes cache files and system files that shouldn't be processed as rules.
523
+ */
524
+ function shouldIncludeRuleFile(filename: string): boolean {
525
+ const basename = path.basename(filename)
526
+
527
+ const cachePatterns = [
528
+ "*.DS_Store",
529
+ "*.bak",
530
+ "*.cache",
531
+ "*.crdownload",
532
+ "*.db",
533
+ "*.dmp",
534
+ "*.dump",
535
+ "*.eslintcache",
536
+ "*.lock",
537
+ "*.log",
538
+ "*.old",
539
+ "*.part",
540
+ "*.partial",
541
+ "*.pyc",
542
+ "*.pyo",
543
+ "*.stackdump",
544
+ "*.swo",
545
+ "*.swp",
546
+ "*.temp",
547
+ "*.tmp",
548
+ "Thumbs.db",
549
+ ]
550
+
551
+ return !cachePatterns.some((pattern) => {
552
+ if (pattern.startsWith("*.")) {
553
+ const extension = pattern.slice(1)
554
+ return basename.endsWith(extension)
555
+ } else {
556
+ return basename === pattern
557
+ }
558
+ })
559
+ }
@@ -0,0 +1,10 @@
1
+ export { getRulesSection } from "./rules"
2
+ export { getSystemInfoSection } from "./system-info"
3
+ export { getObjectiveSection } from "./objective"
4
+ export { addCustomInstructions } from "./custom-instructions"
5
+ export { getSharedToolUseSection } from "./tool-use"
6
+ export { getToolUseGuidelinesSection } from "./tool-use-guidelines"
7
+ export { getCapabilitiesSection } from "./capabilities"
8
+ export { getModesSection } from "./modes"
9
+ export { markdownFormattingSection } from "./markdown-formatting"
10
+ export { getSkillsSection } from "./skills"
@@ -0,0 +1,7 @@
1
+ export function markdownFormattingSection(): string {
2
+ return `====
3
+
4
+ MARKDOWN RULES
5
+
6
+ ALL responses MUST show ANY \`language construct\` OR filename reference as clickable, exactly as [\`filename OR language.declaration()\`](relative/file/path.ext:line); line is required for \`syntax\` and optional for filename links. This applies to ALL markdown responses and ALSO those in attempt_completion`
7
+ }
@@ -0,0 +1,35 @@
1
+ import * as vscode from "../../../../shim/vscode"
2
+
3
+ import type { ModeConfig } from "../../../../types"
4
+
5
+ import { getAllModesWithPrompts } from "../../../shared/modes"
6
+ import { ensureSettingsDirectoryExists } from "../../../utils/globalContext"
7
+
8
+ export async function getModesSection(context: vscode.ExtensionContext): Promise<string> {
9
+ // Make sure path gets created
10
+ await ensureSettingsDirectoryExists(context)
11
+
12
+ // Get all modes with their overrides from extension state
13
+ const allModes = await getAllModesWithPrompts(context)
14
+
15
+ const modesContent = `====
16
+
17
+ MODES
18
+
19
+ - These are the currently available modes:
20
+ ${allModes
21
+ .map((mode: ModeConfig) => {
22
+ let description: string
23
+ if (mode.whenToUse && mode.whenToUse.trim() !== "") {
24
+ // Use whenToUse as the primary description, indenting subsequent lines for readability
25
+ description = mode.whenToUse.replace(/\n/g, "\n ")
26
+ } else {
27
+ // Fallback to the first sentence of roleDefinition if whenToUse is not available
28
+ description = mode.roleDefinition.split(".")[0]
29
+ }
30
+ return ` * "${mode.name}" mode (${mode.slug}) - ${description}`
31
+ })
32
+ .join("\n")}`
33
+
34
+ return modesContent
35
+ }
@@ -0,0 +1,13 @@
1
+ export function getObjectiveSection(): string {
2
+ return `====
3
+
4
+ OBJECTIVE
5
+
6
+ You accomplish a given task iteratively, breaking it down into clear steps and working through them methodically.
7
+
8
+ 1. Analyze the user's task and set clear, achievable goals to accomplish it. Prioritize these goals in a logical order.
9
+ 2. Work through these goals sequentially, utilizing available tools one at a time as necessary. Each goal should correspond to a distinct step in your problem-solving process. You will be informed on the work completed and what's remaining as you go.
10
+ 3. Remember, you have extensive capabilities with access to a wide range of tools that can be used in powerful and clever ways as necessary to accomplish each goal. Before calling a tool, do some analysis. First, analyze the file structure provided in environment_details to gain context and insights for proceeding effectively. Next, think about which of the provided tools is the most relevant tool to accomplish the user's task. Go through each of the required parameters of the relevant tool and determine if the user has directly provided or given enough information to infer a value. When deciding if the parameter can be inferred, carefully consider all the context to see if it supports a specific value. If all of the required parameters are present or can be reasonably inferred, proceed with the tool use. BUT, if one of the values for a required parameter is missing, DO NOT invoke the tool (not even with fillers for the missing params) and instead, ask the user to provide the missing parameters using the ask_followup_question tool. DO NOT ask for more information on optional parameters if it is not provided.
11
+ 4. Once you've completed the user's task, you must use the attempt_completion tool to present the result of the task to the user.
12
+ 5. The user may provide feedback, which you can use to make improvements and try again. But DO NOT continue in pointless back and forth conversations, i.e. don't end your responses with questions or offers for further assistance.`
13
+ }