byterover-cli 0.1.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 +781 -0
- package/bin/dev.cmd +4 -0
- package/bin/dev.js +7 -0
- package/bin/run.cmd +4 -0
- package/bin/run.js +7 -0
- package/dist/commands/add.d.ts +60 -0
- package/dist/commands/add.js +230 -0
- package/dist/commands/clear.d.ts +13 -0
- package/dist/commands/clear.js +57 -0
- package/dist/commands/complete.d.ts +108 -0
- package/dist/commands/complete.js +340 -0
- package/dist/commands/gen-rules.d.ts +26 -0
- package/dist/commands/gen-rules.js +89 -0
- package/dist/commands/init.d.ts +24 -0
- package/dist/commands/init.js +135 -0
- package/dist/commands/login.d.ts +22 -0
- package/dist/commands/login.js +103 -0
- package/dist/commands/push.d.ts +33 -0
- package/dist/commands/push.js +150 -0
- package/dist/commands/retrieve.d.ts +26 -0
- package/dist/commands/retrieve.js +101 -0
- package/dist/commands/space/list.d.ts +22 -0
- package/dist/commands/space/list.js +105 -0
- package/dist/commands/space/switch.d.ts +20 -0
- package/dist/commands/space/switch.js +110 -0
- package/dist/commands/status.d.ts +22 -0
- package/dist/commands/status.js +116 -0
- package/dist/config/auth.config.d.ts +32 -0
- package/dist/config/auth.config.js +35 -0
- package/dist/config/environment.d.ts +35 -0
- package/dist/config/environment.js +39 -0
- package/dist/constants.d.ts +11 -0
- package/dist/constants.js +12 -0
- package/dist/core/domain/entities/agent.d.ts +5 -0
- package/dist/core/domain/entities/agent.js +23 -0
- package/dist/core/domain/entities/auth-token.d.ts +43 -0
- package/dist/core/domain/entities/auth-token.js +70 -0
- package/dist/core/domain/entities/br-config.d.ts +25 -0
- package/dist/core/domain/entities/br-config.js +58 -0
- package/dist/core/domain/entities/bullet.d.ts +51 -0
- package/dist/core/domain/entities/bullet.js +94 -0
- package/dist/core/domain/entities/curator-output.d.ts +14 -0
- package/dist/core/domain/entities/curator-output.js +23 -0
- package/dist/core/domain/entities/delta-batch.d.ts +30 -0
- package/dist/core/domain/entities/delta-batch.js +52 -0
- package/dist/core/domain/entities/delta-operation.d.ts +31 -0
- package/dist/core/domain/entities/delta-operation.js +50 -0
- package/dist/core/domain/entities/event.d.ts +8 -0
- package/dist/core/domain/entities/event.js +15 -0
- package/dist/core/domain/entities/executor-output.d.ts +27 -0
- package/dist/core/domain/entities/executor-output.js +33 -0
- package/dist/core/domain/entities/memory.d.ts +55 -0
- package/dist/core/domain/entities/memory.js +90 -0
- package/dist/core/domain/entities/oauth-token-data.d.ts +13 -0
- package/dist/core/domain/entities/oauth-token-data.js +20 -0
- package/dist/core/domain/entities/playbook.d.ts +97 -0
- package/dist/core/domain/entities/playbook.js +275 -0
- package/dist/core/domain/entities/presigned-url.d.ts +9 -0
- package/dist/core/domain/entities/presigned-url.js +18 -0
- package/dist/core/domain/entities/presigned-urls-response.d.ts +10 -0
- package/dist/core/domain/entities/presigned-urls-response.js +18 -0
- package/dist/core/domain/entities/reflector-output.d.ts +38 -0
- package/dist/core/domain/entities/reflector-output.js +44 -0
- package/dist/core/domain/entities/retrieve-result.d.ts +35 -0
- package/dist/core/domain/entities/retrieve-result.js +35 -0
- package/dist/core/domain/entities/space.d.ts +24 -0
- package/dist/core/domain/entities/space.js +52 -0
- package/dist/core/domain/entities/team.d.ts +42 -0
- package/dist/core/domain/entities/team.js +89 -0
- package/dist/core/domain/entities/user.d.ts +20 -0
- package/dist/core/domain/entities/user.js +32 -0
- package/dist/core/domain/errors/ace-error.d.ts +34 -0
- package/dist/core/domain/errors/ace-error.js +53 -0
- package/dist/core/domain/errors/auth-error.d.ts +10 -0
- package/dist/core/domain/errors/auth-error.js +20 -0
- package/dist/core/domain/errors/discovery-error.d.ts +21 -0
- package/dist/core/domain/errors/discovery-error.js +33 -0
- package/dist/core/domain/errors/rule-error.d.ts +6 -0
- package/dist/core/domain/errors/rule-error.js +12 -0
- package/dist/core/interfaces/i-ace-prompt-builder.d.ts +48 -0
- package/dist/core/interfaces/i-ace-prompt-builder.js +1 -0
- package/dist/core/interfaces/i-auth-service.d.ts +35 -0
- package/dist/core/interfaces/i-auth-service.js +1 -0
- package/dist/core/interfaces/i-browser-launcher.d.ts +11 -0
- package/dist/core/interfaces/i-browser-launcher.js +1 -0
- package/dist/core/interfaces/i-bullet-content-store.d.ts +36 -0
- package/dist/core/interfaces/i-bullet-content-store.js +1 -0
- package/dist/core/interfaces/i-callback-handler.d.ts +35 -0
- package/dist/core/interfaces/i-callback-handler.js +1 -0
- package/dist/core/interfaces/i-delta-store.d.ts +15 -0
- package/dist/core/interfaces/i-delta-store.js +1 -0
- package/dist/core/interfaces/i-executor-output-store.d.ts +14 -0
- package/dist/core/interfaces/i-executor-output-store.js +1 -0
- package/dist/core/interfaces/i-file-service.d.ts +34 -0
- package/dist/core/interfaces/i-file-service.js +1 -0
- package/dist/core/interfaces/i-http-client.d.ts +33 -0
- package/dist/core/interfaces/i-http-client.js +1 -0
- package/dist/core/interfaces/i-memory-retrieval-service.d.ts +40 -0
- package/dist/core/interfaces/i-memory-retrieval-service.js +1 -0
- package/dist/core/interfaces/i-memory-storage-service.d.ts +55 -0
- package/dist/core/interfaces/i-memory-storage-service.js +1 -0
- package/dist/core/interfaces/i-oidc-discovery-service.d.ts +20 -0
- package/dist/core/interfaces/i-oidc-discovery-service.js +1 -0
- package/dist/core/interfaces/i-playbook-service.d.ts +69 -0
- package/dist/core/interfaces/i-playbook-service.js +1 -0
- package/dist/core/interfaces/i-playbook-store.d.ts +38 -0
- package/dist/core/interfaces/i-playbook-store.js +1 -0
- package/dist/core/interfaces/i-project-config-store.d.ts +26 -0
- package/dist/core/interfaces/i-project-config-store.js +1 -0
- package/dist/core/interfaces/i-reflection-store.d.ts +21 -0
- package/dist/core/interfaces/i-reflection-store.js +1 -0
- package/dist/core/interfaces/i-rule-template-service.d.ts +17 -0
- package/dist/core/interfaces/i-rule-template-service.js +4 -0
- package/dist/core/interfaces/i-rule-writer-service.d.ts +13 -0
- package/dist/core/interfaces/i-rule-writer-service.js +1 -0
- package/dist/core/interfaces/i-space-service.d.ts +28 -0
- package/dist/core/interfaces/i-space-service.js +1 -0
- package/dist/core/interfaces/i-team-service.d.ts +29 -0
- package/dist/core/interfaces/i-team-service.js +1 -0
- package/dist/core/interfaces/i-template-loader.d.ts +29 -0
- package/dist/core/interfaces/i-template-loader.js +1 -0
- package/dist/core/interfaces/i-token-store.d.ts +22 -0
- package/dist/core/interfaces/i-token-store.js +1 -0
- package/dist/core/interfaces/i-tracking-service.d.ts +21 -0
- package/dist/core/interfaces/i-tracking-service.js +1 -0
- package/dist/core/interfaces/i-user-service.d.ts +14 -0
- package/dist/core/interfaces/i-user-service.js +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/infra/ace/ace-file-utils.d.ts +46 -0
- package/dist/infra/ace/ace-file-utils.js +83 -0
- package/dist/infra/ace/ace-prompt-templates.d.ts +13 -0
- package/dist/infra/ace/ace-prompt-templates.js +177 -0
- package/dist/infra/ace/file-bullet-content-store.d.ts +27 -0
- package/dist/infra/ace/file-bullet-content-store.js +89 -0
- package/dist/infra/ace/file-delta-store.d.ts +9 -0
- package/dist/infra/ace/file-delta-store.js +26 -0
- package/dist/infra/ace/file-executor-output-store.d.ts +9 -0
- package/dist/infra/ace/file-executor-output-store.js +26 -0
- package/dist/infra/ace/file-playbook-store.d.ts +29 -0
- package/dist/infra/ace/file-playbook-store.js +107 -0
- package/dist/infra/ace/file-reflection-store.d.ts +10 -0
- package/dist/infra/ace/file-reflection-store.js +55 -0
- package/dist/infra/auth/oauth-service.d.ts +49 -0
- package/dist/infra/auth/oauth-service.js +126 -0
- package/dist/infra/auth/oidc-discovery-service.d.ts +51 -0
- package/dist/infra/auth/oidc-discovery-service.js +145 -0
- package/dist/infra/browser/system-browser-launcher.d.ts +10 -0
- package/dist/infra/browser/system-browser-launcher.js +18 -0
- package/dist/infra/config/file-config-store.d.ts +21 -0
- package/dist/infra/config/file-config-store.js +57 -0
- package/dist/infra/file/fs-file-service.d.ts +28 -0
- package/dist/infra/file/fs-file-service.js +57 -0
- package/dist/infra/http/authenticated-http-client.d.ts +46 -0
- package/dist/infra/http/authenticated-http-client.js +99 -0
- package/dist/infra/http/callback-handler.d.ts +13 -0
- package/dist/infra/http/callback-handler.js +24 -0
- package/dist/infra/http/callback-server.d.ts +18 -0
- package/dist/infra/http/callback-server.js +93 -0
- package/dist/infra/memory/http-memory-retrieval-service.d.ts +18 -0
- package/dist/infra/memory/http-memory-retrieval-service.js +63 -0
- package/dist/infra/memory/http-memory-storage-service.d.ts +18 -0
- package/dist/infra/memory/http-memory-storage-service.js +67 -0
- package/dist/infra/memory/memory-to-playbook-mapper.d.ts +33 -0
- package/dist/infra/memory/memory-to-playbook-mapper.js +51 -0
- package/dist/infra/playbook/file-playbook-service.d.ts +43 -0
- package/dist/infra/playbook/file-playbook-service.js +133 -0
- package/dist/infra/rule/agent-rule-config.d.ts +19 -0
- package/dist/infra/rule/agent-rule-config.js +77 -0
- package/dist/infra/rule/rule-template-service.d.ts +18 -0
- package/dist/infra/rule/rule-template-service.js +80 -0
- package/dist/infra/rule/rule-writer-service.d.ts +19 -0
- package/dist/infra/rule/rule-writer-service.js +43 -0
- package/dist/infra/space/http-space-service.d.ts +20 -0
- package/dist/infra/space/http-space-service.js +67 -0
- package/dist/infra/storage/keychain-token-store.d.ts +10 -0
- package/dist/infra/storage/keychain-token-store.js +40 -0
- package/dist/infra/team/http-team-service.d.ts +21 -0
- package/dist/infra/team/http-team-service.js +71 -0
- package/dist/infra/template/fs-template-loader.d.ts +33 -0
- package/dist/infra/template/fs-template-loader.js +62 -0
- package/dist/infra/tracking/mixpanel-tracking-service.d.ts +14 -0
- package/dist/infra/tracking/mixpanel-tracking-service.js +44 -0
- package/dist/infra/user/http-user-service.d.ts +12 -0
- package/dist/infra/user/http-user-service.js +26 -0
- package/dist/templates/README.md +103 -0
- package/dist/templates/base.md +3 -0
- package/dist/templates/sections/command-reference.md +141 -0
- package/dist/templates/sections/workflow.md +46 -0
- package/dist/utils/file-helpers.d.ts +15 -0
- package/dist/utils/file-helpers.js +45 -0
- package/oclif.manifest.json +476 -0
- package/package.json +82 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { mkdir } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { ACE_DIR, BR_DIR, DELTAS_DIR, EXECUTOR_OUTPUTS_DIR, REFLECTIONS_DIR } from '../../constants.js';
|
|
4
|
+
import { Playbook } from '../../core/domain/entities/playbook.js';
|
|
5
|
+
import { FilePlaybookStore } from '../ace/file-playbook-store.js';
|
|
6
|
+
/**
|
|
7
|
+
* File-based implementation of IPlaybookService.
|
|
8
|
+
* Provides high-level playbook operations including initialization,
|
|
9
|
+
* bullet management, delta application, and reflection tag processing.
|
|
10
|
+
*/
|
|
11
|
+
export class FilePlaybookService {
|
|
12
|
+
static BULLETS_DIR = 'bullets';
|
|
13
|
+
static SUBDIRS = [REFLECTIONS_DIR, EXECUTOR_OUTPUTS_DIR, DELTAS_DIR, FilePlaybookService.BULLETS_DIR];
|
|
14
|
+
config;
|
|
15
|
+
playbookStore;
|
|
16
|
+
constructor(config = {}, playbookStore = new FilePlaybookStore()) {
|
|
17
|
+
this.config = config;
|
|
18
|
+
this.playbookStore = playbookStore;
|
|
19
|
+
}
|
|
20
|
+
async addOrUpdateBullet(params) {
|
|
21
|
+
// Validate input
|
|
22
|
+
if (!params.section || params.section.trim().length === 0) {
|
|
23
|
+
throw new Error('Section is required');
|
|
24
|
+
}
|
|
25
|
+
if (!params.content || params.content.trim().length === 0) {
|
|
26
|
+
throw new Error('Content is required');
|
|
27
|
+
}
|
|
28
|
+
const directory = params.directory ?? this.config.baseDirectory;
|
|
29
|
+
// Load existing playbook or create new one
|
|
30
|
+
let playbook = await this.playbookStore.load(directory);
|
|
31
|
+
if (!playbook) {
|
|
32
|
+
playbook = new Playbook();
|
|
33
|
+
}
|
|
34
|
+
let bullet;
|
|
35
|
+
// Determine operation type based on bulletId presence
|
|
36
|
+
if (params.bulletId) {
|
|
37
|
+
// UPDATE operation
|
|
38
|
+
const existingBullet = playbook.getBullet(params.bulletId);
|
|
39
|
+
if (!existingBullet) {
|
|
40
|
+
throw new Error(`Bullet with ID '${params.bulletId}' not found`);
|
|
41
|
+
}
|
|
42
|
+
const updatedBullet = playbook.updateBullet(params.bulletId, {
|
|
43
|
+
content: params.content,
|
|
44
|
+
metadata: params.metadata,
|
|
45
|
+
});
|
|
46
|
+
if (!updatedBullet) {
|
|
47
|
+
throw new Error(`Failed to update bullet '${params.bulletId}'`);
|
|
48
|
+
}
|
|
49
|
+
bullet = updatedBullet;
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
// ADD operation
|
|
53
|
+
// Ensure metadata has at least one tag (required by Bullet entity)
|
|
54
|
+
const metadata = params.metadata ?? {
|
|
55
|
+
relatedFiles: [],
|
|
56
|
+
tags: ['manual'],
|
|
57
|
+
timestamp: new Date().toISOString(),
|
|
58
|
+
};
|
|
59
|
+
// If metadata is provided but tags are empty, add default tag
|
|
60
|
+
if (metadata.tags.length === 0) {
|
|
61
|
+
metadata.tags = ['manual'];
|
|
62
|
+
}
|
|
63
|
+
bullet = playbook.addBullet(params.section, params.content, undefined, metadata);
|
|
64
|
+
}
|
|
65
|
+
// Save updated playbook
|
|
66
|
+
await this.playbookStore.save(playbook, directory);
|
|
67
|
+
return bullet;
|
|
68
|
+
}
|
|
69
|
+
async applyDelta(params) {
|
|
70
|
+
const directory = params.directory ?? this.config.baseDirectory;
|
|
71
|
+
// Load existing playbook or create new one
|
|
72
|
+
let playbook = await this.playbookStore.load(directory);
|
|
73
|
+
if (!playbook) {
|
|
74
|
+
playbook = new Playbook();
|
|
75
|
+
}
|
|
76
|
+
// Apply delta operations
|
|
77
|
+
playbook.applyDelta(params.delta);
|
|
78
|
+
// Save updated playbook
|
|
79
|
+
await this.playbookStore.save(playbook, directory);
|
|
80
|
+
return {
|
|
81
|
+
operationsApplied: params.delta.getOperationCount(),
|
|
82
|
+
playbook,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
async applyReflectionTags(params) {
|
|
86
|
+
const directory = params.directory ?? this.config.baseDirectory;
|
|
87
|
+
// Load playbook
|
|
88
|
+
const playbook = await this.playbookStore.load(directory);
|
|
89
|
+
if (!playbook) {
|
|
90
|
+
throw new Error('Playbook not found. Run `br init` to initialize.');
|
|
91
|
+
}
|
|
92
|
+
// Apply tags from reflection
|
|
93
|
+
let tagsApplied = 0;
|
|
94
|
+
for (const bulletTag of params.reflection.bulletTags) {
|
|
95
|
+
const { id, tag } = bulletTag;
|
|
96
|
+
// Check if bullet exists
|
|
97
|
+
const bullet = playbook.getBullet(id);
|
|
98
|
+
if (!bullet) {
|
|
99
|
+
// Skip non-existent bullets (might have been removed)
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
// Add tag to bullet (appends to tags array if not already present)
|
|
103
|
+
const updated = playbook.addTagToBullet(id, tag);
|
|
104
|
+
if (updated) {
|
|
105
|
+
tagsApplied++;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// Save updated playbook
|
|
109
|
+
await this.playbookStore.save(playbook, directory);
|
|
110
|
+
return {
|
|
111
|
+
playbook,
|
|
112
|
+
tagsApplied,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
async initialize(directory) {
|
|
116
|
+
const baseDir = directory ?? this.config.baseDirectory ?? process.cwd();
|
|
117
|
+
const brDir = join(baseDir, BR_DIR);
|
|
118
|
+
const aceDir = join(brDir, ACE_DIR);
|
|
119
|
+
// Create .br/ace/ directory
|
|
120
|
+
await mkdir(aceDir, { recursive: true });
|
|
121
|
+
// Create subdirectories
|
|
122
|
+
await Promise.all(FilePlaybookService.SUBDIRS.map((subdir) => mkdir(join(aceDir, subdir), { recursive: true })));
|
|
123
|
+
// Check if playbook already exists
|
|
124
|
+
const exists = await this.playbookStore.exists(directory ?? this.config.baseDirectory);
|
|
125
|
+
if (exists) {
|
|
126
|
+
throw new Error('Playbook already exists. Use `br clear` to remove it first.');
|
|
127
|
+
}
|
|
128
|
+
// Create empty playbook
|
|
129
|
+
const playbook = new Playbook();
|
|
130
|
+
await this.playbookStore.save(playbook, directory ?? this.config.baseDirectory);
|
|
131
|
+
return join(aceDir, 'playbook.json');
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type Agent } from '../../core/domain/entities/agent.js';
|
|
2
|
+
import { type WriteMode } from '../../core/interfaces/i-file-service.js';
|
|
3
|
+
/**
|
|
4
|
+
* Configuration for agent-specific rule files.
|
|
5
|
+
*/
|
|
6
|
+
export type AgentRuleConfig = {
|
|
7
|
+
/**
|
|
8
|
+
* The file path where the agent's rules should be written.
|
|
9
|
+
*/
|
|
10
|
+
filePath: string;
|
|
11
|
+
/**
|
|
12
|
+
* The write mode to use when writing the rule file.
|
|
13
|
+
*/
|
|
14
|
+
writeMode: WriteMode;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Mapping of agents to their rule file configurations.
|
|
18
|
+
*/
|
|
19
|
+
export declare const AGENT_RULE_CONFIGS: Record<Agent, AgentRuleConfig>;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mapping of agents to their rule file configurations.
|
|
3
|
+
*/
|
|
4
|
+
export const AGENT_RULE_CONFIGS = {
|
|
5
|
+
Amp: {
|
|
6
|
+
filePath: 'AGENTS.md',
|
|
7
|
+
writeMode: 'append',
|
|
8
|
+
},
|
|
9
|
+
'Augment Code': {
|
|
10
|
+
filePath: '.augment/rules/agent-context-engineering.md',
|
|
11
|
+
writeMode: 'overwrite',
|
|
12
|
+
},
|
|
13
|
+
'Claude Code': {
|
|
14
|
+
filePath: 'CLAUDE.md',
|
|
15
|
+
writeMode: 'append',
|
|
16
|
+
},
|
|
17
|
+
Cline: {
|
|
18
|
+
filePath: '.clinerules/agent-context-engineering.md',
|
|
19
|
+
writeMode: 'overwrite',
|
|
20
|
+
},
|
|
21
|
+
Codex: {
|
|
22
|
+
filePath: 'AGENTS.md',
|
|
23
|
+
writeMode: 'append',
|
|
24
|
+
},
|
|
25
|
+
Cursor: {
|
|
26
|
+
filePath: '.cursor/rules/agent-context-engineering.mdc',
|
|
27
|
+
writeMode: 'overwrite',
|
|
28
|
+
},
|
|
29
|
+
'Gemini CLI': {
|
|
30
|
+
filePath: 'GEMINI.md',
|
|
31
|
+
writeMode: 'append',
|
|
32
|
+
},
|
|
33
|
+
'Github Copilot': {
|
|
34
|
+
filePath: '.github/copilot-instructions.md',
|
|
35
|
+
writeMode: 'append',
|
|
36
|
+
},
|
|
37
|
+
Junie: {
|
|
38
|
+
filePath: '.junie/guidelines.md',
|
|
39
|
+
writeMode: 'append',
|
|
40
|
+
},
|
|
41
|
+
'Kilo Code': {
|
|
42
|
+
filePath: '.kilocode/rules/agent-context-engineering.md',
|
|
43
|
+
writeMode: 'overwrite',
|
|
44
|
+
},
|
|
45
|
+
Kiro: {
|
|
46
|
+
filePath: '.kiro/steering/agent-context-engineering.md',
|
|
47
|
+
writeMode: 'overwrite',
|
|
48
|
+
},
|
|
49
|
+
Qoder: {
|
|
50
|
+
filePath: '.qoder/rules/agent-context-engineering.md',
|
|
51
|
+
writeMode: 'overwrite',
|
|
52
|
+
},
|
|
53
|
+
'Qwen Code': {
|
|
54
|
+
filePath: 'QWEN.md',
|
|
55
|
+
writeMode: 'append',
|
|
56
|
+
},
|
|
57
|
+
'Roo Code': {
|
|
58
|
+
filePath: '.roo/rules/agent-context-engineering.md',
|
|
59
|
+
writeMode: 'overwrite',
|
|
60
|
+
},
|
|
61
|
+
'Trae.ai': {
|
|
62
|
+
filePath: 'project_rules.md',
|
|
63
|
+
writeMode: 'append',
|
|
64
|
+
},
|
|
65
|
+
Warp: {
|
|
66
|
+
filePath: 'WARP.md',
|
|
67
|
+
writeMode: 'append',
|
|
68
|
+
},
|
|
69
|
+
Windsurf: {
|
|
70
|
+
filePath: '.windsurf/rules/agent-context-engineering.md',
|
|
71
|
+
writeMode: 'overwrite',
|
|
72
|
+
},
|
|
73
|
+
Zed: {
|
|
74
|
+
filePath: 'agent-context-engineering.rules',
|
|
75
|
+
writeMode: 'overwrite',
|
|
76
|
+
},
|
|
77
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type Agent } from '../../core/domain/entities/agent.js';
|
|
2
|
+
import { type IRuleTemplateService } from '../../core/interfaces/i-rule-template-service.js';
|
|
3
|
+
import { type ITemplateLoader } from '../../core/interfaces/i-template-loader.js';
|
|
4
|
+
/**
|
|
5
|
+
* Service for generating rule templates for different agents.
|
|
6
|
+
* Loads templates from external files and assembles them with agent-specific context.
|
|
7
|
+
*/
|
|
8
|
+
export declare class RuleTemplateService implements IRuleTemplateService {
|
|
9
|
+
private readonly templateLoader;
|
|
10
|
+
constructor(templateLoader: ITemplateLoader);
|
|
11
|
+
/**
|
|
12
|
+
* Generates rule content for the specified agent by loading and assembling templates.
|
|
13
|
+
* @param agent The agent for which to generate rules.
|
|
14
|
+
* @returns Promise resolving to the rule content as a string.
|
|
15
|
+
* @throws Error if templates cannot be loaded or assembled.
|
|
16
|
+
*/
|
|
17
|
+
generateRuleContent(agent: Agent): Promise<string>;
|
|
18
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { BR_RULE_TAG } from '../../core/interfaces/i-rule-template-service.js';
|
|
2
|
+
const guideHeaders = [
|
|
3
|
+
{
|
|
4
|
+
agent: 'Augment Code',
|
|
5
|
+
value: `---
|
|
6
|
+
type: "always_apply"
|
|
7
|
+
---`,
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
agent: 'Cursor',
|
|
11
|
+
value: `---
|
|
12
|
+
description: ByteRover CLI Rules
|
|
13
|
+
alwaysApply: true
|
|
14
|
+
---`,
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
agent: 'Kiro',
|
|
18
|
+
value: `---
|
|
19
|
+
inclusion: always
|
|
20
|
+
---`,
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
agent: 'Qoder',
|
|
24
|
+
value: `---
|
|
25
|
+
trigger: always_on
|
|
26
|
+
alwaysApply: true
|
|
27
|
+
---`,
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
agent: 'Windsurf',
|
|
31
|
+
value: `---
|
|
32
|
+
trigger: always_on
|
|
33
|
+
---`,
|
|
34
|
+
},
|
|
35
|
+
];
|
|
36
|
+
/**
|
|
37
|
+
* Service for generating rule templates for different agents.
|
|
38
|
+
* Loads templates from external files and assembles them with agent-specific context.
|
|
39
|
+
*/
|
|
40
|
+
export class RuleTemplateService {
|
|
41
|
+
templateLoader;
|
|
42
|
+
constructor(templateLoader) {
|
|
43
|
+
this.templateLoader = templateLoader;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Generates rule content for the specified agent by loading and assembling templates.
|
|
47
|
+
* @param agent The agent for which to generate rules.
|
|
48
|
+
* @returns Promise resolving to the rule content as a string.
|
|
49
|
+
* @throws Error if templates cannot be loaded or assembled.
|
|
50
|
+
*/
|
|
51
|
+
async generateRuleContent(agent) {
|
|
52
|
+
try {
|
|
53
|
+
// Load section templates
|
|
54
|
+
const workflow = await this.templateLoader.loadSection('workflow');
|
|
55
|
+
const commandReference = await this.templateLoader.loadSection('command-reference');
|
|
56
|
+
// Load base template
|
|
57
|
+
const baseTemplate = await this.templateLoader.loadTemplate('base.md');
|
|
58
|
+
// Assemble context for variable substitution
|
|
59
|
+
const context = {
|
|
60
|
+
/* eslint-disable camelcase */
|
|
61
|
+
agent_name: agent,
|
|
62
|
+
command_reference: commandReference,
|
|
63
|
+
workflow,
|
|
64
|
+
/* eslint-enable camelcase */
|
|
65
|
+
};
|
|
66
|
+
// Substitute variables and get content
|
|
67
|
+
const content = this.templateLoader.substituteVariables(baseTemplate, context);
|
|
68
|
+
// Add agent-specific header if available (from develop branch)
|
|
69
|
+
const header = guideHeaders.find((h) => h.agent === agent)?.value || '';
|
|
70
|
+
return `${header}
|
|
71
|
+
${content}
|
|
72
|
+
---
|
|
73
|
+
${BR_RULE_TAG} ${agent}
|
|
74
|
+
`;
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
throw new Error(`Failed to generate rule content for agent '${agent}': ${error instanceof Error ? error.message : String(error)}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type Agent } from '../../core/domain/entities/agent.js';
|
|
2
|
+
import { type IFileService } from '../../core/interfaces/i-file-service.js';
|
|
3
|
+
import { IRuleTemplateService } from '../../core/interfaces/i-rule-template-service.js';
|
|
4
|
+
import { type IRuleWriterService } from '../../core/interfaces/i-rule-writer-service.js';
|
|
5
|
+
/**
|
|
6
|
+
* Service for writing agent-specific rule files.
|
|
7
|
+
* Uses IFileService to write files and RuleTemplateService to generate content.
|
|
8
|
+
*/
|
|
9
|
+
export declare class RuleWriterService implements IRuleWriterService {
|
|
10
|
+
private readonly fileService;
|
|
11
|
+
private readonly templateService;
|
|
12
|
+
/**
|
|
13
|
+
* Creates a new RuleWriterService.
|
|
14
|
+
* @param fileService The file service to use for writing files.
|
|
15
|
+
* @param templateService The template service to use for generating rule content.
|
|
16
|
+
*/
|
|
17
|
+
constructor(fileService: IFileService, templateService: IRuleTemplateService);
|
|
18
|
+
writeRule(agent: Agent, force: boolean): Promise<void>;
|
|
19
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { RuleExistsError } from '../../core/domain/errors/rule-error.js';
|
|
2
|
+
import { BR_RULE_TAG } from '../../core/interfaces/i-rule-template-service.js';
|
|
3
|
+
import { AGENT_RULE_CONFIGS } from './agent-rule-config.js';
|
|
4
|
+
/**
|
|
5
|
+
* Service for writing agent-specific rule files.
|
|
6
|
+
* Uses IFileService to write files and RuleTemplateService to generate content.
|
|
7
|
+
*/
|
|
8
|
+
export class RuleWriterService {
|
|
9
|
+
fileService;
|
|
10
|
+
templateService;
|
|
11
|
+
/**
|
|
12
|
+
* Creates a new RuleWriterService.
|
|
13
|
+
* @param fileService The file service to use for writing files.
|
|
14
|
+
* @param templateService The template service to use for generating rule content.
|
|
15
|
+
*/
|
|
16
|
+
constructor(fileService, templateService) {
|
|
17
|
+
this.fileService = fileService;
|
|
18
|
+
this.templateService = templateService;
|
|
19
|
+
}
|
|
20
|
+
async writeRule(agent, force) {
|
|
21
|
+
const config = AGENT_RULE_CONFIGS[agent];
|
|
22
|
+
if (!config) {
|
|
23
|
+
throw new Error(`No configuration found for agent: ${agent}`);
|
|
24
|
+
}
|
|
25
|
+
const { filePath, writeMode } = config;
|
|
26
|
+
const fileExists = await this.fileService.exists(filePath);
|
|
27
|
+
// Throw an error if the file exists and force is not set
|
|
28
|
+
if (writeMode === 'overwrite' && fileExists && !force) {
|
|
29
|
+
throw new RuleExistsError();
|
|
30
|
+
}
|
|
31
|
+
if (writeMode === 'append' && fileExists && !force) {
|
|
32
|
+
const content = await this.fileService.read(filePath);
|
|
33
|
+
// Throw an error if the rule already exists
|
|
34
|
+
if (content.includes(BR_RULE_TAG)) {
|
|
35
|
+
throw new RuleExistsError();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// Generate rule content
|
|
39
|
+
const ruleContent = await this.templateService.generateRuleContent(agent);
|
|
40
|
+
// Write the rule file
|
|
41
|
+
await this.fileService.write(ruleContent, filePath, writeMode);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ISpaceService } from '../../core/interfaces/i-space-service.js';
|
|
2
|
+
import { Space } from '../../core/domain/entities/space.js';
|
|
3
|
+
export type SpaceServiceConfig = {
|
|
4
|
+
apiBaseUrl: string;
|
|
5
|
+
timeout?: number;
|
|
6
|
+
};
|
|
7
|
+
export declare class HttpSpaceService implements ISpaceService {
|
|
8
|
+
private readonly config;
|
|
9
|
+
constructor(config: SpaceServiceConfig);
|
|
10
|
+
getSpaces(accessToken: string, sessionKey: string, teamId: string, option?: {
|
|
11
|
+
fetchAll?: boolean;
|
|
12
|
+
limit?: number;
|
|
13
|
+
offset?: number;
|
|
14
|
+
}): Promise<{
|
|
15
|
+
spaces: Space[];
|
|
16
|
+
total: number;
|
|
17
|
+
}>;
|
|
18
|
+
private fetchAllSpaces;
|
|
19
|
+
private mapToSpace;
|
|
20
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Space } from '../../core/domain/entities/space.js';
|
|
2
|
+
import { AuthenticatedHttpClient } from '../http/authenticated-http-client.js';
|
|
3
|
+
export class HttpSpaceService {
|
|
4
|
+
config;
|
|
5
|
+
constructor(config) {
|
|
6
|
+
this.config = {
|
|
7
|
+
...config,
|
|
8
|
+
timeout: 10_000, // Default 10 seconds timeout
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
async getSpaces(accessToken, sessionKey, teamId, option) {
|
|
12
|
+
try {
|
|
13
|
+
const httpClient = new AuthenticatedHttpClient(accessToken, sessionKey);
|
|
14
|
+
// Scenario 1: Fetch all automatically via auto-pagination
|
|
15
|
+
if (option?.fetchAll === true) {
|
|
16
|
+
return await this.fetchAllSpaces(httpClient, teamId);
|
|
17
|
+
}
|
|
18
|
+
// Scenario 2 & 3: Single request (with or without pagination params)
|
|
19
|
+
const params = new URLSearchParams();
|
|
20
|
+
params.append('team_id', teamId);
|
|
21
|
+
if (option?.limit !== undefined) {
|
|
22
|
+
params.append('limit', option.limit.toString());
|
|
23
|
+
}
|
|
24
|
+
if (option?.offset !== undefined) {
|
|
25
|
+
params.append('offset', option.offset.toString());
|
|
26
|
+
}
|
|
27
|
+
const url = `${this.config.apiBaseUrl}/spaces?${params.toString()}`;
|
|
28
|
+
const response = await httpClient.get(url, {
|
|
29
|
+
timeout: this.config.timeout,
|
|
30
|
+
});
|
|
31
|
+
return {
|
|
32
|
+
spaces: response.data.spaces.map((spaceData) => this.mapToSpace(spaceData)),
|
|
33
|
+
total: response.data.total,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
throw new Error(`Failed to fetch spaces: ${error.message}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async fetchAllSpaces(httpClient, teamId) {
|
|
41
|
+
const pageSize = 100; // Larger pages for fewer requests
|
|
42
|
+
let offset = 0;
|
|
43
|
+
let allSpaces = [];
|
|
44
|
+
let total = 0;
|
|
45
|
+
while (true) {
|
|
46
|
+
const params = new URLSearchParams({
|
|
47
|
+
limit: pageSize.toString(),
|
|
48
|
+
offset: offset.toString(),
|
|
49
|
+
team_id: teamId,
|
|
50
|
+
});
|
|
51
|
+
// eslint-disable-next-line no-await-in-loop
|
|
52
|
+
const response = await httpClient.get(`${this.config.apiBaseUrl}/spaces?${params.toString()}`, { timeout: this.config.timeout });
|
|
53
|
+
const pageSpaces = response.data.spaces.map((spaceData) => this.mapToSpace(spaceData));
|
|
54
|
+
allSpaces = [...allSpaces, ...pageSpaces];
|
|
55
|
+
total = response.data.total;
|
|
56
|
+
// Stop if we've fetched everything or got empty page
|
|
57
|
+
if (allSpaces.length >= total || pageSpaces.length === 0) {
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
offset += pageSize;
|
|
61
|
+
}
|
|
62
|
+
return { spaces: allSpaces, total };
|
|
63
|
+
}
|
|
64
|
+
mapToSpace(spaceData) {
|
|
65
|
+
return new Space(spaceData.id, spaceData.name, spaceData.team_id, spaceData.team.name);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ITokenStore } from '../../core/interfaces/i-token-store.js';
|
|
2
|
+
import { AuthToken } from '../../core/domain/entities/auth-token.js';
|
|
3
|
+
/**
|
|
4
|
+
* Token store implementation using the system keychain via the keytar library.
|
|
5
|
+
*/
|
|
6
|
+
export declare class KeychainTokenStore implements ITokenStore {
|
|
7
|
+
clear(): Promise<void>;
|
|
8
|
+
load(): Promise<AuthToken | undefined>;
|
|
9
|
+
save(token: AuthToken): Promise<void>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import keytar from 'keytar';
|
|
2
|
+
import { AuthToken } from '../../core/domain/entities/auth-token.js';
|
|
3
|
+
const SERVICE_NAME = 'byterover-cli';
|
|
4
|
+
const ACCOUNT_NAME = 'auth-token';
|
|
5
|
+
/**
|
|
6
|
+
* Token store implementation using the system keychain via the keytar library.
|
|
7
|
+
*/
|
|
8
|
+
export class KeychainTokenStore {
|
|
9
|
+
async clear() {
|
|
10
|
+
try {
|
|
11
|
+
await keytar.deletePassword(SERVICE_NAME, ACCOUNT_NAME);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
// Ignore errors - token might not exist or keychain might be unavailable
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
async load() {
|
|
18
|
+
try {
|
|
19
|
+
const data = await keytar.getPassword(SERVICE_NAME, ACCOUNT_NAME);
|
|
20
|
+
if (data === null) {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
const deserialized = JSON.parse(data);
|
|
24
|
+
return AuthToken.fromJson(deserialized);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// Return undefined on any error (missing token, invalid JSON, keychain errors)
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
async save(token) {
|
|
32
|
+
try {
|
|
33
|
+
const data = JSON.stringify(token);
|
|
34
|
+
await keytar.setPassword(SERVICE_NAME, ACCOUNT_NAME, data);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
throw new Error(`Failed to save token to keychain: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ITeamService } from '../../core/interfaces/i-team-service.js';
|
|
2
|
+
import { Team } from '../../core/domain/entities/team.js';
|
|
3
|
+
export type TeamServiceConfig = {
|
|
4
|
+
apiBaseUrl: string;
|
|
5
|
+
timeout?: number;
|
|
6
|
+
};
|
|
7
|
+
export declare class HttpTeamService implements ITeamService {
|
|
8
|
+
private readonly config;
|
|
9
|
+
constructor(config: TeamServiceConfig);
|
|
10
|
+
getTeams(accessToken: string, sessionKey: string, option?: {
|
|
11
|
+
fetchAll?: boolean;
|
|
12
|
+
isActive?: boolean;
|
|
13
|
+
limit?: number;
|
|
14
|
+
offset?: number;
|
|
15
|
+
}): Promise<{
|
|
16
|
+
teams: Team[];
|
|
17
|
+
total: number;
|
|
18
|
+
}>;
|
|
19
|
+
private fetchAllTeams;
|
|
20
|
+
private mapToTeam;
|
|
21
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { Team } from '../../core/domain/entities/team.js';
|
|
2
|
+
import { AuthenticatedHttpClient } from '../http/authenticated-http-client.js';
|
|
3
|
+
export class HttpTeamService {
|
|
4
|
+
config;
|
|
5
|
+
constructor(config) {
|
|
6
|
+
this.config = {
|
|
7
|
+
...config,
|
|
8
|
+
timeout: 10_000, // Default 10 seconds timeout
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
async getTeams(accessToken, sessionKey, option) {
|
|
12
|
+
try {
|
|
13
|
+
const httpClient = new AuthenticatedHttpClient(accessToken, sessionKey);
|
|
14
|
+
// Scenario 1: Fetch all automatically via auto-pagination
|
|
15
|
+
if (option?.fetchAll === true) {
|
|
16
|
+
return await this.fetchAllTeams(httpClient, option?.isActive);
|
|
17
|
+
}
|
|
18
|
+
// Scenario 2 & 3: Single request (with or without pagination params)
|
|
19
|
+
const params = new URLSearchParams();
|
|
20
|
+
if (option?.limit !== undefined) {
|
|
21
|
+
params.append('limit', option.limit.toString());
|
|
22
|
+
}
|
|
23
|
+
if (option?.offset !== undefined) {
|
|
24
|
+
params.append('offset', option.offset.toString());
|
|
25
|
+
}
|
|
26
|
+
if (option?.isActive !== undefined) {
|
|
27
|
+
params.append('is_active', option.isActive.toString());
|
|
28
|
+
}
|
|
29
|
+
const url = `${this.config.apiBaseUrl}/teams${params.toString() ? `?${params.toString()}` : ''}`;
|
|
30
|
+
const response = await httpClient.get(url, {
|
|
31
|
+
timeout: this.config.timeout,
|
|
32
|
+
});
|
|
33
|
+
return {
|
|
34
|
+
teams: response.data.teams.map((teamData) => this.mapToTeam(teamData)),
|
|
35
|
+
total: response.data.total,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
throw new Error(`Failed to fetch teams: ${error.message}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async fetchAllTeams(httpClient, isActive) {
|
|
43
|
+
const pageSize = 100; // Larger pages for fewer requests
|
|
44
|
+
let offset = 0;
|
|
45
|
+
let allTeams = [];
|
|
46
|
+
let total = 0;
|
|
47
|
+
while (true) {
|
|
48
|
+
const params = new URLSearchParams({
|
|
49
|
+
limit: pageSize.toString(),
|
|
50
|
+
offset: offset.toString(),
|
|
51
|
+
});
|
|
52
|
+
if (isActive !== undefined) {
|
|
53
|
+
params.append('is_active', isActive.toString());
|
|
54
|
+
}
|
|
55
|
+
// eslint-disable-next-line no-await-in-loop
|
|
56
|
+
const response = await httpClient.get(`${this.config.apiBaseUrl}/teams?${params.toString()}`, { timeout: this.config.timeout });
|
|
57
|
+
const pageTeams = response.data.teams.map((teamData) => this.mapToTeam(teamData));
|
|
58
|
+
allTeams = [...allTeams, ...pageTeams];
|
|
59
|
+
total = response.data.total;
|
|
60
|
+
// Stop if we've fetched everything or got empty page
|
|
61
|
+
if (allTeams.length >= total || pageTeams.length === 0) {
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
offset += pageSize;
|
|
65
|
+
}
|
|
66
|
+
return { teams: allTeams, total };
|
|
67
|
+
}
|
|
68
|
+
mapToTeam(teamData) {
|
|
69
|
+
return Team.fromJson(teamData);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type IFileService } from '../../core/interfaces/i-file-service.js';
|
|
2
|
+
import { type ITemplateLoader } from '../../core/interfaces/i-template-loader.js';
|
|
3
|
+
/**
|
|
4
|
+
* File system-based template loader.
|
|
5
|
+
* Loads templates from src/templates/ directory and performs variable substitution.
|
|
6
|
+
*/
|
|
7
|
+
export declare class FsTemplateLoader implements ITemplateLoader {
|
|
8
|
+
private readonly fileService;
|
|
9
|
+
private readonly templatesDir;
|
|
10
|
+
constructor(fileService: IFileService);
|
|
11
|
+
/**
|
|
12
|
+
* Loads a section template from the sections/ directory.
|
|
13
|
+
* @param sectionName - Name of the section (e.g., 'workflow', 'command-reference')
|
|
14
|
+
* @returns Promise resolving to section content
|
|
15
|
+
* @throws Error if section file cannot be read
|
|
16
|
+
*/
|
|
17
|
+
loadSection(sectionName: string): Promise<string>;
|
|
18
|
+
/**
|
|
19
|
+
* Loads a template file from the templates directory.
|
|
20
|
+
* @param templatePath - Relative path to template (e.g., 'base.md', 'sections/workflow.md')
|
|
21
|
+
* @returns Promise resolving to template content
|
|
22
|
+
* @throws Error if template file cannot be read
|
|
23
|
+
*/
|
|
24
|
+
loadTemplate(templatePath: string): Promise<string>;
|
|
25
|
+
/**
|
|
26
|
+
* Substitutes variables in a template string.
|
|
27
|
+
* Replaces {{variable_name}} with corresponding values from context.
|
|
28
|
+
* @param template - Template string with {{variable}} placeholders
|
|
29
|
+
* @param context - Object mapping variable names to values
|
|
30
|
+
* @returns Template with variables replaced
|
|
31
|
+
*/
|
|
32
|
+
substituteVariables(template: string, context: Record<string, string>): string;
|
|
33
|
+
}
|