byterover-cli 3.1.0 → 3.3.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 (158) hide show
  1. package/README.md +17 -0
  2. package/dist/agent/infra/agent/agent-schemas.d.ts +8 -0
  3. package/dist/agent/infra/agent/agent-schemas.js +1 -0
  4. package/dist/agent/infra/sandbox/curate-service.js +14 -0
  5. package/dist/agent/infra/sandbox/sandbox-service.js +1 -0
  6. package/dist/agent/infra/sandbox/tools-sdk.d.ts +10 -0
  7. package/dist/agent/infra/sandbox/tools-sdk.js +9 -1
  8. package/dist/agent/infra/tools/implementations/search-knowledge-service.js +226 -103
  9. package/dist/agent/infra/tools/implementations/write-file-tool.d.ts +2 -1
  10. package/dist/agent/infra/tools/implementations/write-file-tool.js +16 -2
  11. package/dist/agent/infra/tools/tool-registry.js +1 -1
  12. package/dist/agent/infra/tools/write-guard.d.ts +11 -0
  13. package/dist/agent/infra/tools/write-guard.js +48 -0
  14. package/dist/agent/resources/prompts/system-prompt.yml +9 -0
  15. package/dist/agent/resources/tools/expand_knowledge.txt +4 -0
  16. package/dist/agent/resources/tools/search_knowledge.txt +11 -1
  17. package/dist/oclif/commands/curate/index.d.ts +1 -0
  18. package/dist/oclif/commands/curate/index.js +19 -4
  19. package/dist/oclif/commands/curate/view.js +2 -2
  20. package/dist/oclif/commands/main.js +13 -0
  21. package/dist/oclif/commands/query.d.ts +1 -0
  22. package/dist/oclif/commands/query.js +19 -4
  23. package/dist/oclif/commands/search.d.ts +20 -0
  24. package/dist/oclif/commands/search.js +186 -0
  25. package/dist/oclif/commands/source/add.d.ts +12 -0
  26. package/dist/oclif/commands/source/add.js +42 -0
  27. package/dist/oclif/commands/source/index.d.ts +6 -0
  28. package/dist/oclif/commands/source/index.js +8 -0
  29. package/dist/oclif/commands/source/list.d.ts +6 -0
  30. package/dist/oclif/commands/source/list.js +32 -0
  31. package/dist/oclif/commands/source/remove.d.ts +9 -0
  32. package/dist/oclif/commands/source/remove.js +33 -0
  33. package/dist/oclif/commands/status.d.ts +5 -1
  34. package/dist/oclif/commands/status.js +45 -6
  35. package/dist/oclif/commands/worktree/add.d.ts +12 -0
  36. package/dist/oclif/commands/worktree/add.js +44 -0
  37. package/dist/oclif/commands/worktree/index.d.ts +6 -0
  38. package/dist/oclif/commands/worktree/index.js +8 -0
  39. package/dist/oclif/commands/worktree/list.d.ts +6 -0
  40. package/dist/oclif/commands/worktree/list.js +28 -0
  41. package/dist/oclif/commands/worktree/remove.d.ts +9 -0
  42. package/dist/oclif/commands/worktree/remove.js +35 -0
  43. package/dist/oclif/hooks/init/validate-brv-config.js +4 -0
  44. package/dist/oclif/lib/daemon-client.d.ts +4 -2
  45. package/dist/oclif/lib/daemon-client.js +19 -4
  46. package/dist/oclif/lib/search-format.d.ts +10 -0
  47. package/dist/oclif/lib/search-format.js +25 -0
  48. package/dist/oclif/lib/task-client.d.ts +6 -0
  49. package/dist/oclif/lib/task-client.js +10 -3
  50. package/dist/server/constants.d.ts +7 -1
  51. package/dist/server/constants.js +10 -0
  52. package/dist/server/core/domain/client/client-info.d.ts +7 -0
  53. package/dist/server/core/domain/client/client-info.js +11 -0
  54. package/dist/server/core/domain/errors/task-error.d.ts +2 -2
  55. package/dist/server/core/domain/errors/task-error.js +5 -4
  56. package/dist/server/core/domain/project/worktrees-schema.d.ts +29 -0
  57. package/dist/server/core/domain/project/worktrees-schema.js +17 -0
  58. package/dist/server/core/domain/source/source-operations.d.ts +31 -0
  59. package/dist/server/core/domain/source/source-operations.js +201 -0
  60. package/dist/server/core/domain/source/source-schema.d.ts +94 -0
  61. package/dist/server/core/domain/source/source-schema.js +121 -0
  62. package/dist/server/core/domain/transport/schemas.d.ts +18 -10
  63. package/dist/server/core/domain/transport/schemas.js +7 -3
  64. package/dist/server/core/domain/transport/task-info.d.ts +2 -0
  65. package/dist/server/core/interfaces/client/i-client-manager.d.ts +13 -0
  66. package/dist/server/core/interfaces/executor/i-curate-executor.d.ts +4 -0
  67. package/dist/server/core/interfaces/executor/i-folder-pack-executor.d.ts +7 -3
  68. package/dist/server/core/interfaces/executor/i-query-executor.d.ts +2 -0
  69. package/dist/server/core/interfaces/executor/i-search-executor.d.ts +34 -0
  70. package/dist/server/core/interfaces/executor/i-search-executor.js +1 -0
  71. package/dist/server/core/interfaces/executor/index.d.ts +1 -0
  72. package/dist/server/core/interfaces/executor/index.js +1 -0
  73. package/dist/server/infra/client/client-manager.d.ts +1 -0
  74. package/dist/server/infra/client/client-manager.js +16 -0
  75. package/dist/server/infra/daemon/agent-process.js +35 -12
  76. package/dist/server/infra/executor/curate-executor.js +4 -2
  77. package/dist/server/infra/executor/direct-search-responder.js +5 -1
  78. package/dist/server/infra/executor/folder-pack-executor.js +23 -12
  79. package/dist/server/infra/executor/query-executor.d.ts +23 -0
  80. package/dist/server/infra/executor/query-executor.js +115 -21
  81. package/dist/server/infra/executor/search-executor.d.ts +17 -0
  82. package/dist/server/infra/executor/search-executor.js +30 -0
  83. package/dist/server/infra/mcp/mcp-mode-detector.d.ts +7 -5
  84. package/dist/server/infra/mcp/mcp-mode-detector.js +11 -18
  85. package/dist/server/infra/mcp/mcp-server.d.ts +1 -0
  86. package/dist/server/infra/mcp/mcp-server.js +11 -6
  87. package/dist/server/infra/mcp/tools/brv-curate-tool.d.ts +2 -1
  88. package/dist/server/infra/mcp/tools/brv-curate-tool.js +9 -16
  89. package/dist/server/infra/mcp/tools/brv-query-tool.d.ts +2 -1
  90. package/dist/server/infra/mcp/tools/brv-query-tool.js +9 -16
  91. package/dist/server/infra/mcp/tools/mcp-project-context.d.ts +11 -0
  92. package/dist/server/infra/mcp/tools/mcp-project-context.js +54 -0
  93. package/dist/server/infra/process/connection-coordinator.js +11 -0
  94. package/dist/server/infra/process/feature-handlers.js +4 -1
  95. package/dist/server/infra/process/task-router.d.ts +1 -0
  96. package/dist/server/infra/process/task-router.js +60 -5
  97. package/dist/server/infra/project/resolve-project.d.ts +106 -0
  98. package/dist/server/infra/project/resolve-project.js +473 -0
  99. package/dist/server/infra/transport/handlers/index.d.ts +4 -0
  100. package/dist/server/infra/transport/handlers/index.js +2 -0
  101. package/dist/server/infra/transport/handlers/pull-handler.js +3 -3
  102. package/dist/server/infra/transport/handlers/push-handler.js +3 -3
  103. package/dist/server/infra/transport/handlers/source-handler.d.ts +12 -0
  104. package/dist/server/infra/transport/handlers/source-handler.js +37 -0
  105. package/dist/server/infra/transport/handlers/status-handler.js +76 -27
  106. package/dist/server/infra/transport/handlers/worktree-handler.d.ts +12 -0
  107. package/dist/server/infra/transport/handlers/worktree-handler.js +67 -0
  108. package/dist/server/infra/transport/transport-connector.d.ts +10 -4
  109. package/dist/server/infra/transport/transport-connector.js +2 -2
  110. package/dist/server/templates/skill/SKILL.md +25 -5
  111. package/dist/server/utils/path-utils.d.ts +5 -0
  112. package/dist/server/utils/path-utils.js +11 -1
  113. package/dist/shared/transport/events/client-events.d.ts +3 -0
  114. package/dist/shared/transport/events/client-events.js +3 -0
  115. package/dist/shared/transport/events/index.d.ts +13 -0
  116. package/dist/shared/transport/events/index.js +9 -0
  117. package/dist/shared/transport/events/source-events.d.ts +30 -0
  118. package/dist/shared/transport/events/source-events.js +5 -0
  119. package/dist/shared/transport/events/status-events.d.ts +5 -0
  120. package/dist/shared/transport/events/task-events.d.ts +4 -1
  121. package/dist/shared/transport/events/worktree-events.d.ts +31 -0
  122. package/dist/shared/transport/events/worktree-events.js +5 -0
  123. package/dist/shared/transport/search-content.d.ts +28 -0
  124. package/dist/shared/transport/search-content.js +38 -0
  125. package/dist/shared/transport/types/dto.d.ts +20 -1
  126. package/dist/tui/features/commands/definitions/index.js +6 -0
  127. package/dist/tui/features/commands/definitions/source-add.d.ts +2 -0
  128. package/dist/tui/features/commands/definitions/source-add.js +48 -0
  129. package/dist/tui/features/commands/definitions/source-list.d.ts +2 -0
  130. package/dist/tui/features/commands/definitions/source-list.js +47 -0
  131. package/dist/tui/features/commands/definitions/source-remove.d.ts +2 -0
  132. package/dist/tui/features/commands/definitions/source-remove.js +38 -0
  133. package/dist/tui/features/commands/definitions/source.d.ts +2 -0
  134. package/dist/tui/features/commands/definitions/source.js +8 -0
  135. package/dist/tui/features/commands/definitions/worktree-add.d.ts +2 -0
  136. package/dist/tui/features/commands/definitions/worktree-add.js +35 -0
  137. package/dist/tui/features/commands/definitions/worktree-list.d.ts +2 -0
  138. package/dist/tui/features/commands/definitions/worktree-list.js +36 -0
  139. package/dist/tui/features/commands/definitions/worktree-remove.d.ts +2 -0
  140. package/dist/tui/features/commands/definitions/worktree-remove.js +33 -0
  141. package/dist/tui/features/commands/definitions/worktree.d.ts +2 -0
  142. package/dist/tui/features/commands/definitions/worktree.js +8 -0
  143. package/dist/tui/features/curate/api/create-curate-task.js +3 -1
  144. package/dist/tui/features/query/api/create-query-task.js +3 -1
  145. package/dist/tui/features/source/api/source-api.d.ts +4 -0
  146. package/dist/tui/features/source/api/source-api.js +22 -0
  147. package/dist/tui/features/status/api/get-status.js +2 -1
  148. package/dist/tui/features/status/utils/format-status.js +28 -1
  149. package/dist/tui/features/transport/components/transport-initializer.js +36 -1
  150. package/dist/tui/features/worktree/api/worktree-api.d.ts +4 -0
  151. package/dist/tui/features/worktree/api/worktree-api.js +22 -0
  152. package/dist/tui/repl-startup.d.ts +2 -0
  153. package/dist/tui/repl-startup.js +5 -3
  154. package/dist/tui/stores/transport-store.d.ts +6 -0
  155. package/dist/tui/stores/transport-store.js +6 -0
  156. package/dist/tui/utils/error-messages.js +2 -2
  157. package/oclif.manifest.json +380 -36
  158. package/package.json +1 -1
@@ -2,6 +2,8 @@ import { readFile } from 'node:fs/promises';
2
2
  import { join } from 'node:path';
3
3
  import { StatusEvents } from '../../../../shared/transport/events/status-events.js';
4
4
  import { BRV_DIR, CONTEXT_TREE_DIR } from '../../../constants.js';
5
+ import { listSourceStatuses } from '../../../core/domain/source/source-operations.js';
6
+ import { BrokenWorktreePointerError, MalformedWorktreePointerError, resolveProject } from '../../project/resolve-project.js';
5
7
  import { resolveRequiredProjectPath } from './handler-types.js';
6
8
  /**
7
9
  * Handles status:get event.
@@ -25,18 +27,45 @@ export class StatusHandler {
25
27
  this.transport = deps.transport;
26
28
  }
27
29
  setup() {
28
- this.transport.onRequest(StatusEvents.GET, async (_data, clientId) => {
30
+ this.transport.onRequest(StatusEvents.GET, async (data, clientId) => {
29
31
  const projectPath = resolveRequiredProjectPath(this.resolveProjectPath, clientId);
30
- const status = await this.collectStatus(projectPath);
32
+ const request = data;
33
+ const cwd = request?.cwd;
34
+ const projectRootFlag = request?.projectRootFlag;
35
+ const status = await this.collectStatus(projectPath, cwd, projectRootFlag);
31
36
  return { status };
32
37
  });
33
38
  }
34
- async collectStatus(projectPath) {
39
+ async collectStatus(projectPath, clientCwd, projectRootFlag) {
35
40
  const result = {
36
41
  authStatus: 'unknown',
37
42
  contextTreeStatus: 'unknown',
38
43
  currentDirectory: projectPath,
44
+ projectRoot: projectPath,
39
45
  };
46
+ // Resolve workspace awareness from client cwd (if provided)
47
+ // Use resolved projectRoot for all downstream checks to avoid inconsistency
48
+ let effectiveProjectPath = projectPath;
49
+ if (clientCwd || projectRootFlag) {
50
+ try {
51
+ const resolution = resolveProject({ cwd: clientCwd, projectRootFlag });
52
+ if (resolution) {
53
+ result.projectRoot = resolution.projectRoot;
54
+ result.worktreeRoot = resolution.worktreeRoot;
55
+ result.resolutionSource = resolution.source;
56
+ effectiveProjectPath = resolution.projectRoot;
57
+ }
58
+ }
59
+ catch (error) {
60
+ // Surface broken/malformed link errors as actionable status info
61
+ if (error instanceof BrokenWorktreePointerError || error instanceof MalformedWorktreePointerError) {
62
+ result.resolverError = error.message;
63
+ }
64
+ // Fall through with projectPath defaults for config/context checks
65
+ }
66
+ }
67
+ // Preserve actual client working directory for backward compat
68
+ result.currentDirectory = clientCwd ?? projectPath;
40
69
  // Auth status
41
70
  try {
42
71
  const token = await this.tokenStore.load();
@@ -54,14 +83,15 @@ export class StatusHandler {
54
83
  catch {
55
84
  result.authStatus = 'unknown';
56
85
  }
57
- // Project status
86
+ // Project status — use effectiveProjectPath for consistency with resolved root
87
+ let projectConfig;
58
88
  try {
59
- const isInitialized = await this.projectConfigStore.exists(projectPath);
89
+ const isInitialized = await this.projectConfigStore.exists(effectiveProjectPath);
60
90
  if (isInitialized) {
61
- const config = await this.projectConfigStore.read(projectPath);
62
- if (config) {
63
- result.teamName = config.teamName;
64
- result.spaceName = config.spaceName;
91
+ projectConfig = await this.projectConfigStore.read(effectiveProjectPath);
92
+ if (projectConfig) {
93
+ result.teamName = projectConfig.teamName;
94
+ result.spaceName = projectConfig.spaceName;
65
95
  }
66
96
  }
67
97
  }
@@ -75,33 +105,39 @@ export class StatusHandler {
75
105
  catch {
76
106
  // File doesn't exist yet — no queue running
77
107
  }
78
- // Context tree status
108
+ // Context tree status — use effectiveProjectPath for consistency with resolved root
79
109
  try {
80
- const contextTreeExists = await this.contextTreeService.exists(projectPath);
110
+ const contextTreeExists = await this.contextTreeService.exists(effectiveProjectPath);
81
111
  if (contextTreeExists) {
82
- const hasGitVc = await this.contextTreeService.hasGitRepo(projectPath);
112
+ const hasGitVc = await this.contextTreeService.hasGitRepo(effectiveProjectPath);
83
113
  if (hasGitVc) {
84
114
  result.contextTreeStatus = 'git_vc';
85
115
  }
86
116
  else {
87
- result.contextTreeDir = join(projectPath, BRV_DIR, CONTEXT_TREE_DIR);
117
+ result.contextTreeDir = join(effectiveProjectPath, BRV_DIR, CONTEXT_TREE_DIR);
88
118
  result.contextTreeRelativeDir = join(BRV_DIR, CONTEXT_TREE_DIR);
89
- const hasSnapshot = await this.contextTreeSnapshotService.hasSnapshot(projectPath);
90
- if (!hasSnapshot) {
91
- await this.contextTreeSnapshotService.initEmptySnapshot(projectPath);
92
- }
93
- const changes = await this.contextTreeSnapshotService.getChanges(projectPath);
94
- const hasChanges = changes.added.length > 0 || changes.modified.length > 0 || changes.deleted.length > 0;
95
- if (hasChanges) {
96
- result.contextTreeStatus = 'has_changes';
97
- result.contextTreeChanges = {
98
- added: changes.added,
99
- deleted: changes.deleted,
100
- modified: changes.modified,
101
- };
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
+ }
102
138
  }
103
139
  else {
104
- result.contextTreeStatus = 'no_changes';
140
+ result.contextTreeStatus = 'no_vc';
105
141
  }
106
142
  }
107
143
  }
@@ -139,6 +175,19 @@ export class StatusHandler {
139
175
  catch {
140
176
  // Best-effort — if the log is unavailable, skip review info
141
177
  }
178
+ // Knowledge sources status
179
+ try {
180
+ const sourcesResult = listSourceStatuses(effectiveProjectPath);
181
+ if (sourcesResult.error) {
182
+ result.sourcesError = sourcesResult.error;
183
+ }
184
+ else if (sourcesResult.statuses.length > 0) {
185
+ result.sources = sourcesResult.statuses;
186
+ }
187
+ }
188
+ catch {
189
+ // Best-effort — swallow errors
190
+ }
142
191
  return result;
143
192
  }
144
193
  }
@@ -0,0 +1,12 @@
1
+ import type { ITransportServer } from '../../../core/interfaces/transport/i-transport-server.js';
2
+ import { type ProjectPathResolver } from './handler-types.js';
3
+ export interface WorktreeHandlerDeps {
4
+ resolveProjectPath: ProjectPathResolver;
5
+ transport: ITransportServer;
6
+ }
7
+ export declare class WorktreeHandler {
8
+ private readonly resolveProjectPath;
9
+ private readonly transport;
10
+ constructor(deps: WorktreeHandlerDeps);
11
+ setup(): void;
12
+ }
@@ -0,0 +1,67 @@
1
+ import { WorktreeEvents, } from '../../../../shared/transport/events/worktree-events.js';
2
+ import { addWorktree, findParentProject, listWorktrees, removeWorktree, resolveProject } from '../../project/resolve-project.js';
3
+ import { resolveRequiredProjectPath } from './handler-types.js';
4
+ export class WorktreeHandler {
5
+ resolveProjectPath;
6
+ transport;
7
+ constructor(deps) {
8
+ this.resolveProjectPath = deps.resolveProjectPath;
9
+ this.transport = deps.transport;
10
+ }
11
+ setup() {
12
+ this.transport.onRequest(WorktreeEvents.ADD, async (data, clientId) => {
13
+ // Resolve the parent project from client registration
14
+ let projectPath;
15
+ try {
16
+ projectPath = resolveRequiredProjectPath(this.resolveProjectPath, clientId);
17
+ }
18
+ catch {
19
+ // Client not associated — fall through to auto-detect
20
+ }
21
+ // Auto-detect: if no project resolved, or client's project IS the worktree path
22
+ // (user ran `brv worktree add` from a child dir with no args), walk up to find parent
23
+ if (!projectPath || projectPath === data.worktreePath) {
24
+ const parent = findParentProject(data.worktreePath);
25
+ if (parent) {
26
+ projectPath = parent;
27
+ }
28
+ else if (!projectPath) {
29
+ return { message: 'No parent project found for the target directory.', success: false };
30
+ }
31
+ }
32
+ const result = addWorktree(projectPath, data.worktreePath, { force: data.force });
33
+ return {
34
+ backedUp: result.backedUp,
35
+ message: result.message,
36
+ success: result.success,
37
+ };
38
+ });
39
+ this.transport.onRequest(WorktreeEvents.REMOVE, async (data) => {
40
+ const targetPath = data.worktreePath;
41
+ const result = removeWorktree(targetPath);
42
+ return {
43
+ message: result.message,
44
+ success: result.success,
45
+ };
46
+ });
47
+ this.transport.onRequest(WorktreeEvents.LIST, async (_data, clientId) => {
48
+ const projectPath = resolveRequiredProjectPath(this.resolveProjectPath, clientId);
49
+ const resolution = resolveProject({ cwd: projectPath });
50
+ if (!resolution) {
51
+ return {
52
+ projectRoot: projectPath,
53
+ source: 'direct',
54
+ worktreeRoot: projectPath,
55
+ worktrees: [],
56
+ };
57
+ }
58
+ const worktrees = listWorktrees(resolution.projectRoot);
59
+ return {
60
+ projectRoot: resolution.projectRoot,
61
+ source: resolution.source,
62
+ worktreeRoot: resolution.worktreeRoot,
63
+ worktrees,
64
+ };
65
+ });
66
+ }
67
+ }
@@ -1,13 +1,19 @@
1
1
  import { type ConnectionResult } from '@campfirein/brv-transport-client';
2
- /** Function type for transport connection (for DI/testing in use cases). */
3
- export type TransportConnector = (fromDir?: string) => Promise<ConnectionResult>;
2
+ /**
3
+ * Function type for transport connection (for DI/testing in use cases).
4
+ *
5
+ * Callers must pass an explicit resolved projectPath. This avoids silently
6
+ * falling back to the transport library's own walk-up discovery, which is
7
+ * not workspace-link-aware.
8
+ */
9
+ export type TransportConnector = (fromDir: string | undefined, projectPath: string) => Promise<ConnectionResult>;
4
10
  /**
5
11
  * Creates a transport connector that auto-starts the daemon if needed.
6
12
  *
7
13
  * Thin wrapper around connectToDaemon() for DI compatibility with use cases
8
14
  * (QueryUseCase, CurateUseCase, StatusUseCase).
9
15
  *
10
- * projectPath is auto-filled by the transport library from the discovered
11
- * project root (walks up from fromDir to find .brv/).
16
+ * When an explicit projectPath is provided it takes priority over the
17
+ * transport library's walk-up discovery, making the connector workspace-link-aware.
12
18
  */
13
19
  export declare function createDaemonAwareConnector(projectPath?: string): TransportConnector;
@@ -6,8 +6,8 @@ import { resolveLocalServerMainPath } from '../../utils/server-main-resolver.js'
6
6
  * Thin wrapper around connectToDaemon() for DI compatibility with use cases
7
7
  * (QueryUseCase, CurateUseCase, StatusUseCase).
8
8
  *
9
- * projectPath is auto-filled by the transport library from the discovered
10
- * project root (walks up from fromDir to find .brv/).
9
+ * When an explicit projectPath is provided it takes priority over the
10
+ * transport library's walk-up discovery, making the connector workspace-link-aware.
11
11
  */
12
12
  export function createDaemonAwareConnector(projectPath) {
13
13
  return (fromDir) => connectToDaemon({
@@ -34,7 +34,27 @@ Knowledge is stored in `.brv/context-tree/` as human-readable Markdown files.
34
34
  brv query "How is authentication implemented?"
35
35
  ```
36
36
 
37
- ### 2. Curate Context
37
+ ### 2. Search Context Tree
38
+ **Overview:** Retrieve a ranked list of matching files from `.brv/context-tree/` via pure BM25 lookup. Unlike `brv query`, this does NOT call an LLM — no synthesis, no token cost, no provider setup needed. Returns structured results with paths, scores, and excerpts.
39
+
40
+ **Use this skill when:**
41
+ - You need file paths to read rather than a synthesized answer
42
+ - You want fast, cheap retrieval with no LLM overhead
43
+ - You're in an automated pipeline that consumes structured results
44
+
45
+ **Do NOT use this skill when:**
46
+ - You need a natural-language answer synthesized from multiple files — use `brv query` instead
47
+ - The information is already present in your current context
48
+
49
+ ```bash
50
+ brv search "authentication patterns"
51
+ brv search "JWT tokens" --limit 5 --scope "auth/"
52
+ brv search "auth" --format json
53
+ ```
54
+
55
+ **Flags:** `--limit N` (1-50, default 10), `--scope "domain/"` (path prefix filter), `--format json` (structured output for automation).
56
+
57
+ ### 3. Curate Context
38
58
  **Overview:** Analyze and save knowledge to the local knowledge base. Uses a configured LLM provider to categorize and structure the context you provide.
39
59
 
40
60
  **Use this skill when:**
@@ -79,7 +99,7 @@ brv curate view --since 1h --status completed
79
99
  brv curate view --help
80
100
  ```
81
101
 
82
- ### 3. Review Pending Changes
102
+ ### 4. Review Pending Changes
83
103
  **Overview:** After a curate operation, some changes may require human review before being applied. Use `brv review` to list, approve, or reject pending operations.
84
104
 
85
105
  **Use this when:**
@@ -142,7 +162,7 @@ brv review approve <taskId> --format json
142
162
  brv review reject <taskId> --format json
143
163
  ```
144
164
 
145
- ### 4. LLM Provider Setup
165
+ ### 5. LLM Provider Setup
146
166
  `brv query` and `brv curate` require a configured LLM provider. Connect the default ByteRover provider (no API key needed):
147
167
 
148
168
  ```bash
@@ -156,7 +176,7 @@ brv providers list
156
176
  brv providers connect openai --api-key sk-xxx --model gpt-4.1
157
177
  ```
158
178
 
159
- ### 5. Project Locations
179
+ ### 6. Project Locations
160
180
  **Overview:** List registered projects and their context tree paths. Returns project metadata including initialization status and active state. Use `-f json` for machine-readable output.
161
181
 
162
182
  **Use this when:**
@@ -174,7 +194,7 @@ brv locations -f json
174
194
 
175
195
  JSON fields: `projectPath`, `contextTreePath`, `isCurrent`, `isActive`, `isInitialized`.
176
196
 
177
- ### 6. Version Control
197
+ ### 7. Version Control
178
198
  **Overview:** `brv vc` provides git-based version control for your context tree. It uses standard git semantics — branching, committing, merging, history, and conflict resolution — all working locally with no authentication required. Remote sync with a team is optional. The legacy `brv push`, `brv pull`, and `brv space` commands are deprecated — use `brv vc push`, `brv vc pull`, and `brv vc clone`/`brv vc remote add` instead.
179
199
 
180
200
  **Use this when:**
@@ -36,3 +36,8 @@ export declare const sanitizeProjectPath: (resolvedPath: string) => string;
36
36
  * @returns Absolute path to the project's data directory
37
37
  */
38
38
  export declare const getProjectDataDir: (cwd: string) => string;
39
+ /**
40
+ * Checks that candidate is an ancestor of (or equal to) descendant.
41
+ * Both paths are normalized via resolve() before comparison.
42
+ */
43
+ export declare function isDescendantOf(descendant: string, ancestor: string): boolean;
@@ -1,6 +1,6 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { realpathSync } from 'node:fs';
3
- import { join } from 'node:path';
3
+ import { join, resolve, sep } from 'node:path';
4
4
  import { GLOBAL_PROJECTS_DIR } from '../constants.js';
5
5
  import { getGlobalDataDir } from './global-data-path.js';
6
6
  /**
@@ -90,3 +90,13 @@ export const getProjectDataDir = (cwd) => {
90
90
  const sanitized = sanitizeProjectPath(resolved);
91
91
  return join(getGlobalDataDir(), GLOBAL_PROJECTS_DIR, sanitized);
92
92
  };
93
+ /**
94
+ * Checks that candidate is an ancestor of (or equal to) descendant.
95
+ * Both paths are normalized via resolve() before comparison.
96
+ */
97
+ export function isDescendantOf(descendant, ancestor) {
98
+ const normalizedDescendant = resolve(descendant);
99
+ const normalizedAncestor = resolve(ancestor);
100
+ const ancestorPrefix = normalizedAncestor.endsWith(sep) ? normalizedAncestor : normalizedAncestor + sep;
101
+ return normalizedDescendant === normalizedAncestor || normalizedDescendant.startsWith(ancestorPrefix);
102
+ }
@@ -0,0 +1,3 @@
1
+ export declare const ClientEvents: {
2
+ readonly ASSOCIATE_PROJECT: "client:associateProject";
3
+ };
@@ -0,0 +1,3 @@
1
+ export const ClientEvents = {
2
+ ASSOCIATE_PROJECT: 'client:associateProject',
3
+ };
@@ -1,6 +1,7 @@
1
1
  export * from '../types/dto.js';
2
2
  export * from './agent-events.js';
3
3
  export * from './auth-events.js';
4
+ export * from './client-events.js';
4
5
  export * from './config-events.js';
5
6
  export * from './connector-events.js';
6
7
  export * from './hub-events.js';
@@ -15,9 +16,11 @@ export * from './push-events.js';
15
16
  export * from './reset-events.js';
16
17
  export * from './review-events.js';
17
18
  export * from './session-events.js';
19
+ export * from './source-events.js';
18
20
  export * from './space-events.js';
19
21
  export * from './status-events.js';
20
22
  export * from './task-events.js';
23
+ export * from './worktree-events.js';
21
24
  /**
22
25
  * Array of all event group objects for iteration.
23
26
  * Use this to subscribe to all events without key collisions.
@@ -41,6 +44,8 @@ export declare const AllEventGroups: readonly [{
41
44
  readonly START_LOGIN: "auth:startLogin";
42
45
  readonly STATE_CHANGED: "auth:stateChanged";
43
46
  readonly UPDATED: "auth:updated";
47
+ }, {
48
+ readonly ASSOCIATE_PROJECT: "client:associateProject";
44
49
  }, {
45
50
  readonly GET_ENVIRONMENT: "config:getEnvironment";
46
51
  readonly GET_PROJECT: "config:getProject";
@@ -117,6 +122,10 @@ export declare const AllEventGroups: readonly [{
117
122
  readonly SWITCHED: "session:switched";
118
123
  }, {
119
124
  readonly GET: "locations:get";
125
+ }, {
126
+ readonly ADD: "source:add";
127
+ readonly LIST: "source:list";
128
+ readonly REMOVE: "source:remove";
120
129
  }, {
121
130
  readonly LIST: "space:list";
122
131
  readonly SWITCH: "space:switch";
@@ -130,6 +139,10 @@ export declare const AllEventGroups: readonly [{
130
139
  readonly CREATED: "task:created";
131
140
  readonly ERROR: "task:error";
132
141
  readonly STARTED: "task:started";
142
+ }, {
143
+ readonly ADD: "worktree:add";
144
+ readonly LIST: "worktree:list";
145
+ readonly REMOVE: "worktree:remove";
133
146
  }];
134
147
  /**
135
148
  * Get all unique event values from all event groups.
@@ -3,6 +3,7 @@ export * from '../types/dto.js';
3
3
  // Event constants and types
4
4
  export * from './agent-events.js';
5
5
  export * from './auth-events.js';
6
+ export * from './client-events.js';
6
7
  export * from './config-events.js';
7
8
  export * from './connector-events.js';
8
9
  export * from './hub-events.js';
@@ -17,12 +18,15 @@ export * from './push-events.js';
17
18
  export * from './reset-events.js';
18
19
  export * from './review-events.js';
19
20
  export * from './session-events.js';
21
+ export * from './source-events.js';
20
22
  export * from './space-events.js';
21
23
  export * from './status-events.js';
22
24
  export * from './task-events.js';
25
+ export * from './worktree-events.js';
23
26
  // Utility exports
24
27
  import { AgentEvents } from './agent-events.js';
25
28
  import { AuthEvents } from './auth-events.js';
29
+ import { ClientEvents } from './client-events.js';
26
30
  import { ConfigEvents } from './config-events.js';
27
31
  import { ConnectorEvents } from './connector-events.js';
28
32
  import { HubEvents } from './hub-events.js';
@@ -37,9 +41,11 @@ import { PushEvents } from './push-events.js';
37
41
  import { ResetEvents } from './reset-events.js';
38
42
  import { ReviewEvents } from './review-events.js';
39
43
  import { SessionEvents } from './session-events.js';
44
+ import { SourceEvents } from './source-events.js';
40
45
  import { SpaceEvents } from './space-events.js';
41
46
  import { StatusEvents } from './status-events.js';
42
47
  import { TaskEvents } from './task-events.js';
48
+ import { WorktreeEvents } from './worktree-events.js';
43
49
  /**
44
50
  * Array of all event group objects for iteration.
45
51
  * Use this to subscribe to all events without key collisions.
@@ -47,6 +53,7 @@ import { TaskEvents } from './task-events.js';
47
53
  export const AllEventGroups = [
48
54
  AgentEvents,
49
55
  AuthEvents,
56
+ ClientEvents,
50
57
  ConfigEvents,
51
58
  ConnectorEvents,
52
59
  HubEvents,
@@ -61,9 +68,11 @@ export const AllEventGroups = [
61
68
  ReviewEvents,
62
69
  SessionEvents,
63
70
  LocationsEvents,
71
+ SourceEvents,
64
72
  SpaceEvents,
65
73
  StatusEvents,
66
74
  TaskEvents,
75
+ WorktreeEvents,
67
76
  ];
68
77
  /**
69
78
  * Get all unique event values from all event groups.
@@ -0,0 +1,30 @@
1
+ export declare const SourceEvents: {
2
+ readonly ADD: "source:add";
3
+ readonly LIST: "source:list";
4
+ readonly REMOVE: "source:remove";
5
+ };
6
+ export interface SourceAddRequest {
7
+ alias?: string;
8
+ targetPath: string;
9
+ }
10
+ export interface SourceAddResponse {
11
+ message: string;
12
+ success: boolean;
13
+ }
14
+ export interface SourceRemoveRequest {
15
+ aliasOrPath: string;
16
+ }
17
+ export interface SourceRemoveResponse {
18
+ message: string;
19
+ success: boolean;
20
+ }
21
+ export type SourceListRequest = void;
22
+ export interface SourceListResponse {
23
+ error?: string;
24
+ statuses: Array<{
25
+ alias: string;
26
+ contextTreeSize?: number;
27
+ projectRoot: string;
28
+ valid: boolean;
29
+ }>;
30
+ }
@@ -0,0 +1,5 @@
1
+ export const SourceEvents = {
2
+ ADD: 'source:add',
3
+ LIST: 'source:list',
4
+ REMOVE: 'source:remove',
5
+ };
@@ -2,6 +2,11 @@ import type { StatusDTO } from '../types/dto.js';
2
2
  export declare const StatusEvents: {
3
3
  readonly GET: "status:get";
4
4
  };
5
+ export interface StatusGetRequest {
6
+ cwd?: string;
7
+ projectRootFlag?: string;
8
+ verbose?: boolean;
9
+ }
5
10
  export interface StatusGetResponse {
6
11
  status: StatusDTO;
7
12
  }
@@ -11,8 +11,11 @@ export interface TaskCreateRequest {
11
11
  clientCwd?: string;
12
12
  content: string;
13
13
  files?: string[];
14
+ folderPath?: string;
15
+ projectPath?: string;
14
16
  taskId: string;
15
- type: 'curate' | 'query';
17
+ type: 'curate' | 'curate-folder' | 'query';
18
+ worktreeRoot?: string;
16
19
  }
17
20
  export interface TaskAckResponse {
18
21
  taskId: string;
@@ -0,0 +1,31 @@
1
+ export declare const WorktreeEvents: {
2
+ readonly ADD: "worktree:add";
3
+ readonly LIST: "worktree:list";
4
+ readonly REMOVE: "worktree:remove";
5
+ };
6
+ export interface WorktreeAddRequest {
7
+ force?: boolean;
8
+ worktreePath: string;
9
+ }
10
+ export interface WorktreeAddResponse {
11
+ backedUp?: boolean;
12
+ message: string;
13
+ success: boolean;
14
+ }
15
+ export interface WorktreeRemoveRequest {
16
+ worktreePath: string;
17
+ }
18
+ export interface WorktreeRemoveResponse {
19
+ message: string;
20
+ success: boolean;
21
+ }
22
+ export type WorktreeListRequest = void;
23
+ export interface WorktreeListResponse {
24
+ projectRoot: string;
25
+ source: 'direct' | 'flag' | 'linked';
26
+ worktreeRoot: string;
27
+ worktrees: Array<{
28
+ name: string;
29
+ worktreePath: string;
30
+ }>;
31
+ }
@@ -0,0 +1,5 @@
1
+ export const WorktreeEvents = {
2
+ ADD: 'worktree:add',
3
+ LIST: 'worktree:list',
4
+ REMOVE: 'worktree:remove',
5
+ };
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Encode/decode helpers for search task content payloads.
3
+ *
4
+ * The transport layer's TaskCreateRequest has a single `content: string`
5
+ * field. For search tasks, we pack {query, limit?, scope?} as JSON so
6
+ * the agent process can reconstruct the structured options.
7
+ *
8
+ * Lives in shared/ because both the CLI (encoder) and the daemon
9
+ * agent-process (decoder) depend on it. Keeping it in oclif/ would
10
+ * create a circular dependency (server → oclif).
11
+ */
12
+ /**
13
+ * Encode search options as JSON content payload for the transport layer.
14
+ */
15
+ export declare function encodeSearchContent(options: {
16
+ limit?: number;
17
+ query: string;
18
+ scope?: string;
19
+ }): string;
20
+ /**
21
+ * Parse a JSON-encoded search content payload back into options.
22
+ * Falls back to treating the entire string as a plain query if parsing fails.
23
+ */
24
+ export declare function decodeSearchContent(content: string): {
25
+ limit?: number;
26
+ query: string;
27
+ scope?: string;
28
+ };
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Encode/decode helpers for search task content payloads.
3
+ *
4
+ * The transport layer's TaskCreateRequest has a single `content: string`
5
+ * field. For search tasks, we pack {query, limit?, scope?} as JSON so
6
+ * the agent process can reconstruct the structured options.
7
+ *
8
+ * Lives in shared/ because both the CLI (encoder) and the daemon
9
+ * agent-process (decoder) depend on it. Keeping it in oclif/ would
10
+ * create a circular dependency (server → oclif).
11
+ */
12
+ /**
13
+ * Encode search options as JSON content payload for the transport layer.
14
+ */
15
+ export function encodeSearchContent(options) {
16
+ return JSON.stringify({
17
+ limit: options.limit,
18
+ query: options.query,
19
+ scope: options.scope,
20
+ });
21
+ }
22
+ /**
23
+ * Parse a JSON-encoded search content payload back into options.
24
+ * Falls back to treating the entire string as a plain query if parsing fails.
25
+ */
26
+ export function decodeSearchContent(content) {
27
+ try {
28
+ const parsed = JSON.parse(content);
29
+ return {
30
+ limit: typeof parsed.limit === 'number' ? parsed.limit : undefined,
31
+ query: typeof parsed.query === 'string' ? parsed.query : content,
32
+ scope: typeof parsed.scope === 'string' ? parsed.scope : undefined,
33
+ };
34
+ }
35
+ catch {
36
+ return { query: content };
37
+ }
38
+ }