opencode-branch-memory-manager 0.1.2 → 0.1.4
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/storage.ts +6 -4
- package/.opencode/branch-memory/types.ts +2 -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 +79 -19
- package/package.json +14 -2
|
@@ -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
|
/**
|
|
@@ -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(() => {})
|
|
@@ -12336,8 +12336,10 @@ import { existsSync } from "fs";
|
|
|
12336
12336
|
|
|
12337
12337
|
class ContextStorage {
|
|
12338
12338
|
storageDir;
|
|
12339
|
-
|
|
12339
|
+
maxBackups;
|
|
12340
|
+
constructor(storageDir, config2) {
|
|
12340
12341
|
this.storageDir = storageDir;
|
|
12342
|
+
this.maxBackups = config2?.storage?.maxBackups ?? 5;
|
|
12341
12343
|
}
|
|
12342
12344
|
getBranchFile(branch) {
|
|
12343
12345
|
return path.join(this.storageDir, `${this.sanitizeBranchName(branch)}.json`);
|
|
@@ -12415,7 +12417,7 @@ class ContextStorage {
|
|
|
12415
12417
|
const backups = files.filter((f) => f.startsWith(`${safeBranch}.backup.`) && f.endsWith(".json")).map((f) => {
|
|
12416
12418
|
const match = f.match(/\.backup\.(\d+)\.json$/);
|
|
12417
12419
|
return { name: f, timestamp: match ? parseInt(match[1], 10) : 0 };
|
|
12418
|
-
}).sort((a, b) => b.timestamp - a.timestamp).slice(
|
|
12420
|
+
}).sort((a, b) => b.timestamp - a.timestamp).slice(this.maxBackups);
|
|
12419
12421
|
for (const backup of backups) {
|
|
12420
12422
|
await fs.unlink(path.join(this.storageDir, backup.name)).catch(() => {});
|
|
12421
12423
|
}
|
|
@@ -12595,7 +12597,7 @@ class GitOperations {
|
|
|
12595
12597
|
`).filter((f) => f.length > 0);
|
|
12596
12598
|
}
|
|
12597
12599
|
static async getAllBranches() {
|
|
12598
|
-
const result = await this.runGitCommand("branch", "--format
|
|
12600
|
+
const result = await this.runGitCommand("branch", "--format=%(refname:short)");
|
|
12599
12601
|
if (result.exitCode !== 0) {
|
|
12600
12602
|
return [];
|
|
12601
12603
|
}
|
|
@@ -12649,8 +12651,10 @@ class GitOperations {
|
|
|
12649
12651
|
// .opencode/branch-memory/collector.ts
|
|
12650
12652
|
class ContextCollector {
|
|
12651
12653
|
config;
|
|
12652
|
-
|
|
12654
|
+
client;
|
|
12655
|
+
constructor(config2, client) {
|
|
12653
12656
|
this.config = config2;
|
|
12657
|
+
this.client = client;
|
|
12654
12658
|
}
|
|
12655
12659
|
async collectContext(includeMessages = true, includeTodos = true, includeFiles = true, description = "") {
|
|
12656
12660
|
const currentBranch = await GitOperations.getCurrentBranch();
|
|
@@ -12685,10 +12689,40 @@ class ContextCollector {
|
|
|
12685
12689
|
return context;
|
|
12686
12690
|
}
|
|
12687
12691
|
async collectMessages() {
|
|
12688
|
-
|
|
12692
|
+
if (!this.client) {
|
|
12693
|
+
console.warn("Client not available, skipping message collection");
|
|
12694
|
+
return [];
|
|
12695
|
+
}
|
|
12696
|
+
try {
|
|
12697
|
+
const messages = await this.client.session?.getMessages?.() || [];
|
|
12698
|
+
const limited = messages.slice(-this.config.context.maxMessages);
|
|
12699
|
+
return limited.map((msg) => ({
|
|
12700
|
+
role: msg.role || "user",
|
|
12701
|
+
content: msg.content || "",
|
|
12702
|
+
timestamp: msg.timestamp || new Date().toISOString()
|
|
12703
|
+
}));
|
|
12704
|
+
} catch (error45) {
|
|
12705
|
+
console.error("Failed to collect messages:", error45);
|
|
12706
|
+
return [];
|
|
12707
|
+
}
|
|
12689
12708
|
}
|
|
12690
12709
|
async collectTodos() {
|
|
12691
|
-
|
|
12710
|
+
if (!this.client) {
|
|
12711
|
+
console.warn("Client not available, skipping todo collection");
|
|
12712
|
+
return [];
|
|
12713
|
+
}
|
|
12714
|
+
try {
|
|
12715
|
+
const todos = await this.client.session?.getTodos?.() || [];
|
|
12716
|
+
const limited = todos.slice(0, this.config.context.maxTodos);
|
|
12717
|
+
return limited.map((todo) => ({
|
|
12718
|
+
id: todo.id || String(Date.now()),
|
|
12719
|
+
content: todo.content || "",
|
|
12720
|
+
status: todo.status || "pending"
|
|
12721
|
+
}));
|
|
12722
|
+
} catch (error45) {
|
|
12723
|
+
console.error("Failed to collect todos:", error45);
|
|
12724
|
+
return [];
|
|
12725
|
+
}
|
|
12692
12726
|
}
|
|
12693
12727
|
}
|
|
12694
12728
|
// .opencode/branch-memory/injector.ts
|
|
@@ -12698,12 +12732,37 @@ class ContextInjector {
|
|
|
12698
12732
|
this.context = context;
|
|
12699
12733
|
}
|
|
12700
12734
|
async injectContext(branchContext) {
|
|
12735
|
+
if (!this.context || !this.hasContextData(branchContext)) {
|
|
12736
|
+
return;
|
|
12737
|
+
}
|
|
12701
12738
|
const summary = this.formatContextSummary(branchContext);
|
|
12702
|
-
|
|
12703
|
-
|
|
12704
|
-
|
|
12705
|
-
|
|
12706
|
-
|
|
12739
|
+
try {
|
|
12740
|
+
const client = this.context.client;
|
|
12741
|
+
if (client?.session?.addMessage) {
|
|
12742
|
+
await client.session.addMessage({
|
|
12743
|
+
role: "system",
|
|
12744
|
+
content: summary,
|
|
12745
|
+
silent: true
|
|
12746
|
+
});
|
|
12747
|
+
console.log("✅ Context injected for branch:", branchContext.branch);
|
|
12748
|
+
} else if (client?.session?.prompt) {
|
|
12749
|
+
await client.session.prompt(summary, { noReply: true });
|
|
12750
|
+
console.log("✅ Context injected for branch:", branchContext.branch);
|
|
12751
|
+
} else {
|
|
12752
|
+
console.log(`
|
|
12753
|
+
\uD83D\uDCE5 Context Summary (SDK not available):`);
|
|
12754
|
+
console.log("─".repeat(50));
|
|
12755
|
+
console.log(summary);
|
|
12756
|
+
console.log("─".repeat(50));
|
|
12757
|
+
}
|
|
12758
|
+
} catch (error45) {
|
|
12759
|
+
console.error("Failed to inject context:", error45);
|
|
12760
|
+
console.log(`
|
|
12761
|
+
\uD83D\uDCE5 Context Summary (injection failed):`);
|
|
12762
|
+
console.log("─".repeat(50));
|
|
12763
|
+
console.log(summary);
|
|
12764
|
+
console.log("─".repeat(50));
|
|
12765
|
+
}
|
|
12707
12766
|
}
|
|
12708
12767
|
async askUserAboutContextLoading(branchContext) {
|
|
12709
12768
|
const summary = this.formatContextSummary(branchContext);
|
|
@@ -12762,7 +12821,9 @@ var DEFAULT_CONFIG = {
|
|
|
12762
12821
|
enabled: true,
|
|
12763
12822
|
onMessageChange: true,
|
|
12764
12823
|
onBranchChange: true,
|
|
12765
|
-
onToolExecute: true
|
|
12824
|
+
onToolExecute: true,
|
|
12825
|
+
throttleMs: 5000,
|
|
12826
|
+
periodicIntervalMs: 60000
|
|
12766
12827
|
},
|
|
12767
12828
|
contextLoading: "auto",
|
|
12768
12829
|
context: {
|
|
@@ -12782,13 +12843,13 @@ var DEFAULT_CONFIG = {
|
|
|
12782
12843
|
};
|
|
12783
12844
|
|
|
12784
12845
|
class ConfigManager {
|
|
12785
|
-
|
|
12786
|
-
|
|
12787
|
-
|
|
12846
|
+
configPath;
|
|
12847
|
+
projectPath;
|
|
12848
|
+
constructor(projectPath) {
|
|
12788
12849
|
this.projectPath = projectPath;
|
|
12789
12850
|
this.configPath = path2.join(projectPath, ".opencode", "config", "branch-memory.json");
|
|
12790
12851
|
}
|
|
12791
|
-
|
|
12852
|
+
async load() {
|
|
12792
12853
|
if (existsSync2(this.configPath)) {
|
|
12793
12854
|
try {
|
|
12794
12855
|
const content = await fs2.readFile(this.configPath, "utf8");
|
|
@@ -12801,13 +12862,13 @@ class ConfigManager {
|
|
|
12801
12862
|
}
|
|
12802
12863
|
return { ...DEFAULT_CONFIG };
|
|
12803
12864
|
}
|
|
12804
|
-
|
|
12865
|
+
getDefault() {
|
|
12805
12866
|
return JSON.parse(JSON.stringify(DEFAULT_CONFIG));
|
|
12806
12867
|
}
|
|
12807
|
-
|
|
12808
|
-
return path2.join(projectPath, ".opencode", "branch-memory");
|
|
12868
|
+
getStorageDir() {
|
|
12869
|
+
return path2.join(this.projectPath, ".opencode", "branch-memory");
|
|
12809
12870
|
}
|
|
12810
|
-
|
|
12871
|
+
async save(config2) {
|
|
12811
12872
|
const configDir = path2.dirname(this.configPath);
|
|
12812
12873
|
try {
|
|
12813
12874
|
await fs2.mkdir(configDir, { recursive: true });
|
|
@@ -12817,7 +12878,7 @@ class ConfigManager {
|
|
|
12817
12878
|
throw error45;
|
|
12818
12879
|
}
|
|
12819
12880
|
}
|
|
12820
|
-
|
|
12881
|
+
deepMerge(target, source) {
|
|
12821
12882
|
const result = { ...target };
|
|
12822
12883
|
for (const key in source) {
|
|
12823
12884
|
const sourceValue = source[key];
|
|
@@ -12834,6 +12895,12 @@ class ConfigManager {
|
|
|
12834
12895
|
}
|
|
12835
12896
|
}
|
|
12836
12897
|
// .opencode/tool/branch-memory.ts
|
|
12898
|
+
async function initializeContext() {
|
|
12899
|
+
const configManager = new ConfigManager(process.cwd());
|
|
12900
|
+
const config2 = await configManager.load();
|
|
12901
|
+
const storage = new ContextStorage(configManager.getStorageDir(), config2);
|
|
12902
|
+
return { configManager, config: config2, storage };
|
|
12903
|
+
}
|
|
12837
12904
|
var save = tool({
|
|
12838
12905
|
description: "Save current session context for current git branch with optional filters",
|
|
12839
12906
|
args: {
|
|
@@ -12844,15 +12911,13 @@ var save = tool({
|
|
|
12844
12911
|
},
|
|
12845
12912
|
async execute(args, context) {
|
|
12846
12913
|
try {
|
|
12847
|
-
|
|
12848
|
-
const config2 = await ConfigManager.load();
|
|
12914
|
+
const { config: config2, storage } = await initializeContext();
|
|
12849
12915
|
const currentBranch = await GitOperations.getCurrentBranch();
|
|
12850
12916
|
if (!currentBranch) {
|
|
12851
12917
|
return "⚠️ Not on a git branch, context not saved";
|
|
12852
12918
|
}
|
|
12853
12919
|
const collector = new ContextCollector(config2);
|
|
12854
12920
|
const branchContext = await collector.collectContext(args.includeMessages ?? config2.context.defaultInclude.includes("messages"), args.includeTodos ?? config2.context.defaultInclude.includes("todos"), args.includeFiles ?? config2.context.defaultInclude.includes("files"), args.description || "");
|
|
12855
|
-
const storage = new ContextStorage(ConfigManager.getStorageDir(process.cwd()));
|
|
12856
12921
|
await storage.saveContext(currentBranch, branchContext);
|
|
12857
12922
|
return `✅ Saved context for branch '${currentBranch}'
|
|
12858
12923
|
├─ Messages: ${branchContext.metadata.messageCount}
|
|
@@ -12872,13 +12937,12 @@ var load = tool({
|
|
|
12872
12937
|
},
|
|
12873
12938
|
async execute(args, context) {
|
|
12874
12939
|
try {
|
|
12875
|
-
|
|
12940
|
+
const { storage } = await initializeContext();
|
|
12876
12941
|
const git = GitOperations;
|
|
12877
12942
|
const targetBranch = args.branch || await git.getCurrentBranch();
|
|
12878
12943
|
if (!targetBranch) {
|
|
12879
12944
|
return "⚠️ Not on a git branch";
|
|
12880
12945
|
}
|
|
12881
|
-
const storage = new ContextStorage(ConfigManager.getStorageDir(process.cwd()));
|
|
12882
12946
|
const branchContext = await storage.loadContext(targetBranch);
|
|
12883
12947
|
if (!branchContext) {
|
|
12884
12948
|
return `⚠️ No context found for branch '${targetBranch}'`;
|
|
@@ -12901,10 +12965,9 @@ var status = tool({
|
|
|
12901
12965
|
args: {},
|
|
12902
12966
|
async execute(args, context) {
|
|
12903
12967
|
try {
|
|
12904
|
-
|
|
12968
|
+
const { storage } = await initializeContext();
|
|
12905
12969
|
const git = GitOperations;
|
|
12906
12970
|
const currentBranch = await git.getCurrentBranch();
|
|
12907
|
-
const storage = new ContextStorage(ConfigManager.getStorageDir(process.cwd()));
|
|
12908
12971
|
const branches = await storage.listBranches();
|
|
12909
12972
|
let output = `
|
|
12910
12973
|
\uD83D\uDCCA Branch Memory Status`;
|
|
@@ -12972,8 +13035,7 @@ var deleteContext = tool({
|
|
|
12972
13035
|
},
|
|
12973
13036
|
async execute(args, context) {
|
|
12974
13037
|
try {
|
|
12975
|
-
|
|
12976
|
-
const storage = new ContextStorage(ConfigManager.getStorageDir(process.cwd()));
|
|
13038
|
+
const { storage } = await initializeContext();
|
|
12977
13039
|
await storage.deleteContext(args.branch);
|
|
12978
13040
|
return `✅ Deleted context for branch '${args.branch}'`;
|
|
12979
13041
|
} catch (error45) {
|
|
@@ -12989,8 +13051,7 @@ var list = tool({
|
|
|
12989
13051
|
},
|
|
12990
13052
|
async execute(args, context) {
|
|
12991
13053
|
try {
|
|
12992
|
-
|
|
12993
|
-
const storage = new ContextStorage(ConfigManager.getStorageDir(process.cwd()));
|
|
13054
|
+
const { storage } = await initializeContext();
|
|
12994
13055
|
const branches = await storage.listBranches();
|
|
12995
13056
|
if (branches.length === 0) {
|
|
12996
13057
|
return "No saved contexts found";
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "branch-memory-tools",
|
|
3
|
+
"owner": {
|
|
4
|
+
"name": "Davidcreador",
|
|
5
|
+
"email": "contact@davidcreador.com"
|
|
6
|
+
},
|
|
7
|
+
"metadata": {
|
|
8
|
+
"description": "OpenCode plugins for branch memory management",
|
|
9
|
+
"version": "1.0.0"
|
|
10
|
+
},
|
|
11
|
+
"plugins": [
|
|
12
|
+
{
|
|
13
|
+
"name": "branch-memory-manager",
|
|
14
|
+
"source": ".",
|
|
15
|
+
"description": "Automatically manages branch-specific context with auto-save and auto-load. Never lose your development context when switching git branches.",
|
|
16
|
+
"version": "0.1.3",
|
|
17
|
+
"author": {
|
|
18
|
+
"name": "Davidcreador"
|
|
19
|
+
},
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"keywords": [
|
|
22
|
+
"branch",
|
|
23
|
+
"context",
|
|
24
|
+
"git",
|
|
25
|
+
"auto-save",
|
|
26
|
+
"memory",
|
|
27
|
+
"workflow"
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { Plugin } from '@opencode-ai/plugin'
|
|
2
|
-
import { ContextStorage, GitOperations, ContextCollector, ConfigManager } from '../branch-memory/index.js'
|
|
2
|
+
import { ContextStorage, GitOperations, ContextCollector, ConfigManager, BranchMonitor } from '../branch-memory/index.js'
|
|
3
3
|
|
|
4
4
|
export const BranchMemoryPlugin: Plugin = async ({ project, client, $, directory, worktree }) => {
|
|
5
5
|
console.log('🧠 Branch Memory Plugin initializing...')
|
|
6
6
|
|
|
7
7
|
// Load configuration
|
|
8
|
-
ConfigManager
|
|
8
|
+
const configManager = new ConfigManager(directory)
|
|
9
9
|
|
|
10
10
|
// Check if we're in a git repository
|
|
11
11
|
const isGitRepo = await GitOperations.isGitRepo()
|
|
@@ -14,28 +14,28 @@ export const BranchMemoryPlugin: Plugin = async ({ project, client, $, directory
|
|
|
14
14
|
return {}
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
const
|
|
18
|
-
const
|
|
17
|
+
const config = await configManager.load()
|
|
18
|
+
const storage = new ContextStorage(configManager.getStorageDir(), config)
|
|
19
|
+
const collector = new ContextCollector(config, client)
|
|
19
20
|
|
|
20
21
|
// Track last auto-save time to avoid too frequent saves
|
|
21
22
|
let lastAutoSave = 0
|
|
22
|
-
const AUTO_SAVE_THROTTLE = 5000 // 5 seconds
|
|
23
23
|
|
|
24
24
|
// Auto-save function with throttling
|
|
25
25
|
const autoSave = async (reason: string) => {
|
|
26
|
-
const
|
|
27
|
-
if (
|
|
26
|
+
const currentConfig = await configManager.load()
|
|
27
|
+
if (currentConfig.autoSave.enabled) {
|
|
28
28
|
const now = Date.now()
|
|
29
29
|
|
|
30
|
-
if (now - lastAutoSave >
|
|
30
|
+
if (now - lastAutoSave > currentConfig.autoSave.throttleMs) {
|
|
31
31
|
try {
|
|
32
32
|
const currentBranch = await GitOperations.getCurrentBranch()
|
|
33
33
|
|
|
34
34
|
if (currentBranch) {
|
|
35
35
|
const context = await collector.collectContext(
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
currentConfig.context.defaultInclude.includes('messages'),
|
|
37
|
+
currentConfig.context.defaultInclude.includes('todos'),
|
|
38
|
+
currentConfig.context.defaultInclude.includes('files'),
|
|
39
39
|
reason
|
|
40
40
|
)
|
|
41
41
|
|
|
@@ -50,62 +50,53 @@ export const BranchMemoryPlugin: Plugin = async ({ project, client, $, directory
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
//
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
if (
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
config.context.defaultInclude.includes('messages'),
|
|
72
|
-
config.context.defaultInclude.includes('todos'),
|
|
73
|
-
config.context.defaultInclude.includes('files'),
|
|
74
|
-
'branch change'
|
|
75
|
-
))
|
|
76
|
-
console.log(`💾 Saved context for old branch '${oldBranchName}'`)
|
|
77
|
-
}
|
|
53
|
+
// Initialize branch monitor with callback
|
|
54
|
+
const branchMonitor = new BranchMonitor(
|
|
55
|
+
async (oldBranch, newBranch) => {
|
|
56
|
+
console.log(`🔄 Branch changed: ${oldBranch || '(none)'} → ${newBranch}`)
|
|
57
|
+
|
|
58
|
+
const currentConfig = await configManager.load()
|
|
59
|
+
|
|
60
|
+
// Auto-save old branch context
|
|
61
|
+
if (oldBranch && currentConfig.autoSave.onBranchChange) {
|
|
62
|
+
const context = await collector.collectContext(
|
|
63
|
+
currentConfig.context.defaultInclude.includes('messages'),
|
|
64
|
+
currentConfig.context.defaultInclude.includes('todos'),
|
|
65
|
+
currentConfig.context.defaultInclude.includes('files'),
|
|
66
|
+
'branch change'
|
|
67
|
+
)
|
|
68
|
+
await storage.saveContext(oldBranch, context)
|
|
69
|
+
console.log(`💾 Saved context for old branch '${oldBranch}'`)
|
|
70
|
+
}
|
|
78
71
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
88
|
-
} else if (config.contextLoading === 'ask') {
|
|
89
|
-
console.log(`ℹ️ Context available for branch '${newBranch}'`)
|
|
90
|
-
console.log(` Use @branch-memory_load to restore it`)
|
|
72
|
+
// Auto-load new branch context
|
|
73
|
+
if (currentConfig.contextLoading === 'auto') {
|
|
74
|
+
const branchContext = await storage.loadContext(newBranch)
|
|
75
|
+
if (branchContext) {
|
|
76
|
+
console.log(`📥 Found context for branch '${newBranch}'`)
|
|
77
|
+
console.log(' Use @branch-memory_load to restore it')
|
|
78
|
+
} else {
|
|
79
|
+
console.log(`ℹ️ No saved context for branch '${newBranch}'`)
|
|
91
80
|
}
|
|
81
|
+
} else if (currentConfig.contextLoading === 'ask') {
|
|
82
|
+
console.log(`ℹ️ Context available for branch '${newBranch}'`)
|
|
83
|
+
console.log(` Use @branch-memory_load to restore it`)
|
|
92
84
|
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
85
|
+
},
|
|
86
|
+
config
|
|
87
|
+
)
|
|
97
88
|
|
|
98
|
-
// Start branch monitoring
|
|
99
|
-
|
|
89
|
+
// Start branch monitoring
|
|
90
|
+
await branchMonitor.start()
|
|
100
91
|
|
|
101
92
|
return {
|
|
102
93
|
// Hook: Auto-load context when session is created
|
|
103
94
|
'session.created': async (input: any, output: any) => {
|
|
104
95
|
console.log('🚀 Session created - checking for saved context...')
|
|
105
|
-
const
|
|
96
|
+
const currentConfig = await configManager.load()
|
|
106
97
|
const branch = await GitOperations.getCurrentBranch()
|
|
107
98
|
|
|
108
|
-
if (branch &&
|
|
99
|
+
if (branch && currentConfig.contextLoading === 'auto') {
|
|
109
100
|
const branchContext = await storage.loadContext(branch)
|
|
110
101
|
if (branchContext) {
|
|
111
102
|
console.log(`📥 Found context for branch '${branch}'`)
|
|
@@ -121,11 +112,11 @@ export const BranchMemoryPlugin: Plugin = async ({ project, client, $, directory
|
|
|
121
112
|
|
|
122
113
|
// Hook: Auto-save when session is updated (periodic checkpoints)
|
|
123
114
|
'session.updated': async (input: any, output: any) => {
|
|
124
|
-
const
|
|
125
|
-
if (
|
|
115
|
+
const currentConfig = await configManager.load()
|
|
116
|
+
if (currentConfig.autoSave.enabled) {
|
|
126
117
|
const now = Date.now()
|
|
127
|
-
// Only auto-save periodically
|
|
128
|
-
if (now - lastAutoSave >
|
|
118
|
+
// Only auto-save periodically
|
|
119
|
+
if (now - lastAutoSave > currentConfig.autoSave.periodicIntervalMs) {
|
|
129
120
|
await autoSave('session update')
|
|
130
121
|
}
|
|
131
122
|
}
|
|
@@ -135,8 +126,8 @@ export const BranchMemoryPlugin: Plugin = async ({ project, client, $, directory
|
|
|
135
126
|
unload: () => {
|
|
136
127
|
console.log('🧠 Branch Memory Plugin shutting down...')
|
|
137
128
|
|
|
138
|
-
//
|
|
139
|
-
|
|
129
|
+
// Stop branch monitoring
|
|
130
|
+
branchMonitor.stop()
|
|
140
131
|
|
|
141
132
|
// Save one last time before shutdown
|
|
142
133
|
autoSave('plugin unload').catch((error) => {
|
|
@@ -7,6 +7,22 @@ import {
|
|
|
7
7
|
ContextInjector,
|
|
8
8
|
} from "../branch-memory/index.js";
|
|
9
9
|
import type { ToolContext } from "@opencode-ai/plugin";
|
|
10
|
+
import type { PluginConfig } from "../branch-memory/types.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Shared initialization helper for all tools
|
|
14
|
+
* Reduces code duplication across tool implementations
|
|
15
|
+
*/
|
|
16
|
+
async function initializeContext(): Promise<{
|
|
17
|
+
configManager: ConfigManager;
|
|
18
|
+
config: PluginConfig;
|
|
19
|
+
storage: ContextStorage;
|
|
20
|
+
}> {
|
|
21
|
+
const configManager = new ConfigManager(process.cwd());
|
|
22
|
+
const config = await configManager.load();
|
|
23
|
+
const storage = new ContextStorage(configManager.getStorageDir(), config);
|
|
24
|
+
return { configManager, config, storage };
|
|
25
|
+
}
|
|
10
26
|
|
|
11
27
|
/**
|
|
12
28
|
* Save current session context for current git branch with optional filters
|
|
@@ -34,8 +50,7 @@ export const save: ToolDefinition = tool({
|
|
|
34
50
|
},
|
|
35
51
|
async execute(args, context: ToolContext) {
|
|
36
52
|
try {
|
|
37
|
-
|
|
38
|
-
const config = await ConfigManager.load();
|
|
53
|
+
const { config, storage } = await initializeContext();
|
|
39
54
|
|
|
40
55
|
const currentBranch = await GitOperations.getCurrentBranch();
|
|
41
56
|
|
|
@@ -52,9 +67,6 @@ export const save: ToolDefinition = tool({
|
|
|
52
67
|
args.description || "",
|
|
53
68
|
);
|
|
54
69
|
|
|
55
|
-
const storage = new ContextStorage(
|
|
56
|
-
ConfigManager.getStorageDir(process.cwd()),
|
|
57
|
-
);
|
|
58
70
|
await storage.saveContext(currentBranch, branchContext);
|
|
59
71
|
|
|
60
72
|
return `✅ Saved context for branch '${currentBranch}'
|
|
@@ -82,7 +94,7 @@ export const load: ToolDefinition = tool({
|
|
|
82
94
|
},
|
|
83
95
|
async execute(args, context: ToolContext) {
|
|
84
96
|
try {
|
|
85
|
-
|
|
97
|
+
const { storage } = await initializeContext();
|
|
86
98
|
|
|
87
99
|
const git = GitOperations;
|
|
88
100
|
const targetBranch = args.branch || (await git.getCurrentBranch());
|
|
@@ -91,9 +103,6 @@ export const load: ToolDefinition = tool({
|
|
|
91
103
|
return "⚠️ Not on a git branch";
|
|
92
104
|
}
|
|
93
105
|
|
|
94
|
-
const storage = new ContextStorage(
|
|
95
|
-
ConfigManager.getStorageDir(process.cwd()),
|
|
96
|
-
);
|
|
97
106
|
const branchContext = await storage.loadContext(targetBranch);
|
|
98
107
|
|
|
99
108
|
if (!branchContext) {
|
|
@@ -123,13 +132,10 @@ export const status: ToolDefinition = tool({
|
|
|
123
132
|
args: {},
|
|
124
133
|
async execute(args, context: ToolContext) {
|
|
125
134
|
try {
|
|
126
|
-
|
|
135
|
+
const { storage } = await initializeContext();
|
|
127
136
|
|
|
128
137
|
const git = GitOperations;
|
|
129
138
|
const currentBranch = await git.getCurrentBranch();
|
|
130
|
-
const storage = new ContextStorage(
|
|
131
|
-
ConfigManager.getStorageDir(process.cwd()),
|
|
132
|
-
);
|
|
133
139
|
const branches = await storage.listBranches();
|
|
134
140
|
|
|
135
141
|
let output = "\n📊 Branch Memory Status";
|
|
@@ -187,11 +193,8 @@ export const deleteContext: ToolDefinition = tool({
|
|
|
187
193
|
},
|
|
188
194
|
async execute(args, context: ToolContext) {
|
|
189
195
|
try {
|
|
190
|
-
|
|
196
|
+
const { storage } = await initializeContext();
|
|
191
197
|
|
|
192
|
-
const storage = new ContextStorage(
|
|
193
|
-
ConfigManager.getStorageDir(process.cwd()),
|
|
194
|
-
);
|
|
195
198
|
await storage.deleteContext(args.branch);
|
|
196
199
|
|
|
197
200
|
return `✅ Deleted context for branch '${args.branch}'`;
|
|
@@ -215,11 +218,8 @@ export const list: ToolDefinition = tool({
|
|
|
215
218
|
},
|
|
216
219
|
async execute(args, context: ToolContext) {
|
|
217
220
|
try {
|
|
218
|
-
|
|
221
|
+
const { storage } = await initializeContext();
|
|
219
222
|
|
|
220
|
-
const storage = new ContextStorage(
|
|
221
|
-
ConfigManager.getStorageDir(process.cwd()),
|
|
222
|
-
);
|
|
223
223
|
const branches = await storage.listBranches();
|
|
224
224
|
|
|
225
225
|
if (branches.length === 0) {
|
package/README.md
CHANGED
|
@@ -18,41 +18,74 @@ Automatically manages branch-specific context for OpenCode so you never lose you
|
|
|
18
18
|
|
|
19
19
|
### Installation
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
The plugin supports **three installation methods** to fit different workflows:
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
```bash
|
|
25
|
-
bunx install @davecodes/opencode-branch-memory-manager
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
**Windows (PowerShell):**
|
|
29
|
-
```powershell
|
|
30
|
-
bunx install @davecodes/opencode-branch-memory-manager
|
|
31
|
-
```
|
|
23
|
+
#### Method 1: NPM Package (Recommended for Individual Users)
|
|
32
24
|
|
|
33
|
-
|
|
25
|
+
Add the plugin to your `opencode.json`:
|
|
34
26
|
|
|
35
|
-
|
|
27
|
+
```json
|
|
36
28
|
{
|
|
37
29
|
"plugin": ["opencode-branch-memory-manager"]
|
|
38
30
|
}
|
|
39
31
|
```
|
|
40
32
|
|
|
33
|
+
OpenCode will automatically install the package from npm when you run `opencode`.
|
|
34
|
+
|
|
35
|
+
**Manual installation:**
|
|
36
|
+
```bash
|
|
37
|
+
bunx install opencode-branch-memory-manager
|
|
38
|
+
```
|
|
39
|
+
|
|
41
40
|
**Note:** This will add the plugin without removing any existing plugins you have.
|
|
42
41
|
|
|
43
|
-
####
|
|
42
|
+
#### Method 2: Direct Git Clone (For Development/Testing)
|
|
43
|
+
|
|
44
|
+
Clone the repository directly into your project:
|
|
44
45
|
|
|
45
|
-
**macOS/Linux:**
|
|
46
46
|
```bash
|
|
47
|
-
|
|
47
|
+
# Navigate to your project's .opencode directory
|
|
48
|
+
cd .opencode
|
|
49
|
+
|
|
50
|
+
# Clone the plugin
|
|
51
|
+
git clone https://github.com/Davidcreador/opencode-branch-memory-manager.git
|
|
52
|
+
|
|
53
|
+
# Install dependencies and build
|
|
54
|
+
cd opencode-branch-memory-manager
|
|
55
|
+
bun install
|
|
56
|
+
bun run build:local
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
The plugin will be automatically loaded when you run `opencode` from your project.
|
|
60
|
+
|
|
61
|
+
#### Method 3: Marketplace (For Teams/Organizations)
|
|
62
|
+
|
|
63
|
+
For team or organizational distribution, you can set up a marketplace:
|
|
64
|
+
|
|
65
|
+
**1. Add the marketplace to your `.opencode/settings.json`:**
|
|
66
|
+
```json
|
|
67
|
+
{
|
|
68
|
+
"extraKnownMarketplaces": {
|
|
69
|
+
"your-org": {
|
|
70
|
+
"source": {
|
|
71
|
+
"source": "github",
|
|
72
|
+
"repo": "your-org/opencode-plugins"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
48
77
|
```
|
|
49
78
|
|
|
50
|
-
**
|
|
51
|
-
```
|
|
52
|
-
|
|
79
|
+
**2. Enable the plugin:**
|
|
80
|
+
```json
|
|
81
|
+
{
|
|
82
|
+
"enabledPlugins": {
|
|
83
|
+
"branch-memory-manager@your-org": true
|
|
84
|
+
}
|
|
85
|
+
}
|
|
53
86
|
```
|
|
54
87
|
|
|
55
|
-
The plugin
|
|
88
|
+
The plugin will be installed from your organization's marketplace.
|
|
56
89
|
|
|
57
90
|
### Getting Started
|
|
58
91
|
|
|
@@ -466,6 +499,33 @@ bun run typecheck
|
|
|
466
499
|
bun test
|
|
467
500
|
```
|
|
468
501
|
|
|
502
|
+
## 🛠️ Local Development
|
|
503
|
+
|
|
504
|
+
For development, testing, and local usage, you can install directly without npm:
|
|
505
|
+
|
|
506
|
+
```bash
|
|
507
|
+
# 1. Install dependencies
|
|
508
|
+
bun install
|
|
509
|
+
|
|
510
|
+
# 2. Build
|
|
511
|
+
bun run build
|
|
512
|
+
|
|
513
|
+
# 3. The .opencode/ directory is ready to use
|
|
514
|
+
# The build creates .opencode/dist/ with all plugin files
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
The `.opencode/` directory structure is now ready:
|
|
518
|
+
```
|
|
519
|
+
.opencode/
|
|
520
|
+
├── dist/
|
|
521
|
+
│ └── branch-memory.js # Bundled plugin + tools
|
|
522
|
+
├── config/
|
|
523
|
+
│ └── branch-memory.json # Configuration
|
|
524
|
+
└── package.json # Dependencies
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
**Note:** For development, you can work directly in `src/` and run `npm run build:local` to test the `.opencode/` version.
|
|
528
|
+
|
|
469
529
|
## 📦 Publishing to npm
|
|
470
530
|
|
|
471
531
|
1. Update version in `package.json`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-branch-memory-manager",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Automatically manages branch-specific context for OpenCode with auto-save, auto-load, and git workflow integration",
|
|
5
5
|
"author": "Davidcreador",
|
|
6
6
|
"license": "MIT",
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
"url": "https://github.com/Davidcreador/opencode-branch-memory-manager/issues"
|
|
17
17
|
},
|
|
18
18
|
"files": [
|
|
19
|
-
".opencode"
|
|
19
|
+
".opencode",
|
|
20
|
+
"dist"
|
|
20
21
|
],
|
|
21
22
|
"scripts": {
|
|
22
23
|
"build": "npm run build:local",
|
|
@@ -39,6 +40,17 @@
|
|
|
39
40
|
"auto-save",
|
|
40
41
|
"workspace"
|
|
41
42
|
],
|
|
43
|
+
"opencode": {
|
|
44
|
+
"type": "plugin",
|
|
45
|
+
"entrypoint": ".opencode/plugin/branch-memory-plugin.ts",
|
|
46
|
+
"tools": [
|
|
47
|
+
"@branch-memory_save",
|
|
48
|
+
"@branch-memory_load",
|
|
49
|
+
"@branch-memory_status",
|
|
50
|
+
"@branch-memory_list",
|
|
51
|
+
"@branch-memory_deleteContext"
|
|
52
|
+
]
|
|
53
|
+
},
|
|
42
54
|
"dependencies": {
|
|
43
55
|
"chokidar": "^4.0.0",
|
|
44
56
|
"zod": "^3.22.0"
|