opencode-branch-memory-manager 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/.opencode/branch-memory/.gitkeep +0 -0
- package/.opencode/branch-memory/collector.ts +90 -0
- package/.opencode/branch-memory/config.ts +127 -0
- package/.opencode/branch-memory/git.ts +169 -0
- package/.opencode/branch-memory/index.ts +7 -0
- package/.opencode/branch-memory/injector.ts +108 -0
- package/.opencode/branch-memory/monitor.ts +230 -0
- package/.opencode/branch-memory/storage.ts +322 -0
- package/.opencode/branch-memory/types.ts +68 -0
- package/.opencode/dist/branch-memory.js +13040 -0
- package/.opencode/package-lock.json +82 -0
- package/.opencode/plugin/branch-memory-plugin.ts +149 -0
- package/.opencode/tool/branch-memory.ts +254 -0
- package/LICENSE +21 -0
- package/README.md +500 -0
- package/package.json +61 -0
|
File without changes
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { BranchContext, PluginConfig } from "./types.js";
|
|
2
|
+
import { GitOperations } from "./git.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Collects context data from various sources
|
|
6
|
+
*/
|
|
7
|
+
export class ContextCollector {
|
|
8
|
+
private config: PluginConfig;
|
|
9
|
+
|
|
10
|
+
constructor(config: PluginConfig) {
|
|
11
|
+
this.config = config;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Collect context from available sources
|
|
16
|
+
* @param includeMessages - Include conversation messages
|
|
17
|
+
* @param includeTodos - Include todo items
|
|
18
|
+
* @param includeFiles - Include file references
|
|
19
|
+
* @param description - Description of what's being saved
|
|
20
|
+
* @returns Complete context object
|
|
21
|
+
*/
|
|
22
|
+
async collectContext(
|
|
23
|
+
includeMessages: boolean = true,
|
|
24
|
+
includeTodos: boolean = true,
|
|
25
|
+
includeFiles: boolean = true,
|
|
26
|
+
description: string = "",
|
|
27
|
+
): Promise<BranchContext> {
|
|
28
|
+
const currentBranch = await GitOperations.getCurrentBranch();
|
|
29
|
+
|
|
30
|
+
if (!currentBranch) {
|
|
31
|
+
throw new Error("Not on a git branch");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const data: BranchContext["data"] = {
|
|
35
|
+
description: description || "",
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// Collect messages (placeholder - will use OpenCode SDK when available)
|
|
39
|
+
if (includeMessages) {
|
|
40
|
+
data.messages = await this.collectMessages();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Collect todos (placeholder - will use OpenCode SDK when available)
|
|
44
|
+
if (includeTodos) {
|
|
45
|
+
data.todos = await this.collectTodos();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Collect modified files from git
|
|
49
|
+
if (includeFiles) {
|
|
50
|
+
data.files = await GitOperations.getModifiedFiles();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Calculate metadata
|
|
54
|
+
const context: BranchContext = {
|
|
55
|
+
branch: currentBranch,
|
|
56
|
+
savedAt: new Date().toISOString(),
|
|
57
|
+
metadata: {
|
|
58
|
+
version: "1.0.0",
|
|
59
|
+
platform: process.platform,
|
|
60
|
+
size: JSON.stringify(data).length,
|
|
61
|
+
messageCount: data.messages?.length || 0,
|
|
62
|
+
todoCount: data.todos?.length || 0,
|
|
63
|
+
fileCount: data.files?.length || 0,
|
|
64
|
+
},
|
|
65
|
+
data,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
return context;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Collect conversation messages
|
|
73
|
+
* @returns Array of messages
|
|
74
|
+
*/
|
|
75
|
+
private async collectMessages(): Promise<BranchContext["data"]["messages"]> {
|
|
76
|
+
// Placeholder for SDK integration
|
|
77
|
+
// When OpenCode SDK is available, this will fetch recent messages
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Collect todo items
|
|
83
|
+
* @returns Array of todos
|
|
84
|
+
*/
|
|
85
|
+
private async collectTodos(): Promise<BranchContext["data"]["todos"]> {
|
|
86
|
+
// Placeholder for SDK integration
|
|
87
|
+
// When OpenCode SDK is available, this will fetch todo items
|
|
88
|
+
return [];
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import * as fs from 'fs/promises'
|
|
2
|
+
import * as path from 'path'
|
|
3
|
+
import { existsSync } from 'fs'
|
|
4
|
+
import type { PluginConfig } from './types.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Default configuration for the branch memory plugin
|
|
8
|
+
*/
|
|
9
|
+
const DEFAULT_CONFIG: PluginConfig = {
|
|
10
|
+
autoSave: {
|
|
11
|
+
enabled: true,
|
|
12
|
+
onMessageChange: true,
|
|
13
|
+
onBranchChange: true,
|
|
14
|
+
onToolExecute: true
|
|
15
|
+
},
|
|
16
|
+
contextLoading: 'auto',
|
|
17
|
+
context: {
|
|
18
|
+
defaultInclude: ['messages', 'todos', 'files'],
|
|
19
|
+
maxMessages: 50,
|
|
20
|
+
maxTodos: 20,
|
|
21
|
+
compression: false
|
|
22
|
+
},
|
|
23
|
+
storage: {
|
|
24
|
+
maxBackups: 5,
|
|
25
|
+
retentionDays: 90
|
|
26
|
+
},
|
|
27
|
+
monitoring: {
|
|
28
|
+
method: 'both',
|
|
29
|
+
pollingInterval: 1000
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Configuration manager for branch memory plugin
|
|
35
|
+
*/
|
|
36
|
+
export class ConfigManager {
|
|
37
|
+
private static configPath: string
|
|
38
|
+
private static projectPath: string
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Set the project path for configuration
|
|
42
|
+
* @param projectPath - The root directory of the project
|
|
43
|
+
*/
|
|
44
|
+
static setProjectPath(projectPath: string): void {
|
|
45
|
+
this.projectPath = projectPath
|
|
46
|
+
this.configPath = path.join(projectPath, '.opencode', 'config', 'branch-memory.json')
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Load configuration from project directory, falling back to defaults
|
|
51
|
+
* @returns Configuration object
|
|
52
|
+
*/
|
|
53
|
+
static async load(): Promise<PluginConfig> {
|
|
54
|
+
if (existsSync(this.configPath)) {
|
|
55
|
+
try {
|
|
56
|
+
const content = await fs.readFile(this.configPath, 'utf8')
|
|
57
|
+
const userConfig = JSON.parse(content) as Partial<PluginConfig>
|
|
58
|
+
|
|
59
|
+
// Deep merge user config with defaults
|
|
60
|
+
return this.deepMerge(DEFAULT_CONFIG, userConfig) as PluginConfig
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.warn('Failed to load config, using defaults:', error instanceof Error ? error.message : error)
|
|
63
|
+
return { ...DEFAULT_CONFIG }
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return { ...DEFAULT_CONFIG }
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Get default configuration
|
|
71
|
+
* @returns Default configuration object
|
|
72
|
+
*/
|
|
73
|
+
static getDefault(): PluginConfig {
|
|
74
|
+
return JSON.parse(JSON.stringify(DEFAULT_CONFIG)) as PluginConfig
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Get storage directory path
|
|
79
|
+
* @param projectPath - The root directory of the project
|
|
80
|
+
* @returns Path to storage directory
|
|
81
|
+
*/
|
|
82
|
+
static getStorageDir(projectPath: string): string {
|
|
83
|
+
return path.join(projectPath, '.opencode', 'branch-memory')
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Save configuration to project directory
|
|
88
|
+
* @param config - Configuration object to save
|
|
89
|
+
*/
|
|
90
|
+
static async save(config: PluginConfig): Promise<void> {
|
|
91
|
+
const configDir = path.dirname(this.configPath)
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
await fs.mkdir(configDir, { recursive: true })
|
|
95
|
+
await fs.writeFile(this.configPath, JSON.stringify(config, null, 2), 'utf8')
|
|
96
|
+
} catch (error) {
|
|
97
|
+
console.error('Failed to save configuration:', error)
|
|
98
|
+
throw error
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Deep merge two objects
|
|
104
|
+
* @param target - Target object (defaults)
|
|
105
|
+
* @param source - Source object (user config)
|
|
106
|
+
* @returns Merged object
|
|
107
|
+
*/
|
|
108
|
+
private static deepMerge<T>(target: T, source: Partial<T>): T {
|
|
109
|
+
const result = { ...target }
|
|
110
|
+
|
|
111
|
+
for (const key in source) {
|
|
112
|
+
const sourceValue = source[key]
|
|
113
|
+
const targetValue = result[key]
|
|
114
|
+
|
|
115
|
+
if (sourceValue !== undefined) {
|
|
116
|
+
if (typeof sourceValue === 'object' && sourceValue !== null && !Array.isArray(sourceValue) &&
|
|
117
|
+
typeof targetValue === 'object' && targetValue !== null && !Array.isArray(targetValue)) {
|
|
118
|
+
result[key] = this.deepMerge(targetValue as any, sourceValue as any) as any
|
|
119
|
+
} else {
|
|
120
|
+
result[key] = sourceValue as any
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return result
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { spawn } from 'child_process'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Git operations for branch memory manager
|
|
5
|
+
*/
|
|
6
|
+
export class GitOperations {
|
|
7
|
+
private static async runGitCommand(...args: string[]): Promise<{
|
|
8
|
+
stdout: string
|
|
9
|
+
stderr: string
|
|
10
|
+
exitCode: number
|
|
11
|
+
}> {
|
|
12
|
+
return new Promise((resolve, reject) => {
|
|
13
|
+
const git = spawn('git', args, {
|
|
14
|
+
cwd: process.cwd(),
|
|
15
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
let stdout = ''
|
|
19
|
+
let stderr = ''
|
|
20
|
+
|
|
21
|
+
git.stdout.on('data', (data) => {
|
|
22
|
+
stdout += data.toString()
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
git.stderr.on('data', (data) => {
|
|
26
|
+
stderr += data.toString()
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
git.on('close', (code) => {
|
|
30
|
+
resolve({
|
|
31
|
+
stdout: stdout.trim(),
|
|
32
|
+
stderr: stderr.trim(),
|
|
33
|
+
exitCode: code ?? 0
|
|
34
|
+
})
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
git.on('error', (err) => {
|
|
38
|
+
resolve({
|
|
39
|
+
stdout: stdout.trim(),
|
|
40
|
+
stderr: stderr.trim(),
|
|
41
|
+
exitCode: 1
|
|
42
|
+
})
|
|
43
|
+
})
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
static async getCurrentBranch(): Promise<string | null> {
|
|
48
|
+
const result = await this.runGitCommand('symbolic-ref', '--short', 'HEAD')
|
|
49
|
+
|
|
50
|
+
if (result.exitCode !== 0 || result.stderr.length > 0) {
|
|
51
|
+
return null
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const branch = result.stdout
|
|
55
|
+
|
|
56
|
+
if (branch === 'HEAD' || branch === '') {
|
|
57
|
+
return null
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return branch
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
static async getGitDir(): Promise<string | null> {
|
|
64
|
+
const result = await this.runGitCommand('rev-parse', '--git-dir')
|
|
65
|
+
|
|
66
|
+
if (result.exitCode !== 0 || result.stderr.length > 0) {
|
|
67
|
+
return null
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return result.stdout
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
static async isGitRepo(): Promise<boolean> {
|
|
74
|
+
const gitDir = await this.getGitDir()
|
|
75
|
+
return gitDir !== null
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
static async isBareRepo(): Promise<boolean> {
|
|
79
|
+
const result = await this.runGitCommand('rev-parse', '--is-bare-repository')
|
|
80
|
+
return result.stdout === 'true' && result.exitCode === 0
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
static async getModifiedFiles(): Promise<string[]> {
|
|
84
|
+
const result = await this.runGitCommand('diff', '--name-only')
|
|
85
|
+
|
|
86
|
+
if (result.exitCode !== 0) {
|
|
87
|
+
return []
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (result.stdout.length === 0) {
|
|
91
|
+
return []
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return result.stdout.split('\n').filter(f => f.length > 0)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
static async getAllBranches(): Promise<string[]> {
|
|
98
|
+
const result = await this.runGitCommand('branch', "--format='%(refname:short)'")
|
|
99
|
+
|
|
100
|
+
if (result.exitCode !== 0) {
|
|
101
|
+
return []
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (result.stdout.length === 0) {
|
|
105
|
+
return []
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return result.stdout.split('\n').filter(f => f.length > 0)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
static sanitizeBranchName(branch: string): string {
|
|
112
|
+
return branch
|
|
113
|
+
.replace(/[\/\\:*?"<>|]/g, '_')
|
|
114
|
+
.replace(/\s+/g, '-')
|
|
115
|
+
.replace(/[\u0000-\u001F\u007F-\u009F]/g, '')
|
|
116
|
+
.replace(/[\uE000-\uF8FF\uFFF0-\uFFFF]/g, '')
|
|
117
|
+
.substring(0, 255)
|
|
118
|
+
.trim()
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
static async getCurrentCommit(): Promise<string | null> {
|
|
122
|
+
const result = await this.runGitCommand('rev-parse', 'HEAD')
|
|
123
|
+
|
|
124
|
+
if (result.exitCode !== 0 || result.stderr.length > 0) {
|
|
125
|
+
return null
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return result.stdout
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
static async isGitAvailable(): Promise<boolean> {
|
|
132
|
+
const result = await this.runGitCommand('--version')
|
|
133
|
+
return result.exitCode === 0
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
static async getRemoteUrl(): Promise<string | null> {
|
|
137
|
+
const branch = await this.getCurrentBranch()
|
|
138
|
+
if (!branch) {
|
|
139
|
+
return null
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const remoteResult = await this.runGitCommand('config', `branch.${branch}.remote`)
|
|
143
|
+
if (remoteResult.exitCode !== 0) {
|
|
144
|
+
return null
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const remote = remoteResult.stdout
|
|
148
|
+
if (remote.length === 0) {
|
|
149
|
+
return null
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const urlResult = await this.runGitCommand('remote', 'get-url', remote)
|
|
153
|
+
if (urlResult.exitCode !== 0) {
|
|
154
|
+
return null
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return urlResult.stdout
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
static async hasUncommittedChanges(): Promise<boolean> {
|
|
161
|
+
const result = await this.runGitCommand('status', '--porcelain')
|
|
162
|
+
|
|
163
|
+
if (result.exitCode !== 0) {
|
|
164
|
+
return false
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return result.stdout.length > 0
|
|
168
|
+
}
|
|
169
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { ContextStorage } from "./storage.js";
|
|
2
|
+
export { GitOperations } from "./git.js";
|
|
3
|
+
export { ContextCollector } from "./collector.js";
|
|
4
|
+
export { ContextInjector } from "./injector.js";
|
|
5
|
+
export { BranchMonitor } from "./monitor.js";
|
|
6
|
+
export { ConfigManager } from "./config.js";
|
|
7
|
+
export type { BranchContext, PluginConfig, Message, Todo } from "./types.js";
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type { ToolContext } from '@opencode-ai/plugin'
|
|
2
|
+
import type { BranchContext } from './types.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Injects context into OpenCode sessions
|
|
6
|
+
*/
|
|
7
|
+
export class ContextInjector {
|
|
8
|
+
private context: ToolContext
|
|
9
|
+
|
|
10
|
+
constructor(context: ToolContext) {
|
|
11
|
+
this.context = context
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Inject context into current session without triggering AI response
|
|
16
|
+
* @param branchContext - Branch context to inject
|
|
17
|
+
*/
|
|
18
|
+
async injectContext(branchContext: BranchContext): Promise<void> {
|
|
19
|
+
const summary = this.formatContextSummary(branchContext)
|
|
20
|
+
|
|
21
|
+
// Inject context without triggering AI response
|
|
22
|
+
// This would use the OpenCode SDK client.session.prompt() with noReply: true
|
|
23
|
+
// For now, log the injection
|
|
24
|
+
console.log('\nš„ Context injected for branch:', branchContext.branch)
|
|
25
|
+
console.log('ā'.repeat(50))
|
|
26
|
+
console.log(summary)
|
|
27
|
+
console.log('ā'.repeat(50))
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Ask user if they want to load context (interactive mode)
|
|
32
|
+
* @param branchContext - Branch context to load
|
|
33
|
+
* @returns User's decision
|
|
34
|
+
*/
|
|
35
|
+
async askUserAboutContextLoading(
|
|
36
|
+
branchContext: BranchContext
|
|
37
|
+
): Promise<boolean> {
|
|
38
|
+
const summary = this.formatContextSummary(branchContext)
|
|
39
|
+
|
|
40
|
+
console.log('\nš„ Context available for branch:', branchContext.branch)
|
|
41
|
+
console.log('ā'.repeat(50))
|
|
42
|
+
console.log(summary)
|
|
43
|
+
console.log('ā'.repeat(50))
|
|
44
|
+
console.log('Load this context? (y/n)')
|
|
45
|
+
|
|
46
|
+
// For now, auto-return true (would use TUI API in production)
|
|
47
|
+
// In real implementation, this would use OpenCode TUI to prompt user
|
|
48
|
+
return true
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Format context as human-readable summary
|
|
53
|
+
* @param context - Branch context to format
|
|
54
|
+
* @returns Formatted summary string
|
|
55
|
+
*/
|
|
56
|
+
private formatContextSummary(context: BranchContext): string {
|
|
57
|
+
const lines = [
|
|
58
|
+
`# Branch Context Loaded: ${context.branch}`,
|
|
59
|
+
`Restored from: ${context.savedAt}`,
|
|
60
|
+
''
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
if (context.data.description) {
|
|
64
|
+
lines.push('## Description')
|
|
65
|
+
lines.push(context.data.description)
|
|
66
|
+
lines.push('')
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (context.data.messages && context.data.messages.length > 0) {
|
|
70
|
+
lines.push(`## Recent Messages (${context.data.messages.length})`)
|
|
71
|
+
lines.push('... [Conversation history loaded] ...')
|
|
72
|
+
lines.push('')
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (context.data.todos && context.data.todos.length > 0) {
|
|
76
|
+
lines.push(`## Todo Items (${context.data.todos.length})`)
|
|
77
|
+
for (const todo of context.data.todos) {
|
|
78
|
+
const checkbox = todo.status === 'completed' ? 'x' : ' '
|
|
79
|
+
lines.push(`- [${checkbox}] ${todo.content}`)
|
|
80
|
+
}
|
|
81
|
+
lines.push('')
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (context.data.files && context.data.files.length > 0) {
|
|
85
|
+
lines.push(`## Modified Files (${context.data.files.length})`)
|
|
86
|
+
for (const file of context.data.files) {
|
|
87
|
+
lines.push(`- ${file}`)
|
|
88
|
+
}
|
|
89
|
+
lines.push('')
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return lines.join('\n')
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Check if there is context data to inject
|
|
97
|
+
* @param context - Branch context to check
|
|
98
|
+
* @returns True if context has data
|
|
99
|
+
*/
|
|
100
|
+
hasContextData(context: BranchContext): boolean {
|
|
101
|
+
return !!(
|
|
102
|
+
context.data.description ||
|
|
103
|
+
(context.data.messages && context.data.messages.length > 0) ||
|
|
104
|
+
(context.data.todos && context.data.todos.length > 0) ||
|
|
105
|
+
(context.data.files && context.data.files.length > 0)
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
}
|