@syntesseraai/opencode-feature-factory 0.2.45 → 0.3.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/agents/building.md +13 -14
- package/agents/ff-acceptance.md +12 -15
- package/agents/ff-research.md +12 -16
- package/agents/ff-review.md +12 -15
- package/agents/ff-security.md +12 -15
- package/agents/ff-validate.md +12 -15
- package/agents/ff-well-architected.md +12 -15
- package/agents/planning.md +12 -24
- package/agents/reviewing.md +12 -24
- package/dist/index.js +7 -7
- package/dist/local-recall/daemon.d.ts +35 -0
- package/dist/local-recall/daemon.js +188 -0
- package/dist/local-recall/index.d.ts +14 -0
- package/dist/local-recall/index.js +20 -0
- package/dist/local-recall/mcp-server.d.ts +38 -0
- package/dist/local-recall/mcp-server.js +71 -0
- package/dist/local-recall/mcp-tools.d.ts +90 -0
- package/dist/local-recall/mcp-tools.js +162 -0
- package/dist/local-recall/memory-service.d.ts +31 -0
- package/dist/local-recall/memory-service.js +156 -0
- package/dist/local-recall/model-router.d.ts +23 -0
- package/dist/local-recall/model-router.js +41 -0
- package/dist/local-recall/processed-log.d.ts +41 -0
- package/dist/local-recall/processed-log.js +82 -0
- package/dist/local-recall/session-extractor.d.ts +19 -0
- package/dist/local-recall/session-extractor.js +172 -0
- package/dist/local-recall/storage-reader.d.ts +40 -0
- package/dist/local-recall/storage-reader.js +147 -0
- package/dist/local-recall/thinking-extractor.d.ts +16 -0
- package/dist/local-recall/thinking-extractor.js +132 -0
- package/dist/local-recall/types.d.ts +129 -0
- package/dist/local-recall/types.js +7 -0
- package/package.json +1 -1
- package/skills/ff-learning/SKILL.md +166 -689
- package/dist/learning/memory-get.d.ts +0 -24
- package/dist/learning/memory-get.js +0 -155
- package/dist/learning/memory-search.d.ts +0 -20
- package/dist/learning/memory-search.js +0 -193
- package/dist/learning/memory-store.d.ts +0 -20
- package/dist/learning/memory-store.js +0 -85
- package/dist/plugins/ff-learning-get-plugin.d.ts +0 -2
- package/dist/plugins/ff-learning-get-plugin.js +0 -55
- package/dist/plugins/ff-learning-search-plugin.d.ts +0 -2
- package/dist/plugins/ff-learning-search-plugin.js +0 -65
- package/dist/plugins/ff-learning-store-plugin.d.ts +0 -2
- package/dist/plugins/ff-learning-store-plugin.js +0 -70
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
export interface GetCriteria {
|
|
2
|
-
memoryId?: string;
|
|
3
|
-
filePath?: string;
|
|
4
|
-
}
|
|
5
|
-
export interface MemoryData {
|
|
6
|
-
id: string;
|
|
7
|
-
title: string;
|
|
8
|
-
description: string;
|
|
9
|
-
date: string;
|
|
10
|
-
memoryType: string;
|
|
11
|
-
agentId: string;
|
|
12
|
-
importance: number;
|
|
13
|
-
tags: string[];
|
|
14
|
-
source?: string;
|
|
15
|
-
relatedMemories?: string[];
|
|
16
|
-
context?: {
|
|
17
|
-
project?: string;
|
|
18
|
-
task?: string;
|
|
19
|
-
files?: string[];
|
|
20
|
-
};
|
|
21
|
-
content: string;
|
|
22
|
-
filePath: string;
|
|
23
|
-
}
|
|
24
|
-
export declare function getMemory(directory: string, criteria: GetCriteria): Promise<MemoryData | null>;
|
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
import { readFile } from 'fs/promises';
|
|
2
|
-
export async function getMemory(directory, criteria) {
|
|
3
|
-
try {
|
|
4
|
-
let filePath = null;
|
|
5
|
-
if (criteria.filePath) {
|
|
6
|
-
filePath = `${directory}/${criteria.filePath}`;
|
|
7
|
-
}
|
|
8
|
-
else if (criteria.memoryId) {
|
|
9
|
-
// Search for file by ID
|
|
10
|
-
filePath = await findFileById(directory, criteria.memoryId);
|
|
11
|
-
if (!filePath) {
|
|
12
|
-
return null;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
else {
|
|
16
|
-
return null;
|
|
17
|
-
}
|
|
18
|
-
const content = await readFile(filePath, 'utf-8');
|
|
19
|
-
const metadata = parseFrontmatter(content);
|
|
20
|
-
if (!metadata) {
|
|
21
|
-
return null;
|
|
22
|
-
}
|
|
23
|
-
// Extract content after frontmatter
|
|
24
|
-
const contentMatch = content.match(/^---\n[\s\S]*?\n---\n\n([\s\S]*)$/);
|
|
25
|
-
const bodyContent = contentMatch ? contentMatch[1] : '';
|
|
26
|
-
return {
|
|
27
|
-
id: metadata.id,
|
|
28
|
-
title: metadata.title,
|
|
29
|
-
description: metadata.description,
|
|
30
|
-
date: metadata.date,
|
|
31
|
-
memoryType: metadata.memory_type,
|
|
32
|
-
agentId: metadata.agent_id,
|
|
33
|
-
importance: metadata.importance,
|
|
34
|
-
tags: metadata.tags || [],
|
|
35
|
-
source: metadata.source,
|
|
36
|
-
relatedMemories: metadata.related_memories,
|
|
37
|
-
context: metadata.context,
|
|
38
|
-
content: bodyContent,
|
|
39
|
-
filePath: criteria.filePath || (filePath ? filePath.replace(`${directory}/`, '') : ''),
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
catch {
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
async function findFileById(cwd, id) {
|
|
47
|
-
const { glob } = await import('glob');
|
|
48
|
-
try {
|
|
49
|
-
const files = await glob('**/*.md', {
|
|
50
|
-
cwd: `${cwd}/.feature-factory/memories`,
|
|
51
|
-
absolute: true,
|
|
52
|
-
});
|
|
53
|
-
for (const file of files) {
|
|
54
|
-
try {
|
|
55
|
-
const content = await readFile(file, 'utf-8');
|
|
56
|
-
const metadata = parseFrontmatter(content);
|
|
57
|
-
if (metadata && metadata.id === id) {
|
|
58
|
-
return file;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
catch {
|
|
62
|
-
continue;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
catch {
|
|
67
|
-
// Directory might not exist
|
|
68
|
-
}
|
|
69
|
-
return null;
|
|
70
|
-
}
|
|
71
|
-
function parseFrontmatter(content) {
|
|
72
|
-
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
73
|
-
if (!match)
|
|
74
|
-
return null;
|
|
75
|
-
const frontmatter = match[1];
|
|
76
|
-
const result = {};
|
|
77
|
-
// Simple YAML parsing for our specific format
|
|
78
|
-
const lines = frontmatter.split('\n');
|
|
79
|
-
let currentKey = null;
|
|
80
|
-
let currentArray = [];
|
|
81
|
-
let currentObject = {};
|
|
82
|
-
let objectKey = null;
|
|
83
|
-
for (const line of lines) {
|
|
84
|
-
const trimmed = line.trim();
|
|
85
|
-
// Check for array item
|
|
86
|
-
if (trimmed.startsWith('- ')) {
|
|
87
|
-
const value = trimmed.substring(2).replace(/^['"]|['"]$/g, '');
|
|
88
|
-
if (currentKey) {
|
|
89
|
-
currentArray.push(value);
|
|
90
|
-
}
|
|
91
|
-
continue;
|
|
92
|
-
}
|
|
93
|
-
// Check for nested object property
|
|
94
|
-
if (trimmed.match(/^\w+:\s*/)) {
|
|
95
|
-
const colonIndex = trimmed.indexOf(':');
|
|
96
|
-
const key = trimmed.substring(0, colonIndex).trim();
|
|
97
|
-
const value = trimmed
|
|
98
|
-
.substring(colonIndex + 1)
|
|
99
|
-
.trim()
|
|
100
|
-
.replace(/^['"]|['"]$/g, '');
|
|
101
|
-
// Check if this is a top-level key or nested
|
|
102
|
-
if (line.startsWith(' ') && objectKey) {
|
|
103
|
-
// Nested property
|
|
104
|
-
if (key === 'files' && value === '') {
|
|
105
|
-
// Start of files array
|
|
106
|
-
currentObject[key] = [];
|
|
107
|
-
}
|
|
108
|
-
else {
|
|
109
|
-
currentObject[key] = value;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
else {
|
|
113
|
-
// Top-level key
|
|
114
|
-
if (currentKey && currentArray.length > 0) {
|
|
115
|
-
result[currentKey] = currentArray;
|
|
116
|
-
currentArray = [];
|
|
117
|
-
}
|
|
118
|
-
if (objectKey && Object.keys(currentObject).length > 0) {
|
|
119
|
-
result[objectKey] = currentObject;
|
|
120
|
-
currentObject = {};
|
|
121
|
-
}
|
|
122
|
-
if (value === '') {
|
|
123
|
-
// Could be start of array or object
|
|
124
|
-
currentKey = key;
|
|
125
|
-
currentArray = [];
|
|
126
|
-
objectKey = key;
|
|
127
|
-
currentObject = {};
|
|
128
|
-
}
|
|
129
|
-
else {
|
|
130
|
-
// Simple value
|
|
131
|
-
result[key] = value;
|
|
132
|
-
currentKey = null;
|
|
133
|
-
objectKey = null;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
continue;
|
|
137
|
-
}
|
|
138
|
-
// Check for array item in nested context
|
|
139
|
-
if (trimmed.startsWith('- ') && objectKey) {
|
|
140
|
-
const value = trimmed.substring(2).replace(/^['"]|['"]$/g, '');
|
|
141
|
-
if (!currentObject.files) {
|
|
142
|
-
currentObject.files = [];
|
|
143
|
-
}
|
|
144
|
-
currentObject.files.push(value);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
// Handle any remaining data
|
|
148
|
-
if (currentKey && currentArray.length > 0) {
|
|
149
|
-
result[currentKey] = currentArray;
|
|
150
|
-
}
|
|
151
|
-
if (objectKey && Object.keys(currentObject).length > 0) {
|
|
152
|
-
result[objectKey] = currentObject;
|
|
153
|
-
}
|
|
154
|
-
return result;
|
|
155
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
export interface SearchCriteria {
|
|
2
|
-
query: string;
|
|
3
|
-
tags?: string[];
|
|
4
|
-
memoryType?: 'episodic' | 'semantic' | 'procedural';
|
|
5
|
-
agentId?: string;
|
|
6
|
-
limit?: number;
|
|
7
|
-
minImportance?: number;
|
|
8
|
-
}
|
|
9
|
-
export interface MemoryMetadata {
|
|
10
|
-
id: string;
|
|
11
|
-
title: string;
|
|
12
|
-
description: string;
|
|
13
|
-
date: string;
|
|
14
|
-
memoryType: string;
|
|
15
|
-
agentId: string;
|
|
16
|
-
importance: number;
|
|
17
|
-
tags: string[];
|
|
18
|
-
filePath: string;
|
|
19
|
-
}
|
|
20
|
-
export declare function searchMemories(directory: string, criteria: SearchCriteria): Promise<MemoryMetadata[]>;
|
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
import { readFile, readdir } from 'fs/promises';
|
|
2
|
-
import { join, relative } from 'path';
|
|
3
|
-
export async function searchMemories(directory, criteria) {
|
|
4
|
-
const memoriesDir = `${directory}/.feature-factory/memories`;
|
|
5
|
-
const results = [];
|
|
6
|
-
try {
|
|
7
|
-
// Find all memory files
|
|
8
|
-
const files = await findAllMemoryFiles(memoriesDir);
|
|
9
|
-
// Parse and filter each file
|
|
10
|
-
for (const filePath of files) {
|
|
11
|
-
try {
|
|
12
|
-
const content = await readFile(filePath, 'utf-8');
|
|
13
|
-
const metadata = parseFrontmatter(content);
|
|
14
|
-
if (!metadata)
|
|
15
|
-
continue;
|
|
16
|
-
// Apply filters
|
|
17
|
-
if (criteria.memoryType && metadata.memory_type !== criteria.memoryType) {
|
|
18
|
-
continue;
|
|
19
|
-
}
|
|
20
|
-
if (criteria.agentId && metadata.agent_id !== criteria.agentId) {
|
|
21
|
-
continue;
|
|
22
|
-
}
|
|
23
|
-
if (criteria.minImportance !== undefined &&
|
|
24
|
-
metadata.importance < criteria.minImportance) {
|
|
25
|
-
continue;
|
|
26
|
-
}
|
|
27
|
-
if (criteria.tags && criteria.tags.length > 0) {
|
|
28
|
-
const tags = metadata.tags || [];
|
|
29
|
-
const hasMatchingTag = criteria.tags.some((tag) => tags.includes(tag));
|
|
30
|
-
if (!hasMatchingTag)
|
|
31
|
-
continue;
|
|
32
|
-
}
|
|
33
|
-
// Check query match
|
|
34
|
-
const queryLower = criteria.query.toLowerCase();
|
|
35
|
-
const title = metadata.title || '';
|
|
36
|
-
const description = metadata.description || '';
|
|
37
|
-
const tags = metadata.tags || [];
|
|
38
|
-
const titleMatch = title.toLowerCase().includes(queryLower);
|
|
39
|
-
const descMatch = description.toLowerCase().includes(queryLower);
|
|
40
|
-
const tagMatch = tags.some((tag) => tag.toLowerCase().includes(queryLower));
|
|
41
|
-
if (!titleMatch && !descMatch && !tagMatch) {
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
|
-
// Calculate relevance score
|
|
45
|
-
let relevance = 0;
|
|
46
|
-
if (titleMatch)
|
|
47
|
-
relevance += 3;
|
|
48
|
-
if (descMatch)
|
|
49
|
-
relevance += 2;
|
|
50
|
-
if (tagMatch)
|
|
51
|
-
relevance += 1;
|
|
52
|
-
results.push({
|
|
53
|
-
id: metadata.id,
|
|
54
|
-
title: metadata.title,
|
|
55
|
-
description: metadata.description,
|
|
56
|
-
date: metadata.date,
|
|
57
|
-
memoryType: metadata.memory_type,
|
|
58
|
-
agentId: metadata.agent_id,
|
|
59
|
-
importance: metadata.importance,
|
|
60
|
-
tags: tags,
|
|
61
|
-
filePath: relative(directory, filePath),
|
|
62
|
-
relevance,
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
catch {
|
|
66
|
-
// Skip files that can't be parsed
|
|
67
|
-
continue;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
// Sort by relevance and importance
|
|
71
|
-
results.sort((a, b) => {
|
|
72
|
-
const scoreA = a.relevance * a.importance;
|
|
73
|
-
const scoreB = b.relevance * b.importance;
|
|
74
|
-
return scoreB - scoreA;
|
|
75
|
-
});
|
|
76
|
-
// Limit results
|
|
77
|
-
const limit = criteria.limit || 10;
|
|
78
|
-
return results.slice(0, limit).map((r) => ({
|
|
79
|
-
id: r.id,
|
|
80
|
-
title: r.title,
|
|
81
|
-
description: r.description,
|
|
82
|
-
date: r.date,
|
|
83
|
-
memoryType: r.memoryType,
|
|
84
|
-
agentId: r.agentId,
|
|
85
|
-
importance: r.importance,
|
|
86
|
-
tags: r.tags,
|
|
87
|
-
filePath: r.filePath,
|
|
88
|
-
}));
|
|
89
|
-
}
|
|
90
|
-
catch {
|
|
91
|
-
return [];
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
async function findAllMemoryFiles(dir) {
|
|
95
|
-
const files = [];
|
|
96
|
-
try {
|
|
97
|
-
const entries = await readdir(dir, { withFileTypes: true, recursive: true });
|
|
98
|
-
for (const entry of entries) {
|
|
99
|
-
if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
100
|
-
files.push(join(dir, entry.parentPath || '', entry.name));
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
catch {
|
|
105
|
-
// Directory might not exist
|
|
106
|
-
}
|
|
107
|
-
return files;
|
|
108
|
-
}
|
|
109
|
-
function parseFrontmatter(content) {
|
|
110
|
-
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
111
|
-
if (!match)
|
|
112
|
-
return null;
|
|
113
|
-
const frontmatter = match[1];
|
|
114
|
-
const result = {};
|
|
115
|
-
// Simple YAML parsing for our specific format
|
|
116
|
-
const lines = frontmatter.split('\n');
|
|
117
|
-
let currentKey = null;
|
|
118
|
-
let currentArray = [];
|
|
119
|
-
let currentObject = {};
|
|
120
|
-
let objectKey = null;
|
|
121
|
-
for (const line of lines) {
|
|
122
|
-
const trimmed = line.trim();
|
|
123
|
-
// Check for array item
|
|
124
|
-
if (trimmed.startsWith('- ')) {
|
|
125
|
-
const value = trimmed.substring(2).replace(/^['"]|['"]$/g, '');
|
|
126
|
-
if (currentKey) {
|
|
127
|
-
currentArray.push(value);
|
|
128
|
-
}
|
|
129
|
-
continue;
|
|
130
|
-
}
|
|
131
|
-
// Check for nested object property
|
|
132
|
-
if (trimmed.match(/^\w+:\s*/)) {
|
|
133
|
-
const colonIndex = trimmed.indexOf(':');
|
|
134
|
-
const key = trimmed.substring(0, colonIndex).trim();
|
|
135
|
-
const value = trimmed
|
|
136
|
-
.substring(colonIndex + 1)
|
|
137
|
-
.trim()
|
|
138
|
-
.replace(/^['"]|['"]$/g, '');
|
|
139
|
-
// Check if this is a top-level key or nested
|
|
140
|
-
if (line.startsWith(' ') && objectKey) {
|
|
141
|
-
// Nested property
|
|
142
|
-
if (key === 'files' && value === '') {
|
|
143
|
-
// Start of files array
|
|
144
|
-
currentObject[key] = [];
|
|
145
|
-
}
|
|
146
|
-
else {
|
|
147
|
-
currentObject[key] = value;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
else {
|
|
151
|
-
// Top-level key
|
|
152
|
-
if (currentKey && currentArray.length > 0) {
|
|
153
|
-
result[currentKey] = currentArray;
|
|
154
|
-
currentArray = [];
|
|
155
|
-
}
|
|
156
|
-
if (objectKey && Object.keys(currentObject).length > 0) {
|
|
157
|
-
result[objectKey] = currentObject;
|
|
158
|
-
currentObject = {};
|
|
159
|
-
}
|
|
160
|
-
if (value === '') {
|
|
161
|
-
// Could be start of array or object
|
|
162
|
-
currentKey = key;
|
|
163
|
-
currentArray = [];
|
|
164
|
-
objectKey = key;
|
|
165
|
-
currentObject = {};
|
|
166
|
-
}
|
|
167
|
-
else {
|
|
168
|
-
// Simple value
|
|
169
|
-
result[key] = value;
|
|
170
|
-
currentKey = null;
|
|
171
|
-
objectKey = null;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
continue;
|
|
175
|
-
}
|
|
176
|
-
// Check for array item in nested context
|
|
177
|
-
if (trimmed.startsWith('- ') && objectKey) {
|
|
178
|
-
const value = trimmed.substring(2).replace(/^['"]|['"]$/g, '');
|
|
179
|
-
if (!currentObject.files) {
|
|
180
|
-
currentObject.files = [];
|
|
181
|
-
}
|
|
182
|
-
currentObject.files.push(value);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
// Handle any remaining data
|
|
186
|
-
if (currentKey && currentArray.length > 0) {
|
|
187
|
-
result[currentKey] = currentArray;
|
|
188
|
-
}
|
|
189
|
-
if (objectKey && Object.keys(currentObject).length > 0) {
|
|
190
|
-
result[objectKey] = currentObject;
|
|
191
|
-
}
|
|
192
|
-
return result;
|
|
193
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
export interface MemoryInput {
|
|
2
|
-
title: string;
|
|
3
|
-
description: string;
|
|
4
|
-
memoryType: 'episodic' | 'semantic' | 'procedural';
|
|
5
|
-
tags: string[];
|
|
6
|
-
importance: number;
|
|
7
|
-
content?: string;
|
|
8
|
-
source?: 'conversation' | 'research' | 'implementation' | 'review';
|
|
9
|
-
relatedMemories?: string[];
|
|
10
|
-
context?: {
|
|
11
|
-
project?: string;
|
|
12
|
-
task?: string;
|
|
13
|
-
files?: string[];
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
export interface MemoryResult {
|
|
17
|
-
id: string;
|
|
18
|
-
filePath: string;
|
|
19
|
-
}
|
|
20
|
-
export declare function storeMemory(directory: string, memoryInput: MemoryInput): Promise<MemoryResult>;
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import { v4 as uuidv4 } from 'uuid';
|
|
2
|
-
import { writeFile, mkdir } from 'fs/promises';
|
|
3
|
-
import { dirname } from 'path';
|
|
4
|
-
export async function storeMemory(directory, memoryInput) {
|
|
5
|
-
const id = uuidv4();
|
|
6
|
-
const date = new Date().toISOString();
|
|
7
|
-
// Determine file path based on memory type
|
|
8
|
-
const filePath = generateMemoryFilePath(memoryInput.memoryType, id, date);
|
|
9
|
-
const fullPath = `${directory}/${filePath}`;
|
|
10
|
-
// Generate frontmatter
|
|
11
|
-
const frontmatter = generateFrontmatter(id, date, memoryInput);
|
|
12
|
-
// Generate full content
|
|
13
|
-
const fullContent = `${frontmatter}\n\n${memoryInput.content || ''}`;
|
|
14
|
-
// Ensure directory exists
|
|
15
|
-
await mkdir(dirname(fullPath), { recursive: true });
|
|
16
|
-
// Write file
|
|
17
|
-
await writeFile(fullPath, fullContent, 'utf-8');
|
|
18
|
-
return {
|
|
19
|
-
id,
|
|
20
|
-
filePath,
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
function generateMemoryFilePath(memoryType, id, date) {
|
|
24
|
-
const dateObj = new Date(date);
|
|
25
|
-
const year = dateObj.getUTCFullYear();
|
|
26
|
-
const month = String(dateObj.getUTCMonth() + 1).padStart(2, '0');
|
|
27
|
-
const day = String(dateObj.getUTCDate()).padStart(2, '0');
|
|
28
|
-
const hours = String(dateObj.getUTCHours()).padStart(2, '0');
|
|
29
|
-
const minutes = String(dateObj.getUTCMinutes()).padStart(2, '0');
|
|
30
|
-
const seconds = String(dateObj.getUTCSeconds()).padStart(2, '0');
|
|
31
|
-
switch (memoryType) {
|
|
32
|
-
case 'episodic':
|
|
33
|
-
return `.feature-factory/memories/episodic/${year}/${month}/${day}/${year}-${month}-${day}-${hours}-${minutes}-${seconds}-episodic.md`;
|
|
34
|
-
case 'semantic':
|
|
35
|
-
return `.feature-factory/memories/semantic/${memoryType}-${id}.md`;
|
|
36
|
-
case 'procedural':
|
|
37
|
-
return `.feature-factory/memories/procedural/procedural-${id}.md`;
|
|
38
|
-
default:
|
|
39
|
-
return `.feature-factory/memories/semantic/semantic-${id}.md`;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
function generateFrontmatter(id, date, input) {
|
|
43
|
-
const frontmatter = {
|
|
44
|
-
id,
|
|
45
|
-
title: input.title,
|
|
46
|
-
description: input.description,
|
|
47
|
-
date,
|
|
48
|
-
memory_type: input.memoryType,
|
|
49
|
-
agent_id: 'agent', // This would be populated from context
|
|
50
|
-
importance: input.importance,
|
|
51
|
-
tags: input.tags,
|
|
52
|
-
};
|
|
53
|
-
if (input.source) {
|
|
54
|
-
frontmatter.source = input.source;
|
|
55
|
-
}
|
|
56
|
-
if (input.relatedMemories && input.relatedMemories.length > 0) {
|
|
57
|
-
frontmatter.related_memories = input.relatedMemories;
|
|
58
|
-
}
|
|
59
|
-
if (input.context) {
|
|
60
|
-
frontmatter.context = input.context;
|
|
61
|
-
}
|
|
62
|
-
// Convert to YAML format
|
|
63
|
-
const yamlLines = Object.entries(frontmatter).map(([key, value]) => {
|
|
64
|
-
if (Array.isArray(value)) {
|
|
65
|
-
return `${key}:\n${value.map((v) => ` - '${v}'`).join('\n')}`;
|
|
66
|
-
}
|
|
67
|
-
else if (typeof value === 'object' && value !== null) {
|
|
68
|
-
return `${key}:\n${Object.entries(value)
|
|
69
|
-
.map(([k, v]) => {
|
|
70
|
-
if (Array.isArray(v)) {
|
|
71
|
-
return ` ${k}:\n${v.map((item) => ` - '${item}'`).join('\n')}`;
|
|
72
|
-
}
|
|
73
|
-
return ` ${k}: '${v}'`;
|
|
74
|
-
})
|
|
75
|
-
.join('\n')}`;
|
|
76
|
-
}
|
|
77
|
-
else if (typeof value === 'number') {
|
|
78
|
-
return `${key}: ${value}`;
|
|
79
|
-
}
|
|
80
|
-
else {
|
|
81
|
-
return `${key}: '${value}'`;
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
|
-
return `---\n${yamlLines.join('\n')}\n---`;
|
|
85
|
-
}
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { tool } from '@opencode-ai/plugin/tool';
|
|
2
|
-
import { getMemory } from '../learning/memory-get.js';
|
|
3
|
-
export function createFFLearningGetTool() {
|
|
4
|
-
return tool({
|
|
5
|
-
description: 'Retrieve the full content of a specific memory by ID or file path',
|
|
6
|
-
args: {
|
|
7
|
-
memoryId: tool.schema.string().optional().describe('UUID of the memory to retrieve'),
|
|
8
|
-
filePath: tool.schema.string().optional().describe('Direct file path to the memory file'),
|
|
9
|
-
},
|
|
10
|
-
async execute(args, toolCtx) {
|
|
11
|
-
try {
|
|
12
|
-
if (!args.memoryId && !args.filePath) {
|
|
13
|
-
return JSON.stringify({
|
|
14
|
-
success: false,
|
|
15
|
-
error: 'Either memoryId or filePath must be provided',
|
|
16
|
-
}, null, 2);
|
|
17
|
-
}
|
|
18
|
-
const result = await getMemory(toolCtx.directory, {
|
|
19
|
-
memoryId: args.memoryId,
|
|
20
|
-
filePath: args.filePath,
|
|
21
|
-
});
|
|
22
|
-
if (!result) {
|
|
23
|
-
return JSON.stringify({
|
|
24
|
-
success: false,
|
|
25
|
-
error: `Memory not found: ${args.memoryId || args.filePath}`,
|
|
26
|
-
}, null, 2);
|
|
27
|
-
}
|
|
28
|
-
return JSON.stringify({
|
|
29
|
-
success: true,
|
|
30
|
-
memory: {
|
|
31
|
-
id: result.id,
|
|
32
|
-
title: result.title,
|
|
33
|
-
description: result.description,
|
|
34
|
-
date: result.date,
|
|
35
|
-
memoryType: result.memoryType,
|
|
36
|
-
agentId: result.agentId,
|
|
37
|
-
importance: result.importance,
|
|
38
|
-
tags: result.tags,
|
|
39
|
-
source: result.source,
|
|
40
|
-
relatedMemories: result.relatedMemories,
|
|
41
|
-
context: result.context,
|
|
42
|
-
content: result.content,
|
|
43
|
-
filePath: result.filePath,
|
|
44
|
-
},
|
|
45
|
-
}, null, 2);
|
|
46
|
-
}
|
|
47
|
-
catch (error) {
|
|
48
|
-
return JSON.stringify({
|
|
49
|
-
success: false,
|
|
50
|
-
error: `Failed to get memory: ${error}`,
|
|
51
|
-
}, null, 2);
|
|
52
|
-
}
|
|
53
|
-
},
|
|
54
|
-
});
|
|
55
|
-
}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { tool } from '@opencode-ai/plugin/tool';
|
|
2
|
-
import { searchMemories } from '../learning/memory-search.js';
|
|
3
|
-
export function createFFLearningSearchTool() {
|
|
4
|
-
return tool({
|
|
5
|
-
description: 'Search for memories by query, tags, type, or agent. Returns matching memory metadata sorted by relevance and importance',
|
|
6
|
-
args: {
|
|
7
|
-
query: tool.schema
|
|
8
|
-
.string()
|
|
9
|
-
.describe('Search query string to match against titles and descriptions'),
|
|
10
|
-
tags: tool.schema.array(tool.schema.string()).optional().describe('Filter by specific tags'),
|
|
11
|
-
memoryType: tool.schema
|
|
12
|
-
.enum(['episodic', 'semantic', 'procedural'])
|
|
13
|
-
.optional()
|
|
14
|
-
.describe('Filter by memory type'),
|
|
15
|
-
agentId: tool.schema.string().optional().describe('Filter by agent that created the memory'),
|
|
16
|
-
limit: tool.schema
|
|
17
|
-
.number()
|
|
18
|
-
.min(1)
|
|
19
|
-
.max(50)
|
|
20
|
-
.default(10)
|
|
21
|
-
.describe('Maximum number of results to return (default: 10)'),
|
|
22
|
-
minImportance: tool.schema
|
|
23
|
-
.number()
|
|
24
|
-
.min(0)
|
|
25
|
-
.max(1)
|
|
26
|
-
.optional()
|
|
27
|
-
.describe('Minimum importance threshold (0.0-1.0)'),
|
|
28
|
-
},
|
|
29
|
-
async execute(args, toolCtx) {
|
|
30
|
-
try {
|
|
31
|
-
const criteria = {
|
|
32
|
-
query: args.query,
|
|
33
|
-
tags: args.tags,
|
|
34
|
-
memoryType: args.memoryType,
|
|
35
|
-
agentId: args.agentId,
|
|
36
|
-
limit: args.limit,
|
|
37
|
-
minImportance: args.minImportance,
|
|
38
|
-
};
|
|
39
|
-
const results = await searchMemories(toolCtx.directory, criteria);
|
|
40
|
-
return JSON.stringify({
|
|
41
|
-
success: true,
|
|
42
|
-
count: results.length,
|
|
43
|
-
query: args.query,
|
|
44
|
-
memories: results.map((m) => ({
|
|
45
|
-
id: m.id,
|
|
46
|
-
title: m.title,
|
|
47
|
-
description: m.description,
|
|
48
|
-
memoryType: m.memoryType,
|
|
49
|
-
agentId: m.agentId,
|
|
50
|
-
importance: m.importance,
|
|
51
|
-
tags: m.tags,
|
|
52
|
-
date: m.date,
|
|
53
|
-
filePath: m.filePath,
|
|
54
|
-
})),
|
|
55
|
-
}, null, 2);
|
|
56
|
-
}
|
|
57
|
-
catch (error) {
|
|
58
|
-
return JSON.stringify({
|
|
59
|
-
success: false,
|
|
60
|
-
error: `Failed to search memories: ${error}`,
|
|
61
|
-
}, null, 2);
|
|
62
|
-
}
|
|
63
|
-
},
|
|
64
|
-
});
|
|
65
|
-
}
|