byterover-cli 3.2.0 → 3.4.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.
- package/.env.production +0 -4
- package/dist/agent/core/domain/swarm/types.d.ts +132 -0
- package/dist/agent/core/domain/swarm/types.js +128 -0
- package/dist/agent/core/domain/tools/constants.d.ts +2 -0
- package/dist/agent/core/domain/tools/constants.js +2 -0
- package/dist/agent/core/interfaces/i-memory-provider.d.ts +45 -0
- package/dist/agent/core/interfaces/i-memory-provider.js +1 -0
- package/dist/agent/core/interfaces/i-sandbox-service.d.ts +8 -0
- package/dist/agent/core/interfaces/i-swarm-coordinator.d.ts +127 -0
- package/dist/agent/core/interfaces/i-swarm-coordinator.js +1 -0
- package/dist/agent/infra/agent/service-initializer.js +48 -0
- package/dist/agent/infra/map/map-shared.d.ts +2 -2
- package/dist/agent/infra/sandbox/sandbox-service.d.ts +10 -0
- package/dist/agent/infra/sandbox/sandbox-service.js +13 -0
- package/dist/agent/infra/sandbox/tools-sdk.d.ts +25 -0
- package/dist/agent/infra/sandbox/tools-sdk.js +24 -1
- package/dist/agent/infra/swarm/adapters/byterover-adapter.d.ts +39 -0
- package/dist/agent/infra/swarm/adapters/byterover-adapter.js +62 -0
- package/dist/agent/infra/swarm/adapters/gbrain-adapter.d.ts +63 -0
- package/dist/agent/infra/swarm/adapters/gbrain-adapter.js +209 -0
- package/dist/agent/infra/swarm/adapters/local-markdown-adapter.d.ts +41 -0
- package/dist/agent/infra/swarm/adapters/local-markdown-adapter.js +256 -0
- package/dist/agent/infra/swarm/adapters/memory-wiki-adapter.d.ts +29 -0
- package/dist/agent/infra/swarm/adapters/memory-wiki-adapter.js +244 -0
- package/dist/agent/infra/swarm/adapters/obsidian-adapter.d.ts +37 -0
- package/dist/agent/infra/swarm/adapters/obsidian-adapter.js +201 -0
- package/dist/agent/infra/swarm/cli/query-renderer.d.ts +15 -0
- package/dist/agent/infra/swarm/cli/query-renderer.js +126 -0
- package/dist/agent/infra/swarm/config/swarm-config-loader.d.ts +14 -0
- package/dist/agent/infra/swarm/config/swarm-config-loader.js +82 -0
- package/dist/agent/infra/swarm/config/swarm-config-schema.d.ts +667 -0
- package/dist/agent/infra/swarm/config/swarm-config-schema.js +305 -0
- package/dist/agent/infra/swarm/provider-factory.d.ts +21 -0
- package/dist/agent/infra/swarm/provider-factory.js +67 -0
- package/dist/agent/infra/swarm/search-precision.d.ts +95 -0
- package/dist/agent/infra/swarm/search-precision.js +141 -0
- package/dist/agent/infra/swarm/swarm-coordinator.d.ts +59 -0
- package/dist/agent/infra/swarm/swarm-coordinator.js +436 -0
- package/dist/agent/infra/swarm/swarm-graph.d.ts +63 -0
- package/dist/agent/infra/swarm/swarm-graph.js +167 -0
- package/dist/agent/infra/swarm/swarm-merger.d.ts +29 -0
- package/dist/agent/infra/swarm/swarm-merger.js +66 -0
- package/dist/agent/infra/swarm/swarm-router.d.ts +12 -0
- package/dist/agent/infra/swarm/swarm-router.js +40 -0
- package/dist/agent/infra/swarm/swarm-write-router.d.ts +23 -0
- package/dist/agent/infra/swarm/swarm-write-router.js +45 -0
- package/dist/agent/infra/swarm/validation/config-validator.d.ts +16 -0
- package/dist/agent/infra/swarm/validation/config-validator.js +402 -0
- package/dist/agent/infra/swarm/validation/memory-swarm-validation-error.d.ts +33 -0
- package/dist/agent/infra/swarm/validation/memory-swarm-validation-error.js +27 -0
- package/dist/agent/infra/swarm/wizard/config-scaffolder.d.ts +36 -0
- package/dist/agent/infra/swarm/wizard/config-scaffolder.js +96 -0
- package/dist/agent/infra/swarm/wizard/provider-detector.d.ts +54 -0
- package/dist/agent/infra/swarm/wizard/provider-detector.js +153 -0
- package/dist/agent/infra/swarm/wizard/swarm-wizard.d.ts +61 -0
- package/dist/agent/infra/swarm/wizard/swarm-wizard.js +187 -0
- package/dist/agent/infra/system-prompt/contributors/index.d.ts +1 -0
- package/dist/agent/infra/system-prompt/contributors/index.js +1 -0
- package/dist/agent/infra/system-prompt/contributors/swarm-state-contributor.d.ts +15 -0
- package/dist/agent/infra/system-prompt/contributors/swarm-state-contributor.js +65 -0
- package/dist/agent/infra/tools/implementations/curate-tool.d.ts +14 -14
- package/dist/agent/infra/tools/implementations/curate-tool.js +2 -0
- package/dist/agent/infra/tools/implementations/search-knowledge-service.js +12 -2
- package/dist/agent/infra/tools/implementations/swarm-query-tool.d.ts +9 -0
- package/dist/agent/infra/tools/implementations/swarm-query-tool.js +44 -0
- package/dist/agent/infra/tools/implementations/swarm-store-tool.d.ts +9 -0
- package/dist/agent/infra/tools/implementations/swarm-store-tool.js +43 -0
- package/dist/agent/infra/tools/tool-provider.js +1 -0
- package/dist/agent/infra/tools/tool-registry.d.ts +3 -0
- package/dist/agent/infra/tools/tool-registry.js +25 -1
- package/dist/agent/resources/tools/code_exec.txt +2 -0
- package/dist/agent/resources/tools/swarm_query.txt +38 -0
- package/dist/agent/resources/tools/swarm_store.txt +35 -0
- package/dist/oclif/commands/curate/index.d.ts +1 -0
- package/dist/oclif/commands/curate/index.js +15 -1
- package/dist/oclif/commands/query.d.ts +1 -0
- package/dist/oclif/commands/query.js +17 -3
- package/dist/oclif/commands/search.d.ts +20 -0
- package/dist/oclif/commands/search.js +186 -0
- package/dist/oclif/commands/status.js +4 -0
- package/dist/oclif/commands/swarm/curate.d.ts +13 -0
- package/dist/oclif/commands/swarm/curate.js +81 -0
- package/dist/oclif/commands/swarm/onboard.d.ts +6 -0
- package/dist/oclif/commands/swarm/onboard.js +233 -0
- package/dist/oclif/commands/swarm/query.d.ts +14 -0
- package/dist/oclif/commands/swarm/query.js +84 -0
- package/dist/oclif/commands/swarm/status.d.ts +41 -0
- package/dist/oclif/commands/swarm/status.js +278 -0
- package/dist/oclif/lib/daemon-client.js +0 -1
- package/dist/oclif/lib/search-format.d.ts +10 -0
- package/dist/oclif/lib/search-format.js +25 -0
- package/dist/oclif/lib/task-client.d.ts +6 -0
- package/dist/oclif/lib/task-client.js +10 -3
- package/dist/server/constants.d.ts +3 -2
- package/dist/server/constants.js +10 -7
- package/dist/server/core/domain/errors/task-error.d.ts +2 -2
- package/dist/server/core/domain/errors/task-error.js +5 -4
- package/dist/server/core/domain/source/source-schema.d.ts +6 -6
- package/dist/server/core/domain/transport/schemas.d.ts +14 -14
- package/dist/server/core/domain/transport/schemas.js +3 -3
- package/dist/server/core/interfaces/executor/i-search-executor.d.ts +34 -0
- package/dist/server/core/interfaces/executor/i-search-executor.js +1 -0
- package/dist/server/core/interfaces/executor/index.d.ts +1 -0
- package/dist/server/core/interfaces/executor/index.js +1 -0
- package/dist/server/infra/daemon/agent-process.js +20 -7
- package/dist/server/infra/executor/search-executor.d.ts +17 -0
- package/dist/server/infra/executor/search-executor.js +30 -0
- package/dist/server/infra/http/provider-model-fetchers.js +1 -0
- package/dist/server/infra/process/feature-handlers.js +13 -0
- package/dist/server/infra/project/project-registry.js +13 -1
- package/dist/server/infra/transport/handlers/locations-handler.d.ts +2 -0
- package/dist/server/infra/transport/handlers/locations-handler.js +16 -1
- package/dist/server/infra/transport/handlers/pull-handler.js +3 -3
- package/dist/server/infra/transport/handlers/push-handler.js +3 -3
- package/dist/server/infra/transport/handlers/status-handler.js +25 -18
- package/dist/server/infra/transport/handlers/vc-handler.d.ts +0 -4
- package/dist/server/infra/transport/handlers/vc-handler.js +5 -16
- package/dist/server/templates/skill/SKILL.md +188 -5
- package/dist/server/utils/gitignore.d.ts +1 -0
- package/dist/server/utils/gitignore.js +36 -4
- package/dist/shared/transport/search-content.d.ts +28 -0
- package/dist/shared/transport/search-content.js +38 -0
- package/dist/shared/transport/types/dto.d.ts +1 -1
- package/dist/tui/features/status/utils/format-status.js +5 -0
- package/dist/tui/utils/error-messages.js +2 -2
- package/oclif.manifest.json +581 -317
- package/package.json +2 -2
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { SearchKnowledgeResult } from '../../../../agent/infra/sandbox/tools-sdk.js';
|
|
2
|
+
/**
|
|
3
|
+
* Options for executing a context tree search.
|
|
4
|
+
* Search is stateless — no agent session, no LLM, no task isolation needed.
|
|
5
|
+
*/
|
|
6
|
+
export interface SearchExecuteOptions {
|
|
7
|
+
/** Maximum number of results to return (default: 10) */
|
|
8
|
+
limit?: number;
|
|
9
|
+
/** Search query */
|
|
10
|
+
query: string;
|
|
11
|
+
/** Path prefix to scope results (e.g. "auth/" for auth domain only) */
|
|
12
|
+
scope?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* ISearchExecutor - Executes search against the context tree's BM25 index.
|
|
16
|
+
*
|
|
17
|
+
* Unlike QueryExecutor (which requires a CipherAgent for LLM synthesis),
|
|
18
|
+
* SearchExecutor is stateless and returns raw search results directly.
|
|
19
|
+
* No agent session, no sandbox, no LLM call.
|
|
20
|
+
*
|
|
21
|
+
* Architecture:
|
|
22
|
+
* - SearchKnowledgeService provides BM25-indexed search
|
|
23
|
+
* - Executor wraps it with option validation
|
|
24
|
+
* - Results are SearchKnowledgeResult (paths, scores, excerpts)
|
|
25
|
+
*/
|
|
26
|
+
export interface ISearchExecutor {
|
|
27
|
+
/**
|
|
28
|
+
* Execute a context tree search.
|
|
29
|
+
*
|
|
30
|
+
* @param options - Search options (query, limit, scope)
|
|
31
|
+
* @returns Raw search results with paths, scores, and excerpts
|
|
32
|
+
*/
|
|
33
|
+
execute(options: SearchExecuteOptions): Promise<SearchKnowledgeResult>;
|
|
34
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -28,6 +28,7 @@ import { FolderPackService } from '../../../agent/infra/folder-pack/folder-pack-
|
|
|
28
28
|
import { SessionMetadataStore } from '../../../agent/infra/session/session-metadata-store.js';
|
|
29
29
|
import { createSearchKnowledgeService } from '../../../agent/infra/tools/implementations/search-knowledge-service.js';
|
|
30
30
|
import { AuthEvents } from '../../../shared/transport/events/auth-events.js';
|
|
31
|
+
import { decodeSearchContent } from '../../../shared/transport/search-content.js';
|
|
31
32
|
import { getCurrentConfig } from '../../config/environment.js';
|
|
32
33
|
import { DEFAULT_LLM_MODEL, PROJECT } from '../../constants.js';
|
|
33
34
|
import { serializeTaskError, TaskError, TaskErrorCode } from '../../core/domain/errors/task-error.js';
|
|
@@ -36,6 +37,7 @@ import { TransportAgentEventNames, TransportDaemonEventNames, TransportStateEven
|
|
|
36
37
|
import { CurateExecutor } from '../executor/curate-executor.js';
|
|
37
38
|
import { FolderPackExecutor } from '../executor/folder-pack-executor.js';
|
|
38
39
|
import { QueryExecutor } from '../executor/query-executor.js';
|
|
40
|
+
import { SearchExecutor } from '../executor/search-executor.js';
|
|
39
41
|
import { AgentInstanceDiscovery } from '../transport/agent-instance-discovery.js';
|
|
40
42
|
import { createAgentLogger } from './agent-logger.js';
|
|
41
43
|
import { resolveSessionId } from './session-resolver.js';
|
|
@@ -301,10 +303,11 @@ async function start() {
|
|
|
301
303
|
fileSystem: fileSystemService,
|
|
302
304
|
searchService,
|
|
303
305
|
});
|
|
306
|
+
const searchExecutor = new SearchExecutor(searchService);
|
|
304
307
|
transport.on(TransportTaskEventNames.EXECUTE, (task) => {
|
|
305
308
|
agentLog(`task:execute received taskId=${task.taskId} type=${task.type} activeTaskCount=${activeTaskCount + 1}`);
|
|
306
309
|
// eslint-disable-next-line no-void
|
|
307
|
-
void executeTask(task, curateExecutor, folderPackExecutor, queryExecutor);
|
|
310
|
+
void executeTask(task, curateExecutor, folderPackExecutor, queryExecutor, searchExecutor);
|
|
308
311
|
});
|
|
309
312
|
// 8. Register with transport server (for TransportHandlers tracking)
|
|
310
313
|
await transport.requestWithAck('agent:register', { projectPath });
|
|
@@ -312,15 +315,19 @@ async function start() {
|
|
|
312
315
|
process.send?.({ clientId, type: 'ready' });
|
|
313
316
|
agentLog('Ready — listening for tasks');
|
|
314
317
|
}
|
|
315
|
-
async function executeTask(task, curateExecutor, folderPackExecutor, queryExecutor) {
|
|
318
|
+
async function executeTask(task, curateExecutor, folderPackExecutor, queryExecutor, searchExecutor) {
|
|
316
319
|
const { clientCwd, clientId, content, files, folderPath, taskId, type, worktreeRoot } = task;
|
|
317
320
|
if (!transport || !agent)
|
|
318
321
|
return;
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
if (
|
|
322
|
-
transport.
|
|
323
|
-
|
|
322
|
+
// Search tasks are pure BM25 retrieval — no LLM, no provider needed.
|
|
323
|
+
// Skip provider validation so search works even without a configured provider.
|
|
324
|
+
if (type !== 'search') {
|
|
325
|
+
const freshProviderConfig = await transport.requestWithAck(TransportStateEventNames.GET_PROVIDER_CONFIG);
|
|
326
|
+
const validationError = validateProviderForTask(freshProviderConfig);
|
|
327
|
+
if (validationError) {
|
|
328
|
+
transport.request(TransportTaskEventNames.ERROR, { clientId, error: validationError, taskId });
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
324
331
|
}
|
|
325
332
|
activeTaskCount++;
|
|
326
333
|
try {
|
|
@@ -398,6 +405,12 @@ async function executeTask(task, curateExecutor, folderPackExecutor, queryExecut
|
|
|
398
405
|
result = await queryExecutor.executeWithAgent(agent, { query: content, taskId, worktreeRoot });
|
|
399
406
|
break;
|
|
400
407
|
}
|
|
408
|
+
case 'search': {
|
|
409
|
+
const searchOptions = decodeSearchContent(content);
|
|
410
|
+
const searchResult = await searchExecutor.execute(searchOptions);
|
|
411
|
+
result = JSON.stringify(searchResult);
|
|
412
|
+
break;
|
|
413
|
+
}
|
|
401
414
|
}
|
|
402
415
|
// Emit task:completed
|
|
403
416
|
agentLog(`task:completed taskId=${taskId}`);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SearchExecutor - Executes context tree searches via SearchKnowledgeService.
|
|
3
|
+
*
|
|
4
|
+
* Unlike QueryExecutor (Tier 0-4 with LLM synthesis), SearchExecutor is
|
|
5
|
+
* pure retrieval: BM25 index lookup → scored results. No LLM, no agent
|
|
6
|
+
* session, no sandbox, no token cost.
|
|
7
|
+
*
|
|
8
|
+
* This is the engine behind `brv search`. The CLI command and transport
|
|
9
|
+
* layer handle I/O; this module handles the search logic.
|
|
10
|
+
*/
|
|
11
|
+
import type { ISearchKnowledgeService, SearchKnowledgeResult } from '../../../agent/infra/sandbox/tools-sdk.js';
|
|
12
|
+
import type { ISearchExecutor, SearchExecuteOptions } from '../../core/interfaces/executor/i-search-executor.js';
|
|
13
|
+
export declare class SearchExecutor implements ISearchExecutor {
|
|
14
|
+
private readonly searchService;
|
|
15
|
+
constructor(searchService: ISearchKnowledgeService);
|
|
16
|
+
execute(options: SearchExecuteOptions): Promise<SearchKnowledgeResult>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SearchExecutor - Executes context tree searches via SearchKnowledgeService.
|
|
3
|
+
*
|
|
4
|
+
* Unlike QueryExecutor (Tier 0-4 with LLM synthesis), SearchExecutor is
|
|
5
|
+
* pure retrieval: BM25 index lookup → scored results. No LLM, no agent
|
|
6
|
+
* session, no sandbox, no token cost.
|
|
7
|
+
*
|
|
8
|
+
* This is the engine behind `brv search`. The CLI command and transport
|
|
9
|
+
* layer handle I/O; this module handles the search logic.
|
|
10
|
+
*/
|
|
11
|
+
const DEFAULT_LIMIT = 10;
|
|
12
|
+
const MAX_LIMIT = 50;
|
|
13
|
+
export class SearchExecutor {
|
|
14
|
+
searchService;
|
|
15
|
+
constructor(searchService) {
|
|
16
|
+
this.searchService = searchService;
|
|
17
|
+
}
|
|
18
|
+
async execute(options) {
|
|
19
|
+
const query = options.query.trim();
|
|
20
|
+
if (!query) {
|
|
21
|
+
return { message: 'Empty query', results: [], totalFound: 0 };
|
|
22
|
+
}
|
|
23
|
+
const scope = options.scope?.trim() || undefined;
|
|
24
|
+
const limit = Math.min(MAX_LIMIT, Math.max(1, Math.trunc(options.limit ?? DEFAULT_LIMIT)));
|
|
25
|
+
return this.searchService.search(query, {
|
|
26
|
+
limit,
|
|
27
|
+
...(scope ? { scope } : {}),
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -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
|
-
|
|
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
|
|
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)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { PullEvents, } from '../../../../shared/transport/events/pull-events.js';
|
|
2
|
-
import { LocalChangesExistError, NotAuthenticatedError, ProjectNotInitError,
|
|
2
|
+
import { LegacySyncUnavailableError, LocalChangesExistError, NotAuthenticatedError, ProjectNotInitError, } from '../../../core/domain/errors/task-error.js';
|
|
3
3
|
import { guardAgainstGitVc, hasAnyChanges, resolveRequiredProjectPath, } from './handler-types.js';
|
|
4
4
|
/**
|
|
5
5
|
* Handles pull:* events.
|
|
@@ -42,7 +42,7 @@ export class PullHandler {
|
|
|
42
42
|
throw new ProjectNotInitError();
|
|
43
43
|
}
|
|
44
44
|
if (!config.teamId || !config.spaceId) {
|
|
45
|
-
throw new
|
|
45
|
+
throw new LegacySyncUnavailableError();
|
|
46
46
|
}
|
|
47
47
|
// Check for local changes that would be overwritten
|
|
48
48
|
const changes = await this.contextTreeSnapshotService.getChanges(projectPath);
|
|
@@ -79,7 +79,7 @@ export class PullHandler {
|
|
|
79
79
|
throw new ProjectNotInitError();
|
|
80
80
|
}
|
|
81
81
|
if (!config.teamId || !config.spaceId) {
|
|
82
|
-
throw new
|
|
82
|
+
throw new LegacySyncUnavailableError();
|
|
83
83
|
}
|
|
84
84
|
const changes = await this.contextTreeSnapshotService.getChanges(projectPath);
|
|
85
85
|
const hasLocalChanges = hasAnyChanges(changes);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { join, relative } from 'node:path';
|
|
2
2
|
import { PushEvents, } from '../../../../shared/transport/events/push-events.js';
|
|
3
|
-
import { NotAuthenticatedError, ProjectNotInitError,
|
|
3
|
+
import { LegacySyncUnavailableError, NotAuthenticatedError, ProjectNotInitError, } from '../../../core/domain/errors/task-error.js';
|
|
4
4
|
import { mapToPushContexts } from '../../cogit/context-tree-to-push-context-mapper.js';
|
|
5
5
|
import { guardAgainstGitVc, resolveRequiredProjectPath, } from './handler-types.js';
|
|
6
6
|
/** Path prefix of the context tree relative to the project root. */
|
|
@@ -88,7 +88,7 @@ export class PushHandler {
|
|
|
88
88
|
throw new ProjectNotInitError();
|
|
89
89
|
}
|
|
90
90
|
if (!config.teamId || !config.spaceId) {
|
|
91
|
-
throw new
|
|
91
|
+
throw new LegacySyncUnavailableError();
|
|
92
92
|
}
|
|
93
93
|
this.broadcastToProject(projectPath, PushEvents.PROGRESS, { message: 'Reading context files...', step: 'reading' });
|
|
94
94
|
const changes = await this.contextTreeSnapshotService.getChanges(projectPath);
|
|
@@ -163,7 +163,7 @@ export class PushHandler {
|
|
|
163
163
|
throw new ProjectNotInitError();
|
|
164
164
|
}
|
|
165
165
|
if (!config.teamId || !config.spaceId) {
|
|
166
|
-
throw new
|
|
166
|
+
throw new LegacySyncUnavailableError();
|
|
167
167
|
}
|
|
168
168
|
const hasSnapshot = await this.contextTreeSnapshotService.hasSnapshot(projectPath);
|
|
169
169
|
if (!hasSnapshot) {
|
|
@@ -84,13 +84,14 @@ export class StatusHandler {
|
|
|
84
84
|
result.authStatus = 'unknown';
|
|
85
85
|
}
|
|
86
86
|
// Project status — use effectiveProjectPath for consistency with resolved root
|
|
87
|
+
let projectConfig;
|
|
87
88
|
try {
|
|
88
89
|
const isInitialized = await this.projectConfigStore.exists(effectiveProjectPath);
|
|
89
90
|
if (isInitialized) {
|
|
90
|
-
|
|
91
|
-
if (
|
|
92
|
-
result.teamName =
|
|
93
|
-
result.spaceName =
|
|
91
|
+
projectConfig = await this.projectConfigStore.read(effectiveProjectPath);
|
|
92
|
+
if (projectConfig) {
|
|
93
|
+
result.teamName = projectConfig.teamName;
|
|
94
|
+
result.spaceName = projectConfig.spaceName;
|
|
94
95
|
}
|
|
95
96
|
}
|
|
96
97
|
}
|
|
@@ -115,22 +116,28 @@ export class StatusHandler {
|
|
|
115
116
|
else {
|
|
116
117
|
result.contextTreeDir = join(effectiveProjectPath, BRV_DIR, CONTEXT_TREE_DIR);
|
|
117
118
|
result.contextTreeRelativeDir = join(BRV_DIR, CONTEXT_TREE_DIR);
|
|
118
|
-
const
|
|
119
|
-
if (
|
|
120
|
-
await this.contextTreeSnapshotService.
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
119
|
+
const hasLegacySyncConfig = Boolean(projectConfig?.teamId && projectConfig?.spaceId);
|
|
120
|
+
if (hasLegacySyncConfig) {
|
|
121
|
+
const hasSnapshot = await this.contextTreeSnapshotService.hasSnapshot(effectiveProjectPath);
|
|
122
|
+
if (!hasSnapshot) {
|
|
123
|
+
await this.contextTreeSnapshotService.initEmptySnapshot(effectiveProjectPath);
|
|
124
|
+
}
|
|
125
|
+
const changes = await this.contextTreeSnapshotService.getChanges(effectiveProjectPath);
|
|
126
|
+
const hasChanges = changes.added.length > 0 || changes.modified.length > 0 || changes.deleted.length > 0;
|
|
127
|
+
if (hasChanges) {
|
|
128
|
+
result.contextTreeStatus = 'has_changes';
|
|
129
|
+
result.contextTreeChanges = {
|
|
130
|
+
added: changes.added,
|
|
131
|
+
deleted: changes.deleted,
|
|
132
|
+
modified: changes.modified,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
result.contextTreeStatus = 'no_changes';
|
|
137
|
+
}
|
|
131
138
|
}
|
|
132
139
|
else {
|
|
133
|
-
result.contextTreeStatus = '
|
|
140
|
+
result.contextTreeStatus = 'no_vc';
|
|
134
141
|
}
|
|
135
142
|
}
|
|
136
143
|
}
|
|
@@ -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
|
|
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
|
|
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)
|