byterover-cli 3.3.0 → 3.5.0

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 (106) hide show
  1. package/dist/agent/core/domain/swarm/types.d.ts +132 -0
  2. package/dist/agent/core/domain/swarm/types.js +128 -0
  3. package/dist/agent/core/domain/tools/constants.d.ts +2 -0
  4. package/dist/agent/core/domain/tools/constants.js +2 -0
  5. package/dist/agent/core/interfaces/i-memory-provider.d.ts +45 -0
  6. package/dist/agent/core/interfaces/i-memory-provider.js +1 -0
  7. package/dist/agent/core/interfaces/i-sandbox-service.d.ts +8 -0
  8. package/dist/agent/core/interfaces/i-swarm-coordinator.d.ts +127 -0
  9. package/dist/agent/core/interfaces/i-swarm-coordinator.js +1 -0
  10. package/dist/agent/infra/agent/service-initializer.js +48 -0
  11. package/dist/agent/infra/map/map-shared.d.ts +2 -2
  12. package/dist/agent/infra/sandbox/sandbox-service.d.ts +10 -0
  13. package/dist/agent/infra/sandbox/sandbox-service.js +13 -0
  14. package/dist/agent/infra/sandbox/tools-sdk.d.ts +25 -0
  15. package/dist/agent/infra/sandbox/tools-sdk.js +24 -1
  16. package/dist/agent/infra/swarm/adapters/byterover-adapter.d.ts +39 -0
  17. package/dist/agent/infra/swarm/adapters/byterover-adapter.js +62 -0
  18. package/dist/agent/infra/swarm/adapters/gbrain-adapter.d.ts +63 -0
  19. package/dist/agent/infra/swarm/adapters/gbrain-adapter.js +209 -0
  20. package/dist/agent/infra/swarm/adapters/local-markdown-adapter.d.ts +41 -0
  21. package/dist/agent/infra/swarm/adapters/local-markdown-adapter.js +256 -0
  22. package/dist/agent/infra/swarm/adapters/memory-wiki-adapter.d.ts +29 -0
  23. package/dist/agent/infra/swarm/adapters/memory-wiki-adapter.js +244 -0
  24. package/dist/agent/infra/swarm/adapters/obsidian-adapter.d.ts +37 -0
  25. package/dist/agent/infra/swarm/adapters/obsidian-adapter.js +201 -0
  26. package/dist/agent/infra/swarm/cli/query-renderer.d.ts +15 -0
  27. package/dist/agent/infra/swarm/cli/query-renderer.js +126 -0
  28. package/dist/agent/infra/swarm/config/swarm-config-loader.d.ts +14 -0
  29. package/dist/agent/infra/swarm/config/swarm-config-loader.js +82 -0
  30. package/dist/agent/infra/swarm/config/swarm-config-schema.d.ts +667 -0
  31. package/dist/agent/infra/swarm/config/swarm-config-schema.js +305 -0
  32. package/dist/agent/infra/swarm/provider-factory.d.ts +21 -0
  33. package/dist/agent/infra/swarm/provider-factory.js +67 -0
  34. package/dist/agent/infra/swarm/search-precision.d.ts +95 -0
  35. package/dist/agent/infra/swarm/search-precision.js +141 -0
  36. package/dist/agent/infra/swarm/swarm-coordinator.d.ts +59 -0
  37. package/dist/agent/infra/swarm/swarm-coordinator.js +436 -0
  38. package/dist/agent/infra/swarm/swarm-graph.d.ts +63 -0
  39. package/dist/agent/infra/swarm/swarm-graph.js +167 -0
  40. package/dist/agent/infra/swarm/swarm-merger.d.ts +29 -0
  41. package/dist/agent/infra/swarm/swarm-merger.js +66 -0
  42. package/dist/agent/infra/swarm/swarm-router.d.ts +12 -0
  43. package/dist/agent/infra/swarm/swarm-router.js +40 -0
  44. package/dist/agent/infra/swarm/swarm-write-router.d.ts +23 -0
  45. package/dist/agent/infra/swarm/swarm-write-router.js +45 -0
  46. package/dist/agent/infra/swarm/validation/config-validator.d.ts +16 -0
  47. package/dist/agent/infra/swarm/validation/config-validator.js +402 -0
  48. package/dist/agent/infra/swarm/validation/memory-swarm-validation-error.d.ts +33 -0
  49. package/dist/agent/infra/swarm/validation/memory-swarm-validation-error.js +27 -0
  50. package/dist/agent/infra/swarm/wizard/config-scaffolder.d.ts +36 -0
  51. package/dist/agent/infra/swarm/wizard/config-scaffolder.js +96 -0
  52. package/dist/agent/infra/swarm/wizard/provider-detector.d.ts +54 -0
  53. package/dist/agent/infra/swarm/wizard/provider-detector.js +153 -0
  54. package/dist/agent/infra/swarm/wizard/swarm-wizard.d.ts +61 -0
  55. package/dist/agent/infra/swarm/wizard/swarm-wizard.js +187 -0
  56. package/dist/agent/infra/system-prompt/contributors/index.d.ts +1 -0
  57. package/dist/agent/infra/system-prompt/contributors/index.js +1 -0
  58. package/dist/agent/infra/system-prompt/contributors/swarm-state-contributor.d.ts +15 -0
  59. package/dist/agent/infra/system-prompt/contributors/swarm-state-contributor.js +65 -0
  60. package/dist/agent/infra/tools/implementations/curate-tool.d.ts +14 -14
  61. package/dist/agent/infra/tools/implementations/curate-tool.js +2 -0
  62. package/dist/agent/infra/tools/implementations/swarm-query-tool.d.ts +9 -0
  63. package/dist/agent/infra/tools/implementations/swarm-query-tool.js +44 -0
  64. package/dist/agent/infra/tools/implementations/swarm-store-tool.d.ts +9 -0
  65. package/dist/agent/infra/tools/implementations/swarm-store-tool.js +43 -0
  66. package/dist/agent/infra/tools/tool-provider.js +1 -0
  67. package/dist/agent/infra/tools/tool-registry.d.ts +3 -0
  68. package/dist/agent/infra/tools/tool-registry.js +25 -1
  69. package/dist/agent/resources/tools/code_exec.txt +2 -0
  70. package/dist/agent/resources/tools/swarm_query.txt +38 -0
  71. package/dist/agent/resources/tools/swarm_store.txt +35 -0
  72. package/dist/oclif/commands/connectors/install.d.ts +2 -2
  73. package/dist/oclif/commands/connectors/install.js +15 -7
  74. package/dist/oclif/commands/swarm/curate.d.ts +13 -0
  75. package/dist/oclif/commands/swarm/curate.js +81 -0
  76. package/dist/oclif/commands/swarm/onboard.d.ts +6 -0
  77. package/dist/oclif/commands/swarm/onboard.js +233 -0
  78. package/dist/oclif/commands/swarm/query.d.ts +14 -0
  79. package/dist/oclif/commands/swarm/query.js +84 -0
  80. package/dist/oclif/commands/swarm/status.d.ts +41 -0
  81. package/dist/oclif/commands/swarm/status.js +278 -0
  82. package/dist/server/constants.d.ts +3 -2
  83. package/dist/server/constants.js +10 -9
  84. package/dist/server/core/domain/entities/agent.js +4 -0
  85. package/dist/server/core/domain/source/source-schema.d.ts +6 -6
  86. package/dist/server/core/domain/transport/schemas.d.ts +4 -4
  87. package/dist/server/infra/connectors/mcp/claude-desktop-config-path.d.ts +20 -0
  88. package/dist/server/infra/connectors/mcp/claude-desktop-config-path.js +47 -0
  89. package/dist/server/infra/connectors/mcp/mcp-connector-config.d.ts +19 -0
  90. package/dist/server/infra/connectors/mcp/mcp-connector-config.js +9 -0
  91. package/dist/server/infra/connectors/mcp/mcp-connector.js +12 -3
  92. package/dist/server/infra/http/provider-model-fetchers.js +1 -0
  93. package/dist/server/infra/process/feature-handlers.js +13 -0
  94. package/dist/server/infra/project/project-registry.js +13 -1
  95. package/dist/server/infra/transport/handlers/locations-handler.d.ts +2 -0
  96. package/dist/server/infra/transport/handlers/locations-handler.js +16 -1
  97. package/dist/server/infra/transport/handlers/vc-handler.d.ts +0 -4
  98. package/dist/server/infra/transport/handlers/vc-handler.js +5 -16
  99. package/dist/server/templates/skill/SKILL.md +163 -0
  100. package/dist/server/utils/gitignore.d.ts +1 -0
  101. package/dist/server/utils/gitignore.js +36 -4
  102. package/dist/shared/types/agent.d.ts +2 -1
  103. package/dist/shared/types/agent.js +2 -0
  104. package/dist/tui/features/connectors/components/connectors-flow.js +7 -2
  105. package/oclif.manifest.json +503 -323
  106. package/package.json +2 -2
@@ -88,12 +88,13 @@ export const OVERVIEW_EXTENSION = '.overview.md';
88
88
  export const MANIFEST_FILE = '_manifest.json';
89
89
  export const ARCHIVE_IMPORTANCE_THRESHOLD = 35;
90
90
  export const DEFAULT_GHOST_CUE_MAX_TOKENS = 220;
91
- /** .gitignore content for the context tree ignore derived artifacts only */
92
- export const CONTEXT_TREE_GITIGNORE = `# Derived artifacts — do not track
93
- .gitignore
94
- .snapshot.json
95
- _manifest.json
96
- _index.md
97
- *.abstract.md
98
- *.overview.md
99
- `;
91
+ /** Patterns the context-tree .gitignore must contain (derived artifacts only). */
92
+ export const CONTEXT_TREE_GITIGNORE_PATTERNS = [
93
+ '.gitignore',
94
+ '.snapshot.json',
95
+ '_manifest.json',
96
+ '_index.md',
97
+ '*.abstract.md',
98
+ '*.overview.md',
99
+ ];
100
+ export const CONTEXT_TREE_GITIGNORE_HEADER = '# Derived artifacts — do not track';
@@ -30,6 +30,10 @@ export const AGENT_CONNECTOR_CONFIG = {
30
30
  default: 'skill',
31
31
  supported: ['rules', 'hook', 'mcp', 'skill'],
32
32
  },
33
+ 'Claude Desktop': {
34
+ default: 'mcp',
35
+ supported: ['mcp'],
36
+ },
33
37
  Cline: {
34
38
  default: 'mcp',
35
39
  supported: ['rules', 'mcp'],
@@ -5,15 +5,15 @@ export declare const SourceSchema: z.ZodObject<{
5
5
  projectRoot: z.ZodString;
6
6
  readOnly: z.ZodLiteral<true>;
7
7
  }, "strip", z.ZodTypeAny, {
8
+ readOnly: true;
8
9
  addedAt: string;
9
10
  alias: string;
10
11
  projectRoot: string;
11
- readOnly: true;
12
12
  }, {
13
+ readOnly: true;
13
14
  addedAt: string;
14
15
  alias: string;
15
16
  projectRoot: string;
16
- readOnly: true;
17
17
  }>;
18
18
  export declare const SourcesFileSchema: z.ZodObject<{
19
19
  sources: z.ZodArray<z.ZodObject<{
@@ -22,32 +22,32 @@ export declare const SourcesFileSchema: z.ZodObject<{
22
22
  projectRoot: z.ZodString;
23
23
  readOnly: z.ZodLiteral<true>;
24
24
  }, "strip", z.ZodTypeAny, {
25
+ readOnly: true;
25
26
  addedAt: string;
26
27
  alias: string;
27
28
  projectRoot: string;
28
- readOnly: true;
29
29
  }, {
30
+ readOnly: true;
30
31
  addedAt: string;
31
32
  alias: string;
32
33
  projectRoot: string;
33
- readOnly: true;
34
34
  }>, "many">;
35
35
  version: z.ZodLiteral<1>;
36
36
  }, "strip", z.ZodTypeAny, {
37
37
  version: 1;
38
38
  sources: {
39
+ readOnly: true;
39
40
  addedAt: string;
40
41
  alias: string;
41
42
  projectRoot: string;
42
- readOnly: true;
43
43
  }[];
44
44
  }, {
45
45
  version: 1;
46
46
  sources: {
47
+ readOnly: true;
47
48
  addedAt: string;
48
49
  alias: string;
49
50
  projectRoot: string;
50
- readOnly: true;
51
51
  }[];
52
52
  }>;
53
53
  export type Source = z.infer<typeof SourceSchema>;
@@ -755,13 +755,13 @@ export declare const TaskCompletedEventSchema: z.ZodObject<{
755
755
  }, "strip", z.ZodTypeAny, {
756
756
  result: string;
757
757
  taskId: string;
758
- clientId?: string | undefined;
759
758
  logId?: string | undefined;
759
+ clientId?: string | undefined;
760
760
  }, {
761
761
  result: string;
762
762
  taskId: string;
763
- clientId?: string | undefined;
764
763
  logId?: string | undefined;
764
+ clientId?: string | undefined;
765
765
  }>;
766
766
  /**
767
767
  * Structured error object
@@ -815,8 +815,8 @@ export declare const TaskErrorEventSchema: z.ZodObject<{
815
815
  details?: Record<string, unknown> | undefined;
816
816
  };
817
817
  taskId: string;
818
- clientId?: string | undefined;
819
818
  logId?: string | undefined;
819
+ clientId?: string | undefined;
820
820
  }, {
821
821
  error: {
822
822
  message: string;
@@ -825,8 +825,8 @@ export declare const TaskErrorEventSchema: z.ZodObject<{
825
825
  details?: Record<string, unknown> | undefined;
826
826
  };
827
827
  taskId: string;
828
- clientId?: string | undefined;
829
828
  logId?: string | undefined;
829
+ clientId?: string | undefined;
830
830
  }>;
831
831
  /**
832
832
  * llmservice:response - LLM text output
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Dependencies for platform detection, injectable for testing.
3
+ */
4
+ type PlatformDeps = {
5
+ env: Record<string, string | undefined>;
6
+ existsSync?: (path: string) => boolean;
7
+ homedir: () => string;
8
+ platform: () => NodeJS.Platform;
9
+ };
10
+ /**
11
+ * Returns the absolute path to the Claude Desktop config file,
12
+ * following platform conventions:
13
+ * - macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
14
+ * - Windows: %APPDATA%\Claude\claude_desktop_config.json
15
+ * - Linux: ~/.config/Claude/claude_desktop_config.json
16
+ *
17
+ * @returns Absolute path to the Claude Desktop config file
18
+ */
19
+ export declare const getClaudeDesktopConfigPath: (deps?: PlatformDeps) => string;
20
+ export {};
@@ -0,0 +1,47 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { homedir, platform } from 'node:os';
3
+ import { join } from 'node:path';
4
+ const CLAUDE_DESKTOP_CONFIG_FILE = 'claude_desktop_config.json';
5
+ const CLAUDE_DESKTOP_DIR = 'Claude';
6
+ // Publisher hash is derived from Anthropic's signing certificate — stable per publisher identity
7
+ const MSIX_PACKAGE_DIR = 'Claude_pzs8sxrjxfjjc';
8
+ const defaultDeps = {
9
+ env: process.env,
10
+ homedir,
11
+ platform,
12
+ };
13
+ /**
14
+ * Returns the absolute path to the Claude Desktop config file,
15
+ * following platform conventions:
16
+ * - macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
17
+ * - Windows: %APPDATA%\Claude\claude_desktop_config.json
18
+ * - Linux: ~/.config/Claude/claude_desktop_config.json
19
+ *
20
+ * @returns Absolute path to the Claude Desktop config file
21
+ */
22
+ export const getClaudeDesktopConfigPath = (deps = defaultDeps) => {
23
+ const currentPlatform = deps.platform();
24
+ if (currentPlatform === 'win32') {
25
+ const checkExists = deps.existsSync ?? existsSync;
26
+ const localAppData = deps.env.LOCALAPPDATA ?? join(deps.homedir(), 'AppData', 'Local');
27
+ const msixDir = join(localAppData, 'Packages', MSIX_PACKAGE_DIR, 'LocalCache', 'Roaming', CLAUDE_DESKTOP_DIR);
28
+ // Check directory, not the config file — file may not exist yet on first launch
29
+ if (checkExists(msixDir))
30
+ return join(msixDir, CLAUDE_DESKTOP_CONFIG_FILE);
31
+ const appData = deps.env.APPDATA;
32
+ if (appData !== undefined) {
33
+ return join(appData, CLAUDE_DESKTOP_DIR, CLAUDE_DESKTOP_CONFIG_FILE);
34
+ }
35
+ return join(deps.homedir(), 'AppData', 'Roaming', CLAUDE_DESKTOP_DIR, CLAUDE_DESKTOP_CONFIG_FILE);
36
+ }
37
+ if (currentPlatform === 'darwin') {
38
+ return join(deps.homedir(), 'Library', 'Application Support', CLAUDE_DESKTOP_DIR, CLAUDE_DESKTOP_CONFIG_FILE);
39
+ }
40
+ // Linux and other platforms: respect XDG_CONFIG_HOME if set
41
+ const xdgConfigHome = deps.env.XDG_CONFIG_HOME;
42
+ if (xdgConfigHome !== undefined) {
43
+ return join(xdgConfigHome, CLAUDE_DESKTOP_DIR, CLAUDE_DESKTOP_CONFIG_FILE);
44
+ }
45
+ // Default fallback (Linux and other platforms)
46
+ return join(deps.homedir(), '.config', CLAUDE_DESKTOP_DIR, CLAUDE_DESKTOP_CONFIG_FILE);
47
+ };
@@ -19,6 +19,12 @@ export type McpConfigMode = 'auto' | 'manual';
19
19
  type McpConnectorConfigBase = {
20
20
  /** Path to the MCP config file (relative to project root). Used when scope is 'project'. */
21
21
  configPath?: string;
22
+ /**
23
+ * Function that returns an absolute path to the config file.
24
+ * Takes precedence over configPath when present.
25
+ * Used when the path varies by platform (e.g., Claude Desktop).
26
+ */
27
+ configPathResolver?: () => string;
22
28
  /** Config file format */
23
29
  format: McpConfigFormat;
24
30
  /** Guide URL or instructions for manual setup. Required when mode is 'manual'. */
@@ -106,6 +112,19 @@ export declare const MCP_CONNECTOR_CONFIGS: {
106
112
  };
107
113
  readonly serverKeyPath: readonly ["mcpServers", "brv"];
108
114
  };
115
+ readonly 'Claude Desktop': {
116
+ readonly configPathResolver: (deps?: {
117
+ env: Record<string, string | undefined>;
118
+ existsSync?: (path: string) => boolean;
119
+ homedir: () => string;
120
+ platform: () => NodeJS.Platform;
121
+ }) => string;
122
+ readonly format: "json";
123
+ readonly mode: "auto";
124
+ readonly scope: "global";
125
+ readonly serverConfig: McpServerConfig;
126
+ readonly serverKeyPath: readonly ["mcpServers", "brv"];
127
+ };
109
128
  readonly Cline: {
110
129
  readonly format: "json";
111
130
  readonly manualGuide: "https://docs.cline.bot/mcp/configuring-mcp-servers#editing-mcp-settings-files";
@@ -1,3 +1,4 @@
1
+ import { getClaudeDesktopConfigPath } from './claude-desktop-config-path.js';
1
2
  /* eslint-disable perfectionist/sort-objects */
2
3
  /** Default MCP server configuration */
3
4
  const DEFAULT_SERVER_CONFIG = {
@@ -60,6 +61,14 @@ export const MCP_CONNECTOR_CONFIGS = {
60
61
  },
61
62
  serverKeyPath: STANDARD_KEY_PATH,
62
63
  },
64
+ 'Claude Desktop': {
65
+ configPathResolver: getClaudeDesktopConfigPath,
66
+ format: 'json',
67
+ mode: 'auto',
68
+ scope: 'global',
69
+ serverConfig: DEFAULT_SERVER_CONFIG,
70
+ serverKeyPath: STANDARD_KEY_PATH,
71
+ },
63
72
  Cline: {
64
73
  format: 'json',
65
74
  manualGuide: 'https://docs.cline.bot/mcp/configuring-mcp-servers#editing-mcp-settings-files',
@@ -198,6 +198,9 @@ export class McpConnector {
198
198
  * - Global scope: relative to os.homedir()
199
199
  */
200
200
  getFullConfigPath(config) {
201
+ if (config.configPathResolver) {
202
+ return config.configPathResolver();
203
+ }
201
204
  if (!config.configPath) {
202
205
  return '';
203
206
  }
@@ -265,7 +268,9 @@ export class McpConnector {
265
268
  * Install the rule file with MCP-specific content.
266
269
  */
267
270
  async installRuleFile(agent) {
268
- const rulesConfig = agent in RULES_CONNECTOR_CONFIGS ? RULES_CONNECTOR_CONFIGS[agent] : undefined;
271
+ const rulesConfig = agent in RULES_CONNECTOR_CONFIGS
272
+ ? RULES_CONNECTOR_CONFIGS[agent]
273
+ : undefined;
269
274
  if (!rulesConfig) {
270
275
  return;
271
276
  }
@@ -300,7 +305,9 @@ export class McpConnector {
300
305
  * Checks for markers AND MCP-specific tool references (brv-query, brv-curate).
301
306
  */
302
307
  async statusManual(agent) {
303
- const rulesConfig = agent in RULES_CONNECTOR_CONFIGS ? RULES_CONNECTOR_CONFIGS[agent] : undefined;
308
+ const rulesConfig = agent in RULES_CONNECTOR_CONFIGS
309
+ ? RULES_CONNECTOR_CONFIGS[agent]
310
+ : undefined;
304
311
  if (!rulesConfig) {
305
312
  return {
306
313
  configExists: false,
@@ -330,7 +337,9 @@ export class McpConnector {
330
337
  * Uninstall the rule file content.
331
338
  */
332
339
  async uninstallRuleFile(agent) {
333
- const rulesConfig = agent in RULES_CONNECTOR_CONFIGS ? RULES_CONNECTOR_CONFIGS[agent] : undefined;
340
+ const rulesConfig = agent in RULES_CONNECTOR_CONFIGS
341
+ ? RULES_CONNECTOR_CONFIGS[agent]
342
+ : undefined;
334
343
  if (!rulesConfig) {
335
344
  return;
336
345
  }
@@ -121,6 +121,7 @@ export const CODEX_ALLOWED_MODELS = new Set([
121
121
  'gpt-5.2-codex',
122
122
  'gpt-5.3-codex',
123
123
  'gpt-5.4',
124
+ 'gpt-5.4-mini',
124
125
  ]);
125
126
  /**
126
127
  * Fallback Codex models used when models.dev is unreachable and no disk cache exists.
@@ -4,6 +4,7 @@
4
4
  * Registers all feature handlers (auth, init, status, etc.) on the transport server.
5
5
  * These handlers implement the TUI ↔ Server event contract.
6
6
  */
7
+ import { access } from 'node:fs/promises';
7
8
  import { join } from 'node:path';
8
9
  import { ReviewEvents } from '../../../shared/transport/events/review-events.js';
9
10
  import { getAuthConfig } from '../../config/auth.config.js';
@@ -106,6 +107,18 @@ export async function setupFeatureHandlers({ authStateStore, broadcastToProject,
106
107
  new LocationsHandler({
107
108
  contextTreeService,
108
109
  getActiveProjectPaths,
110
+ async pathExists(path) {
111
+ try {
112
+ await access(path);
113
+ return true;
114
+ }
115
+ catch (error) {
116
+ if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
117
+ return false;
118
+ }
119
+ throw error;
120
+ }
121
+ },
109
122
  projectRegistry,
110
123
  resolveProjectPath,
111
124
  transport,
@@ -82,7 +82,19 @@ export class ProjectRegistry {
82
82
  return info;
83
83
  }
84
84
  unregister(projectPath) {
85
- const resolved = resolvePath(projectPath);
85
+ let resolved;
86
+ try {
87
+ resolved = resolvePath(projectPath);
88
+ }
89
+ catch (error) {
90
+ // ENOENT — path deleted from disk. Keys are already resolved, so try the raw input.
91
+ if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
92
+ resolved = projectPath;
93
+ }
94
+ else {
95
+ throw error;
96
+ }
97
+ }
86
98
  if (!this.projects.has(resolved)) {
87
99
  return false;
88
100
  }
@@ -5,6 +5,7 @@ import { type ProjectPathResolver } from './handler-types.js';
5
5
  export interface LocationsHandlerDeps {
6
6
  contextTreeService: IContextTreeService;
7
7
  getActiveProjectPaths: () => string[];
8
+ pathExists: (path: string) => Promise<boolean>;
8
9
  projectRegistry: IProjectRegistry;
9
10
  resolveProjectPath: ProjectPathResolver;
10
11
  transport: ITransportServer;
@@ -16,6 +17,7 @@ export interface LocationsHandlerDeps {
16
17
  export declare class LocationsHandler {
17
18
  private readonly contextTreeService;
18
19
  private readonly getActiveProjectPaths;
20
+ private readonly pathExists;
19
21
  private readonly projectRegistry;
20
22
  private readonly resolveProjectPath;
21
23
  private readonly transport;
@@ -9,12 +9,14 @@ import { resolveRequiredProjectPath } from './handler-types.js';
9
9
  export class LocationsHandler {
10
10
  contextTreeService;
11
11
  getActiveProjectPaths;
12
+ pathExists;
12
13
  projectRegistry;
13
14
  resolveProjectPath;
14
15
  transport;
15
16
  constructor(deps) {
16
17
  this.contextTreeService = deps.contextTreeService;
17
18
  this.getActiveProjectPaths = deps.getActiveProjectPaths;
19
+ this.pathExists = deps.pathExists;
18
20
  this.projectRegistry = deps.projectRegistry;
19
21
  this.resolveProjectPath = deps.resolveProjectPath;
20
22
  this.transport = deps.transport;
@@ -35,6 +37,18 @@ export class LocationsHandler {
35
37
  const all = this.projectRegistry.getAll();
36
38
  const activeSet = new Set(this.getActiveProjectPaths());
37
39
  const results = await Promise.all([...all.entries()].map(async ([path]) => {
40
+ try {
41
+ const exists = await this.pathExists(path);
42
+ if (!exists) {
43
+ this.projectRegistry.unregister(path);
44
+ return null;
45
+ }
46
+ }
47
+ catch {
48
+ // pathExists threw unexpectedly — skip this entry but do NOT unregister,
49
+ // since the path may still exist (e.g. a transient permission error).
50
+ return null;
51
+ }
38
52
  let isInitialized = false;
39
53
  try {
40
54
  isInitialized = await this.contextTreeService.exists(path);
@@ -50,8 +64,9 @@ export class LocationsHandler {
50
64
  projectPath: path,
51
65
  };
52
66
  }));
67
+ const filtered = results.filter((r) => r !== null);
53
68
  // Sort: current first → active (has clients) → initialized → rest, all by registeredAt desc
54
- return results.sort((a, b) => {
69
+ return filtered.sort((a, b) => {
55
70
  if (a.isCurrent !== b.isCurrent)
56
71
  return a.isCurrent ? -1 : 1;
57
72
  if (a.isActive !== b.isActive)
@@ -41,10 +41,6 @@ export declare class VcHandler {
41
41
  setup(): void;
42
42
  private buildAuthorHint;
43
43
  private buildNoRemoteMessage;
44
- /**
45
- * Writes a .gitignore to the context-tree directory only if one does not already exist.
46
- */
47
- private ensureGitignore;
48
44
  /**
49
45
  * When force is NOT set, checks for uncommitted changes and throws
50
46
  * VcError(UNCOMMITTED_CHANGES) if the working tree is dirty.
@@ -1,13 +1,12 @@
1
1
  import fs from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import { VcErrorCode, VcEvents, } from '../../../../shared/transport/events/vc-events.js';
4
- import { CONTEXT_TREE_GITIGNORE } from '../../../constants.js';
5
4
  import { BrvConfig } from '../../../core/domain/entities/brv-config.js';
6
5
  import { Space } from '../../../core/domain/entities/space.js';
7
6
  import { GitAuthError, GitError } from '../../../core/domain/errors/git-error.js';
8
7
  import { NotAuthenticatedError } from '../../../core/domain/errors/task-error.js';
9
8
  import { VcError } from '../../../core/domain/errors/vc-error.js';
10
- import { ensureGitignoreEntries } from '../../../utils/gitignore.js';
9
+ import { ensureContextTreeGitignore, ensureGitignoreEntries } from '../../../utils/gitignore.js';
11
10
  import { buildCogitRemoteUrl, isValidBranchName, parseUserFacingUrl } from '../../git/cogit-url.js';
12
11
  import { resolveRequiredProjectPath } from './handler-types.js';
13
12
  /**
@@ -101,18 +100,6 @@ export class VcHandler {
101
100
  ` 3. Run: brv vc remote add origin <url>\n` +
102
101
  ` 4. Then: ${nextStep}`);
103
102
  }
104
- /**
105
- * Writes a .gitignore to the context-tree directory only if one does not already exist.
106
- */
107
- async ensureGitignore(contextTreeDir) {
108
- const gitignorePath = join(contextTreeDir, '.gitignore');
109
- try {
110
- await fs.promises.access(gitignorePath);
111
- }
112
- catch {
113
- await fs.promises.writeFile(gitignorePath, CONTEXT_TREE_GITIGNORE, 'utf8');
114
- }
115
- }
116
103
  /**
117
104
  * When force is NOT set, checks for uncommitted changes and throws
118
105
  * VcError(UNCOMMITTED_CHANGES) if the working tree is dirty.
@@ -134,6 +121,7 @@ export class VcHandler {
134
121
  if (!gitInitialized) {
135
122
  throw new VcError('ByteRover version control not initialized.', VcErrorCode.GIT_NOT_INITIALIZED);
136
123
  }
124
+ await ensureContextTreeGitignore(directory);
137
125
  const statusBefore = await this.gitService.status({ directory });
138
126
  const stagedBefore = new Set(statusBefore.files.filter((f) => f.staged).map((f) => f.path));
139
127
  const hadUnstagedBefore = new Set(statusBefore.files.filter((f) => !f.staged).map((f) => f.path));
@@ -350,7 +338,7 @@ export class VcHandler {
350
338
  await this.projectConfigStore.write(updated, projectPath);
351
339
  }
352
340
  // Ensure .gitignore exists (remote may not have one)
353
- await this.ensureGitignore(contextTreeDir);
341
+ await ensureContextTreeGitignore(contextTreeDir);
354
342
  // Add .brv entries to project .gitignore (prevents `git add .` fatal error from nested .git)
355
343
  await ensureGitignoreEntries(projectPath);
356
344
  }
@@ -456,7 +444,7 @@ export class VcHandler {
456
444
  const reinitialized = await this.gitService.isInitialized({ directory: contextTreeDir });
457
445
  await this.gitService.init({ defaultBranch: 'main', directory: contextTreeDir });
458
446
  // 3. Ensure .gitignore exists with correct content (idempotent)
459
- await this.ensureGitignore(contextTreeDir);
447
+ await ensureContextTreeGitignore(contextTreeDir);
460
448
  // 4. Add .brv entries to project .gitignore (prevents `git add .` fatal error from nested .git)
461
449
  await ensureGitignoreEntries(projectPath);
462
450
  return {
@@ -818,6 +806,7 @@ export class VcHandler {
818
806
  untracked: [],
819
807
  };
820
808
  }
809
+ await ensureContextTreeGitignore(contextTreeDir);
821
810
  const branch = await this.gitService.getCurrentBranch({ directory: contextTreeDir });
822
811
  const gitStatus = await this.gitService.status({ directory: contextTreeDir });
823
812
  // Detect empty repo (no commits yet)
@@ -310,6 +310,169 @@ brv vc push -u origin main # push and set upstream tracking
310
310
  brv vc clone https://byterover.dev/<team>/<space>.git
311
311
  ```
312
312
 
313
+ ### 8. Swarm Query
314
+ **Overview:** Search across all active memory providers simultaneously — ByteRover context tree, Obsidian vault, Local Markdown folders, GBrain, and Memory Wiki. Results are fused via Reciprocal Rank Fusion (RRF) and ranked by provider weight and relevance. No LLM call — pure algorithmic search.
315
+
316
+ **Use this skill when:**
317
+ - You need to search across multiple knowledge sources at once
318
+ - The user has configured multiple memory providers (check with `brv swarm status`)
319
+ - You want results from Obsidian notes, GBrain entities, or wiki pages alongside ByteRover context
320
+
321
+ **Do NOT use this skill when:**
322
+ - The user only has ByteRover configured — use `brv query` instead (it synthesizes via LLM)
323
+ - You need an LLM-synthesized answer — `brv swarm query` returns raw search results, not synthesized text
324
+
325
+ ```bash
326
+ brv swarm query "How does JWT refresh work?"
327
+ ```
328
+
329
+ Output:
330
+ ```
331
+ Swarm Query: "How does JWT refresh work?"
332
+ Type: factual | Providers: 4 queried | Latency: 398ms
333
+ ──────────────────────────────────────────────────
334
+ 1. [memory-wiki] sources/jwt-token-lifecycle.md score: 0.0150 [keyword]
335
+ # JWT Token Lifecycle ...
336
+ 2. [obsidian] SwarmTestData/Authentication System.md score: 0.0142 [keyword]
337
+ # Authentication System ...
338
+ 3. [gbrain] alex-chen score: 0.0117 [semantic]
339
+ # Alex Chen — Senior Backend Engineer ...
340
+ ```
341
+
342
+ **With explain mode** (shows classification, provider selection, enrichment):
343
+ ```bash
344
+ brv swarm query "authentication patterns" --explain
345
+ ```
346
+
347
+ Output:
348
+ ```
349
+ Classification: factual
350
+ Provider selection: 4 of 4 available
351
+ ✓ byterover (healthy, selected, 0 results, 14ms)
352
+ ✓ obsidian (healthy, selected, 5 results, 91ms)
353
+ ✓ memory-wiki (healthy, selected, 2 results, 15ms)
354
+ ✓ gbrain (healthy, selected, 1 results, 260ms)
355
+ Enrichment:
356
+ byterover → obsidian
357
+ byterover → memory-wiki
358
+ Results: 8 raw → 7 after RRF fusion + precision filtering
359
+ ```
360
+
361
+ **JSON output:**
362
+ ```bash
363
+ brv swarm query "rate limiting" --format json
364
+ ```
365
+
366
+ Output:
367
+ ```json
368
+ {
369
+ "meta": {
370
+ "queryType": "factual",
371
+ "totalLatencyMs": 340,
372
+ "providers": {
373
+ "byterover": { "selected": true, "resultCount": 0 },
374
+ "obsidian": { "selected": true, "resultCount": 5 },
375
+ "gbrain": { "selected": true, "resultCount": 1 },
376
+ "memory-wiki": { "selected": true, "resultCount": 1 }
377
+ }
378
+ },
379
+ "results": [
380
+ { "provider": "memory-wiki", "providerType": "memory-wiki", "score": 0.015, "content": "# Rate Limiting ..." }
381
+ ]
382
+ }
383
+ ```
384
+
385
+ **Limit results:**
386
+ ```bash
387
+ brv swarm query "testing strategy" -n 5
388
+ ```
389
+
390
+ **Flags:** `--explain` (show routing details), `--format json` (structured output), `-n <value>` (max results).
391
+
392
+ ### 9. Swarm Curate
393
+ **Overview:** Store knowledge in the best available external memory provider. ByteRover automatically classifies the content type and routes accordingly: entities (people, orgs) go to GBrain, notes (meeting notes, TODOs) go to Local Markdown, general content goes to the first writable provider. Falls back to ByteRover context tree if no external providers are available.
394
+
395
+ **Use this skill when:**
396
+ - You want to store knowledge in an external provider (GBrain, Local Markdown, Memory Wiki)
397
+ - The user has configured writable swarm providers
398
+
399
+ **Do NOT use this skill when:**
400
+ - You want to store in ByteRover's context tree specifically — use `brv curate` instead
401
+ - No swarm providers are configured — use `brv curate` instead
402
+
403
+ ```bash
404
+ brv swarm curate "Jane Smith is the CTO of TechCorp"
405
+ ```
406
+
407
+ Output:
408
+ ```
409
+ Stored to gbrain as concept/jane-smith-cto
410
+ ```
411
+
412
+ **Target a specific provider:**
413
+ ```bash
414
+ brv swarm curate "meeting notes: decided on JWT" --provider local-markdown:notes
415
+ ```
416
+
417
+ Output:
418
+ ```
419
+ Stored to local-markdown:notes as note-1776052527043.md
420
+ ```
421
+
422
+ ```bash
423
+ brv swarm curate "Architecture uses event sourcing" --provider gbrain
424
+ ```
425
+
426
+ Output:
427
+ ```
428
+ Stored to gbrain as concept/event-sourcing-architecture
429
+ ```
430
+
431
+ **JSON output:**
432
+ ```bash
433
+ brv swarm curate "Test content" --format json
434
+ ```
435
+
436
+ Output:
437
+ ```json
438
+ {
439
+ "id": "note-1776052594462.md",
440
+ "provider": "local-markdown:project-docs",
441
+ "success": true,
442
+ "latencyMs": 1
443
+ }
444
+ ```
445
+
446
+ **Flags:** `--provider <id>` (target specific provider), `--format json` (structured output).
447
+
448
+ ### 10. Swarm Status
449
+ **Overview:** Check provider health and write targets before running swarm query or curate. Use this to verify which providers are available and operational.
450
+
451
+ **Use this skill when:**
452
+ - Before running `brv swarm query` or `brv swarm curate` to check available providers
453
+ - Diagnosing why swarm results are missing from a specific provider
454
+
455
+ ```bash
456
+ brv swarm status
457
+ ```
458
+
459
+ Output:
460
+ ```
461
+ Memory Swarm Health Check
462
+ ════════════════════════════════════════
463
+ ✓ ByteRover context-tree (always on)
464
+ ✓ Obsidian /Users/you/Documents/MyObsidian
465
+ ✓ Local .md 1 folder(s)
466
+ ✓ GBrain /Users/you/workspaces/gbrain
467
+ ✓ Memory Wiki /Users/you/.openclaw/wiki/main
468
+
469
+ Write Targets:
470
+ gbrain (entity, general)
471
+ local-markdown:project-docs (note, general)
472
+
473
+ Swarm is operational (5/5 providers configured).
474
+ ```
475
+
313
476
  ## Data Handling
314
477
 
315
478
  **Storage**: All knowledge is stored as Markdown files in `.brv/context-tree/` within the project directory. Files are human-readable and version-controllable.
@@ -7,3 +7,4 @@
7
7
  * is a convenience feature that should never block the caller.
8
8
  */
9
9
  export declare function ensureGitignoreEntries(directory: string): Promise<void>;
10
+ export declare function ensureContextTreeGitignore(contextTreeDir: string): Promise<void>;