@veedubin/boomerang-v3 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/.github/workflows/npm-publish.yml +58 -0
- package/.opencode/skills/boomerang-agent-builder/SKILL.md +226 -0
- package/.opencode/skills/boomerang-architect/SKILL.md +252 -0
- package/.opencode/skills/boomerang-coder/SKILL.md +283 -0
- package/.opencode/skills/boomerang-explorer/SKILL.md +58 -0
- package/.opencode/skills/boomerang-git/SKILL.md +115 -0
- package/.opencode/skills/boomerang-handoff/SKILL.md +209 -0
- package/.opencode/skills/boomerang-init/SKILL.md +117 -0
- package/.opencode/skills/boomerang-linter/SKILL.md +92 -0
- package/.opencode/skills/boomerang-orchestrator/SKILL.md +401 -0
- package/.opencode/skills/boomerang-release/SKILL.md +116 -0
- package/.opencode/skills/boomerang-scraper/SKILL.md +105 -0
- package/.opencode/skills/boomerang-tester/SKILL.md +107 -0
- package/.opencode/skills/boomerang-writer/SKILL.md +93 -0
- package/.opencode/skills/mcp-specialist/SKILL.md +130 -0
- package/.opencode/skills/researcher/SKILL.md +118 -0
- package/AGENTS.md +333 -0
- package/README.md +305 -0
- package/dist/index.js +13 -0
- package/dist/memini-client/index.js +560 -0
- package/dist/memini-client/schema.js +13 -0
- package/dist/memory/contradictions.js +119 -0
- package/dist/memory/graph.js +86 -0
- package/dist/memory/index.js +314 -0
- package/dist/memory/kg.js +111 -0
- package/dist/memory/schema.js +10 -0
- package/dist/memory/tiered.js +104 -0
- package/dist/memory/trust.js +148 -0
- package/dist/protocol/types.js +6 -0
- package/package.json +41 -0
- package/packages/opencode-plugin/src/asset-loader.ts +201 -0
- package/packages/opencode-plugin/src/git.ts +77 -0
- package/packages/opencode-plugin/src/index.ts +346 -0
- package/packages/opencode-plugin/src/memory.ts +109 -0
- package/packages/opencode-plugin/src/orchestrator.ts +263 -0
- package/packages/opencode-plugin/src/quality-gates.ts +75 -0
- package/packages/opencode-plugin/src/types.ts +141 -0
- package/src/index.ts +16 -0
- package/src/memini-client/index.ts +762 -0
- package/src/memini-client/schema.ts +60 -0
- package/src/memory/contradictions.ts +164 -0
- package/src/memory/graph.ts +116 -0
- package/src/memory/index.ts +422 -0
- package/src/memory/kg.ts +166 -0
- package/src/memory/schema.ts +274 -0
- package/src/memory/tiered.ts +133 -0
- package/src/memory/trust.ts +218 -0
- package/src/protocol/types.ts +79 -0
- package/tests/index.test.ts +58 -0
- package/tests/memini-client.test.ts +321 -0
- package/tests/memory/index.test.ts +214 -0
- package/tsconfig.json +17 -0
- package/vitest.config.ts +19 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trust engine integration for memini-ai
|
|
3
|
+
*
|
|
4
|
+
* Provides trust scoring and adjustment for memory entries
|
|
5
|
+
*/
|
|
6
|
+
import { getClient } from '../memini-client/index.js';
|
|
7
|
+
const TRUST_SIGNALS = ['agent_used', 'agent_ignored', 'user_corrected', 'user_confirmed'];
|
|
8
|
+
/**
|
|
9
|
+
* Get trust score for a memory entry
|
|
10
|
+
*/
|
|
11
|
+
export async function getTrustScore(memoryId, client) {
|
|
12
|
+
const mc = client ?? getClient();
|
|
13
|
+
const result = await mc.getTrustScore(memoryId);
|
|
14
|
+
return {
|
|
15
|
+
id: memoryId,
|
|
16
|
+
trustScore: result ?? null,
|
|
17
|
+
trustLevel: null,
|
|
18
|
+
retrievalCount: null,
|
|
19
|
+
isArchived: null,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Adjust trust score based on feedback signal
|
|
24
|
+
*/
|
|
25
|
+
export async function adjustTrust(memoryId, signal, client) {
|
|
26
|
+
if (!TRUST_SIGNALS.includes(signal)) {
|
|
27
|
+
throw new Error(`Invalid trust signal: ${signal}. Must be one of: ${TRUST_SIGNALS.join(', ')}`);
|
|
28
|
+
}
|
|
29
|
+
const mc = client ?? getClient();
|
|
30
|
+
await mc.adjustTrust(memoryId, signal);
|
|
31
|
+
return {
|
|
32
|
+
success: true,
|
|
33
|
+
memoryId,
|
|
34
|
+
oldScore: null,
|
|
35
|
+
newScore: null,
|
|
36
|
+
signal,
|
|
37
|
+
action: null,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* List archived memories (trust below archive threshold)
|
|
42
|
+
*/
|
|
43
|
+
export async function listArchived(limit = 50, offset = 0, client) {
|
|
44
|
+
const mc = client ?? getClient();
|
|
45
|
+
const result = await mc.listArchived(limit, offset);
|
|
46
|
+
return result.map((e) => ({
|
|
47
|
+
id: e.id,
|
|
48
|
+
text: e.text,
|
|
49
|
+
vector: Array.from(e.vector || []),
|
|
50
|
+
sourceType: e.sourceType,
|
|
51
|
+
sourcePath: e.sourcePath || '',
|
|
52
|
+
timestamp: e.timestamp,
|
|
53
|
+
contentHash: e.contentHash || '',
|
|
54
|
+
}));
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Get decay engine status
|
|
58
|
+
*/
|
|
59
|
+
export async function getDecayStatus(client) {
|
|
60
|
+
const mc = client ?? getClient();
|
|
61
|
+
const result = await mc.getDecayStatus();
|
|
62
|
+
if (!result) {
|
|
63
|
+
return {
|
|
64
|
+
enabled: false,
|
|
65
|
+
fadingMemories: [],
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
enabled: result.enabled,
|
|
70
|
+
decayStats: {
|
|
71
|
+
totalMemories: result.totalMemories,
|
|
72
|
+
archivedCount: result.archivedCount,
|
|
73
|
+
fadingCount: result.fadingCount,
|
|
74
|
+
},
|
|
75
|
+
fadingMemories: [],
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* List fading memories (approaching archive threshold)
|
|
80
|
+
*/
|
|
81
|
+
export async function listFadingMemories(limit = 20, client) {
|
|
82
|
+
const mc = client ?? getClient();
|
|
83
|
+
const result = await mc.listFadingMemories(limit);
|
|
84
|
+
return result.map((e) => ({
|
|
85
|
+
id: e.id,
|
|
86
|
+
text: e.text,
|
|
87
|
+
vector: Array.from(e.vector || []),
|
|
88
|
+
sourceType: e.sourceType,
|
|
89
|
+
sourcePath: e.sourcePath || '',
|
|
90
|
+
timestamp: e.timestamp,
|
|
91
|
+
contentHash: e.contentHash || '',
|
|
92
|
+
}));
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Adjust decay rate for a specific memory
|
|
96
|
+
*/
|
|
97
|
+
export async function adjustDecayRate(memoryId, decayRate, client) {
|
|
98
|
+
// Clamp decay rate to valid range
|
|
99
|
+
const clampedRate = Math.max(0.1, Math.min(10.0, decayRate));
|
|
100
|
+
const mc = client ?? getClient();
|
|
101
|
+
await mc.adjustDecayRate(memoryId, clampedRate);
|
|
102
|
+
return {
|
|
103
|
+
success: true,
|
|
104
|
+
memoryId,
|
|
105
|
+
newDecayRate: clampedRate,
|
|
106
|
+
message: 'Decay rate adjusted',
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Trigger memory consolidation
|
|
111
|
+
*/
|
|
112
|
+
export async function triggerConsolidation(force = false, client) {
|
|
113
|
+
const mc = client ?? getClient();
|
|
114
|
+
const result = await mc.triggerConsolidation(force);
|
|
115
|
+
if (!result) {
|
|
116
|
+
return {
|
|
117
|
+
success: false,
|
|
118
|
+
pairsFound: 0,
|
|
119
|
+
pairsMerged: 0,
|
|
120
|
+
memoriesConsolidated: 0,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
success: true,
|
|
125
|
+
pairsFound: 0,
|
|
126
|
+
pairsMerged: result.mergedCount,
|
|
127
|
+
memoriesConsolidated: result.mergedCount,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Adapt a raw memory from memini-ai to our MemoryEntry type
|
|
132
|
+
*/
|
|
133
|
+
function adaptMemoryEntry(meminiEntry) {
|
|
134
|
+
return {
|
|
135
|
+
id: meminiEntry.id,
|
|
136
|
+
text: meminiEntry.text,
|
|
137
|
+
vector: Array.from(meminiEntry.vector || []),
|
|
138
|
+
sourceType: meminiEntry.sourceType,
|
|
139
|
+
sourcePath: meminiEntry.sourcePath || '',
|
|
140
|
+
timestamp: meminiEntry.timestamp,
|
|
141
|
+
contentHash: meminiEntry.contentHash || '',
|
|
142
|
+
metadataJson: meminiEntry.metadataJson,
|
|
143
|
+
sessionId: meminiEntry.sessionId,
|
|
144
|
+
projectId: meminiEntry.projectId,
|
|
145
|
+
score: meminiEntry.score,
|
|
146
|
+
trustScore: meminiEntry.trustScore,
|
|
147
|
+
};
|
|
148
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@veedubin/boomerang-v3",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Multi-agent orchestration plugin for OpenCode with memini-ai memory",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/Veedubin/Boomerang-v3"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/Veedubin/Boomerang-v3#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/Veedubin/Boomerang-v3/issues"
|
|
15
|
+
},
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"import": "./dist/index.js",
|
|
19
|
+
"types": "./dist/index.d.ts"
|
|
20
|
+
},
|
|
21
|
+
"./package.json": "./package.json"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsc",
|
|
25
|
+
"typecheck": "tsc --noEmit",
|
|
26
|
+
"test": "vitest run",
|
|
27
|
+
"test:watch": "vitest",
|
|
28
|
+
"lint": "eslint src --ext .ts"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@modelcontextprotocol/sdk": "^1.0.0"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/node": "^22.0.0",
|
|
35
|
+
"typescript": "^5.7.0",
|
|
36
|
+
"vitest": "^3.0.0"
|
|
37
|
+
},
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=22.0.0"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Boomerang Asset Loader v3.0.0
|
|
3
|
+
*
|
|
4
|
+
* Loads agents and skills from the bundled directories.
|
|
5
|
+
* Self-contained - no cross-package imports.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { readFileSync, readdirSync } from 'fs';
|
|
9
|
+
import { join, dirname } from 'path';
|
|
10
|
+
import { fileURLToPath } from 'url';
|
|
11
|
+
import type { AgentDefinition, SkillDefinition } from './types.js';
|
|
12
|
+
|
|
13
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
14
|
+
const __dirname = dirname(__filename);
|
|
15
|
+
|
|
16
|
+
// Cache for loaded assets
|
|
17
|
+
let agentsCache: AgentDefinition[] | null = null;
|
|
18
|
+
let skillsCache: SkillDefinition[] | null = null;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Parse YAML frontmatter from markdown content
|
|
22
|
+
*/
|
|
23
|
+
function parseFrontmatter(content: string): Record<string, string> {
|
|
24
|
+
const frontmatter: Record<string, string> = {};
|
|
25
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
26
|
+
|
|
27
|
+
if (!match) {
|
|
28
|
+
return frontmatter;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const lines = match[1].split('\n');
|
|
32
|
+
let currentKey = '';
|
|
33
|
+
let currentValue = '';
|
|
34
|
+
let inMultiline = false;
|
|
35
|
+
|
|
36
|
+
for (const line of lines) {
|
|
37
|
+
if (inMultiline) {
|
|
38
|
+
if (line.startsWith(' ') || line.startsWith('\t')) {
|
|
39
|
+
currentValue += '\n' + line.replace(/^[ ]{4}|^\t/, '');
|
|
40
|
+
} else {
|
|
41
|
+
frontmatter[currentKey] = currentValue.trim();
|
|
42
|
+
inMultiline = false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const keyMatch = line.match(/^(\w+):\s*(.*)/);
|
|
47
|
+
if (keyMatch) {
|
|
48
|
+
currentKey = keyMatch[1];
|
|
49
|
+
currentValue = keyMatch[2];
|
|
50
|
+
|
|
51
|
+
if (currentValue === '' || currentValue === '|') {
|
|
52
|
+
inMultiline = true;
|
|
53
|
+
} else {
|
|
54
|
+
frontmatter[currentKey] = currentValue.trim();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return frontmatter;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Extract content after frontmatter (the main body)
|
|
64
|
+
*/
|
|
65
|
+
function extractContent(content: string): string {
|
|
66
|
+
const match = content.match(/^---\n[\s\S]*?\n---\n?([\s\S]*)/);
|
|
67
|
+
return match ? match[1].trim() : content;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Parse skills array from frontmatter value
|
|
72
|
+
*/
|
|
73
|
+
function parseSkillsArray(frontmatterValue: string): string[] {
|
|
74
|
+
const skillsMatch = frontmatterValue.match(/\[([\s\S]*?)\]/);
|
|
75
|
+
if (!skillsMatch) {
|
|
76
|
+
return [];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const skillsContent = skillsMatch[1];
|
|
80
|
+
return skillsContent
|
|
81
|
+
.split('\n')
|
|
82
|
+
.map(s => s.replace(/^\s*["']|["']\s*$/g, '').trim())
|
|
83
|
+
.filter(s => s.length > 0);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Load all agents from the agents/ directory
|
|
88
|
+
*/
|
|
89
|
+
export function loadAgents(): AgentDefinition[] {
|
|
90
|
+
if (agentsCache) {
|
|
91
|
+
return agentsCache;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const agents: AgentDefinition[] = [];
|
|
95
|
+
const agentsDir = join(__dirname, '..', 'agents');
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
const files = readdirSync(agentsDir).filter(f => f.endsWith('.md'));
|
|
99
|
+
|
|
100
|
+
for (const file of files) {
|
|
101
|
+
const filePath = join(agentsDir, file);
|
|
102
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
103
|
+
const frontmatter = parseFrontmatter(content);
|
|
104
|
+
const systemPrompt = extractContent(content);
|
|
105
|
+
|
|
106
|
+
const name = file.replace(/\.md$/, '');
|
|
107
|
+
const skills = frontmatter.skills ? parseSkillsArray(frontmatter.skills) : [];
|
|
108
|
+
|
|
109
|
+
agents.push({
|
|
110
|
+
name,
|
|
111
|
+
description: frontmatter.description || '',
|
|
112
|
+
systemPrompt,
|
|
113
|
+
skills
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
} catch (error) {
|
|
117
|
+
console.error('Error loading agents:', error);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
agentsCache = agents;
|
|
121
|
+
return agents;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Load all skills from the skills/ subdirectories
|
|
126
|
+
*/
|
|
127
|
+
export function loadSkills(): SkillDefinition[] {
|
|
128
|
+
if (skillsCache) {
|
|
129
|
+
return skillsCache;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const skills: SkillDefinition[] = [];
|
|
133
|
+
const skillsDir = join(__dirname, '..', 'skills');
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
const skillDirs = readdirSync(skillsDir).filter(f => {
|
|
137
|
+
try {
|
|
138
|
+
return readdirSync(join(skillsDir, f)).some(file => file === 'SKILL.md');
|
|
139
|
+
} catch {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
for (const dir of skillDirs) {
|
|
145
|
+
const skillPath = join(skillsDir, dir, 'SKILL.md');
|
|
146
|
+
const content = readFileSync(skillPath, 'utf-8');
|
|
147
|
+
const frontmatter = parseFrontmatter(content);
|
|
148
|
+
const instructions = extractContent(content);
|
|
149
|
+
const name = frontmatter.name || dir;
|
|
150
|
+
|
|
151
|
+
skills.push({
|
|
152
|
+
name,
|
|
153
|
+
description: frontmatter.description || '',
|
|
154
|
+
instructions
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
} catch (error) {
|
|
158
|
+
console.error('Error loading skills:', error);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
skillsCache = skills;
|
|
162
|
+
return skills;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Get a specific agent by name
|
|
167
|
+
*/
|
|
168
|
+
export function getAgent(name: string): AgentDefinition | undefined {
|
|
169
|
+
const agents = loadAgents();
|
|
170
|
+
return agents.find(a => a.name === name);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Get a specific skill by name
|
|
175
|
+
*/
|
|
176
|
+
export function getSkill(name: string): SkillDefinition | undefined {
|
|
177
|
+
const skills = loadSkills();
|
|
178
|
+
return skills.find(s => s.name === name);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Clear the cache (useful for testing)
|
|
183
|
+
*/
|
|
184
|
+
export function clearCache(): void {
|
|
185
|
+
agentsCache = null;
|
|
186
|
+
skillsCache = null;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* List available agents
|
|
191
|
+
*/
|
|
192
|
+
export function listAvailableAgents(): string[] {
|
|
193
|
+
return loadAgents().map(a => a.name);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* List available skills
|
|
198
|
+
*/
|
|
199
|
+
export function listAvailableSkills(): string[] {
|
|
200
|
+
return loadSkills().map(s => s.name);
|
|
201
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { GitStatus, GitCommitResult } from './types.js';
|
|
2
|
+
|
|
3
|
+
export async function checkGitStatus(
|
|
4
|
+
$: (strings: TemplateStringsArray, ...values: unknown[]) => Promise<unknown>
|
|
5
|
+
): Promise<GitStatus> {
|
|
6
|
+
try {
|
|
7
|
+
const statusResult = await $`git status --porcelain`;
|
|
8
|
+
const files = (statusResult as { stdout: string }).stdout
|
|
9
|
+
.split('\n')
|
|
10
|
+
.filter((line: string) => line.trim().length > 0)
|
|
11
|
+
.map((line: string) => line.slice(3));
|
|
12
|
+
const branchResult = await $`git branch --show-current`;
|
|
13
|
+
const branch = (branchResult as { stdout: string }).stdout.trim();
|
|
14
|
+
return {
|
|
15
|
+
isDirty: files.length > 0,
|
|
16
|
+
files,
|
|
17
|
+
branch,
|
|
18
|
+
ahead: 0,
|
|
19
|
+
behind: 0,
|
|
20
|
+
};
|
|
21
|
+
} catch {
|
|
22
|
+
return { isDirty: false, files: [], branch: '', ahead: 0, behind: 0 };
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function commitCheckpoint(
|
|
27
|
+
$: (strings: TemplateStringsArray, ...values: unknown[]) => Promise<unknown>,
|
|
28
|
+
message = 'wip: checkpoint'
|
|
29
|
+
): Promise<GitCommitResult> {
|
|
30
|
+
try {
|
|
31
|
+
await $`git add -A`;
|
|
32
|
+
const result = await $`git commit -m ${message}`;
|
|
33
|
+
const hashMatch = (result as { stdout: string }).stdout.match(/\[.+?\s+([a-f0-9]+)\]/);
|
|
34
|
+
return {
|
|
35
|
+
success: true,
|
|
36
|
+
hash: hashMatch?.[1],
|
|
37
|
+
message,
|
|
38
|
+
};
|
|
39
|
+
} catch (error) {
|
|
40
|
+
return {
|
|
41
|
+
success: false,
|
|
42
|
+
error: error instanceof Error ? error.message : String(error),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function commitWithMessage(
|
|
48
|
+
$: (strings: TemplateStringsArray, ...values: unknown[]) => Promise<unknown>,
|
|
49
|
+
message: string
|
|
50
|
+
): Promise<GitCommitResult> {
|
|
51
|
+
try {
|
|
52
|
+
await $`git add -A`;
|
|
53
|
+
const result = await $`git commit -m ${message}`;
|
|
54
|
+
const hashMatch = (result as { stdout: string }).stdout.match(/\[.+?\s+([a-f0-9]+)\]/);
|
|
55
|
+
return {
|
|
56
|
+
success: true,
|
|
57
|
+
hash: hashMatch?.[1],
|
|
58
|
+
message,
|
|
59
|
+
};
|
|
60
|
+
} catch (error) {
|
|
61
|
+
return {
|
|
62
|
+
success: false,
|
|
63
|
+
error: error instanceof Error ? error.message : String(error),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function generateCommitMessage(prompt: string): string {
|
|
69
|
+
const lower = prompt.toLowerCase();
|
|
70
|
+
let prefix = 'feat:';
|
|
71
|
+
if (lower.includes('fix') || lower.includes('bug')) prefix = 'fix:';
|
|
72
|
+
else if (lower.includes('test')) prefix = 'test:';
|
|
73
|
+
else if (lower.includes('doc')) prefix = 'docs:';
|
|
74
|
+
else if (lower.includes('refactor')) prefix = 'refactor:';
|
|
75
|
+
const summary = prompt.split('\n')[0].slice(0, 72);
|
|
76
|
+
return `${prefix} ${summary}`;
|
|
77
|
+
}
|