byterover-cli 1.2.1 → 1.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/README.md +76 -8
- package/dist/constants.d.ts +0 -5
- package/dist/constants.js +0 -5
- package/dist/core/domain/cipher/agent/agent-info.d.ts +17 -17
- package/dist/core/domain/cipher/errors/file-system-error.d.ts +11 -0
- package/dist/core/domain/cipher/errors/file-system-error.js +17 -0
- package/dist/core/domain/cipher/file-system/types.d.ts +40 -6
- package/dist/core/domain/cipher/llm/schemas.d.ts +14 -14
- package/dist/core/domain/cipher/session/session-metadata.d.ts +2 -2
- package/dist/core/domain/entities/agent.d.ts +1 -1
- package/dist/core/domain/entities/agent.js +11 -6
- package/dist/core/domain/entities/connector-type.d.ts +2 -1
- package/dist/core/domain/entities/connector-type.js +2 -1
- package/dist/core/domain/transport/schemas.d.ts +66 -66
- package/dist/core/interfaces/cipher/cipher-services.d.ts +0 -3
- package/dist/core/interfaces/cipher/index.d.ts +0 -2
- package/dist/core/interfaces/connectors/i-connector.d.ts +2 -2
- package/dist/core/interfaces/i-file-service.d.ts +7 -0
- package/dist/infra/auth/oauth-service.d.ts +15 -0
- package/dist/infra/auth/oauth-service.js +38 -2
- package/dist/infra/cipher/agent/agent-schemas.d.ts +42 -42
- package/dist/infra/cipher/file-system/binary-utils.d.ts +15 -2
- package/dist/infra/cipher/file-system/binary-utils.js +26 -3
- package/dist/infra/cipher/file-system/file-system-service.d.ts +9 -0
- package/dist/infra/cipher/file-system/file-system-service.js +91 -8
- package/dist/infra/cipher/file-system/pdf-extractor.d.ts +100 -0
- package/dist/infra/cipher/file-system/pdf-extractor.js +226 -0
- package/dist/infra/cipher/llm/context/context-manager.js +7 -9
- package/dist/infra/cipher/llm/internal-llm-service.d.ts +5 -1
- package/dist/infra/cipher/llm/internal-llm-service.js +57 -46
- package/dist/infra/cipher/system-prompt/contributor-schemas.d.ts +8 -8
- package/dist/infra/cipher/system-prompt/schemas.d.ts +5 -5
- package/dist/infra/cipher/tools/implementations/read-file-tool.js +24 -4
- package/dist/infra/connectors/connector-manager.js +2 -0
- package/dist/infra/connectors/hook/hook-connector.d.ts +1 -1
- package/dist/infra/connectors/hook/hook-connector.js +3 -3
- package/dist/infra/connectors/mcp/mcp-connector.d.ts +1 -1
- package/dist/infra/connectors/mcp/mcp-connector.js +4 -4
- package/dist/infra/connectors/rules/rules-connector-config.d.ts +4 -0
- package/dist/infra/connectors/rules/rules-connector-config.js +4 -0
- package/dist/infra/connectors/rules/rules-connector.d.ts +1 -1
- package/dist/infra/connectors/rules/rules-connector.js +4 -4
- package/dist/infra/connectors/shared/template-service.js +4 -0
- package/dist/infra/connectors/skill/index.d.ts +1 -0
- package/dist/infra/connectors/skill/index.js +1 -0
- package/dist/infra/connectors/skill/skill-connector-config.d.ts +45 -0
- package/dist/infra/connectors/skill/skill-connector-config.js +26 -0
- package/dist/infra/connectors/skill/skill-connector.d.ts +39 -0
- package/dist/infra/connectors/skill/skill-connector.js +160 -0
- package/dist/infra/connectors/skill/skill-content-loader.d.ts +18 -0
- package/dist/infra/connectors/skill/skill-content-loader.js +33 -0
- package/dist/infra/file/fs-file-service.d.ts +7 -0
- package/dist/infra/file/fs-file-service.js +15 -1
- package/dist/infra/mcp/tools/brv-curate-tool.d.ts +10 -4
- package/dist/infra/mcp/tools/brv-curate-tool.js +9 -4
- package/dist/infra/mcp/tools/task-result-waiter.js +8 -0
- package/dist/infra/process/agent-worker.js +30 -14
- package/dist/infra/process/task-queue-manager.d.ts +23 -34
- package/dist/infra/process/task-queue-manager.js +57 -118
- package/dist/infra/process/transport-handlers.js +1 -7
- package/dist/infra/repl/commands/connectors-command.js +1 -1
- package/dist/infra/repl/commands/space/switch-command.js +0 -2
- package/dist/infra/usecase/connectors-use-case.js +8 -2
- package/dist/infra/usecase/curate-use-case.js +10 -4
- package/dist/infra/usecase/init-use-case.js +1 -1
- package/dist/infra/usecase/reset-use-case.d.ts +1 -0
- package/dist/infra/usecase/reset-use-case.js +4 -1
- package/dist/infra/usecase/space-switch-use-case.d.ts +0 -10
- package/dist/infra/usecase/space-switch-use-case.js +7 -37
- package/dist/{commands → oclif/commands}/curate.d.ts +1 -1
- package/dist/{commands → oclif/commands}/curate.js +6 -6
- package/dist/{commands → oclif/commands}/hook-prompt-submit.d.ts +1 -1
- package/dist/{commands → oclif/commands}/hook-prompt-submit.js +3 -3
- package/dist/{commands → oclif/commands}/main.js +10 -10
- package/dist/{commands → oclif/commands}/mcp.js +2 -2
- package/dist/{commands → oclif/commands}/query.d.ts +1 -1
- package/dist/{commands → oclif/commands}/query.js +6 -6
- package/dist/{commands → oclif/commands}/status.d.ts +1 -1
- package/dist/{commands → oclif/commands}/status.js +8 -8
- package/dist/oclif/constants.d.ts +11 -0
- package/dist/oclif/constants.js +11 -0
- package/dist/{hooks → oclif/hooks}/init/welcome.js +4 -17
- package/dist/{hooks → oclif/hooks}/prerun/validate-brv-config-version.d.ts +1 -1
- package/dist/{hooks → oclif/hooks}/prerun/validate-brv-config-version.js +2 -2
- package/dist/resources/prompts/curate.yml +1 -0
- package/dist/resources/tools/read_file.txt +5 -2
- package/dist/templates/skill/SKILL.md +91 -0
- package/dist/templates/skill/TROUBLESHOOTING.md +50 -0
- package/dist/templates/skill/WORKFLOWS.md +229 -0
- package/dist/utils/file-validator.js +8 -4
- package/dist/utils/type-guards.d.ts +11 -0
- package/dist/utils/type-guards.js +13 -0
- package/oclif.manifest.json +7 -53
- package/package.json +12 -10
- package/dist/commands/watch.d.ts +0 -25
- package/dist/commands/watch.js +0 -175
- package/dist/core/interfaces/cipher/i-coding-agent-log-parser.d.ts +0 -20
- package/dist/core/interfaces/cipher/i-coding-agent-log-parser.js +0 -1
- package/dist/core/interfaces/cipher/i-coding-agent-log-watcher.d.ts +0 -31
- package/dist/core/interfaces/cipher/i-coding-agent-log-watcher.js +0 -1
- package/dist/core/interfaces/i-file-watcher-service.d.ts +0 -41
- package/dist/core/interfaces/i-file-watcher-service.js +0 -1
- package/dist/core/interfaces/parser/i-clean-parser-service.d.ts +0 -18
- package/dist/core/interfaces/parser/i-clean-parser-service.js +0 -1
- package/dist/core/interfaces/parser/i-raw-parser-service.d.ts +0 -17
- package/dist/core/interfaces/parser/i-raw-parser-service.js +0 -1
- package/dist/core/interfaces/parser/i-session-normalizer.d.ts +0 -56
- package/dist/core/interfaces/parser/i-session-normalizer.js +0 -1
- package/dist/infra/cipher/parsers/coding-agent-log-parser.d.ts +0 -24
- package/dist/infra/cipher/parsers/coding-agent-log-parser.js +0 -51
- package/dist/infra/cipher/watcher/coding-agent-log-watcher.d.ts +0 -14
- package/dist/infra/cipher/watcher/coding-agent-log-watcher.js +0 -55
- package/dist/infra/parsers/clean/clean-claude-service.d.ts +0 -111
- package/dist/infra/parsers/clean/clean-claude-service.js +0 -271
- package/dist/infra/parsers/clean/clean-codex-service.d.ts +0 -231
- package/dist/infra/parsers/clean/clean-codex-service.js +0 -534
- package/dist/infra/parsers/clean/clean-copilot-service.d.ts +0 -255
- package/dist/infra/parsers/clean/clean-copilot-service.js +0 -729
- package/dist/infra/parsers/clean/clean-cursor-service.d.ts +0 -161
- package/dist/infra/parsers/clean/clean-cursor-service.js +0 -432
- package/dist/infra/parsers/clean/clean-parser-service-factory.d.ts +0 -54
- package/dist/infra/parsers/clean/clean-parser-service-factory.js +0 -80
- package/dist/infra/parsers/clean/shared.d.ts +0 -84
- package/dist/infra/parsers/clean/shared.js +0 -273
- package/dist/infra/parsers/raw/raw-claude-service.d.ts +0 -195
- package/dist/infra/parsers/raw/raw-claude-service.js +0 -548
- package/dist/infra/parsers/raw/raw-codex-service.d.ts +0 -313
- package/dist/infra/parsers/raw/raw-codex-service.js +0 -782
- package/dist/infra/parsers/raw/raw-copilot-service.d.ts +0 -196
- package/dist/infra/parsers/raw/raw-copilot-service.js +0 -558
- package/dist/infra/parsers/raw/raw-cursor-service.d.ts +0 -316
- package/dist/infra/parsers/raw/raw-cursor-service.js +0 -818
- package/dist/infra/parsers/raw/raw-parser-service-factory.d.ts +0 -54
- package/dist/infra/parsers/raw/raw-parser-service-factory.js +0 -81
- package/dist/infra/process/constants.d.ts +0 -1
- package/dist/infra/process/constants.js +0 -1
- package/dist/infra/watcher/file-watcher-service.d.ts +0 -10
- package/dist/infra/watcher/file-watcher-service.js +0 -81
- /package/dist/{commands → oclif/commands}/main.d.ts +0 -0
- /package/dist/{commands → oclif/commands}/mcp.d.ts +0 -0
- /package/dist/{hooks → oclif/hooks}/command_not_found/handle-invalid-commands.d.ts +0 -0
- /package/dist/{hooks → oclif/hooks}/command_not_found/handle-invalid-commands.js +0 -0
- /package/dist/{hooks → oclif/hooks}/error/clean-errors.d.ts +0 -0
- /package/dist/{hooks → oclif/hooks}/error/clean-errors.js +0 -0
- /package/dist/{hooks → oclif/hooks}/init/update-notifier.d.ts +0 -0
- /package/dist/{hooks → oclif/hooks}/init/update-notifier.js +0 -0
- /package/dist/{hooks → oclif/hooks}/init/welcome.d.ts +0 -0
|
@@ -19,7 +19,7 @@ import { TomlMcpConfigWriter } from './toml-mcp-config-writer.js';
|
|
|
19
19
|
* - Safe uninstall: Only removes ByteRover's MCP server entry and rule content
|
|
20
20
|
*/
|
|
21
21
|
export class McpConnector {
|
|
22
|
-
|
|
22
|
+
connectorType = 'mcp';
|
|
23
23
|
fileService;
|
|
24
24
|
projectRoot;
|
|
25
25
|
ruleFileManager;
|
|
@@ -34,7 +34,7 @@ export class McpConnector {
|
|
|
34
34
|
projectRoot: options.projectRoot,
|
|
35
35
|
});
|
|
36
36
|
this.supportedAgents = Object.entries(AGENT_CONNECTOR_CONFIG)
|
|
37
|
-
.filter(([_, config]) => config.supported.includes(this.
|
|
37
|
+
.filter(([_, config]) => config.supported.includes(this.connectorType))
|
|
38
38
|
.map(([agent]) => agent);
|
|
39
39
|
}
|
|
40
40
|
getConfigPath(agent) {
|
|
@@ -66,7 +66,7 @@ export class McpConnector {
|
|
|
66
66
|
return this.installAutomatic(agent, config);
|
|
67
67
|
}
|
|
68
68
|
isSupported(agent) {
|
|
69
|
-
return agent in MCP_CONNECTOR_CONFIGS && AGENT_CONNECTOR_CONFIG[agent].supported.includes(this.
|
|
69
|
+
return agent in MCP_CONNECTOR_CONFIGS && AGENT_CONNECTOR_CONFIG[agent].supported.includes(this.connectorType);
|
|
70
70
|
}
|
|
71
71
|
async status(agent) {
|
|
72
72
|
if (!this.isSupported(agent)) {
|
|
@@ -254,7 +254,7 @@ export class McpConnector {
|
|
|
254
254
|
if (!rulesConfig) {
|
|
255
255
|
return;
|
|
256
256
|
}
|
|
257
|
-
const ruleContent = await this.templateService.generateRuleContent(agent, this.
|
|
257
|
+
const ruleContent = await this.templateService.generateRuleContent(agent, this.connectorType);
|
|
258
258
|
await this.ruleFileManager.install(rulesConfig.filePath, rulesConfig.writeMode, ruleContent);
|
|
259
259
|
}
|
|
260
260
|
/**
|
|
@@ -20,6 +20,10 @@ export declare const RULES_CONNECTOR_CONFIGS: {
|
|
|
20
20
|
readonly filePath: "AGENTS.md";
|
|
21
21
|
readonly writeMode: "append";
|
|
22
22
|
};
|
|
23
|
+
readonly Antigravity: {
|
|
24
|
+
readonly filePath: ".agent/rules/agent-context.md";
|
|
25
|
+
readonly writeMode: "overwrite";
|
|
26
|
+
};
|
|
23
27
|
readonly 'Augment Code': {
|
|
24
28
|
readonly filePath: ".augment/rules/agent-context.md";
|
|
25
29
|
readonly writeMode: "overwrite";
|
|
@@ -6,6 +6,10 @@ export const RULES_CONNECTOR_CONFIGS = {
|
|
|
6
6
|
filePath: 'AGENTS.md',
|
|
7
7
|
writeMode: 'append',
|
|
8
8
|
},
|
|
9
|
+
Antigravity: {
|
|
10
|
+
filePath: '.agent/rules/agent-context.md',
|
|
11
|
+
writeMode: 'overwrite',
|
|
12
|
+
},
|
|
9
13
|
'Augment Code': {
|
|
10
14
|
filePath: '.augment/rules/agent-context.md',
|
|
11
15
|
writeMode: 'overwrite',
|
|
@@ -17,7 +17,7 @@ type RulesConnectorOptions = {
|
|
|
17
17
|
* Manages the installation, uninstallation, and status of rule files.
|
|
18
18
|
*/
|
|
19
19
|
export declare class RulesConnector implements IConnector {
|
|
20
|
-
readonly
|
|
20
|
+
readonly connectorType: ConnectorType;
|
|
21
21
|
private readonly fileService;
|
|
22
22
|
private readonly projectRoot;
|
|
23
23
|
private readonly ruleFileManager;
|
|
@@ -7,7 +7,7 @@ import { RULES_CONNECTOR_CONFIGS } from './rules-connector-config.js';
|
|
|
7
7
|
* Manages the installation, uninstallation, and status of rule files.
|
|
8
8
|
*/
|
|
9
9
|
export class RulesConnector {
|
|
10
|
-
|
|
10
|
+
connectorType = 'rules';
|
|
11
11
|
fileService;
|
|
12
12
|
projectRoot;
|
|
13
13
|
ruleFileManager;
|
|
@@ -22,7 +22,7 @@ export class RulesConnector {
|
|
|
22
22
|
projectRoot: options.projectRoot,
|
|
23
23
|
});
|
|
24
24
|
this.supportedAgents = Object.entries(AGENT_CONNECTOR_CONFIG)
|
|
25
|
-
.filter(([_, config]) => config.supported.includes(this.
|
|
25
|
+
.filter(([_, config]) => config.supported.includes(this.connectorType))
|
|
26
26
|
.map(([agent]) => agent);
|
|
27
27
|
}
|
|
28
28
|
getConfigPath(agent) {
|
|
@@ -34,7 +34,7 @@ export class RulesConnector {
|
|
|
34
34
|
async install(agent) {
|
|
35
35
|
const config = RULES_CONNECTOR_CONFIGS[agent];
|
|
36
36
|
try {
|
|
37
|
-
const ruleContent = await this.templateService.generateRuleContent(agent, this.
|
|
37
|
+
const ruleContent = await this.templateService.generateRuleContent(agent, this.connectorType);
|
|
38
38
|
// Write the rule content to the file
|
|
39
39
|
await this.ruleFileManager.install(config.filePath, config.writeMode, ruleContent);
|
|
40
40
|
return {
|
|
@@ -54,7 +54,7 @@ export class RulesConnector {
|
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
isSupported(agent) {
|
|
57
|
-
return AGENT_CONNECTOR_CONFIG[agent].supported.includes(this.
|
|
57
|
+
return AGENT_CONNECTOR_CONFIG[agent].supported.includes(this.connectorType);
|
|
58
58
|
}
|
|
59
59
|
async status(agent) {
|
|
60
60
|
const config = RULES_CONNECTOR_CONFIGS[agent];
|
|
@@ -8,6 +8,10 @@ import { BRV_RULE_MARKERS, BRV_RULE_TAG } from './constants.js';
|
|
|
8
8
|
* @returns The wrapped content with boundary markers.
|
|
9
9
|
*/
|
|
10
10
|
const wrapContentWithBoundaryMarkers = (content, agent, header) => {
|
|
11
|
+
if (agent === 'Windsurf' && header !== '') {
|
|
12
|
+
const parts = [header, BRV_RULE_MARKERS.START, content, '---', `${BRV_RULE_TAG} ${agent}`, BRV_RULE_MARKERS.END];
|
|
13
|
+
return parts.join('\n');
|
|
14
|
+
}
|
|
11
15
|
const parts = [BRV_RULE_MARKERS.START, header, content, '---', `${BRV_RULE_TAG} ${agent}`, BRV_RULE_MARKERS.END];
|
|
12
16
|
return parts.join('\n');
|
|
13
17
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { SkillConnector } from './skill-connector.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { SkillConnector } from './skill-connector.js';
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scope for skill connector configuration.
|
|
3
|
+
* - 'project': Path relative to project root
|
|
4
|
+
* - 'global': Path relative to user home directory
|
|
5
|
+
*/
|
|
6
|
+
export type SkillConfigScope = 'global' | 'project';
|
|
7
|
+
/**
|
|
8
|
+
* Configuration for agent-specific skill file directories.
|
|
9
|
+
*/
|
|
10
|
+
export type SkillConnectorConfig = {
|
|
11
|
+
/** Base directory for skill files (relative to scope root) */
|
|
12
|
+
basePath: string;
|
|
13
|
+
/** Whether path is relative to project root or home dir */
|
|
14
|
+
scope: SkillConfigScope;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Agent-specific skill connector configurations.
|
|
18
|
+
* Maps each supported agent to its skill directory path and scope.
|
|
19
|
+
*/
|
|
20
|
+
export declare const SKILL_CONNECTOR_CONFIGS: {
|
|
21
|
+
readonly 'Claude Code': {
|
|
22
|
+
readonly basePath: ".claude/skills/byterover";
|
|
23
|
+
readonly scope: "project";
|
|
24
|
+
};
|
|
25
|
+
readonly Codex: {
|
|
26
|
+
readonly basePath: ".codex/skills/byterover";
|
|
27
|
+
readonly scope: "global";
|
|
28
|
+
};
|
|
29
|
+
readonly Cursor: {
|
|
30
|
+
readonly basePath: ".cursor/skills/byterover";
|
|
31
|
+
readonly scope: "project";
|
|
32
|
+
};
|
|
33
|
+
readonly 'Github Copilot': {
|
|
34
|
+
readonly basePath: ".github/skills/byterover";
|
|
35
|
+
readonly scope: "project";
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Type representing agents that have skill connector support.
|
|
40
|
+
*/
|
|
41
|
+
export type SkillSupportedAgent = keyof typeof SKILL_CONNECTOR_CONFIGS;
|
|
42
|
+
/**
|
|
43
|
+
* Names of the skill files written by the skill connector.
|
|
44
|
+
*/
|
|
45
|
+
export declare const SKILL_FILE_NAMES: readonly ["SKILL.md", "TROUBLESHOOTING.md", "WORKFLOWS.md"];
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent-specific skill connector configurations.
|
|
3
|
+
* Maps each supported agent to its skill directory path and scope.
|
|
4
|
+
*/
|
|
5
|
+
export const SKILL_CONNECTOR_CONFIGS = {
|
|
6
|
+
'Claude Code': {
|
|
7
|
+
basePath: '.claude/skills/byterover',
|
|
8
|
+
scope: 'project',
|
|
9
|
+
},
|
|
10
|
+
Codex: {
|
|
11
|
+
basePath: '.codex/skills/byterover',
|
|
12
|
+
scope: 'global',
|
|
13
|
+
},
|
|
14
|
+
Cursor: {
|
|
15
|
+
basePath: '.cursor/skills/byterover',
|
|
16
|
+
scope: 'project',
|
|
17
|
+
},
|
|
18
|
+
'Github Copilot': {
|
|
19
|
+
basePath: '.github/skills/byterover',
|
|
20
|
+
scope: 'project',
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Names of the skill files written by the skill connector.
|
|
25
|
+
*/
|
|
26
|
+
export const SKILL_FILE_NAMES = ['SKILL.md', 'TROUBLESHOOTING.md', 'WORKFLOWS.md'];
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Agent } from '../../../core/domain/entities/agent.js';
|
|
2
|
+
import type { ConnectorType } from '../../../core/domain/entities/connector-type.js';
|
|
3
|
+
import type { ConnectorInstallResult, ConnectorStatus, ConnectorUninstallResult } from '../../../core/interfaces/connectors/connector-types.js';
|
|
4
|
+
import type { IConnector } from '../../../core/interfaces/connectors/i-connector.js';
|
|
5
|
+
import type { IFileService } from '../../../core/interfaces/i-file-service.js';
|
|
6
|
+
import type { SkillSupportedAgent } from './skill-connector-config.js';
|
|
7
|
+
/**
|
|
8
|
+
* Options for constructing SkillConnector.
|
|
9
|
+
*/
|
|
10
|
+
type SkillConnectorOptions = {
|
|
11
|
+
fileService: IFileService;
|
|
12
|
+
projectRoot: string;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Connector that integrates BRV with coding agents via skill files.
|
|
16
|
+
* Writes static markdown files (SKILL.md, TROUBLESHOOTING.md, WORKFLOWS.md)
|
|
17
|
+
* into an agent-specific subdirectory.
|
|
18
|
+
*/
|
|
19
|
+
export declare class SkillConnector implements IConnector {
|
|
20
|
+
readonly connectorType: ConnectorType;
|
|
21
|
+
private readonly contentLoader;
|
|
22
|
+
private readonly fileService;
|
|
23
|
+
private readonly projectRoot;
|
|
24
|
+
private readonly supportedAgents;
|
|
25
|
+
constructor(options: SkillConnectorOptions);
|
|
26
|
+
getConfigPath(agent: Agent): string;
|
|
27
|
+
getSupportedAgents(): Agent[];
|
|
28
|
+
install(agent: Agent): Promise<ConnectorInstallResult>;
|
|
29
|
+
isSupported(agent: Agent): agent is SkillSupportedAgent;
|
|
30
|
+
status(agent: Agent): Promise<ConnectorStatus>;
|
|
31
|
+
uninstall(agent: Agent): Promise<ConnectorUninstallResult>;
|
|
32
|
+
/**
|
|
33
|
+
* Get the full (absolute) path for skill file operations.
|
|
34
|
+
* - Project scope: relative to project root
|
|
35
|
+
* - Global scope: relative to os.homedir()
|
|
36
|
+
*/
|
|
37
|
+
private getFullPath;
|
|
38
|
+
}
|
|
39
|
+
export {};
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { AGENT_CONNECTOR_CONFIG } from '../../../core/domain/entities/agent.js';
|
|
4
|
+
import { SKILL_CONNECTOR_CONFIGS, SKILL_FILE_NAMES } from './skill-connector-config.js';
|
|
5
|
+
import { SkillContentLoader } from './skill-content-loader.js';
|
|
6
|
+
/**
|
|
7
|
+
* Connector that integrates BRV with coding agents via skill files.
|
|
8
|
+
* Writes static markdown files (SKILL.md, TROUBLESHOOTING.md, WORKFLOWS.md)
|
|
9
|
+
* into an agent-specific subdirectory.
|
|
10
|
+
*/
|
|
11
|
+
export class SkillConnector {
|
|
12
|
+
connectorType = 'skill';
|
|
13
|
+
contentLoader;
|
|
14
|
+
fileService;
|
|
15
|
+
projectRoot;
|
|
16
|
+
supportedAgents;
|
|
17
|
+
constructor(options) {
|
|
18
|
+
this.fileService = options.fileService;
|
|
19
|
+
this.projectRoot = options.projectRoot;
|
|
20
|
+
this.contentLoader = new SkillContentLoader(options.fileService);
|
|
21
|
+
this.supportedAgents = Object.entries(AGENT_CONNECTOR_CONFIG)
|
|
22
|
+
.filter(([_, config]) => config.supported.includes(this.connectorType))
|
|
23
|
+
.map(([agent]) => agent);
|
|
24
|
+
}
|
|
25
|
+
getConfigPath(agent) {
|
|
26
|
+
if (!this.isSupported(agent)) {
|
|
27
|
+
throw new Error(`Skill connector does not support agent: ${agent}`);
|
|
28
|
+
}
|
|
29
|
+
return SKILL_CONNECTOR_CONFIGS[agent].basePath;
|
|
30
|
+
}
|
|
31
|
+
getSupportedAgents() {
|
|
32
|
+
return this.supportedAgents;
|
|
33
|
+
}
|
|
34
|
+
async install(agent) {
|
|
35
|
+
if (!this.isSupported(agent)) {
|
|
36
|
+
return {
|
|
37
|
+
alreadyInstalled: false,
|
|
38
|
+
configPath: '',
|
|
39
|
+
message: `Skill connector does not support agent: ${agent}`,
|
|
40
|
+
success: false,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
const config = SKILL_CONNECTOR_CONFIGS[agent];
|
|
44
|
+
const fullDir = this.getFullPath(config.basePath, config.scope);
|
|
45
|
+
try {
|
|
46
|
+
// Check if already installed
|
|
47
|
+
const skillFilePath = path.join(fullDir, SKILL_FILE_NAMES[0]);
|
|
48
|
+
if (await this.fileService.exists(skillFilePath)) {
|
|
49
|
+
return {
|
|
50
|
+
alreadyInstalled: true,
|
|
51
|
+
configPath: config.basePath,
|
|
52
|
+
message: `Skill connector is already installed for ${agent}`,
|
|
53
|
+
success: true,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
// Write all skill files
|
|
57
|
+
await Promise.all(SKILL_FILE_NAMES.map(async (fileName) => {
|
|
58
|
+
const content = await this.contentLoader.loadSkillFile(fileName);
|
|
59
|
+
const filePath = path.join(fullDir, fileName);
|
|
60
|
+
await this.fileService.write(content, filePath, 'overwrite');
|
|
61
|
+
}));
|
|
62
|
+
return {
|
|
63
|
+
alreadyInstalled: false,
|
|
64
|
+
configPath: fullDir,
|
|
65
|
+
message: `Skill connector installed for ${agent} (created ${fullDir}/)`,
|
|
66
|
+
success: true,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
return {
|
|
71
|
+
alreadyInstalled: false,
|
|
72
|
+
configPath: config.basePath,
|
|
73
|
+
message: `Failed to install skill connector for ${agent}: ${error instanceof Error ? error.message : String(error)}`,
|
|
74
|
+
success: false,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
isSupported(agent) {
|
|
79
|
+
return agent in SKILL_CONNECTOR_CONFIGS && AGENT_CONNECTOR_CONFIG[agent].supported.includes(this.connectorType);
|
|
80
|
+
}
|
|
81
|
+
async status(agent) {
|
|
82
|
+
if (!this.isSupported(agent)) {
|
|
83
|
+
return {
|
|
84
|
+
configExists: false,
|
|
85
|
+
configPath: '',
|
|
86
|
+
error: `Skill connector does not support agent: ${agent}`,
|
|
87
|
+
installed: false,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
const config = SKILL_CONNECTOR_CONFIGS[agent];
|
|
91
|
+
const fullDir = this.getFullPath(config.basePath, config.scope);
|
|
92
|
+
try {
|
|
93
|
+
const skillFilePath = path.join(fullDir, SKILL_FILE_NAMES[0]);
|
|
94
|
+
const exists = await this.fileService.exists(skillFilePath);
|
|
95
|
+
return {
|
|
96
|
+
configExists: exists,
|
|
97
|
+
configPath: config.basePath,
|
|
98
|
+
installed: exists,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
return {
|
|
103
|
+
configExists: false,
|
|
104
|
+
configPath: config.basePath,
|
|
105
|
+
error: error instanceof Error ? error.message : String(error),
|
|
106
|
+
installed: false,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
async uninstall(agent) {
|
|
111
|
+
if (!this.isSupported(agent)) {
|
|
112
|
+
return {
|
|
113
|
+
configPath: '',
|
|
114
|
+
message: `Skill connector does not support agent: ${agent}`,
|
|
115
|
+
success: false,
|
|
116
|
+
wasInstalled: false,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
const config = SKILL_CONNECTOR_CONFIGS[agent];
|
|
120
|
+
const fullDir = this.getFullPath(config.basePath, config.scope);
|
|
121
|
+
try {
|
|
122
|
+
const skillFilePath = path.join(fullDir, SKILL_FILE_NAMES[0]);
|
|
123
|
+
const exists = await this.fileService.exists(skillFilePath);
|
|
124
|
+
if (!exists) {
|
|
125
|
+
return {
|
|
126
|
+
configPath: config.basePath,
|
|
127
|
+
message: `Skill connector is not installed for ${agent}`,
|
|
128
|
+
success: true,
|
|
129
|
+
wasInstalled: false,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
await this.fileService.deleteDirectory(fullDir);
|
|
133
|
+
return {
|
|
134
|
+
configPath: config.basePath,
|
|
135
|
+
message: `Skill connector uninstalled for ${agent}`,
|
|
136
|
+
success: true,
|
|
137
|
+
wasInstalled: true,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
return {
|
|
142
|
+
configPath: config.basePath,
|
|
143
|
+
message: `Failed to uninstall skill connector for ${agent}: ${error instanceof Error ? error.message : String(error)}`,
|
|
144
|
+
success: false,
|
|
145
|
+
wasInstalled: true,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Get the full (absolute) path for skill file operations.
|
|
151
|
+
* - Project scope: relative to project root
|
|
152
|
+
* - Global scope: relative to os.homedir()
|
|
153
|
+
*/
|
|
154
|
+
getFullPath(basePath, scope) {
|
|
155
|
+
if (scope === 'global') {
|
|
156
|
+
return path.join(os.homedir(), basePath);
|
|
157
|
+
}
|
|
158
|
+
return path.join(this.projectRoot, basePath);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { IFileService } from '../../../core/interfaces/i-file-service.js';
|
|
2
|
+
/**
|
|
3
|
+
* Loads static skill markdown files from the templates/skill/ directory.
|
|
4
|
+
* Uses the same import.meta.url path resolution pattern as FsTemplateLoader.
|
|
5
|
+
*/
|
|
6
|
+
export declare class SkillContentLoader {
|
|
7
|
+
private readonly fileService;
|
|
8
|
+
private readonly skillDir;
|
|
9
|
+
constructor(fileService: IFileService);
|
|
10
|
+
/**
|
|
11
|
+
* Loads a skill file by name from the templates/skill/ directory.
|
|
12
|
+
*
|
|
13
|
+
* @param fileName - Name of the skill file (e.g., 'SKILL.md')
|
|
14
|
+
* @returns Promise resolving to the file content
|
|
15
|
+
* @throws Error if the file cannot be read
|
|
16
|
+
*/
|
|
17
|
+
loadSkillFile(fileName: string): Promise<string>;
|
|
18
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
/**
|
|
4
|
+
* Loads static skill markdown files from the templates/skill/ directory.
|
|
5
|
+
* Uses the same import.meta.url path resolution pattern as FsTemplateLoader.
|
|
6
|
+
*/
|
|
7
|
+
export class SkillContentLoader {
|
|
8
|
+
fileService;
|
|
9
|
+
skillDir;
|
|
10
|
+
constructor(fileService) {
|
|
11
|
+
this.fileService = fileService;
|
|
12
|
+
const currentFilePath = fileURLToPath(import.meta.url);
|
|
13
|
+
const currentDir = path.dirname(currentFilePath);
|
|
14
|
+
// Navigate from src/infra/connectors/skill/ to src/templates/skill/
|
|
15
|
+
this.skillDir = path.join(currentDir, '..', '..', '..', 'templates', 'skill');
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Loads a skill file by name from the templates/skill/ directory.
|
|
19
|
+
*
|
|
20
|
+
* @param fileName - Name of the skill file (e.g., 'SKILL.md')
|
|
21
|
+
* @returns Promise resolving to the file content
|
|
22
|
+
* @throws Error if the file cannot be read
|
|
23
|
+
*/
|
|
24
|
+
async loadSkillFile(fileName) {
|
|
25
|
+
const fullPath = path.join(this.skillDir, fileName);
|
|
26
|
+
try {
|
|
27
|
+
return await this.fileService.read(fullPath);
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
throw new Error(`Failed to load skill file '${fileName}': ${error instanceof Error ? error.message : String(error)}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -11,6 +11,13 @@ export declare class FsFileService implements IFileService {
|
|
|
11
11
|
* @returns A promise that resolves when the file has been deleted.
|
|
12
12
|
*/
|
|
13
13
|
delete(filePath: string): Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* Deletes a directory and all its contents recursively.
|
|
16
|
+
*
|
|
17
|
+
* @param dirPath The path to the directory to delete.
|
|
18
|
+
* @returns A promise that resolves when the directory has been deleted.
|
|
19
|
+
*/
|
|
20
|
+
deleteDirectory(dirPath: string): Promise<void>;
|
|
14
21
|
/**
|
|
15
22
|
* Checks if a file exists at the specified path.
|
|
16
23
|
*
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { access, appendFile, copyFile, mkdir, readFile, unlink, writeFile } from 'node:fs/promises';
|
|
1
|
+
import { access, appendFile, copyFile, mkdir, readFile, rm, unlink, writeFile } from 'node:fs/promises';
|
|
2
2
|
import { dirname } from 'node:path';
|
|
3
3
|
/**
|
|
4
4
|
* File service implementation using Node.js fs module.
|
|
@@ -30,6 +30,20 @@ export class FsFileService {
|
|
|
30
30
|
throw new Error(`Failed to delete file '${filePath}': ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Deletes a directory and all its contents recursively.
|
|
35
|
+
*
|
|
36
|
+
* @param dirPath The path to the directory to delete.
|
|
37
|
+
* @returns A promise that resolves when the directory has been deleted.
|
|
38
|
+
*/
|
|
39
|
+
async deleteDirectory(dirPath) {
|
|
40
|
+
try {
|
|
41
|
+
await rm(dirPath, { force: true, recursive: true });
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
throw new Error(`Failed to delete directory '${dirPath}': ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
33
47
|
/**
|
|
34
48
|
* Checks if a file exists at the specified path.
|
|
35
49
|
*
|
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import type { ITransportClient } from '../../../core/interfaces/transport/index.js';
|
|
4
|
-
export declare const BrvCurateInputSchema: z.ZodObject<{
|
|
5
|
-
context: z.ZodString
|
|
4
|
+
export declare const BrvCurateInputSchema: z.ZodEffects<z.ZodObject<{
|
|
5
|
+
context: z.ZodOptional<z.ZodString>;
|
|
6
6
|
files: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
7
7
|
}, "strip", z.ZodTypeAny, {
|
|
8
|
-
context
|
|
8
|
+
context?: string | undefined;
|
|
9
9
|
files?: string[] | undefined;
|
|
10
10
|
}, {
|
|
11
|
-
context
|
|
11
|
+
context?: string | undefined;
|
|
12
|
+
files?: string[] | undefined;
|
|
13
|
+
}>, {
|
|
14
|
+
context?: string | undefined;
|
|
15
|
+
files?: string[] | undefined;
|
|
16
|
+
}, {
|
|
17
|
+
context?: string | undefined;
|
|
12
18
|
files?: string[] | undefined;
|
|
13
19
|
}>;
|
|
14
20
|
/**
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
export const BrvCurateInputSchema = z.object({
|
|
4
|
-
context: z
|
|
4
|
+
context: z
|
|
5
|
+
.string()
|
|
6
|
+
.optional()
|
|
7
|
+
.describe('Knowledge to store: patterns, decisions, errors, or insights about the codebase. Required unless files are provided.'),
|
|
5
8
|
files: z
|
|
6
9
|
.array(z.string())
|
|
7
10
|
.max(5)
|
|
8
11
|
.optional()
|
|
9
|
-
.describe('Optional file paths with critical context to include (max 5 files)'),
|
|
10
|
-
});
|
|
12
|
+
.describe('Optional file paths with critical context to include (max 5 files). Required if context not provided.'),
|
|
13
|
+
}).refine((data) => Boolean(data.context?.trim()) || Boolean(data.files?.length), { message: 'Either context or files must be provided' });
|
|
11
14
|
/**
|
|
12
15
|
* Registers the brv-curate tool with the MCP server.
|
|
13
16
|
*
|
|
@@ -41,9 +44,11 @@ export function registerBrvCurateTool(server, getClient, getWorkingDirectory) {
|
|
|
41
44
|
try {
|
|
42
45
|
const taskId = randomUUID();
|
|
43
46
|
// Create task via transport (same pattern as brv curate command)
|
|
47
|
+
// Use provided context, or empty string for file-only mode
|
|
48
|
+
const resolvedContent = context?.trim() ? context : '';
|
|
44
49
|
await client.request('task:create', {
|
|
45
50
|
clientCwd: getWorkingDirectory(),
|
|
46
|
-
content:
|
|
51
|
+
content: resolvedContent,
|
|
47
52
|
taskId,
|
|
48
53
|
type: 'curate',
|
|
49
54
|
...(files?.length ? { files } : {}),
|
|
@@ -29,6 +29,14 @@ export async function waitForTaskResult(client, taskId, timeoutMs = 120_000) {
|
|
|
29
29
|
}, timeoutMs);
|
|
30
30
|
// Set up all event listeners
|
|
31
31
|
unsubscribers.push(
|
|
32
|
+
// Listen for connection state changes - fail fast on disconnect
|
|
33
|
+
client.onStateChange((state) => {
|
|
34
|
+
if (state === 'disconnected' && !completed) {
|
|
35
|
+
completed = true;
|
|
36
|
+
cleanup();
|
|
37
|
+
reject(new Error('Connection lost to ByteRover instance'));
|
|
38
|
+
}
|
|
39
|
+
}),
|
|
32
40
|
// Listen for LLM response content
|
|
33
41
|
client.on('llmservice:response', (payload) => {
|
|
34
42
|
if (payload.taskId === taskId && payload.content) {
|