opencode-branch-memory-manager 0.1.3 ā 0.1.5
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/.opencode/branch-memory/collector.ts +47 -7
- package/.opencode/branch-memory/config.ts +24 -23
- package/.opencode/branch-memory/git.ts +1 -1
- package/.opencode/branch-memory/injector.ts +37 -9
- package/.opencode/branch-memory/monitor.ts +4 -1
- package/.opencode/branch-memory/storage.ts +6 -4
- package/.opencode/branch-memory/types.ts +2 -0
- package/.opencode/dist/branch-memory-plugin.js +2270 -0
- package/.opencode/dist/branch-memory.js +93 -32
- package/.opencode/marketplace.json +31 -0
- package/.opencode/plugin/branch-memory-plugin.ts +53 -62
- package/.opencode/tool/branch-memory.ts +21 -21
- package/README.md +56 -20
- package/package.json +16 -3
|
@@ -6,9 +6,11 @@ import { GitOperations } from "./git.js";
|
|
|
6
6
|
*/
|
|
7
7
|
export class ContextCollector {
|
|
8
8
|
private config: PluginConfig;
|
|
9
|
+
private client?: any;
|
|
9
10
|
|
|
10
|
-
constructor(config: PluginConfig) {
|
|
11
|
+
constructor(config: PluginConfig, client?: any) {
|
|
11
12
|
this.config = config;
|
|
13
|
+
this.client = client;
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
/**
|
|
@@ -73,9 +75,28 @@ export class ContextCollector {
|
|
|
73
75
|
* @returns Array of messages
|
|
74
76
|
*/
|
|
75
77
|
private async collectMessages(): Promise<BranchContext["data"]["messages"]> {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
78
|
+
if (!this.client) {
|
|
79
|
+
console.warn('Client not available, skipping message collection');
|
|
80
|
+
return [];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
// Use OpenCode SDK to fetch session messages
|
|
85
|
+
// Note: The actual API may differ - this is based on common patterns
|
|
86
|
+
const messages = await this.client.session?.getMessages?.() || [];
|
|
87
|
+
|
|
88
|
+
// Limit to maxMessages from config
|
|
89
|
+
const limited = messages.slice(-this.config.context.maxMessages);
|
|
90
|
+
|
|
91
|
+
return limited.map((msg: any) => ({
|
|
92
|
+
role: msg.role || 'user',
|
|
93
|
+
content: msg.content || '',
|
|
94
|
+
timestamp: msg.timestamp || new Date().toISOString()
|
|
95
|
+
}));
|
|
96
|
+
} catch (error) {
|
|
97
|
+
console.error('Failed to collect messages:', error);
|
|
98
|
+
return [];
|
|
99
|
+
}
|
|
79
100
|
}
|
|
80
101
|
|
|
81
102
|
/**
|
|
@@ -83,8 +104,27 @@ export class ContextCollector {
|
|
|
83
104
|
* @returns Array of todos
|
|
84
105
|
*/
|
|
85
106
|
private async collectTodos(): Promise<BranchContext["data"]["todos"]> {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
107
|
+
if (!this.client) {
|
|
108
|
+
console.warn('Client not available, skipping todo collection');
|
|
109
|
+
return [];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
// Use OpenCode SDK to fetch session todos
|
|
114
|
+
// Note: The actual API may differ - this is based on common patterns
|
|
115
|
+
const todos = await this.client.session?.getTodos?.() || [];
|
|
116
|
+
|
|
117
|
+
// Limit to maxTodos from config
|
|
118
|
+
const limited = todos.slice(0, this.config.context.maxTodos);
|
|
119
|
+
|
|
120
|
+
return limited.map((todo: any) => ({
|
|
121
|
+
id: todo.id || String(Date.now()),
|
|
122
|
+
content: todo.content || '',
|
|
123
|
+
status: todo.status || 'pending'
|
|
124
|
+
}));
|
|
125
|
+
} catch (error) {
|
|
126
|
+
console.error('Failed to collect todos:', error);
|
|
127
|
+
return [];
|
|
128
|
+
}
|
|
89
129
|
}
|
|
90
130
|
}
|
|
@@ -11,7 +11,9 @@ const DEFAULT_CONFIG: PluginConfig = {
|
|
|
11
11
|
enabled: true,
|
|
12
12
|
onMessageChange: true,
|
|
13
13
|
onBranchChange: true,
|
|
14
|
-
onToolExecute: true
|
|
14
|
+
onToolExecute: true,
|
|
15
|
+
throttleMs: 5000,
|
|
16
|
+
periodicIntervalMs: 60000
|
|
15
17
|
},
|
|
16
18
|
contextLoading: 'auto',
|
|
17
19
|
context: {
|
|
@@ -34,28 +36,28 @@ const DEFAULT_CONFIG: PluginConfig = {
|
|
|
34
36
|
* Configuration manager for branch memory plugin
|
|
35
37
|
*/
|
|
36
38
|
export class ConfigManager {
|
|
37
|
-
private
|
|
38
|
-
private
|
|
39
|
-
|
|
39
|
+
private configPath: string
|
|
40
|
+
private projectPath: string
|
|
41
|
+
|
|
40
42
|
/**
|
|
41
|
-
*
|
|
43
|
+
* Create a new ConfigManager instance
|
|
42
44
|
* @param projectPath - The root directory of the project
|
|
43
45
|
*/
|
|
44
|
-
|
|
46
|
+
constructor(projectPath: string) {
|
|
45
47
|
this.projectPath = projectPath
|
|
46
48
|
this.configPath = path.join(projectPath, '.opencode', 'config', 'branch-memory.json')
|
|
47
49
|
}
|
|
48
|
-
|
|
50
|
+
|
|
49
51
|
/**
|
|
50
52
|
* Load configuration from project directory, falling back to defaults
|
|
51
53
|
* @returns Configuration object
|
|
52
54
|
*/
|
|
53
|
-
|
|
55
|
+
async load(): Promise<PluginConfig> {
|
|
54
56
|
if (existsSync(this.configPath)) {
|
|
55
57
|
try {
|
|
56
58
|
const content = await fs.readFile(this.configPath, 'utf8')
|
|
57
59
|
const userConfig = JSON.parse(content) as Partial<PluginConfig>
|
|
58
|
-
|
|
60
|
+
|
|
59
61
|
// Deep merge user config with defaults
|
|
60
62
|
return this.deepMerge(DEFAULT_CONFIG, userConfig) as PluginConfig
|
|
61
63
|
} catch (error) {
|
|
@@ -65,31 +67,30 @@ export class ConfigManager {
|
|
|
65
67
|
}
|
|
66
68
|
return { ...DEFAULT_CONFIG }
|
|
67
69
|
}
|
|
68
|
-
|
|
70
|
+
|
|
69
71
|
/**
|
|
70
72
|
* Get default configuration
|
|
71
73
|
* @returns Default configuration object
|
|
72
74
|
*/
|
|
73
|
-
|
|
75
|
+
getDefault(): PluginConfig {
|
|
74
76
|
return JSON.parse(JSON.stringify(DEFAULT_CONFIG)) as PluginConfig
|
|
75
77
|
}
|
|
76
|
-
|
|
78
|
+
|
|
77
79
|
/**
|
|
78
80
|
* Get storage directory path
|
|
79
|
-
* @param projectPath - The root directory of the project
|
|
80
81
|
* @returns Path to storage directory
|
|
81
82
|
*/
|
|
82
|
-
|
|
83
|
-
return path.join(projectPath, '.opencode', 'branch-memory')
|
|
83
|
+
getStorageDir(): string {
|
|
84
|
+
return path.join(this.projectPath, '.opencode', 'branch-memory')
|
|
84
85
|
}
|
|
85
|
-
|
|
86
|
+
|
|
86
87
|
/**
|
|
87
88
|
* Save configuration to project directory
|
|
88
89
|
* @param config - Configuration object to save
|
|
89
90
|
*/
|
|
90
|
-
|
|
91
|
+
async save(config: PluginConfig): Promise<void> {
|
|
91
92
|
const configDir = path.dirname(this.configPath)
|
|
92
|
-
|
|
93
|
+
|
|
93
94
|
try {
|
|
94
95
|
await fs.mkdir(configDir, { recursive: true })
|
|
95
96
|
await fs.writeFile(this.configPath, JSON.stringify(config, null, 2), 'utf8')
|
|
@@ -98,20 +99,20 @@ export class ConfigManager {
|
|
|
98
99
|
throw error
|
|
99
100
|
}
|
|
100
101
|
}
|
|
101
|
-
|
|
102
|
+
|
|
102
103
|
/**
|
|
103
104
|
* Deep merge two objects
|
|
104
105
|
* @param target - Target object (defaults)
|
|
105
106
|
* @param source - Source object (user config)
|
|
106
107
|
* @returns Merged object
|
|
107
108
|
*/
|
|
108
|
-
private
|
|
109
|
+
private deepMerge<T>(target: T, source: Partial<T>): T {
|
|
109
110
|
const result = { ...target }
|
|
110
|
-
|
|
111
|
+
|
|
111
112
|
for (const key in source) {
|
|
112
113
|
const sourceValue = source[key]
|
|
113
114
|
const targetValue = result[key]
|
|
114
|
-
|
|
115
|
+
|
|
115
116
|
if (sourceValue !== undefined) {
|
|
116
117
|
if (typeof sourceValue === 'object' && sourceValue !== null && !Array.isArray(sourceValue) &&
|
|
117
118
|
typeof targetValue === 'object' && targetValue !== null && !Array.isArray(targetValue)) {
|
|
@@ -121,7 +122,7 @@ export class ConfigManager {
|
|
|
121
122
|
}
|
|
122
123
|
}
|
|
123
124
|
}
|
|
124
|
-
|
|
125
|
+
|
|
125
126
|
return result
|
|
126
127
|
}
|
|
127
128
|
}
|
|
@@ -95,7 +95,7 @@ export class GitOperations {
|
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
static async getAllBranches(): Promise<string[]> {
|
|
98
|
-
const result = await this.runGitCommand('branch',
|
|
98
|
+
const result = await this.runGitCommand('branch', '--format=%(refname:short)')
|
|
99
99
|
|
|
100
100
|
if (result.exitCode !== 0) {
|
|
101
101
|
return []
|
|
@@ -16,15 +16,43 @@ export class ContextInjector {
|
|
|
16
16
|
* @param branchContext - Branch context to inject
|
|
17
17
|
*/
|
|
18
18
|
async injectContext(branchContext: BranchContext): Promise<void> {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
19
|
+
if (!this.context || !this.hasContextData(branchContext)) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const summary = this.formatContextSummary(branchContext);
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
// Inject context as a system message that doesn't trigger response
|
|
27
|
+
// Using the OpenCode SDK client to add context to the session
|
|
28
|
+
const client = (this.context as any).client;
|
|
29
|
+
|
|
30
|
+
if (client?.session?.addMessage) {
|
|
31
|
+
await client.session.addMessage({
|
|
32
|
+
role: 'system',
|
|
33
|
+
content: summary,
|
|
34
|
+
silent: true // Don't trigger AI response
|
|
35
|
+
});
|
|
36
|
+
console.log('ā
Context injected for branch:', branchContext.branch);
|
|
37
|
+
} else if (client?.session?.prompt) {
|
|
38
|
+
// Fallback: use prompt with noReply flag
|
|
39
|
+
await client.session.prompt(summary, { noReply: true });
|
|
40
|
+
console.log('ā
Context injected for branch:', branchContext.branch);
|
|
41
|
+
} else {
|
|
42
|
+
// Fallback to console output if SDK methods not available
|
|
43
|
+
console.log('\nš„ Context Summary (SDK not available):');
|
|
44
|
+
console.log('ā'.repeat(50));
|
|
45
|
+
console.log(summary);
|
|
46
|
+
console.log('ā'.repeat(50));
|
|
47
|
+
}
|
|
48
|
+
} catch (error) {
|
|
49
|
+
console.error('Failed to inject context:', error);
|
|
50
|
+
// Fall back to console output
|
|
51
|
+
console.log('\nš„ Context Summary (injection failed):');
|
|
52
|
+
console.log('ā'.repeat(50));
|
|
53
|
+
console.log(summary);
|
|
54
|
+
console.log('ā'.repeat(50));
|
|
55
|
+
}
|
|
28
56
|
}
|
|
29
57
|
|
|
30
58
|
/**
|
|
@@ -22,7 +22,10 @@ export class BranchMonitor {
|
|
|
22
22
|
newBranch: string,
|
|
23
23
|
) => void,
|
|
24
24
|
private config: PluginConfig,
|
|
25
|
-
) {
|
|
25
|
+
) {
|
|
26
|
+
// Register the callback so it gets called on branch changes
|
|
27
|
+
this.changeCallbacks.push(this.onBranchChange);
|
|
28
|
+
}
|
|
26
29
|
|
|
27
30
|
/**
|
|
28
31
|
* Start monitoring git branch changes
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import * as fs from 'fs/promises'
|
|
2
2
|
import * as path from 'path'
|
|
3
3
|
import { existsSync } from 'fs'
|
|
4
|
-
import type { BranchContext } from './types.js'
|
|
4
|
+
import type { BranchContext, PluginConfig } from './types.js'
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Context storage manager for branch-specific contexts
|
|
8
8
|
*/
|
|
9
9
|
export class ContextStorage {
|
|
10
10
|
private storageDir: string
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
private maxBackups: number
|
|
12
|
+
|
|
13
|
+
constructor(storageDir: string, config?: PluginConfig) {
|
|
13
14
|
this.storageDir = storageDir
|
|
15
|
+
this.maxBackups = config?.storage?.maxBackups ?? 5
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
/**
|
|
@@ -162,7 +164,7 @@ export class ContextStorage {
|
|
|
162
164
|
return { name: f, timestamp: match ? parseInt(match[1], 10) : 0 }
|
|
163
165
|
})
|
|
164
166
|
.sort((a, b) => b.timestamp - a.timestamp)
|
|
165
|
-
.slice(
|
|
167
|
+
.slice(this.maxBackups) // Keep last maxBackups
|
|
166
168
|
|
|
167
169
|
for (const backup of backups) {
|
|
168
170
|
await fs.unlink(path.join(this.storageDir, backup.name)).catch(() => {})
|