forge-server 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/bin/setup-forge.sh +1 -1
  2. package/bin/setup.js +99 -0
  3. package/dist/cli.js +37 -37
  4. package/dist/cli.js.map +1 -1
  5. package/dist/index.js +4 -4
  6. package/dist/index.js.map +1 -1
  7. package/dist/storage/schema.js +113 -113
  8. package/dist/storage/schema.js.map +1 -1
  9. package/dist/storage/sqlite.js +1 -1
  10. package/dist/storage/sqlite.js.map +1 -1
  11. package/dist/util/logger.d.ts +1 -1
  12. package/dist/util/logger.js +1 -1
  13. package/dist/util/types.js +1 -1
  14. package/dist/util/types.js.map +1 -1
  15. package/package.json +8 -2
  16. package/plugin/.mcp.json +1 -1
  17. package/.claude/hooks/worktree-create.sh +0 -64
  18. package/.claude/hooks/worktree-remove.sh +0 -57
  19. package/.claude/settings.local.json +0 -29
  20. package/.forge/knowledge/conventions.yaml +0 -1
  21. package/.forge/knowledge/decisions.yaml +0 -1
  22. package/.forge/knowledge/gotchas.yaml +0 -1
  23. package/.forge/knowledge/patterns.yaml +0 -1
  24. package/.forge/manifest.yaml +0 -6
  25. package/CLAUDE.md +0 -144
  26. package/docker-compose.yml +0 -20
  27. package/docs/plans/2026-02-27-swarm-coordination/architecture.md +0 -203
  28. package/docs/plans/2026-02-27-swarm-coordination/vision.md +0 -57
  29. package/docs/plans/completed/2026-02-26-forge-plugin-bundling/architecture.md +0 -1
  30. package/docs/plans/completed/2026-02-26-forge-plugin-bundling/vision.md +0 -300
  31. package/docs/plans/completed/2026-02-27-forge-swarm-learning/architecture.md +0 -480
  32. package/docs/plans/completed/2026-02-27-forge-swarm-learning/verification-checklist.md +0 -462
  33. package/docs/plans/completed/2026-02-27-git-history-atlassian/git-jira-plan.md +0 -181
  34. package/src/cli.ts +0 -655
  35. package/src/context/.gitkeep +0 -0
  36. package/src/context/codebase.ts +0 -393
  37. package/src/context/injector.ts +0 -797
  38. package/src/context/memory.ts +0 -187
  39. package/src/context/session-index.ts +0 -327
  40. package/src/context/session.ts +0 -152
  41. package/src/index.ts +0 -47
  42. package/src/ingestion/.gitkeep +0 -0
  43. package/src/ingestion/chunker.ts +0 -277
  44. package/src/ingestion/embedder.ts +0 -167
  45. package/src/ingestion/git-analyzer.ts +0 -545
  46. package/src/ingestion/indexer.ts +0 -984
  47. package/src/ingestion/markdown-chunker.ts +0 -337
  48. package/src/ingestion/markdown-knowledge.ts +0 -175
  49. package/src/ingestion/parser.ts +0 -475
  50. package/src/ingestion/watcher.ts +0 -182
  51. package/src/knowledge/.gitkeep +0 -0
  52. package/src/knowledge/hydrator.ts +0 -246
  53. package/src/knowledge/registry.ts +0 -463
  54. package/src/knowledge/search.ts +0 -565
  55. package/src/knowledge/store.ts +0 -262
  56. package/src/learning/.gitkeep +0 -0
  57. package/src/learning/confidence.ts +0 -193
  58. package/src/learning/patterns.ts +0 -360
  59. package/src/learning/trajectory.ts +0 -268
  60. package/src/memory/.gitkeep +0 -0
  61. package/src/memory/memory-compat.ts +0 -233
  62. package/src/memory/observation-store.ts +0 -224
  63. package/src/memory/session-tracker.ts +0 -332
  64. package/src/pipeline/.gitkeep +0 -0
  65. package/src/pipeline/engine.ts +0 -1139
  66. package/src/pipeline/events.ts +0 -253
  67. package/src/pipeline/parallel.ts +0 -394
  68. package/src/pipeline/state-machine.ts +0 -199
  69. package/src/query/.gitkeep +0 -0
  70. package/src/query/graph-queries.ts +0 -262
  71. package/src/query/hybrid-search.ts +0 -337
  72. package/src/query/intent-detector.ts +0 -131
  73. package/src/query/ranking.ts +0 -161
  74. package/src/server.ts +0 -352
  75. package/src/storage/.gitkeep +0 -0
  76. package/src/storage/falkordb-store.ts +0 -388
  77. package/src/storage/file-cache.ts +0 -141
  78. package/src/storage/interfaces.ts +0 -201
  79. package/src/storage/qdrant-store.ts +0 -557
  80. package/src/storage/schema.ts +0 -139
  81. package/src/storage/sqlite.ts +0 -168
  82. package/src/tools/.gitkeep +0 -0
  83. package/src/tools/collaboration-tools.ts +0 -208
  84. package/src/tools/context-tools.ts +0 -493
  85. package/src/tools/graph-tools.ts +0 -295
  86. package/src/tools/ingestion-tools.ts +0 -122
  87. package/src/tools/learning-tools.ts +0 -181
  88. package/src/tools/memory-tools.ts +0 -234
  89. package/src/tools/phase-tools.ts +0 -1452
  90. package/src/tools/pipeline-tools.ts +0 -188
  91. package/src/tools/registration-tools.ts +0 -450
  92. package/src/util/.gitkeep +0 -0
  93. package/src/util/circuit-breaker.ts +0 -193
  94. package/src/util/config.ts +0 -177
  95. package/src/util/logger.ts +0 -53
  96. package/src/util/token-counter.ts +0 -52
  97. package/src/util/types.ts +0 -710
  98. package/tests/context/.gitkeep +0 -0
  99. package/tests/integration/.gitkeep +0 -0
  100. package/tests/knowledge/.gitkeep +0 -0
  101. package/tests/learning/.gitkeep +0 -0
  102. package/tests/pipeline/.gitkeep +0 -0
  103. package/tests/tools/.gitkeep +0 -0
  104. package/tsconfig.json +0 -21
  105. package/vitest.config.ts +0 -10
  106. package/vscode-extension/.vscodeignore +0 -7
  107. package/vscode-extension/README.md +0 -43
  108. package/vscode-extension/out/edge-collector.js +0 -274
  109. package/vscode-extension/out/edge-collector.js.map +0 -1
  110. package/vscode-extension/out/extension.js +0 -264
  111. package/vscode-extension/out/extension.js.map +0 -1
  112. package/vscode-extension/out/forge-client.js +0 -318
  113. package/vscode-extension/out/forge-client.js.map +0 -1
  114. package/vscode-extension/package-lock.json +0 -59
  115. package/vscode-extension/package.json +0 -71
  116. package/vscode-extension/src/edge-collector.ts +0 -320
  117. package/vscode-extension/src/extension.ts +0 -269
  118. package/vscode-extension/src/forge-client.ts +0 -364
  119. package/vscode-extension/tsconfig.json +0 -19
@@ -1,320 +0,0 @@
1
- // edge-collector.ts — Forge LSP Bridge
2
- // On file save, uses VS Code's built-in call hierarchy API to collect
3
- // type-resolved CALLS edges from the TypeScript Language Server.
4
- // Edges are batched and flushed to the forge server after a debounce period.
5
-
6
- import * as vscode from 'vscode';
7
-
8
- // ---------------------------------------------------------------------------
9
- // Types
10
- // ---------------------------------------------------------------------------
11
-
12
- export interface LspEdge {
13
- from_file: string;
14
- from_symbol: string;
15
- to_file: string;
16
- to_symbol: string;
17
- edge_type: string;
18
- }
19
-
20
- export type FlushCallback = (edges: LspEdge[]) => Promise<void>;
21
-
22
- // ---------------------------------------------------------------------------
23
- // EdgeCollector
24
- // ---------------------------------------------------------------------------
25
-
26
- export class EdgeCollector {
27
- private pendingEdges: LspEdge[] = [];
28
- private debounceTimer: ReturnType<typeof setTimeout> | undefined;
29
- private debounceMs: number;
30
- private readonly flushCallback: FlushCallback;
31
- private flushing = false;
32
-
33
- constructor(debounceMs: number, flushCallback: FlushCallback) {
34
- this.debounceMs = debounceMs;
35
- this.flushCallback = flushCallback;
36
- }
37
-
38
- /**
39
- * Update the debounce interval (e.g. when the user changes settings).
40
- */
41
- setDebounceMs(ms: number): void {
42
- this.debounceMs = ms;
43
- }
44
-
45
- /**
46
- * Collect call-hierarchy edges for all functions/methods in a saved document.
47
- */
48
- async collectFromDocument(document: vscode.TextDocument): Promise<void> {
49
- const uri = document.uri;
50
-
51
- // Get all document symbols to find functions, methods, constructors
52
- const symbols = await this.getCallableSymbols(uri);
53
-
54
- if (symbols.length === 0) {
55
- return;
56
- }
57
-
58
- // For each callable symbol, prepare a call hierarchy and collect outgoing calls
59
- for (const sym of symbols) {
60
- try {
61
- const edges = await this.collectOutgoingCalls(uri, sym);
62
- if (edges.length > 0) {
63
- this.pendingEdges.push(...edges);
64
- }
65
- } catch {
66
- // Individual symbol failures are non-fatal — skip and continue.
67
- // This can happen if the TS language server hasn't finished loading
68
- // or if the symbol is in an unsupported position.
69
- }
70
- }
71
-
72
- // Reset the debounce timer
73
- this.scheduleFlush();
74
- }
75
-
76
- /**
77
- * Force an immediate flush of all pending edges, regardless of debounce.
78
- */
79
- async flushNow(): Promise<void> {
80
- this.cancelDebounce();
81
- await this.doFlush();
82
- }
83
-
84
- // -------------------------------------------------------------------------
85
- // Symbol discovery
86
- // -------------------------------------------------------------------------
87
-
88
- /**
89
- * Flatten the document symbol tree and return only callable symbols
90
- * (functions, methods, constructors).
91
- */
92
- private async getCallableSymbols(
93
- uri: vscode.Uri,
94
- ): Promise<FlatSymbol[]> {
95
- let rawSymbols: vscode.DocumentSymbol[] | undefined;
96
- try {
97
- rawSymbols = await vscode.commands.executeCommand<vscode.DocumentSymbol[]>(
98
- 'vscode.executeDocumentSymbolProvider',
99
- uri,
100
- );
101
- } catch {
102
- return [];
103
- }
104
-
105
- if (!rawSymbols || rawSymbols.length === 0) {
106
- return [];
107
- }
108
-
109
- const result: FlatSymbol[] = [];
110
- this.flattenSymbols(rawSymbols, uri, result, undefined);
111
- return result;
112
- }
113
-
114
- /**
115
- * Recursively flatten nested DocumentSymbols into a flat list of callable
116
- * symbols. Tracks parent name to build qualified symbol names like
117
- * "ClassName.methodName".
118
- */
119
- private flattenSymbols(
120
- symbols: vscode.DocumentSymbol[],
121
- uri: vscode.Uri,
122
- out: FlatSymbol[],
123
- parentName: string | undefined,
124
- ): void {
125
- for (const sym of symbols) {
126
- const qualifiedName = parentName
127
- ? `${parentName}.${sym.name}`
128
- : sym.name;
129
-
130
- if (isCallable(sym.kind)) {
131
- out.push({
132
- name: qualifiedName,
133
- kind: sym.kind,
134
- range: sym.selectionRange,
135
- uri,
136
- });
137
- }
138
-
139
- // Recurse into children (e.g. methods inside a class)
140
- if (sym.children && sym.children.length > 0) {
141
- const nextParent =
142
- sym.kind === vscode.SymbolKind.Class ||
143
- sym.kind === vscode.SymbolKind.Interface ||
144
- sym.kind === vscode.SymbolKind.Namespace ||
145
- sym.kind === vscode.SymbolKind.Module
146
- ? qualifiedName
147
- : parentName;
148
- this.flattenSymbols(sym.children, uri, out, nextParent);
149
- }
150
- }
151
- }
152
-
153
- // -------------------------------------------------------------------------
154
- // Call hierarchy collection
155
- // -------------------------------------------------------------------------
156
-
157
- /**
158
- * For a single callable symbol, prepare a call hierarchy item and collect
159
- * its outgoing calls. Returns LspEdge[] for "CALLS" relationships.
160
- */
161
- private async collectOutgoingCalls(
162
- uri: vscode.Uri,
163
- sym: FlatSymbol,
164
- ): Promise<LspEdge[]> {
165
- // Prepare call hierarchy at the symbol's selection position
166
- const position = sym.range.start;
167
- let callItems: vscode.CallHierarchyItem[] | undefined;
168
-
169
- try {
170
- callItems = await vscode.commands.executeCommand<vscode.CallHierarchyItem[]>(
171
- 'vscode.prepareCallHierarchy',
172
- uri,
173
- position,
174
- );
175
- } catch {
176
- return [];
177
- }
178
-
179
- if (!callItems || callItems.length === 0) {
180
- return [];
181
- }
182
-
183
- const edges: LspEdge[] = [];
184
-
185
- for (const item of callItems) {
186
- let outgoingCalls: vscode.CallHierarchyOutgoingCall[] | undefined;
187
- try {
188
- outgoingCalls = await vscode.commands.executeCommand<vscode.CallHierarchyOutgoingCall[]>(
189
- 'vscode.provideOutgoingCalls',
190
- item,
191
- );
192
- } catch {
193
- continue;
194
- }
195
-
196
- if (!outgoingCalls || outgoingCalls.length === 0) {
197
- continue;
198
- }
199
-
200
- const fromFile = normalizeFilePath(item.uri.fsPath);
201
- const fromSymbol = item.name;
202
-
203
- for (const call of outgoingCalls) {
204
- const toFile = normalizeFilePath(call.to.uri.fsPath);
205
- const toSymbol = call.to.name;
206
-
207
- // Skip self-references and node_modules
208
- if (fromSymbol === toSymbol && fromFile === toFile) {
209
- continue;
210
- }
211
- if (toFile.includes('node_modules')) {
212
- continue;
213
- }
214
-
215
- edges.push({
216
- from_file: fromFile,
217
- from_symbol: fromSymbol,
218
- to_file: toFile,
219
- to_symbol: toSymbol,
220
- edge_type: 'CALLS',
221
- });
222
- }
223
- }
224
-
225
- return edges;
226
- }
227
-
228
- // -------------------------------------------------------------------------
229
- // Debounced flush
230
- // -------------------------------------------------------------------------
231
-
232
- private scheduleFlush(): void {
233
- this.cancelDebounce();
234
- this.debounceTimer = setTimeout(() => {
235
- void this.doFlush();
236
- }, this.debounceMs);
237
- }
238
-
239
- private cancelDebounce(): void {
240
- if (this.debounceTimer !== undefined) {
241
- clearTimeout(this.debounceTimer);
242
- this.debounceTimer = undefined;
243
- }
244
- }
245
-
246
- private async doFlush(): Promise<void> {
247
- if (this.flushing) {
248
- // Re-schedule if a flush is already in progress
249
- this.scheduleFlush();
250
- return;
251
- }
252
-
253
- if (this.pendingEdges.length === 0) {
254
- return;
255
- }
256
-
257
- // Take ownership of the pending edges
258
- const batch = this.pendingEdges;
259
- this.pendingEdges = [];
260
- this.flushing = true;
261
-
262
- try {
263
- // Deduplicate edges within the batch
264
- const deduplicated = deduplicateEdges(batch);
265
- await this.flushCallback(deduplicated);
266
- } catch {
267
- // If flush fails, re-queue the edges for the next attempt
268
- this.pendingEdges.unshift(...batch);
269
- } finally {
270
- this.flushing = false;
271
- }
272
- }
273
- }
274
-
275
- // ---------------------------------------------------------------------------
276
- // Helpers
277
- // ---------------------------------------------------------------------------
278
-
279
- interface FlatSymbol {
280
- name: string;
281
- kind: vscode.SymbolKind;
282
- range: vscode.Range;
283
- uri: vscode.Uri;
284
- }
285
-
286
- /**
287
- * Returns true if the symbol kind represents something that can make calls.
288
- */
289
- function isCallable(kind: vscode.SymbolKind): boolean {
290
- return (
291
- kind === vscode.SymbolKind.Function ||
292
- kind === vscode.SymbolKind.Method ||
293
- kind === vscode.SymbolKind.Constructor
294
- );
295
- }
296
-
297
- /**
298
- * Normalize file paths to use forward slashes for consistency.
299
- */
300
- function normalizeFilePath(filePath: string): string {
301
- return filePath.replace(/\\/g, '/');
302
- }
303
-
304
- /**
305
- * Remove duplicate edges within a batch (same from/to file+symbol+type).
306
- */
307
- function deduplicateEdges(edges: LspEdge[]): LspEdge[] {
308
- const seen = new Set<string>();
309
- const result: LspEdge[] = [];
310
-
311
- for (const edge of edges) {
312
- const key = `${edge.from_file}:${edge.from_symbol}->${edge.to_file}:${edge.to_symbol}:${edge.edge_type}`;
313
- if (!seen.has(key)) {
314
- seen.add(key);
315
- result.push(edge);
316
- }
317
- }
318
-
319
- return result;
320
- }
@@ -1,269 +0,0 @@
1
- // extension.ts — Forge LSP Bridge
2
- // VS Code extension entry point. Hooks into file-save events for TS/JS files,
3
- // collects LSP-resolved call edges via the edge collector, and flushes them
4
- // to the dk-forge MCP server via the forge client.
5
-
6
- import * as vscode from 'vscode';
7
- import * as path from 'path';
8
- import * as fs from 'fs';
9
- import { EdgeCollector } from './edge-collector';
10
- import { ForgeClient } from './forge-client';
11
-
12
- // ---------------------------------------------------------------------------
13
- // State
14
- // ---------------------------------------------------------------------------
15
-
16
- let edgeCollector: EdgeCollector | undefined;
17
- let forgeClient: ForgeClient | undefined;
18
- let statusBarItem: vscode.StatusBarItem | undefined;
19
- let saveListener: vscode.Disposable | undefined;
20
- let enabled = true;
21
- let repoId = '';
22
-
23
- // ---------------------------------------------------------------------------
24
- // Activation
25
- // ---------------------------------------------------------------------------
26
-
27
- export async function activate(context: vscode.ExtensionContext): Promise<void> {
28
- const config = vscode.workspace.getConfiguration('forge.lspBridge');
29
- enabled = config.get<boolean>('enabled', true);
30
-
31
- // Auto-detect repo ID from .forge/manifest.yaml if not configured
32
- repoId = config.get<string>('repoId', '') || '';
33
- if (!repoId) {
34
- repoId = await detectRepoId();
35
- }
36
-
37
- const debounceMs = config.get<number>('debounceMs', 5000);
38
- const mcpServerPath = config.get<string>('mcpServerPath', '') || '';
39
-
40
- // Initialize forge MCP client
41
- forgeClient = new ForgeClient(mcpServerPath);
42
-
43
- // Initialize edge collector with flush callback
44
- edgeCollector = new EdgeCollector(debounceMs, async (edges) => {
45
- if (!forgeClient || !repoId) {
46
- return;
47
- }
48
- try {
49
- const result = await forgeClient.submitLspEdges(repoId, edges);
50
- if (result && typeof result.accepted === 'number') {
51
- log(`Flushed ${result.accepted} edges (${result.failed ?? 0} failed)`);
52
- }
53
- } catch (err) {
54
- log(`Flush failed: ${String(err)}`);
55
- }
56
- });
57
-
58
- // Status bar item
59
- statusBarItem = vscode.window.createStatusBarItem(
60
- vscode.StatusBarAlignment.Right,
61
- 50,
62
- );
63
- statusBarItem.command = 'forge.lspBridge.toggle';
64
- updateStatusBar();
65
- statusBarItem.show();
66
- context.subscriptions.push(statusBarItem);
67
-
68
- // Register the save listener
69
- if (enabled) {
70
- registerSaveListener(context);
71
- }
72
-
73
- // Register commands
74
- context.subscriptions.push(
75
- vscode.commands.registerCommand('forge.lspBridge.toggle', () => {
76
- enabled = !enabled;
77
- if (enabled) {
78
- registerSaveListener(context);
79
- log('Enabled');
80
- } else {
81
- disposeSaveListener();
82
- log('Disabled');
83
- }
84
- updateStatusBar();
85
- }),
86
- );
87
-
88
- context.subscriptions.push(
89
- vscode.commands.registerCommand('forge.lspBridge.flush', async () => {
90
- if (!edgeCollector) {
91
- return;
92
- }
93
- await edgeCollector.flushNow();
94
- log('Manual flush complete');
95
- }),
96
- );
97
-
98
- // Watch for configuration changes
99
- context.subscriptions.push(
100
- vscode.workspace.onDidChangeConfiguration((e) => {
101
- if (e.affectsConfiguration('forge.lspBridge')) {
102
- const newConfig = vscode.workspace.getConfiguration('forge.lspBridge');
103
- const newEnabled = newConfig.get<boolean>('enabled', true);
104
- const newRepoId = newConfig.get<string>('repoId', '') || '';
105
- const newDebounce = newConfig.get<number>('debounceMs', 5000);
106
-
107
- if (newRepoId) {
108
- repoId = newRepoId;
109
- }
110
-
111
- if (edgeCollector) {
112
- edgeCollector.setDebounceMs(newDebounce);
113
- }
114
-
115
- if (newEnabled !== enabled) {
116
- enabled = newEnabled;
117
- if (enabled) {
118
- registerSaveListener(context);
119
- } else {
120
- disposeSaveListener();
121
- }
122
- updateStatusBar();
123
- }
124
- }
125
- }),
126
- );
127
-
128
- log(`Activated (repoId: ${repoId || '<none>'}, debounce: ${debounceMs}ms)`);
129
- }
130
-
131
- // ---------------------------------------------------------------------------
132
- // Deactivation
133
- // ---------------------------------------------------------------------------
134
-
135
- export async function deactivate(): Promise<void> {
136
- disposeSaveListener();
137
-
138
- // Flush any remaining edges
139
- if (edgeCollector) {
140
- try {
141
- await edgeCollector.flushNow();
142
- } catch {
143
- // Ignore errors during deactivation
144
- }
145
- }
146
-
147
- // Dispose the forge client (kills the child process)
148
- if (forgeClient) {
149
- forgeClient.dispose();
150
- forgeClient = undefined;
151
- }
152
-
153
- if (statusBarItem) {
154
- statusBarItem.dispose();
155
- statusBarItem = undefined;
156
- }
157
-
158
- edgeCollector = undefined;
159
- }
160
-
161
- // ---------------------------------------------------------------------------
162
- // Internal helpers
163
- // ---------------------------------------------------------------------------
164
-
165
- const SUPPORTED_LANGUAGES = new Set([
166
- 'typescript',
167
- 'javascript',
168
- 'typescriptreact',
169
- 'javascriptreact',
170
- ]);
171
-
172
- function registerSaveListener(context: vscode.ExtensionContext): void {
173
- disposeSaveListener();
174
-
175
- saveListener = vscode.workspace.onDidSaveTextDocument(async (document) => {
176
- if (!enabled || !edgeCollector) {
177
- return;
178
- }
179
-
180
- if (!SUPPORTED_LANGUAGES.has(document.languageId)) {
181
- return;
182
- }
183
-
184
- try {
185
- await edgeCollector.collectFromDocument(document);
186
- } catch (err) {
187
- // Never let a collection error propagate to the save flow
188
- log(`Collection error for ${document.fileName}: ${String(err)}`);
189
- }
190
- });
191
-
192
- context.subscriptions.push(saveListener);
193
- }
194
-
195
- function disposeSaveListener(): void {
196
- if (saveListener) {
197
- saveListener.dispose();
198
- saveListener = undefined;
199
- }
200
- }
201
-
202
- function updateStatusBar(): void {
203
- if (!statusBarItem) {
204
- return;
205
- }
206
- if (enabled) {
207
- statusBarItem.text = '$(symbol-method) Forge LSP';
208
- statusBarItem.tooltip = 'Forge LSP Bridge: Active — click to toggle';
209
- statusBarItem.backgroundColor = undefined;
210
- } else {
211
- statusBarItem.text = '$(circle-slash) Forge LSP';
212
- statusBarItem.tooltip = 'Forge LSP Bridge: Disabled — click to toggle';
213
- statusBarItem.backgroundColor = new vscode.ThemeColor(
214
- 'statusBarItem.warningBackground',
215
- );
216
- }
217
- }
218
-
219
- /**
220
- * Detect the repo ID from .forge/manifest.yaml in the workspace root.
221
- * Falls back to the workspace folder name, or empty string.
222
- */
223
- async function detectRepoId(): Promise<string> {
224
- const folders = vscode.workspace.workspaceFolders;
225
- if (!folders || folders.length === 0) {
226
- return '';
227
- }
228
-
229
- for (const folder of folders) {
230
- const manifestPath = path.join(folder.uri.fsPath, '.forge', 'manifest.yaml');
231
- try {
232
- if (fs.existsSync(manifestPath)) {
233
- const content = fs.readFileSync(manifestPath, 'utf8');
234
- // Simple YAML parsing — look for "name: <value>" line
235
- const nameMatch = content.match(/^name:\s*(.+)$/m);
236
- if (nameMatch && nameMatch[1]) {
237
- const name = nameMatch[1].trim();
238
- if (name) {
239
- return name;
240
- }
241
- }
242
- // Check for repo_id_override
243
- const overrideMatch = content.match(/^repo_id_override:\s*(.+)$/m);
244
- if (overrideMatch && overrideMatch[1]) {
245
- const override = overrideMatch[1].trim();
246
- if (override && override !== 'null') {
247
- return override;
248
- }
249
- }
250
- }
251
- } catch {
252
- // Silently continue if the manifest can't be read
253
- }
254
- }
255
-
256
- // Fallback: use the first workspace folder name
257
- return folders[0]?.name ?? '';
258
- }
259
-
260
- // ---------------------------------------------------------------------------
261
- // Logging
262
- // ---------------------------------------------------------------------------
263
-
264
- const outputChannel = vscode.window.createOutputChannel('Forge LSP Bridge');
265
-
266
- function log(message: string): void {
267
- const timestamp = new Date().toISOString();
268
- outputChannel.appendLine(`[${timestamp}] ${message}`);
269
- }