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.
- package/ATTRIBUTION.md +53 -0
- package/CODE_OF_CONDUCT.md +130 -0
- package/CONTRIBUTING.md +107 -0
- package/LICENSE +202 -0
- package/README.md +486 -0
- package/SECURITY.md +211 -0
- package/bin/headlesscode.mjs +83 -0
- package/package.json +63 -0
- package/shared/prompts/review-mode-prompt-short.md +93 -0
- package/shared/prompts/review-mode-prompt.md +281 -0
- package/shared/rules-code/rules.md +22 -0
- package/shared/stacks/cpp/rules.md +30 -0
- package/shared/stacks/fastapi/rules.md +30 -0
- package/shared/stacks/javascript/rules.md +37 -0
- package/shared/stacks/postgresql/rules.md +31 -0
- package/shared/stacks/python/rules.md +35 -0
- package/shared/stacks/react/rules.md +11 -0
- package/shared/stacks/typescript/rules.md +10 -0
- package/src/budget/budget.ts +221 -0
- package/src/budget/concurrency.ts +126 -0
- package/src/budget/cost.ts +309 -0
- package/src/budget/index.ts +8 -0
- package/src/checkpoints/cli.ts +256 -0
- package/src/checkpoints/service.ts +227 -0
- package/src/cli.ts +1535 -0
- package/src/cloud/docker-provider.ts +334 -0
- package/src/cloud/provider.ts +300 -0
- package/src/codeintel/call-graph.ts +78 -0
- package/src/codeintel/find-references.ts +123 -0
- package/src/codeintel/go-to-definition.ts +193 -0
- package/src/codeintel/handlers.ts +190 -0
- package/src/codeintel/import-graph.ts +173 -0
- package/src/codeintel/outline.ts +180 -0
- package/src/codeintel/position.ts +77 -0
- package/src/codeintel/program.ts +350 -0
- package/src/codeintel/rename-symbol.ts +213 -0
- package/src/codeintel/tools.ts +280 -0
- package/src/codemap/build.ts +135 -0
- package/src/codemap/cli.ts +190 -0
- package/src/codemap/extract.ts +339 -0
- package/src/codemap/files.ts +236 -0
- package/src/codemap/fingerprint.ts +65 -0
- package/src/codemap/flows.ts +62 -0
- package/src/codemap/html.ts +451 -0
- package/src/codemap/lock.ts +80 -0
- package/src/codemap/types.ts +101 -0
- package/src/codesearch/airunner-embedder.ts +185 -0
- package/src/codesearch/chunk.ts +339 -0
- package/src/codesearch/cli.ts +223 -0
- package/src/codesearch/embedder.ts +332 -0
- package/src/codesearch/files.ts +280 -0
- package/src/codesearch/index.ts +469 -0
- package/src/codesearch/ollama-embedder.ts +205 -0
- package/src/codesearch/search.ts +141 -0
- package/src/codesearch/types.ts +100 -0
- package/src/config/mode-models.ts +218 -0
- package/src/dashboard/aggregate.ts +364 -0
- package/src/dashboard/chat-thread.ts +141 -0
- package/src/dashboard/checkpoints.ts +124 -0
- package/src/dashboard/cli.ts +193 -0
- package/src/dashboard/codemap.ts +44 -0
- package/src/dashboard/files.ts +121 -0
- package/src/dashboard/page.ts +2803 -0
- package/src/dashboard/self-improvement-metrics.ts +282 -0
- package/src/dashboard/server.ts +1103 -0
- package/src/dashboard/session-launch.ts +310 -0
- package/src/dashboard/timeline.ts +273 -0
- package/src/dashboard/tool-exec.ts +107 -0
- package/src/dashboard/trend-cli.ts +141 -0
- package/src/dashboard/trend.ts +413 -0
- package/src/decision-proxy/cli.ts +261 -0
- package/src/decision-proxy/proxy.ts +569 -0
- package/src/deploy/gate-cli.ts +147 -0
- package/src/deploy/gate.ts +254 -0
- package/src/engine/condense.ts +512 -0
- package/src/engine/events.ts +428 -0
- package/src/engine/handoff.ts +71 -0
- package/src/engine/lazy-tools.ts +160 -0
- package/src/engine/local-explore.ts +653 -0
- package/src/engine/logger.ts +96 -0
- package/src/engine/loop.ts +5517 -0
- package/src/engine/parser.ts +347 -0
- package/src/engine/prompt.ts +860 -0
- package/src/engine/reports.ts +47 -0
- package/src/engine/stacks.ts +448 -0
- package/src/engine/types.ts +291 -0
- package/src/engine/usage.ts +186 -0
- package/src/github/app-auth.ts +161 -0
- package/src/github/cli.ts +448 -0
- package/src/github/installations.ts +133 -0
- package/src/github/pr.ts +321 -0
- package/src/github/provision.ts +118 -0
- package/src/github/push.ts +122 -0
- package/src/index-util.ts +50 -0
- package/src/index.ts +81 -0
- package/src/init/cli.ts +248 -0
- package/src/init/gitignore.ts +74 -0
- package/src/llm/ollama.ts +308 -0
- package/src/llm/openrouter.ts +868 -0
- package/src/llm/preflight.ts +367 -0
- package/src/llm/transcript-capture.ts +84 -0
- package/src/memory/embed.ts +110 -0
- package/src/memory/index.ts +22 -0
- package/src/memory/local.ts +259 -0
- package/src/memory/summarizer.ts +283 -0
- package/src/memory/types.ts +153 -0
- package/src/memory/uwuchat.ts +157 -0
- package/src/migrate/cli.ts +115 -0
- package/src/orchestrator/analyze-cli.ts +104 -0
- package/src/orchestrator/auto-split.ts +206 -0
- package/src/orchestrator/cleanup.ts +1003 -0
- package/src/orchestrator/cli.ts +3571 -0
- package/src/orchestrator/cost-estimate.ts +564 -0
- package/src/orchestrator/cost-history-cli.ts +242 -0
- package/src/orchestrator/cost-history.ts +397 -0
- package/src/orchestrator/git-sync.ts +250 -0
- package/src/orchestrator/index.ts +153 -0
- package/src/orchestrator/log-analysis.ts +0 -0
- package/src/orchestrator/merge-check.ts +108 -0
- package/src/orchestrator/pipeline.ts +411 -0
- package/src/orchestrator/resume.ts +1940 -0
- package/src/orchestrator/reviewer.ts +503 -0
- package/src/orchestrator/split.ts +296 -0
- package/src/orchestrator/state.ts +542 -0
- package/src/orchestrator/status.ts +697 -0
- package/src/orchestrator/verification-gate.ts +134 -0
- package/src/orchestrator/watch.ts +898 -0
- package/src/permissions/commands.ts +1083 -0
- package/src/permissions/config.ts +241 -0
- package/src/permissions/index.ts +12 -0
- package/src/permissions/protected-files.ts +96 -0
- package/src/permissions/store-protection.ts +272 -0
- package/src/project-store.ts +648 -0
- package/src/projects/cli.ts +382 -0
- package/src/qa/qa.ts +487 -0
- package/src/tools/browser/handler.ts +346 -0
- package/src/tools/browser/service.ts +406 -0
- package/src/tools/browser/smoke.ts +78 -0
- package/src/tools/browser/tool.ts +99 -0
- package/src/tools/executor.ts +2575 -0
- package/src/tools/language-detect.ts +183 -0
- package/src/tools/output-summarizer.ts +369 -0
- package/src/tools/run-tests.ts +302 -0
- package/src/tools/set-indentation-tool.ts +49 -0
- package/src/tools/test-selection.ts +160 -0
- package/src/vendor/tests/smoke.ts +103 -0
- package/src/vendor/zoo-code/VENDOR-NOTES.md +213 -0
- package/src/vendor/zoo-code/shim/anthropic.ts +71 -0
- package/src/vendor/zoo-code/shim/openai.d.ts +60 -0
- package/src/vendor/zoo-code/shim/os-name.ts +18 -0
- package/src/vendor/zoo-code/shim/strip-bom.ts +14 -0
- package/src/vendor/zoo-code/shim/vscode.ts +76 -0
- package/src/vendor/zoo-code/src/core/config/CustomModesManager.ts +1015 -0
- package/src/vendor/zoo-code/src/core/diff/strategies/multi-search-replace.ts +670 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/capabilities.ts +46 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/custom-instructions.ts +559 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/index.ts +10 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/markdown-formatting.ts +7 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/modes.ts +35 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/objective.ts +13 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/rules.ts +95 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/skills.ts +105 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/system-info.ts +30 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/tool-use-guidelines.ts +9 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/tool-use.ts +7 -0
- package/src/vendor/zoo-code/src/core/prompts/system.ts +176 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/access_mcp_resource.ts +41 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/apply_diff.ts +40 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/apply_patch.ts +61 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/ask_followup_question.ts +62 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/attempt_completion.ts +33 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/codebase_search.ts +43 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/converters.ts +109 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/edit.ts +48 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/edit_file.ts +72 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/execute_command.ts +54 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/generate_image.ts +51 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/index.ts +75 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/list_files.ts +41 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/mcp_server.ts +75 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/new_task.ts +39 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/read_command_output.ts +81 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/read_file.ts +169 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/run_slash_command.ts +31 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/search_files.ts +50 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/search_replace.ts +51 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/skill.ts +33 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/switch_mode.ts +31 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/update_todo_list.ts +54 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/write_to_file.ts +40 -0
- package/src/vendor/zoo-code/src/core/prompts/types.ts +12 -0
- package/src/vendor/zoo-code/src/i18n/index.ts +19 -0
- package/src/vendor/zoo-code/src/integrations/misc/extract-text.ts +81 -0
- package/src/vendor/zoo-code/src/services/checkpoints/RepoPerTaskCheckpointService.ts +15 -0
- package/src/vendor/zoo-code/src/services/checkpoints/ShadowCheckpointService.ts +553 -0
- package/src/vendor/zoo-code/src/services/checkpoints/excludes.ts +212 -0
- package/src/vendor/zoo-code/src/services/checkpoints/index.ts +3 -0
- package/src/vendor/zoo-code/src/services/checkpoints/types.ts +35 -0
- package/src/vendor/zoo-code/src/services/code-index/manager.ts +19 -0
- package/src/vendor/zoo-code/src/services/mcp/McpHub.ts +36 -0
- package/src/vendor/zoo-code/src/services/roo-config/index.ts +441 -0
- package/src/vendor/zoo-code/src/services/search/file-search.ts +143 -0
- package/src/vendor/zoo-code/src/services/skills/SkillsManager.ts +20 -0
- package/src/vendor/zoo-code/src/shared/globalFileNames.ts +9 -0
- package/src/vendor/zoo-code/src/shared/language.ts +43 -0
- package/src/vendor/zoo-code/src/shared/modes.ts +257 -0
- package/src/vendor/zoo-code/src/shared/tools.ts +385 -0
- package/src/vendor/zoo-code/src/utils/fs.ts +39 -0
- package/src/vendor/zoo-code/src/utils/globalContext.ts +22 -0
- package/src/vendor/zoo-code/src/utils/json-schema.ts +16 -0
- package/src/vendor/zoo-code/src/utils/logging.ts +21 -0
- package/src/vendor/zoo-code/src/utils/mcp-name.ts +190 -0
- package/src/vendor/zoo-code/src/utils/object.ts +18 -0
- package/src/vendor/zoo-code/src/utils/path.ts +94 -0
- package/src/vendor/zoo-code/src/utils/shell.ts +376 -0
- package/src/vendor/zoo-code/src/utils/text-normalization.ts +99 -0
- package/src/vendor/zoo-code/types/global-settings.ts +19 -0
- package/src/vendor/zoo-code/types/index.ts +22 -0
- package/src/vendor/zoo-code/types/message.ts +375 -0
- package/src/vendor/zoo-code/types/mode.ts +241 -0
- package/src/vendor/zoo-code/types/todo.ts +19 -0
- package/src/vendor/zoo-code/types/tool-params.ts +116 -0
- package/src/vendor/zoo-code/types/tool.ts +67 -0
- package/src/vendor/zoo-code/types/vscode.ts +84 -0
- package/src/vision/describe.ts +242 -0
- package/src/vision/tool.ts +91 -0
- package/src/watcher/cli.ts +369 -0
- package/src/watcher/github.ts +304 -0
- package/src/watcher/index.ts +59 -0
- package/src/watcher/state.ts +254 -0
- package/src/watcher/watch.ts +562 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
import * as path from "path"
|
|
2
|
+
import * as os from "os"
|
|
3
|
+
import fs from "fs/promises"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Gets the GLOBAL shared-config directory used for the mode-agnostic rules
|
|
7
|
+
* (`<dir>/rules/`) and per-mode rules (`<dir>/rules-<mode>/`) that
|
|
8
|
+
* `addCustomInstructions()` scans (see src/core/prompts/sections/
|
|
9
|
+
* custom-instructions.ts).
|
|
10
|
+
*
|
|
11
|
+
* DELIBERATE DEVIATION FROM THE VENDORED UPSTREAM: upstream returns
|
|
12
|
+
* `~/.roo` (the Zoo-Code-branded location). headlesscode relocated the global
|
|
13
|
+
* shared instructions to its own native data location,
|
|
14
|
+
* `~/.local/share/headlesscode/shared/` (see src/project-store.ts), to
|
|
15
|
+
* complete the decoupling from Zoo Code as a tool — the file FORMAT and the
|
|
16
|
+
* rules-directory-scanning convention are unchanged, only the lookup path
|
|
17
|
+
* moved. Project-local `.roo/rules(-<mode>)/` is untouched (the project dir
|
|
18
|
+
* still wins on any overlap).
|
|
19
|
+
*
|
|
20
|
+
* @returns The absolute path to the global shared-instructions directory:
|
|
21
|
+
* `~/.local/share/headlesscode/shared` on macOS/Linux
|
|
22
|
+
*/
|
|
23
|
+
export function getGlobalRooDirectory(): string {
|
|
24
|
+
const homeDir = os.homedir()
|
|
25
|
+
return path.join(homeDir, ".local", "share", "headlesscode", "shared")
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Gets the global .agents directory path based on the current platform.
|
|
30
|
+
* This is a shared directory for agent skills across different AI coding tools.
|
|
31
|
+
*
|
|
32
|
+
* @returns The absolute path to the global .agents directory
|
|
33
|
+
*
|
|
34
|
+
* @example Platform-specific paths:
|
|
35
|
+
* ```
|
|
36
|
+
* // macOS/Linux: ~/.agents/
|
|
37
|
+
* // Example: /Users/john/.agents
|
|
38
|
+
*
|
|
39
|
+
* // Windows: %USERPROFILE%\.agents\
|
|
40
|
+
* // Example: C:\Users\john\.agents
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* @example Usage:
|
|
44
|
+
* ```typescript
|
|
45
|
+
* const globalAgentsDir = getGlobalAgentsDirectory()
|
|
46
|
+
* // Returns: "/Users/john/.agents" (on macOS/Linux)
|
|
47
|
+
* // Returns: "C:\\Users\\john\\.agents" (on Windows)
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export function getGlobalAgentsDirectory(): string {
|
|
51
|
+
const homeDir = os.homedir()
|
|
52
|
+
return path.join(homeDir, ".agents")
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Gets the project-local .agents directory path for a given cwd.
|
|
57
|
+
* This is a shared directory for agent skills across different AI coding tools.
|
|
58
|
+
*
|
|
59
|
+
* @param cwd - Current working directory (project path)
|
|
60
|
+
* @returns The absolute path to the project-local .agents directory
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```typescript
|
|
64
|
+
* const projectAgentsDir = getProjectAgentsDirectoryForCwd('/Users/john/my-project')
|
|
65
|
+
* // Returns: "/Users/john/my-project/.agents"
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
export function getProjectAgentsDirectoryForCwd(cwd: string): string {
|
|
69
|
+
return path.join(cwd, ".agents")
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Gets the project-local .roo directory path for a given cwd
|
|
74
|
+
*
|
|
75
|
+
* @param cwd - Current working directory (project path)
|
|
76
|
+
* @returns The absolute path to the project-local .roo directory
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```typescript
|
|
80
|
+
* const projectDir = getProjectRooDirectoryForCwd('/Users/john/my-project')
|
|
81
|
+
* // Returns: "/Users/john/my-project/.roo"
|
|
82
|
+
*
|
|
83
|
+
* const windowsProjectDir = getProjectRooDirectoryForCwd('C:\\Users\\john\\my-project')
|
|
84
|
+
* // Returns: "C:\\Users\\john\\my-project\\.roo"
|
|
85
|
+
* ```
|
|
86
|
+
*
|
|
87
|
+
* @example Directory structure:
|
|
88
|
+
* ```
|
|
89
|
+
* /Users/john/my-project/
|
|
90
|
+
* ├── .roo/ # Project-local configuration directory
|
|
91
|
+
* │ ├── rules/
|
|
92
|
+
* │ │ └── rules.md
|
|
93
|
+
* │ ├── custom-instructions.md
|
|
94
|
+
* │ └── config/
|
|
95
|
+
* │ └── settings.json
|
|
96
|
+
* ├── src/
|
|
97
|
+
* │ └── index.ts
|
|
98
|
+
* └── package.json
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
101
|
+
export function getProjectRooDirectoryForCwd(cwd: string): string {
|
|
102
|
+
return path.join(cwd, ".roo")
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Checks if a directory exists
|
|
107
|
+
*/
|
|
108
|
+
export async function directoryExists(dirPath: string): Promise<boolean> {
|
|
109
|
+
try {
|
|
110
|
+
const stat = await fs.stat(dirPath)
|
|
111
|
+
return stat.isDirectory()
|
|
112
|
+
} catch (error: any) {
|
|
113
|
+
// Only catch expected "not found" errors
|
|
114
|
+
if (error.code === "ENOENT" || error.code === "ENOTDIR") {
|
|
115
|
+
return false
|
|
116
|
+
}
|
|
117
|
+
// Re-throw unexpected errors (permission, I/O, etc.)
|
|
118
|
+
throw error
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Checks if a file exists
|
|
124
|
+
*/
|
|
125
|
+
export async function fileExists(filePath: string): Promise<boolean> {
|
|
126
|
+
try {
|
|
127
|
+
const stat = await fs.stat(filePath)
|
|
128
|
+
return stat.isFile()
|
|
129
|
+
} catch (error: any) {
|
|
130
|
+
// Only catch expected "not found" errors
|
|
131
|
+
if (error.code === "ENOENT" || error.code === "ENOTDIR") {
|
|
132
|
+
return false
|
|
133
|
+
}
|
|
134
|
+
// Re-throw unexpected errors (permission, I/O, etc.)
|
|
135
|
+
throw error
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Reads a file safely, returning null if it doesn't exist
|
|
141
|
+
*/
|
|
142
|
+
export async function readFileIfExists(filePath: string): Promise<string | null> {
|
|
143
|
+
try {
|
|
144
|
+
return await fs.readFile(filePath, "utf-8")
|
|
145
|
+
} catch (error: any) {
|
|
146
|
+
// Only catch expected "not found" errors
|
|
147
|
+
if (error.code === "ENOENT" || error.code === "ENOTDIR" || error.code === "EISDIR") {
|
|
148
|
+
return null
|
|
149
|
+
}
|
|
150
|
+
// Re-throw unexpected errors (permission, I/O, etc.)
|
|
151
|
+
throw error
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Discovers all .roo directories in subdirectories of the workspace
|
|
157
|
+
*
|
|
158
|
+
* @param cwd - Current working directory (workspace root)
|
|
159
|
+
* @returns Array of absolute paths to .roo directories found in subdirectories,
|
|
160
|
+
* sorted alphabetically. Does not include the root .roo directory.
|
|
161
|
+
*
|
|
162
|
+
* @example
|
|
163
|
+
* ```typescript
|
|
164
|
+
* const subfolderRoos = await discoverSubfolderRooDirectories('/Users/john/monorepo')
|
|
165
|
+
* // Returns:
|
|
166
|
+
* // [
|
|
167
|
+
* // '/Users/john/monorepo/package-a/.roo',
|
|
168
|
+
* // '/Users/john/monorepo/package-b/.roo',
|
|
169
|
+
* // '/Users/john/monorepo/packages/shared/.roo'
|
|
170
|
+
* // ]
|
|
171
|
+
* ```
|
|
172
|
+
*
|
|
173
|
+
* @example Directory structure:
|
|
174
|
+
* ```
|
|
175
|
+
* /Users/john/monorepo/
|
|
176
|
+
* ├── .roo/ # Root .roo (NOT included - use getProjectRooDirectoryForCwd)
|
|
177
|
+
* ├── package-a/
|
|
178
|
+
* │ └── .roo/ # Included
|
|
179
|
+
* │ └── rules/
|
|
180
|
+
* ├── package-b/
|
|
181
|
+
* │ └── .roo/ # Included
|
|
182
|
+
* │ └── rules-code/
|
|
183
|
+
* └── packages/
|
|
184
|
+
* └── shared/
|
|
185
|
+
* └── .roo/ # Included (nested)
|
|
186
|
+
* └── rules/
|
|
187
|
+
* ```
|
|
188
|
+
*/
|
|
189
|
+
export async function discoverSubfolderRooDirectories(cwd: string): Promise<string[]> {
|
|
190
|
+
try {
|
|
191
|
+
// Dynamic import to avoid vscode dependency at module load time
|
|
192
|
+
// This is necessary because file-search.ts imports vscode, which is not
|
|
193
|
+
// available in the webview context
|
|
194
|
+
const { executeRipgrep } = await import("../search/file-search")
|
|
195
|
+
|
|
196
|
+
// Use ripgrep to find any file inside any .roo directory
|
|
197
|
+
// This efficiently discovers all .roo folders regardless of their content
|
|
198
|
+
const args = [
|
|
199
|
+
"--files",
|
|
200
|
+
"--hidden",
|
|
201
|
+
"--follow",
|
|
202
|
+
"-g",
|
|
203
|
+
"**/.roo/**",
|
|
204
|
+
"-g",
|
|
205
|
+
"!node_modules/**",
|
|
206
|
+
"-g",
|
|
207
|
+
"!.git/**",
|
|
208
|
+
cwd,
|
|
209
|
+
]
|
|
210
|
+
|
|
211
|
+
const results = await executeRipgrep({ args, workspacePath: cwd })
|
|
212
|
+
|
|
213
|
+
// Extract unique .roo directory paths
|
|
214
|
+
const rooDirs = new Set<string>()
|
|
215
|
+
const rootRooDir = path.join(cwd, ".roo")
|
|
216
|
+
|
|
217
|
+
for (const result of results) {
|
|
218
|
+
// Match paths like "subfolder/.roo/anything" or "subfolder/nested/.roo/anything"
|
|
219
|
+
// Handle both forward slashes (Unix) and backslashes (Windows)
|
|
220
|
+
const match = result.path.match(/^(.+?)[/\\]\.roo[/\\]/)
|
|
221
|
+
if (match) {
|
|
222
|
+
const rooDir = path.join(cwd, match[1], ".roo")
|
|
223
|
+
// Exclude the root .roo directory (already handled by getProjectRooDirectoryForCwd)
|
|
224
|
+
if (rooDir !== rootRooDir) {
|
|
225
|
+
rooDirs.add(rooDir)
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Return sorted alphabetically
|
|
231
|
+
return Array.from(rooDirs).sort()
|
|
232
|
+
} catch (error) {
|
|
233
|
+
// If discovery fails (e.g., ripgrep not available), return empty array
|
|
234
|
+
return []
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Gets the ordered list of .roo directories to check (global first, then project-local)
|
|
240
|
+
*
|
|
241
|
+
* @param cwd - Current working directory (project path)
|
|
242
|
+
* @returns Array of directory paths to check in order [global, project-local]
|
|
243
|
+
*
|
|
244
|
+
* @example
|
|
245
|
+
* ```typescript
|
|
246
|
+
* // For a project at /Users/john/my-project
|
|
247
|
+
* const directories = getRooDirectoriesForCwd('/Users/john/my-project')
|
|
248
|
+
* // Returns:
|
|
249
|
+
* // [
|
|
250
|
+
* // '/Users/john/.roo', // Global directory
|
|
251
|
+
* // '/Users/john/my-project/.roo' // Project-local directory
|
|
252
|
+
* // ]
|
|
253
|
+
* ```
|
|
254
|
+
*
|
|
255
|
+
* @example Directory structure:
|
|
256
|
+
* ```
|
|
257
|
+
* /Users/john/
|
|
258
|
+
* ├── .roo/ # Global configuration
|
|
259
|
+
* │ ├── rules/
|
|
260
|
+
* │ │ └── rules.md
|
|
261
|
+
* │ └── custom-instructions.md
|
|
262
|
+
* └── my-project/
|
|
263
|
+
* ├── .roo/ # Project-specific configuration
|
|
264
|
+
* │ ├── rules/
|
|
265
|
+
* │ │ └── rules.md # Overrides global rules
|
|
266
|
+
* │ └── project-notes.md
|
|
267
|
+
* └── src/
|
|
268
|
+
* └── index.ts
|
|
269
|
+
* ```
|
|
270
|
+
*/
|
|
271
|
+
export function getRooDirectoriesForCwd(cwd: string): string[] {
|
|
272
|
+
const directories: string[] = []
|
|
273
|
+
|
|
274
|
+
// Add global directory first
|
|
275
|
+
directories.push(getGlobalRooDirectory())
|
|
276
|
+
|
|
277
|
+
// Add project-local directory second
|
|
278
|
+
directories.push(getProjectRooDirectoryForCwd(cwd))
|
|
279
|
+
|
|
280
|
+
return directories
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Gets the ordered list of all .roo directories including subdirectories
|
|
285
|
+
*
|
|
286
|
+
* @param cwd - Current working directory (project path)
|
|
287
|
+
* @returns Array of directory paths in order: [global, project-local, ...subfolders (alphabetically)]
|
|
288
|
+
*
|
|
289
|
+
* @example
|
|
290
|
+
* ```typescript
|
|
291
|
+
* // For a monorepo at /Users/john/monorepo with .roo in subfolders
|
|
292
|
+
* const directories = await getAllRooDirectoriesForCwd('/Users/john/monorepo')
|
|
293
|
+
* // Returns:
|
|
294
|
+
* // [
|
|
295
|
+
* // '/Users/john/.roo', // Global directory
|
|
296
|
+
* // '/Users/john/monorepo/.roo', // Project-local directory
|
|
297
|
+
* // '/Users/john/monorepo/package-a/.roo', // Subfolder (alphabetical)
|
|
298
|
+
* // '/Users/john/monorepo/package-b/.roo' // Subfolder (alphabetical)
|
|
299
|
+
* // ]
|
|
300
|
+
* ```
|
|
301
|
+
*/
|
|
302
|
+
export async function getAllRooDirectoriesForCwd(cwd: string): Promise<string[]> {
|
|
303
|
+
const directories: string[] = []
|
|
304
|
+
|
|
305
|
+
// Add global directory first
|
|
306
|
+
directories.push(getGlobalRooDirectory())
|
|
307
|
+
|
|
308
|
+
// Add project-local directory second
|
|
309
|
+
directories.push(getProjectRooDirectoryForCwd(cwd))
|
|
310
|
+
|
|
311
|
+
// Discover and add subfolder .roo directories
|
|
312
|
+
const subfolderDirs = await discoverSubfolderRooDirectories(cwd)
|
|
313
|
+
directories.push(...subfolderDirs)
|
|
314
|
+
|
|
315
|
+
return directories
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Gets parent directories containing .roo folders, in order from root to subfolders
|
|
320
|
+
*
|
|
321
|
+
* @param cwd - Current working directory (project path)
|
|
322
|
+
* @returns Array of parent directory paths (not .roo paths) containing AGENTS.md or .roo
|
|
323
|
+
*
|
|
324
|
+
* @example
|
|
325
|
+
* ```typescript
|
|
326
|
+
* const dirs = await getAgentsDirectoriesForCwd('/Users/john/monorepo')
|
|
327
|
+
* // Returns: ['/Users/john/monorepo', '/Users/john/monorepo/package-a', ...]
|
|
328
|
+
* ```
|
|
329
|
+
*/
|
|
330
|
+
export async function getAgentsDirectoriesForCwd(cwd: string): Promise<string[]> {
|
|
331
|
+
const directories: string[] = []
|
|
332
|
+
|
|
333
|
+
// Always include the root directory
|
|
334
|
+
directories.push(cwd)
|
|
335
|
+
|
|
336
|
+
// Get all subfolder .roo directories
|
|
337
|
+
const subfolderRooDirs = await discoverSubfolderRooDirectories(cwd)
|
|
338
|
+
|
|
339
|
+
// Extract parent directories (remove .roo from path)
|
|
340
|
+
for (const rooDir of subfolderRooDirs) {
|
|
341
|
+
const parentDir = path.dirname(rooDir)
|
|
342
|
+
directories.push(parentDir)
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return directories
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Loads configuration from multiple .roo directories with project overriding global
|
|
350
|
+
*
|
|
351
|
+
* @param relativePath - The relative path within each .roo directory (e.g., 'rules/rules.md')
|
|
352
|
+
* @param cwd - Current working directory (project path)
|
|
353
|
+
* @returns Object with global and project content, plus merged content
|
|
354
|
+
*
|
|
355
|
+
* @example
|
|
356
|
+
* ```typescript
|
|
357
|
+
* // Load rules configuration for a project
|
|
358
|
+
* const config = await loadConfiguration('rules/rules.md', '/Users/john/my-project')
|
|
359
|
+
*
|
|
360
|
+
* // Returns:
|
|
361
|
+
* // {
|
|
362
|
+
* // global: "Global rules content...", // From ~/.roo/rules/rules.md
|
|
363
|
+
* // project: "Project rules content...", // From /Users/john/my-project/.roo/rules/rules.md
|
|
364
|
+
* // merged: "Global rules content...\n\n# Project-specific rules (override global):\n\nProject rules content..."
|
|
365
|
+
* // }
|
|
366
|
+
* ```
|
|
367
|
+
*
|
|
368
|
+
* @example File paths resolved:
|
|
369
|
+
* ```
|
|
370
|
+
* relativePath: 'rules/rules.md'
|
|
371
|
+
* cwd: '/Users/john/my-project'
|
|
372
|
+
*
|
|
373
|
+
* Reads from:
|
|
374
|
+
* - Global: /Users/john/.roo/rules/rules.md
|
|
375
|
+
* - Project: /Users/john/my-project/.roo/rules/rules.md
|
|
376
|
+
*
|
|
377
|
+
* Other common relativePath examples:
|
|
378
|
+
* - 'custom-instructions.md'
|
|
379
|
+
* - 'config/settings.json'
|
|
380
|
+
* - 'templates/component.tsx'
|
|
381
|
+
* ```
|
|
382
|
+
*
|
|
383
|
+
* @example Merging behavior:
|
|
384
|
+
* ```
|
|
385
|
+
* // If only global exists:
|
|
386
|
+
* { global: "content", project: null, merged: "content" }
|
|
387
|
+
*
|
|
388
|
+
* // If only project exists:
|
|
389
|
+
* { global: null, project: "content", merged: "content" }
|
|
390
|
+
*
|
|
391
|
+
* // If both exist:
|
|
392
|
+
* {
|
|
393
|
+
* global: "global content",
|
|
394
|
+
* project: "project content",
|
|
395
|
+
* merged: "global content\n\n# Project-specific rules (override global):\n\nproject content"
|
|
396
|
+
* }
|
|
397
|
+
* ```
|
|
398
|
+
*/
|
|
399
|
+
export async function loadConfiguration(
|
|
400
|
+
relativePath: string,
|
|
401
|
+
cwd: string,
|
|
402
|
+
): Promise<{
|
|
403
|
+
global: string | null
|
|
404
|
+
project: string | null
|
|
405
|
+
merged: string
|
|
406
|
+
}> {
|
|
407
|
+
const globalDir = getGlobalRooDirectory()
|
|
408
|
+
const projectDir = getProjectRooDirectoryForCwd(cwd)
|
|
409
|
+
|
|
410
|
+
const globalFilePath = path.join(globalDir, relativePath)
|
|
411
|
+
const projectFilePath = path.join(projectDir, relativePath)
|
|
412
|
+
|
|
413
|
+
// Read global configuration
|
|
414
|
+
const globalContent = await readFileIfExists(globalFilePath)
|
|
415
|
+
|
|
416
|
+
// Read project-local configuration
|
|
417
|
+
const projectContent = await readFileIfExists(projectFilePath)
|
|
418
|
+
|
|
419
|
+
// Merge configurations - project overrides global
|
|
420
|
+
let merged = ""
|
|
421
|
+
|
|
422
|
+
if (globalContent) {
|
|
423
|
+
merged += globalContent
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (projectContent) {
|
|
427
|
+
if (merged) {
|
|
428
|
+
merged += "\n\n# Project-specific rules (override global):\n\n"
|
|
429
|
+
}
|
|
430
|
+
merged += projectContent
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
return {
|
|
434
|
+
global: globalContent,
|
|
435
|
+
project: projectContent,
|
|
436
|
+
merged: merged || "",
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Export with backward compatibility alias
|
|
441
|
+
export const loadRooConfiguration: typeof loadConfiguration = loadConfiguration
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SHIM — replaces zoo-code/src/services/search/file-search.ts.
|
|
3
|
+
*
|
|
4
|
+
* The original shells out to ripgrep (`executeRipgrep`) to discover files by
|
|
5
|
+
* glob pattern. Two vendored call sites use it:
|
|
6
|
+
* - `services/roo-config/index.ts#discoverSubfolderRooDirectories()`: finds
|
|
7
|
+
* any file under a nested ".roo" directory, excluding node_modules and
|
|
8
|
+
* ".git" (double-star glob, "-g" flags — see that file for the literal args).
|
|
9
|
+
* - `services/checkpoints/ShadowCheckpointService.ts#getNestedGitRepository()`:
|
|
10
|
+
* finds ".git" + "HEAD" pairs anywhere under the workspace (nested-repo
|
|
11
|
+
* detection — see that file for the literal args).
|
|
12
|
+
*
|
|
13
|
+
* This shim implements the same `{ args, workspacePath } -> results` shape
|
|
14
|
+
* with pure Node `fs`, supporting the small glob subset both callers use
|
|
15
|
+
* (double-star, single-star, and "!"-prefixed excludes via repeated "-g"
|
|
16
|
+
* flags). Each result has `{ path, type }` (relative to `workspacePath`,
|
|
17
|
+
* forward-slash separated), matching the real ripgrep-backed
|
|
18
|
+
* implementation's `FileResult` shape closely enough for both callers (the
|
|
19
|
+
* checkpoints caller destructures `type`).
|
|
20
|
+
*
|
|
21
|
+
* To keep this bounded on real repos, directories named ".git" are not
|
|
22
|
+
* recursed into wholesale (that would walk the entire object store) —
|
|
23
|
+
* instead only their "HEAD" file is checked directly, and "node_modules"
|
|
24
|
+
* directories are never recursed into. Both are reasonable defaults for the
|
|
25
|
+
* two callers above, which either exclude these directories explicitly or
|
|
26
|
+
* (in the checkpoints case) only ever care about a top-level "HEAD" file.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import * as fs from "fs/promises"
|
|
30
|
+
import * as path from "path"
|
|
31
|
+
|
|
32
|
+
export interface RipgrepResult {
|
|
33
|
+
path: string
|
|
34
|
+
type: "file" | "folder"
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function globToRegex(glob: string): RegExp {
|
|
38
|
+
let re = ""
|
|
39
|
+
for (let i = 0; i < glob.length; i++) {
|
|
40
|
+
const c = glob[i]
|
|
41
|
+
if (c === "*") {
|
|
42
|
+
if (glob[i + 1] === "*") {
|
|
43
|
+
re += ".*"
|
|
44
|
+
i++
|
|
45
|
+
if (glob[i + 1] === "/") {
|
|
46
|
+
i++
|
|
47
|
+
}
|
|
48
|
+
} else {
|
|
49
|
+
re += "[^/]*"
|
|
50
|
+
}
|
|
51
|
+
} else if ("+.^${}()|[]\\".includes(c)) {
|
|
52
|
+
re += "\\" + c
|
|
53
|
+
} else {
|
|
54
|
+
re += c
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return new RegExp(`^${re}$`)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function parsePatterns(args: string[]): { include: RegExp[]; exclude: RegExp[] } {
|
|
61
|
+
const include: RegExp[] = []
|
|
62
|
+
const exclude: RegExp[] = []
|
|
63
|
+
|
|
64
|
+
for (let i = 0; i < args.length; i++) {
|
|
65
|
+
if (args[i] === "-g" && args[i + 1] !== undefined) {
|
|
66
|
+
const pattern = args[i + 1]
|
|
67
|
+
if (pattern.startsWith("!")) {
|
|
68
|
+
exclude.push(globToRegex(pattern.slice(1)))
|
|
69
|
+
} else {
|
|
70
|
+
include.push(globToRegex(pattern))
|
|
71
|
+
}
|
|
72
|
+
i++
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return { include, exclude }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export async function executeRipgrep({
|
|
80
|
+
args,
|
|
81
|
+
workspacePath,
|
|
82
|
+
}: {
|
|
83
|
+
args: string[]
|
|
84
|
+
workspacePath: string
|
|
85
|
+
}): Promise<RipgrepResult[]> {
|
|
86
|
+
const { include, exclude } = parsePatterns(args)
|
|
87
|
+
const results: RipgrepResult[] = []
|
|
88
|
+
|
|
89
|
+
const isExcluded = (relPath: string, isDir: boolean) => {
|
|
90
|
+
const candidates = isDir ? [relPath, `${relPath}/`] : [relPath]
|
|
91
|
+
return exclude.some((re) => candidates.some((c) => re.test(c)))
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const matchesInclude = (relPath: string) => include.length === 0 || include.some((re) => re.test(relPath))
|
|
95
|
+
|
|
96
|
+
async function walk(dir: string, relative: string): Promise<void> {
|
|
97
|
+
let entries: import("fs").Dirent[]
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
entries = await fs.readdir(dir, { withFileTypes: true })
|
|
101
|
+
} catch {
|
|
102
|
+
return
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
for (const entry of entries) {
|
|
106
|
+
const entryRelative = relative ? `${relative}/${entry.name}` : entry.name
|
|
107
|
+
|
|
108
|
+
if (entry.isDirectory()) {
|
|
109
|
+
if (entry.name === "node_modules" || isExcluded(entryRelative, true)) {
|
|
110
|
+
continue
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (entry.name === ".git") {
|
|
114
|
+
// Don't walk the object store; only surface HEAD, mirroring what
|
|
115
|
+
// the checkpoints caller's `**/.git/HEAD` pattern is looking for.
|
|
116
|
+
const headRelative = `${entryRelative}/HEAD`
|
|
117
|
+
const headAbsolute = path.join(dir, entry.name, "HEAD")
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
const stat = await fs.stat(headAbsolute)
|
|
121
|
+
|
|
122
|
+
if (stat.isFile() && matchesInclude(headRelative) && !isExcluded(headRelative, false)) {
|
|
123
|
+
results.push({ path: headRelative, type: "file" })
|
|
124
|
+
}
|
|
125
|
+
} catch {
|
|
126
|
+
// No HEAD file — not a real git dir, nothing to report.
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
continue
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
await walk(path.join(dir, entry.name), entryRelative)
|
|
133
|
+
} else if (entry.isFile()) {
|
|
134
|
+
if (matchesInclude(entryRelative) && !isExcluded(entryRelative, false)) {
|
|
135
|
+
results.push({ path: entryRelative, type: "file" })
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
await walk(workspacePath, "")
|
|
142
|
+
return results
|
|
143
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SHIM — replaces zoo-code/src/services/skills/SkillsManager.ts.
|
|
3
|
+
*
|
|
4
|
+
* The real SkillsManager scans global/project skills directories. The vendored
|
|
5
|
+
* `core/prompts/sections/skills.ts` only reads `getSkillsForMode(mode)`, which
|
|
6
|
+
* this stub satisfies with an empty list (no skills registered headlessly).
|
|
7
|
+
* Skill loading is out of scope for Phase 1.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export interface SkillLike {
|
|
11
|
+
name: string
|
|
12
|
+
description: string
|
|
13
|
+
path: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class SkillsManager {
|
|
17
|
+
getSkillsForMode(_mode: string): SkillLike[] {
|
|
18
|
+
return []
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export const GlobalFileNames = {
|
|
2
|
+
apiConversationHistory: "api_conversation_history.json",
|
|
3
|
+
uiMessages: "ui_messages.json",
|
|
4
|
+
mcpSettings: "mcp_settings.json",
|
|
5
|
+
customModes: "custom_modes.yaml",
|
|
6
|
+
taskMetadata: "task_metadata.json",
|
|
7
|
+
historyItem: "history_item.json",
|
|
8
|
+
historyIndex: "_index.json",
|
|
9
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type Language, isLanguage } from "../../types"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Language name mapping from ISO codes to full language names.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export const LANGUAGES: Record<Language, string> = {
|
|
8
|
+
ca: "Català",
|
|
9
|
+
de: "Deutsch",
|
|
10
|
+
en: "English",
|
|
11
|
+
es: "Español",
|
|
12
|
+
fr: "Français",
|
|
13
|
+
hi: "हिन्दी",
|
|
14
|
+
id: "Bahasa Indonesia",
|
|
15
|
+
it: "Italiano",
|
|
16
|
+
ja: "日本語",
|
|
17
|
+
ko: "한국어",
|
|
18
|
+
nl: "Nederlands",
|
|
19
|
+
pl: "Polski",
|
|
20
|
+
"pt-BR": "Português",
|
|
21
|
+
ru: "Русский",
|
|
22
|
+
tr: "Türkçe",
|
|
23
|
+
vi: "Tiếng Việt",
|
|
24
|
+
"zh-CN": "简体中文",
|
|
25
|
+
"zh-TW": "繁體中文",
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Formats a VSCode locale string to ensure the region code is uppercase.
|
|
30
|
+
* For example, transforms "en-us" to "en-US" or "fr-ca" to "fr-CA".
|
|
31
|
+
*
|
|
32
|
+
* @param vscodeLocale - The VSCode locale string to format (e.g., "en-us", "fr-ca")
|
|
33
|
+
* @returns The formatted locale string with uppercase region code
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
export function formatLanguage(vscodeLocale: string): Language {
|
|
37
|
+
if (!vscodeLocale) {
|
|
38
|
+
return "en"
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const formattedLocale = vscodeLocale.replace(/-(\w+)$/, (_, region) => `-${region.toUpperCase()}`)
|
|
42
|
+
return isLanguage(formattedLocale) ? formattedLocale : "en"
|
|
43
|
+
}
|