@leg3ndy/otto-bridge 1.1.5 → 1.1.8
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/README.md +9 -9
- package/dist/agentic_runtime/workspace/manager.js +518 -6
- package/dist/attached_workspaces.js +161 -5
- package/dist/bridge_runtime_instruction_pack.js +35 -0
- package/dist/cli_terminal.js +552 -127
- package/dist/executors/native_macos.js +487 -15
- package/dist/main.js +10 -3
- package/dist/runtime.js +2 -0
- package/dist/runtime_contract.js +90 -1
- package/dist/tool_catalog.js +8 -0
- package/dist/types.js +2 -2
- package/package.json +1 -1
- package/scripts/postinstall.mjs +1 -1
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ Para o estado atual da arquitetura, capacidades entregues, limitacoes e roadmap
|
|
|
15
15
|
|
|
16
16
|
Para o corte de arquitetura do `0.9.0`, veja [`leg3ndy-ai-backend/docs/otto-bridge/releases/OTTO_BRIDGE_0_9_0_RELEASE.md`](../leg3ndy-ai-backend/docs/otto-bridge/releases/OTTO_BRIDGE_0_9_0_RELEASE.md).
|
|
17
17
|
|
|
18
|
-
Para o patch atual `1.1.
|
|
18
|
+
Para o patch atual `1.1.8`, veja [`leg3ndy-ai-backend/docs/otto-bridge/releases/OTTO_BRIDGE_1_1_8_PATCH.md`](../leg3ndy-ai-backend/docs/otto-bridge/releases/OTTO_BRIDGE_1_1_8_PATCH.md). Para o corte funcional da linha `1.1.0`, veja [`leg3ndy-ai-backend/docs/otto-bridge/releases/OTTO_BRIDGE_1_1_0_RELEASE.md`](../leg3ndy-ai-backend/docs/otto-bridge/releases/OTTO_BRIDGE_1_1_0_RELEASE.md).
|
|
19
19
|
|
|
20
20
|
## Distribuicao
|
|
21
21
|
|
|
@@ -38,14 +38,14 @@ Enquanto o pacote nao estiver publicado, voce pode gerar um tarball local:
|
|
|
38
38
|
|
|
39
39
|
```bash
|
|
40
40
|
npm pack
|
|
41
|
-
npm install -g ./leg3ndy-otto-bridge-1.1.
|
|
41
|
+
npm install -g ./leg3ndy-otto-bridge-1.1.8.tgz
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
-
Na linha `1.1.
|
|
44
|
+
Na linha `1.1.8`, `playwright` segue como dependencia obrigatoria no `otto-bridge`. O primeiro `npm install -g @leg3ndy/otto-bridge` pode demorar mais porque instala o browser persistente usado pelo WhatsApp Web e pelos fluxos web em background do bridge.
|
|
45
45
|
|
|
46
|
-
No macOS, a linha `1.1.
|
|
46
|
+
No macOS, a linha `1.1.8` usa o provider `macos-helper`, um helper `WKWebView` sem Dock para o WhatsApp Web. O helper sobe com user-agent de Chrome moderno para evitar o bloqueio do WhatsApp ao detectar Safari/WebKit. O runtime antigo com Chromium/Playwright fica disponivel apenas como override explicito via `OTTO_BRIDGE_WHATSAPP_RUNTIME_PROVIDER=embedded-playwright`.
|
|
47
47
|
|
|
48
|
-
No nivel arquitetural, o `0.9.0` marcou a mudanca de papel do bridge: ele publica tools e resultados estruturados para o Otto, em vez de injetar resposta pronta como caminho principal do chat. O `1.0.0` oficializou isso como runtime agentico; o `1.1.
|
|
48
|
+
No nivel arquitetural, o `0.9.0` marcou a mudanca de papel do bridge: ele publica tools e resultados estruturados para o Otto, em vez de injetar resposta pronta como caminho principal do chat. O `1.0.0` oficializou isso como runtime agentico; o `1.1.8` mantem a camada workspace-first com rail de coding, trust/policy por workspace, source control first-class, working set persistido e grounding remoto por repositório, enquanto endurece o `Otto Console`, evita que `otto-bridge update` dispare setup interativo no meio da autoatualizacao e faz o scroll por wheel/trackpad funcionar no Terminal.app sem reativar click tracking.
|
|
49
49
|
|
|
50
50
|
## Publicacao
|
|
51
51
|
|
|
@@ -154,7 +154,7 @@ Dentro do console, use:
|
|
|
154
154
|
- `/workspace clear` para limpar o binding atual do chat/sessão
|
|
155
155
|
- `/new` para iniciar uma nova sessão e limpar o contexto local do console
|
|
156
156
|
|
|
157
|
-
No TTY, o console agora
|
|
157
|
+
No TTY, o console agora entra em `alternate screen` ao abrir o `Otto Console`, para esconder o scrollbar manual/nativo do terminal e deixar só a viewport interna do Otto. O header é impresso uma vez no topo da sessão, o transcript continua completo dentro da tela do console e o composer fica preso no rodapé com placeholder `Peça algo ao Otto`. Ao digitar `/`, o bridge abre uma palette navegável por setas, com `Enter` preenchendo comandos como `/new` para iniciar uma nova sessão local. Wheel/trackpad usam `alternate scroll` do terminal para navegar o transcript como setas; `PageUp/PageDown` continuam funcionando e cliques deixam de ser reportados como input do console.
|
|
158
158
|
|
|
159
159
|
No modo `OttoAI Thinking`, o terminal agora marca explicitamente o trecho de raciocínio com `Pensando (OttoAI Thinking)` e separa esse bloco da resposta final do Otto.
|
|
160
160
|
|
|
@@ -170,7 +170,7 @@ Esse comando abre um shell local interativo para instalar extensoes, rodar coman
|
|
|
170
170
|
|
|
171
171
|
### WhatsApp Web em background
|
|
172
172
|
|
|
173
|
-
Fluxo recomendado na linha `1.1.
|
|
173
|
+
Fluxo recomendado na linha `1.1.8`:
|
|
174
174
|
|
|
175
175
|
```bash
|
|
176
176
|
otto-bridge extensions --install whatsappweb
|
|
@@ -180,13 +180,13 @@ otto-bridge extensions --status whatsappweb
|
|
|
180
180
|
|
|
181
181
|
O setup agora abre o login do WhatsApp Web no helper/background browser do proprio bridge. Depois do QR code, o Otto usa a sessao local em background, sem depender de aba visivel no Safari.
|
|
182
182
|
|
|
183
|
-
Contrato da linha `1.1.
|
|
183
|
+
Contrato da linha `1.1.8`:
|
|
184
184
|
|
|
185
185
|
- `otto-bridge extensions --setup whatsappweb`: autentica a sessao uma vez
|
|
186
186
|
- `otto-bridge`: mantem o browser persistente do WhatsApp vivo em background enquanto o runtime do hub estiver ativo, sem depender de uma aba aberta no Safari
|
|
187
187
|
- ao fechar o `otto-bridge`: o browser em background e desligado, mas a sessao local fica lembrada para o proximo boot
|
|
188
188
|
|
|
189
|
-
## Handoff rapido da linha 1.1.
|
|
189
|
+
## Handoff rapido da linha 1.1.8
|
|
190
190
|
|
|
191
191
|
Ja fechado no codigo:
|
|
192
192
|
|
|
@@ -56,6 +56,17 @@ const DEFAULT_KEY_FILE_PATTERNS = [
|
|
|
56
56
|
];
|
|
57
57
|
const WORKSPACE_INDEX_EXTENSION_LIMIT = 8;
|
|
58
58
|
const WORKSPACE_INDEX_TOP_LEVEL_LIMIT = 16;
|
|
59
|
+
const WORKSPACE_INDEX_TOP_LEVEL_FILE_LIMIT = 12;
|
|
60
|
+
const WORKSPACE_INDEX_DIRECTORY_SAMPLE_LIMIT = 6;
|
|
61
|
+
const WORKSPACE_INDEX_DIRECTORY_KEY_FILE_LIMIT = 6;
|
|
62
|
+
const WORKSPACE_INDEX_DIRECTORY_EXTENSION_LIMIT = 4;
|
|
63
|
+
const WORKSPACE_INDEX_FOCUS_DIRECTORY_LIMIT = 6;
|
|
64
|
+
const WORKSPACE_INDEX_ENTRYPOINT_LIMIT = 12;
|
|
65
|
+
const WORKSPACE_INDEX_STACK_HINT_LIMIT = 8;
|
|
66
|
+
const WORKSPACE_INDEX_SUGGESTED_READ_LIMIT = 10;
|
|
67
|
+
const WORKSPACE_INDEX_SYMBOL_OUTLINE_LIMIT = 8;
|
|
68
|
+
const WORKSPACE_INDEX_SYMBOL_LIMIT = 8;
|
|
69
|
+
const WORKSPACE_INDEX_SYMBOL_FILE_SIZE_LIMIT = 64_000;
|
|
59
70
|
const LOCKFILE_PACKAGE_MANAGER_MAP = {
|
|
60
71
|
"package-lock.json": "npm",
|
|
61
72
|
"pnpm-lock.yaml": "pnpm",
|
|
@@ -66,6 +77,67 @@ const LOCKFILE_PACKAGE_MANAGER_MAP = {
|
|
|
66
77
|
"Cargo.lock": "cargo",
|
|
67
78
|
"go.sum": "go",
|
|
68
79
|
};
|
|
80
|
+
const WORKSPACE_INDEX_DIRECTORY_ROLE_HINTS = {
|
|
81
|
+
api: ["api", "backend"],
|
|
82
|
+
app: ["app"],
|
|
83
|
+
apps: ["app", "monorepo"],
|
|
84
|
+
backend: ["backend"],
|
|
85
|
+
components: ["ui"],
|
|
86
|
+
config: ["config"],
|
|
87
|
+
docs: ["docs"],
|
|
88
|
+
frontend: ["frontend"],
|
|
89
|
+
lib: ["library"],
|
|
90
|
+
libs: ["library"],
|
|
91
|
+
packages: ["monorepo"],
|
|
92
|
+
pages: ["routing", "frontend"],
|
|
93
|
+
routes: ["routing"],
|
|
94
|
+
scripts: ["automation"],
|
|
95
|
+
server: ["backend"],
|
|
96
|
+
services: ["services"],
|
|
97
|
+
src: ["source"],
|
|
98
|
+
test: ["tests"],
|
|
99
|
+
tests: ["tests"],
|
|
100
|
+
"__tests__": ["tests"],
|
|
101
|
+
web: ["frontend"],
|
|
102
|
+
};
|
|
103
|
+
const WORKSPACE_INDEX_ENTRYPOINT_FILENAMES = [
|
|
104
|
+
"package.json",
|
|
105
|
+
"pyproject.toml",
|
|
106
|
+
"requirements.txt",
|
|
107
|
+
"Cargo.toml",
|
|
108
|
+
"go.mod",
|
|
109
|
+
"README.md",
|
|
110
|
+
"AGENTS.md",
|
|
111
|
+
"tsconfig.json",
|
|
112
|
+
"vite.config.ts",
|
|
113
|
+
"vite.config.js",
|
|
114
|
+
"next.config.js",
|
|
115
|
+
"next.config.mjs",
|
|
116
|
+
"src/index.ts",
|
|
117
|
+
"src/index.tsx",
|
|
118
|
+
"src/main.ts",
|
|
119
|
+
"src/main.tsx",
|
|
120
|
+
"src/App.tsx",
|
|
121
|
+
"app/page.tsx",
|
|
122
|
+
"app/layout.tsx",
|
|
123
|
+
"server.ts",
|
|
124
|
+
"main.go",
|
|
125
|
+
"main.rs",
|
|
126
|
+
];
|
|
127
|
+
const WORKSPACE_INDEX_CODE_EXTENSIONS = new Set([
|
|
128
|
+
".ts",
|
|
129
|
+
".tsx",
|
|
130
|
+
".js",
|
|
131
|
+
".jsx",
|
|
132
|
+
".mjs",
|
|
133
|
+
".cjs",
|
|
134
|
+
".py",
|
|
135
|
+
".go",
|
|
136
|
+
".rs",
|
|
137
|
+
".java",
|
|
138
|
+
".kt",
|
|
139
|
+
".cs",
|
|
140
|
+
]);
|
|
69
141
|
const WORKSPACE_OBSERVE_ONLY_ACTIONS = new Set([
|
|
70
142
|
"filesystem_inspect",
|
|
71
143
|
"list_files",
|
|
@@ -111,6 +183,7 @@ const WORKSPACE_POLICY_ALLOWED_ACTIONS = {
|
|
|
111
183
|
release_operator: new Set([...WORKSPACE_POLICY_ALL_ACTIONS]),
|
|
112
184
|
};
|
|
113
185
|
const WORKSPACE_POLICY_CONFIRM_ACTIONS = new Set([
|
|
186
|
+
"run_shell",
|
|
114
187
|
"git_clone",
|
|
115
188
|
"git_fetch",
|
|
116
189
|
"git_checkout",
|
|
@@ -151,6 +224,356 @@ function uniqueRuntimeStrings(values, limit) {
|
|
|
151
224
|
.filter(Boolean);
|
|
152
225
|
return Array.from(new Set(normalized)).slice(0, limit);
|
|
153
226
|
}
|
|
227
|
+
function topLevelSegment(relativePath) {
|
|
228
|
+
const normalized = asString(relativePath).replace(/^[./]+/, "");
|
|
229
|
+
if (!normalized || normalized === ".") {
|
|
230
|
+
return "";
|
|
231
|
+
}
|
|
232
|
+
return normalized.split(/[\\/]/, 1)[0] || normalized;
|
|
233
|
+
}
|
|
234
|
+
function extensionStatsFromMap(extensionCounts, limit = WORKSPACE_INDEX_EXTENSION_LIMIT) {
|
|
235
|
+
return Array.from(extensionCounts.entries())
|
|
236
|
+
.sort((left, right) => right[1] - left[1] || left[0].localeCompare(right[0]))
|
|
237
|
+
.slice(0, limit)
|
|
238
|
+
.map(([extension, count]) => ({ extension, count }));
|
|
239
|
+
}
|
|
240
|
+
function roleTagsForDirectory(pathValue, sampleEntries) {
|
|
241
|
+
const name = topLevelSegment(pathValue).toLowerCase();
|
|
242
|
+
const tags = new Set(WORKSPACE_INDEX_DIRECTORY_ROLE_HINTS[name] || []);
|
|
243
|
+
const normalizedSamples = sampleEntries.map((item) => item.toLowerCase());
|
|
244
|
+
if (normalizedSamples.some((item) => item.endsWith(".tsx"))) {
|
|
245
|
+
tags.add("react");
|
|
246
|
+
}
|
|
247
|
+
if (normalizedSamples.some((item) => item.endsWith(".ts"))) {
|
|
248
|
+
tags.add("typescript");
|
|
249
|
+
}
|
|
250
|
+
if (normalizedSamples.some((item) => item.endsWith(".py"))) {
|
|
251
|
+
tags.add("python");
|
|
252
|
+
}
|
|
253
|
+
if (normalizedSamples.some((item) => item.includes("route") || item.includes("page"))) {
|
|
254
|
+
tags.add("routing");
|
|
255
|
+
}
|
|
256
|
+
return Array.from(tags).sort((left, right) => left.localeCompare(right));
|
|
257
|
+
}
|
|
258
|
+
function workspaceStackHints(options) {
|
|
259
|
+
const hints = new Set();
|
|
260
|
+
const keyFiles = new Set(options.keyFiles.map((item) => item.toLowerCase()));
|
|
261
|
+
const extensions = new Set(options.dominantExtensions.map((item) => item.extension.toLowerCase()));
|
|
262
|
+
const focusNames = new Set(options.focusDirectories.map((item) => topLevelSegment(item.path).toLowerCase()));
|
|
263
|
+
const focusTags = new Set(options.focusDirectories.flatMap((item) => item.role_tags));
|
|
264
|
+
if (keyFiles.has("package.json")) {
|
|
265
|
+
hints.add("node");
|
|
266
|
+
}
|
|
267
|
+
if (keyFiles.has("pnpm-lock.yaml")) {
|
|
268
|
+
hints.add("pnpm");
|
|
269
|
+
}
|
|
270
|
+
if (keyFiles.has("package-lock.json")) {
|
|
271
|
+
hints.add("npm");
|
|
272
|
+
}
|
|
273
|
+
if (keyFiles.has("yarn.lock")) {
|
|
274
|
+
hints.add("yarn");
|
|
275
|
+
}
|
|
276
|
+
if (keyFiles.has("pyproject.toml") || keyFiles.has("requirements.txt") || keyFiles.has("poetry.lock")) {
|
|
277
|
+
hints.add("python");
|
|
278
|
+
}
|
|
279
|
+
if (keyFiles.has("cargo.toml")) {
|
|
280
|
+
hints.add("rust");
|
|
281
|
+
}
|
|
282
|
+
if (keyFiles.has("go.mod")) {
|
|
283
|
+
hints.add("go");
|
|
284
|
+
}
|
|
285
|
+
if (keyFiles.has("tsconfig.json") || extensions.has(".ts") || extensions.has(".tsx")) {
|
|
286
|
+
hints.add("typescript");
|
|
287
|
+
}
|
|
288
|
+
if (extensions.has(".js") || extensions.has(".jsx") || keyFiles.has("package.json")) {
|
|
289
|
+
hints.add("javascript");
|
|
290
|
+
}
|
|
291
|
+
if (extensions.has(".tsx") || focusNames.has("components") || focusTags.has("ui")) {
|
|
292
|
+
hints.add("react");
|
|
293
|
+
}
|
|
294
|
+
if (keyFiles.has("next.config.js") || keyFiles.has("next.config.mjs") || focusNames.has("app") || focusNames.has("pages")) {
|
|
295
|
+
hints.add("nextjs");
|
|
296
|
+
}
|
|
297
|
+
if (focusNames.has("server") || focusNames.has("api") || focusNames.has("backend")) {
|
|
298
|
+
hints.add("backend");
|
|
299
|
+
}
|
|
300
|
+
if (focusNames.has("frontend") || focusNames.has("web") || focusNames.has("components")) {
|
|
301
|
+
hints.add("frontend");
|
|
302
|
+
}
|
|
303
|
+
if (focusNames.has("tests") || focusNames.has("test") || focusNames.has("__tests__")) {
|
|
304
|
+
hints.add("tests");
|
|
305
|
+
}
|
|
306
|
+
return Array.from(hints).slice(0, WORKSPACE_INDEX_STACK_HINT_LIMIT);
|
|
307
|
+
}
|
|
308
|
+
function workspaceEntrypointPaths(options) {
|
|
309
|
+
const candidates = uniqueStrings([
|
|
310
|
+
...WORKSPACE_INDEX_ENTRYPOINT_FILENAMES.filter((item) => options.keyFiles.includes(item) || options.topLevelFiles.includes(item)),
|
|
311
|
+
...options.keyFiles,
|
|
312
|
+
...options.topLevelFiles,
|
|
313
|
+
...options.focusDirectories.flatMap((item) => (item.sample_entries || [])
|
|
314
|
+
.filter((entry) => /\.(ts|tsx|js|jsx|py|go|rs)$/i.test(entry) || /(?:^|\/)(index|main|app|server)\./i.test(entry))),
|
|
315
|
+
]);
|
|
316
|
+
candidates.sort((left, right) => {
|
|
317
|
+
const leftIndex = WORKSPACE_INDEX_ENTRYPOINT_FILENAMES.indexOf(left);
|
|
318
|
+
const rightIndex = WORKSPACE_INDEX_ENTRYPOINT_FILENAMES.indexOf(right);
|
|
319
|
+
const leftScore = leftIndex >= 0 ? leftIndex : WORKSPACE_INDEX_ENTRYPOINT_FILENAMES.length + left.length;
|
|
320
|
+
const rightScore = rightIndex >= 0 ? rightIndex : WORKSPACE_INDEX_ENTRYPOINT_FILENAMES.length + right.length;
|
|
321
|
+
return leftScore - rightScore || left.localeCompare(right);
|
|
322
|
+
});
|
|
323
|
+
return candidates.slice(0, WORKSPACE_INDEX_ENTRYPOINT_LIMIT);
|
|
324
|
+
}
|
|
325
|
+
function workspaceSuggestedReadPaths(options) {
|
|
326
|
+
return uniqueStrings([
|
|
327
|
+
...options.entrypointPaths,
|
|
328
|
+
...options.keyFiles,
|
|
329
|
+
...options.focusDirectories.flatMap((item) => item.sample_entries || []),
|
|
330
|
+
]).slice(0, WORKSPACE_INDEX_SUGGESTED_READ_LIMIT);
|
|
331
|
+
}
|
|
332
|
+
function outlineLanguageForPath(relativePath) {
|
|
333
|
+
const extension = path.extname(relativePath).toLowerCase();
|
|
334
|
+
switch (extension) {
|
|
335
|
+
case ".tsx":
|
|
336
|
+
return "tsx";
|
|
337
|
+
case ".ts":
|
|
338
|
+
return "typescript";
|
|
339
|
+
case ".jsx":
|
|
340
|
+
return "jsx";
|
|
341
|
+
case ".js":
|
|
342
|
+
case ".mjs":
|
|
343
|
+
case ".cjs":
|
|
344
|
+
return "javascript";
|
|
345
|
+
case ".py":
|
|
346
|
+
return "python";
|
|
347
|
+
case ".go":
|
|
348
|
+
return "go";
|
|
349
|
+
case ".rs":
|
|
350
|
+
return "rust";
|
|
351
|
+
case ".java":
|
|
352
|
+
return "java";
|
|
353
|
+
case ".kt":
|
|
354
|
+
return "kotlin";
|
|
355
|
+
case ".cs":
|
|
356
|
+
return "csharp";
|
|
357
|
+
default:
|
|
358
|
+
return extension.replace(/^\./, "") || "text";
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
function canBuildSymbolOutline(relativePath) {
|
|
362
|
+
return WORKSPACE_INDEX_CODE_EXTENSIONS.has(path.extname(relativePath).toLowerCase());
|
|
363
|
+
}
|
|
364
|
+
function extractNamedList(value) {
|
|
365
|
+
return value
|
|
366
|
+
.split(",")
|
|
367
|
+
.map((item) => item.trim())
|
|
368
|
+
.map((item) => item.split(/\s+as\s+/i)[0]?.trim() || "")
|
|
369
|
+
.filter(Boolean);
|
|
370
|
+
}
|
|
371
|
+
function extractJavascriptLikeSymbols(content) {
|
|
372
|
+
const topLevel = new Set();
|
|
373
|
+
const exported = new Set();
|
|
374
|
+
const frameworkHints = new Set();
|
|
375
|
+
const topLevelPatterns = [
|
|
376
|
+
/^\s*(?:export\s+default\s+)?(?:async\s+)?function\s+([A-Za-z_$][\w$]*)/gm,
|
|
377
|
+
/^\s*(?:export\s+)?class\s+([A-Za-z_$][\w$]*)/gm,
|
|
378
|
+
/^\s*(?:export\s+)?(?:const|let|var)\s+([A-Za-z_$][\w$]*)/gm,
|
|
379
|
+
/^\s*export\s+(?:type|interface|enum)\s+([A-Za-z_$][\w$]*)/gm,
|
|
380
|
+
];
|
|
381
|
+
for (const pattern of topLevelPatterns) {
|
|
382
|
+
for (const match of content.matchAll(pattern)) {
|
|
383
|
+
const symbol = asString(match[1]);
|
|
384
|
+
if (symbol) {
|
|
385
|
+
topLevel.add(symbol);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
const exportPatterns = [
|
|
390
|
+
/^\s*export\s+(?:default\s+)?(?:async\s+)?function\s+([A-Za-z_$][\w$]*)/gm,
|
|
391
|
+
/^\s*export\s+class\s+([A-Za-z_$][\w$]*)/gm,
|
|
392
|
+
/^\s*export\s+(?:const|let|var)\s+([A-Za-z_$][\w$]*)/gm,
|
|
393
|
+
/^\s*export\s+(?:type|interface|enum)\s+([A-Za-z_$][\w$]*)/gm,
|
|
394
|
+
/^\s*export\s*{\s*([^}]+)\s*}/gm,
|
|
395
|
+
];
|
|
396
|
+
for (const pattern of exportPatterns) {
|
|
397
|
+
for (const match of content.matchAll(pattern)) {
|
|
398
|
+
if (match[0]?.includes("{")) {
|
|
399
|
+
for (const symbol of extractNamedList(match[1] || "")) {
|
|
400
|
+
exported.add(symbol);
|
|
401
|
+
topLevel.add(symbol);
|
|
402
|
+
}
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
const symbol = asString(match[1]);
|
|
406
|
+
if (symbol) {
|
|
407
|
+
exported.add(symbol);
|
|
408
|
+
topLevel.add(symbol);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
if (/from\s+["']react["']|React\./.test(content)) {
|
|
413
|
+
frameworkHints.add("react");
|
|
414
|
+
}
|
|
415
|
+
if (/from\s+["']next\/|next\/(app|server|navigation|router)/.test(content)) {
|
|
416
|
+
frameworkHints.add("nextjs");
|
|
417
|
+
}
|
|
418
|
+
if (/from\s+["']express["']|express\(/.test(content)) {
|
|
419
|
+
frameworkHints.add("express");
|
|
420
|
+
}
|
|
421
|
+
if (/from\s+["']fastify["']|fastify\(/.test(content)) {
|
|
422
|
+
frameworkHints.add("fastify");
|
|
423
|
+
}
|
|
424
|
+
return {
|
|
425
|
+
top_level_symbols: Array.from(topLevel).slice(0, WORKSPACE_INDEX_SYMBOL_LIMIT),
|
|
426
|
+
exported_symbols: Array.from(exported).slice(0, WORKSPACE_INDEX_SYMBOL_LIMIT),
|
|
427
|
+
framework_hints: Array.from(frameworkHints).slice(0, 4),
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
function extractPythonSymbols(content) {
|
|
431
|
+
const topLevel = new Set();
|
|
432
|
+
const frameworkHints = new Set();
|
|
433
|
+
for (const match of content.matchAll(/^(?:async\s+)?def\s+([A-Za-z_][\w]*)\s*\(|^class\s+([A-Za-z_][\w]*)\s*[\(:]/gm)) {
|
|
434
|
+
const symbol = asString(match[1] || match[2]);
|
|
435
|
+
if (symbol) {
|
|
436
|
+
topLevel.add(symbol);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
if (/from\s+fastapi\s+import|FastAPI\(/.test(content)) {
|
|
440
|
+
frameworkHints.add("fastapi");
|
|
441
|
+
}
|
|
442
|
+
if (/from\s+flask\s+import|Flask\(/.test(content)) {
|
|
443
|
+
frameworkHints.add("flask");
|
|
444
|
+
}
|
|
445
|
+
return {
|
|
446
|
+
top_level_symbols: Array.from(topLevel).slice(0, WORKSPACE_INDEX_SYMBOL_LIMIT),
|
|
447
|
+
exported_symbols: Array.from(topLevel).filter((item) => !item.startsWith("_")).slice(0, WORKSPACE_INDEX_SYMBOL_LIMIT),
|
|
448
|
+
framework_hints: Array.from(frameworkHints).slice(0, 4),
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
function extractGoSymbols(content) {
|
|
452
|
+
const topLevel = new Set();
|
|
453
|
+
for (const match of content.matchAll(/^(?:func|type)\s+([A-Za-z_][\w]*)/gm)) {
|
|
454
|
+
const symbol = asString(match[1]);
|
|
455
|
+
if (symbol) {
|
|
456
|
+
topLevel.add(symbol);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
return {
|
|
460
|
+
top_level_symbols: Array.from(topLevel).slice(0, WORKSPACE_INDEX_SYMBOL_LIMIT),
|
|
461
|
+
exported_symbols: Array.from(topLevel).filter((item) => /^[A-Z]/.test(item)).slice(0, WORKSPACE_INDEX_SYMBOL_LIMIT),
|
|
462
|
+
framework_hints: [],
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
function extractRustSymbols(content) {
|
|
466
|
+
const topLevel = new Set();
|
|
467
|
+
const exported = new Set();
|
|
468
|
+
for (const match of content.matchAll(/^(?:pub\s+)?(?:fn|struct|enum|trait)\s+([A-Za-z_][\w]*)/gm)) {
|
|
469
|
+
const symbol = asString(match[1]);
|
|
470
|
+
if (symbol) {
|
|
471
|
+
topLevel.add(symbol);
|
|
472
|
+
if (match[0]?.startsWith("pub ")) {
|
|
473
|
+
exported.add(symbol);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
return {
|
|
478
|
+
top_level_symbols: Array.from(topLevel).slice(0, WORKSPACE_INDEX_SYMBOL_LIMIT),
|
|
479
|
+
exported_symbols: Array.from(exported).slice(0, WORKSPACE_INDEX_SYMBOL_LIMIT),
|
|
480
|
+
framework_hints: [],
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
function extractJvmLikeSymbols(content) {
|
|
484
|
+
const topLevel = new Set();
|
|
485
|
+
for (const match of content.matchAll(/^(?:public\s+|private\s+|protected\s+|internal\s+)?(?:class|interface|enum|object)\s+([A-Za-z_][\w]*)/gm)) {
|
|
486
|
+
const symbol = asString(match[1]);
|
|
487
|
+
if (symbol) {
|
|
488
|
+
topLevel.add(symbol);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
return {
|
|
492
|
+
top_level_symbols: Array.from(topLevel).slice(0, WORKSPACE_INDEX_SYMBOL_LIMIT),
|
|
493
|
+
exported_symbols: Array.from(topLevel).slice(0, WORKSPACE_INDEX_SYMBOL_LIMIT),
|
|
494
|
+
framework_hints: [],
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
function extractCSharpSymbols(content) {
|
|
498
|
+
const topLevel = new Set();
|
|
499
|
+
const exported = new Set();
|
|
500
|
+
for (const match of content.matchAll(/^(?:public\s+|internal\s+|private\s+|protected\s+)?(?:class|interface|enum|record)\s+([A-Za-z_][\w]*)/gm)) {
|
|
501
|
+
const symbol = asString(match[1]);
|
|
502
|
+
if (symbol) {
|
|
503
|
+
topLevel.add(symbol);
|
|
504
|
+
if (match[0]?.startsWith("public ") || match[0]?.startsWith("internal ")) {
|
|
505
|
+
exported.add(symbol);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
return {
|
|
510
|
+
top_level_symbols: Array.from(topLevel).slice(0, WORKSPACE_INDEX_SYMBOL_LIMIT),
|
|
511
|
+
exported_symbols: Array.from(exported).slice(0, WORKSPACE_INDEX_SYMBOL_LIMIT),
|
|
512
|
+
framework_hints: [],
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
function buildSymbolOutline(relativePath, content) {
|
|
516
|
+
const extension = path.extname(relativePath).toLowerCase();
|
|
517
|
+
let extracted = null;
|
|
518
|
+
if ([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"].includes(extension)) {
|
|
519
|
+
extracted = extractJavascriptLikeSymbols(content);
|
|
520
|
+
}
|
|
521
|
+
else if (extension === ".py") {
|
|
522
|
+
extracted = extractPythonSymbols(content);
|
|
523
|
+
}
|
|
524
|
+
else if (extension === ".go") {
|
|
525
|
+
extracted = extractGoSymbols(content);
|
|
526
|
+
}
|
|
527
|
+
else if (extension === ".rs") {
|
|
528
|
+
extracted = extractRustSymbols(content);
|
|
529
|
+
}
|
|
530
|
+
else if (extension === ".java" || extension === ".kt") {
|
|
531
|
+
extracted = extractJvmLikeSymbols(content);
|
|
532
|
+
}
|
|
533
|
+
else if (extension === ".cs") {
|
|
534
|
+
extracted = extractCSharpSymbols(content);
|
|
535
|
+
}
|
|
536
|
+
if (!extracted) {
|
|
537
|
+
return null;
|
|
538
|
+
}
|
|
539
|
+
if (extracted.top_level_symbols.length === 0 && extracted.exported_symbols.length === 0 && extracted.framework_hints.length === 0) {
|
|
540
|
+
return null;
|
|
541
|
+
}
|
|
542
|
+
return {
|
|
543
|
+
path: relativePath,
|
|
544
|
+
language: outlineLanguageForPath(relativePath),
|
|
545
|
+
top_level_symbols: extracted.top_level_symbols,
|
|
546
|
+
exported_symbols: extracted.exported_symbols,
|
|
547
|
+
framework_hints: extracted.framework_hints,
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
async function buildWorkspaceSymbolOutlines(basePath, candidatePaths) {
|
|
551
|
+
const outlines = [];
|
|
552
|
+
for (const relativePath of candidatePaths) {
|
|
553
|
+
if (outlines.length >= WORKSPACE_INDEX_SYMBOL_OUTLINE_LIMIT) {
|
|
554
|
+
break;
|
|
555
|
+
}
|
|
556
|
+
if (!canBuildSymbolOutline(relativePath)) {
|
|
557
|
+
continue;
|
|
558
|
+
}
|
|
559
|
+
const absolutePath = path.join(basePath, relativePath);
|
|
560
|
+
try {
|
|
561
|
+
const fileStats = await stat(absolutePath);
|
|
562
|
+
if (!fileStats.isFile() || fileStats.size > WORKSPACE_INDEX_SYMBOL_FILE_SIZE_LIMIT) {
|
|
563
|
+
continue;
|
|
564
|
+
}
|
|
565
|
+
const content = await readFile(absolutePath, "utf8");
|
|
566
|
+
const outline = buildSymbolOutline(relativePath, content);
|
|
567
|
+
if (outline) {
|
|
568
|
+
outlines.push(outline);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
catch {
|
|
572
|
+
continue;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
return outlines;
|
|
576
|
+
}
|
|
154
577
|
function normalizeWorkspaceMemory(value, workspaceId) {
|
|
155
578
|
if (!value) {
|
|
156
579
|
return undefined;
|
|
@@ -661,8 +1084,10 @@ export async function buildWorkspaceIndex(options) {
|
|
|
661
1084
|
]));
|
|
662
1085
|
const queue = [{ directoryPath: basePath, depth: 0 }];
|
|
663
1086
|
const topLevelEntries = [];
|
|
1087
|
+
const topLevelFiles = [];
|
|
664
1088
|
const keyFiles = new Set();
|
|
665
1089
|
const extensionCounts = new Map();
|
|
1090
|
+
const directorySummaryByPath = new Map();
|
|
666
1091
|
let scannedDirectoryCount = 0;
|
|
667
1092
|
let scannedFileCount = 0;
|
|
668
1093
|
let truncated = false;
|
|
@@ -684,6 +1109,10 @@ export async function buildWorkspaceIndex(options) {
|
|
|
684
1109
|
continue;
|
|
685
1110
|
}
|
|
686
1111
|
entries.sort((left, right) => left.name.localeCompare(right.name));
|
|
1112
|
+
const currentRelativePath = toRelativePath(basePath, next.directoryPath);
|
|
1113
|
+
const currentDirectorySummary = next.depth === 1
|
|
1114
|
+
? directorySummaryByPath.get(currentRelativePath)
|
|
1115
|
+
: undefined;
|
|
687
1116
|
for (const entry of entries) {
|
|
688
1117
|
const candidatePath = path.join(next.directoryPath, entry.name);
|
|
689
1118
|
const relativePath = toRelativePath(basePath, candidatePath);
|
|
@@ -697,6 +1126,24 @@ export async function buildWorkspaceIndex(options) {
|
|
|
697
1126
|
if (ignoredDirectoryNames.has(entry.name)) {
|
|
698
1127
|
continue;
|
|
699
1128
|
}
|
|
1129
|
+
if (next.depth === 0) {
|
|
1130
|
+
directorySummaryByPath.set(relativePath, {
|
|
1131
|
+
path: relativePath,
|
|
1132
|
+
child_file_count: 0,
|
|
1133
|
+
child_directory_count: 0,
|
|
1134
|
+
key_files: [],
|
|
1135
|
+
dominant_extensions: [],
|
|
1136
|
+
sample_entries: [],
|
|
1137
|
+
role_tags: [],
|
|
1138
|
+
extensionCounts: new Map(),
|
|
1139
|
+
});
|
|
1140
|
+
}
|
|
1141
|
+
if (currentDirectorySummary) {
|
|
1142
|
+
currentDirectorySummary.child_directory_count += 1;
|
|
1143
|
+
if (currentDirectorySummary.sample_entries.length < WORKSPACE_INDEX_DIRECTORY_SAMPLE_LIMIT) {
|
|
1144
|
+
currentDirectorySummary.sample_entries.push(relativePath);
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
700
1147
|
if (next.depth + 1 <= maxDepth) {
|
|
701
1148
|
queue.push({
|
|
702
1149
|
directoryPath: candidatePath,
|
|
@@ -712,21 +1159,79 @@ export async function buildWorkspaceIndex(options) {
|
|
|
712
1159
|
continue;
|
|
713
1160
|
}
|
|
714
1161
|
scannedFileCount += 1;
|
|
1162
|
+
if (next.depth === 0 && topLevelFiles.length < WORKSPACE_INDEX_TOP_LEVEL_FILE_LIMIT) {
|
|
1163
|
+
topLevelFiles.push(relativePath);
|
|
1164
|
+
}
|
|
715
1165
|
if (keyFilePatterns.has(entry.name)) {
|
|
716
1166
|
keyFiles.add(relativePath);
|
|
1167
|
+
if (currentDirectorySummary && currentDirectorySummary.key_files.length < WORKSPACE_INDEX_DIRECTORY_KEY_FILE_LIMIT) {
|
|
1168
|
+
currentDirectorySummary.key_files.push(relativePath);
|
|
1169
|
+
}
|
|
717
1170
|
}
|
|
718
1171
|
const extension = path.extname(entry.name).toLowerCase() || "[no_ext]";
|
|
719
1172
|
extensionCounts.set(extension, (extensionCounts.get(extension) || 0) + 1);
|
|
1173
|
+
if (currentDirectorySummary) {
|
|
1174
|
+
currentDirectorySummary.child_file_count += 1;
|
|
1175
|
+
currentDirectorySummary.extensionCounts.set(extension, (currentDirectorySummary.extensionCounts.get(extension) || 0) + 1);
|
|
1176
|
+
if (currentDirectorySummary.sample_entries.length < WORKSPACE_INDEX_DIRECTORY_SAMPLE_LIMIT) {
|
|
1177
|
+
currentDirectorySummary.sample_entries.push(relativePath);
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
720
1180
|
if (scannedFileCount >= maxFiles) {
|
|
721
1181
|
truncated = true;
|
|
722
1182
|
break;
|
|
723
1183
|
}
|
|
724
1184
|
}
|
|
725
1185
|
}
|
|
726
|
-
const dominantExtensions =
|
|
727
|
-
|
|
728
|
-
.
|
|
729
|
-
|
|
1186
|
+
const dominantExtensions = extensionStatsFromMap(extensionCounts, WORKSPACE_INDEX_EXTENSION_LIMIT);
|
|
1187
|
+
const directorySummaries = Array.from(directorySummaryByPath.values())
|
|
1188
|
+
.map((item) => {
|
|
1189
|
+
const sampleEntries = uniqueStrings(item.sample_entries).slice(0, WORKSPACE_INDEX_DIRECTORY_SAMPLE_LIMIT);
|
|
1190
|
+
return {
|
|
1191
|
+
path: item.path,
|
|
1192
|
+
child_file_count: item.child_file_count,
|
|
1193
|
+
child_directory_count: item.child_directory_count,
|
|
1194
|
+
key_files: uniqueStrings(item.key_files).slice(0, WORKSPACE_INDEX_DIRECTORY_KEY_FILE_LIMIT),
|
|
1195
|
+
dominant_extensions: extensionStatsFromMap(item.extensionCounts, WORKSPACE_INDEX_DIRECTORY_EXTENSION_LIMIT),
|
|
1196
|
+
sample_entries: sampleEntries,
|
|
1197
|
+
role_tags: roleTagsForDirectory(item.path, sampleEntries),
|
|
1198
|
+
};
|
|
1199
|
+
})
|
|
1200
|
+
.sort((left, right) => left.path.localeCompare(right.path));
|
|
1201
|
+
const focusDirectories = [...directorySummaries]
|
|
1202
|
+
.sort((left, right) => {
|
|
1203
|
+
const leftScore = (left.role_tags.length * 10) + left.child_file_count + left.child_directory_count + (left.key_files.length * 2);
|
|
1204
|
+
const rightScore = (right.role_tags.length * 10) + right.child_file_count + right.child_directory_count + (right.key_files.length * 2);
|
|
1205
|
+
return rightScore - leftScore || left.path.localeCompare(right.path);
|
|
1206
|
+
})
|
|
1207
|
+
.filter((item) => item.child_file_count > 0 || item.child_directory_count > 0 || item.role_tags.length > 0)
|
|
1208
|
+
.slice(0, WORKSPACE_INDEX_FOCUS_DIRECTORY_LIMIT);
|
|
1209
|
+
const normalizedKeyFiles = Array.from(keyFiles).sort((left, right) => left.localeCompare(right));
|
|
1210
|
+
const stackHints = workspaceStackHints({
|
|
1211
|
+
keyFiles: normalizedKeyFiles,
|
|
1212
|
+
dominantExtensions,
|
|
1213
|
+
focusDirectories,
|
|
1214
|
+
});
|
|
1215
|
+
const entrypointPaths = workspaceEntrypointPaths({
|
|
1216
|
+
keyFiles: normalizedKeyFiles,
|
|
1217
|
+
topLevelFiles,
|
|
1218
|
+
focusDirectories,
|
|
1219
|
+
});
|
|
1220
|
+
const suggestedReadPaths = workspaceSuggestedReadPaths({
|
|
1221
|
+
keyFiles: normalizedKeyFiles,
|
|
1222
|
+
entrypointPaths,
|
|
1223
|
+
focusDirectories,
|
|
1224
|
+
});
|
|
1225
|
+
const symbolOutlines = await buildWorkspaceSymbolOutlines(basePath, suggestedReadPaths);
|
|
1226
|
+
const focusSummary = focusDirectories.length > 0
|
|
1227
|
+
? ` Areas foco: ${focusDirectories.map((item) => item.path).join(", ")}.`
|
|
1228
|
+
: "";
|
|
1229
|
+
const stackSummary = stackHints.length > 0
|
|
1230
|
+
? ` Stack sugerida: ${stackHints.join(", ")}.`
|
|
1231
|
+
: "";
|
|
1232
|
+
const outlineSummary = symbolOutlines.length > 0
|
|
1233
|
+
? ` Outline inicial: ${symbolOutlines.map((item) => item.path).join(", ")}.`
|
|
1234
|
+
: "";
|
|
730
1235
|
return {
|
|
731
1236
|
resolver,
|
|
732
1237
|
workspace_id: options.workspaceId,
|
|
@@ -735,9 +1240,16 @@ export async function buildWorkspaceIndex(options) {
|
|
|
735
1240
|
scanned_file_count: scannedFileCount,
|
|
736
1241
|
truncated,
|
|
737
1242
|
top_level_entries: topLevelEntries,
|
|
738
|
-
|
|
1243
|
+
top_level_files: topLevelFiles,
|
|
1244
|
+
key_files: normalizedKeyFiles,
|
|
739
1245
|
dominant_extensions: dominantExtensions,
|
|
740
|
-
|
|
1246
|
+
directory_summaries: directorySummaries,
|
|
1247
|
+
focus_directories: focusDirectories,
|
|
1248
|
+
stack_hints: stackHints,
|
|
1249
|
+
entrypoint_paths: entrypointPaths,
|
|
1250
|
+
suggested_read_paths: suggestedReadPaths,
|
|
1251
|
+
symbol_outlines: symbolOutlines,
|
|
1252
|
+
summary: `Indexei ${scannedFileCount} arquivo${scannedFileCount === 1 ? "" : "s"} e ${scannedDirectoryCount} diretorio${scannedDirectoryCount === 1 ? "" : "s"} em ${path.basename(basePath) || basePath}.${stackSummary}${focusSummary}${outlineSummary}`.trim(),
|
|
741
1253
|
};
|
|
742
1254
|
}
|
|
743
1255
|
export async function buildRepoManifest(options) {
|