@itz4blitz/agentful 1.2.0 → 1.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/README.md +28 -1
- package/bin/cli.js +11 -1055
- package/bin/hooks/block-file-creation.js +271 -0
- package/bin/hooks/product-spec-watcher.js +151 -0
- package/lib/index.js +0 -11
- package/lib/init.js +2 -21
- package/lib/parallel-execution.js +235 -0
- package/lib/presets.js +26 -4
- package/package.json +4 -7
- package/template/.claude/agents/architect.md +2 -2
- package/template/.claude/agents/backend.md +17 -30
- package/template/.claude/agents/frontend.md +17 -39
- package/template/.claude/agents/orchestrator.md +63 -4
- package/template/.claude/agents/product-analyzer.md +1 -1
- package/template/.claude/agents/tester.md +16 -29
- package/template/.claude/commands/agentful-generate.md +221 -14
- package/template/.claude/commands/agentful-init.md +621 -0
- package/template/.claude/commands/agentful-product.md +1 -1
- package/template/.claude/commands/agentful-start.md +99 -1
- package/template/.claude/product/EXAMPLES.md +2 -2
- package/template/.claude/product/index.md +1 -1
- package/template/.claude/settings.json +22 -0
- package/template/.claude/skills/research/SKILL.md +432 -0
- package/template/CLAUDE.md +5 -6
- package/template/bin/hooks/architect-drift-detector.js +242 -0
- package/template/bin/hooks/product-spec-watcher.js +151 -0
- package/version.json +1 -1
- package/bin/hooks/post-agent.js +0 -101
- package/bin/hooks/post-feature.js +0 -227
- package/bin/hooks/pre-agent.js +0 -118
- package/bin/hooks/pre-feature.js +0 -138
- package/lib/VALIDATION_README.md +0 -455
- package/lib/ci/claude-action-integration.js +0 -641
- package/lib/ci/index.js +0 -10
- package/lib/core/analyzer.js +0 -497
- package/lib/core/cli.js +0 -141
- package/lib/core/detectors/conventions.js +0 -342
- package/lib/core/detectors/framework.js +0 -276
- package/lib/core/detectors/index.js +0 -15
- package/lib/core/detectors/language.js +0 -199
- package/lib/core/detectors/patterns.js +0 -356
- package/lib/core/generator.js +0 -626
- package/lib/core/index.js +0 -9
- package/lib/core/output-parser.js +0 -458
- package/lib/core/storage.js +0 -515
- package/lib/core/templates.js +0 -556
- package/lib/pipeline/cli.js +0 -423
- package/lib/pipeline/engine.js +0 -928
- package/lib/pipeline/executor.js +0 -440
- package/lib/pipeline/index.js +0 -33
- package/lib/pipeline/integrations.js +0 -559
- package/lib/pipeline/schemas.js +0 -288
- package/lib/remote/client.js +0 -361
- package/lib/server/auth.js +0 -270
- package/lib/server/client-example.js +0 -190
- package/lib/server/executor.js +0 -477
- package/lib/server/index.js +0 -494
- package/lib/update-helpers.js +0 -505
- package/lib/validation.js +0 -460
package/lib/core/storage.js
DELETED
|
@@ -1,515 +0,0 @@
|
|
|
1
|
-
import fs from 'fs/promises';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import crypto from 'crypto';
|
|
4
|
-
import { atomicWrite } from '../atomic.js';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Storage Manager
|
|
8
|
-
*
|
|
9
|
-
* Handles persistence of generated and custom agents with:
|
|
10
|
-
* - Separation of generated vs custom agents
|
|
11
|
-
* - Version control integration
|
|
12
|
-
* - Metadata tracking
|
|
13
|
-
* - Checksum validation
|
|
14
|
-
* - User modification preservation
|
|
15
|
-
*/
|
|
16
|
-
export class StorageManager {
|
|
17
|
-
constructor(projectPath, options = {}) {
|
|
18
|
-
this.projectPath = projectPath;
|
|
19
|
-
this.options = {
|
|
20
|
-
agentsDir: options.agentsDir || '.agentful/agents',
|
|
21
|
-
preserveCustom: options.preserveCustom !== false,
|
|
22
|
-
gitEnabled: options.gitEnabled !== false,
|
|
23
|
-
...options
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
this.agentsPath = path.join(projectPath, this.options.agentsDir);
|
|
27
|
-
this.generatedPath = path.join(this.agentsPath, 'generated');
|
|
28
|
-
this.customPath = path.join(this.agentsPath, 'custom');
|
|
29
|
-
this.metadataPath = path.join(projectPath, '.agentful', 'metadata.json');
|
|
30
|
-
|
|
31
|
-
this.metadata = null;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Initialize storage system
|
|
36
|
-
*/
|
|
37
|
-
async initialize() {
|
|
38
|
-
await this._createDirectoryStructure();
|
|
39
|
-
await this._loadMetadata();
|
|
40
|
-
await this._updateGitignore();
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Save generated agents
|
|
45
|
-
*
|
|
46
|
-
* @param {Array} agents - Generated agents
|
|
47
|
-
* @param {Object} options - Save options
|
|
48
|
-
* @returns {Promise<void>}
|
|
49
|
-
*/
|
|
50
|
-
async saveAgents(agents, options = {}) {
|
|
51
|
-
// Preserve custom agents before clearing
|
|
52
|
-
await this._preserveCustomAgents();
|
|
53
|
-
|
|
54
|
-
// Clear generated directory
|
|
55
|
-
await this._clearGeneratedDirectory();
|
|
56
|
-
|
|
57
|
-
// Save each agent
|
|
58
|
-
const savedAgents = [];
|
|
59
|
-
for (const agent of agents) {
|
|
60
|
-
const saved = await this._saveAgent(agent);
|
|
61
|
-
savedAgents.push(saved);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Update metadata
|
|
65
|
-
await this._updateMetadata({
|
|
66
|
-
lastGeneration: new Date().toISOString(),
|
|
67
|
-
generatedAgents: savedAgents.map(a => a.name),
|
|
68
|
-
customAgents: await this._listCustomAgents()
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
return savedAgents;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Load agent by name
|
|
76
|
-
*
|
|
77
|
-
* @param {string} name - Agent name
|
|
78
|
-
* @returns {Promise<Object|null>} Agent or null if not found
|
|
79
|
-
*/
|
|
80
|
-
async loadAgent(name) {
|
|
81
|
-
// Check custom first (takes precedence)
|
|
82
|
-
const customAgent = await this._loadAgentFromPath(
|
|
83
|
-
path.join(this.customPath, `${name}.md`)
|
|
84
|
-
);
|
|
85
|
-
if (customAgent) {
|
|
86
|
-
return { ...customAgent, customized: true };
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Check generated
|
|
90
|
-
const generatedAgent = await this._loadAgentFromPath(
|
|
91
|
-
path.join(this.generatedPath, `${name}.md`)
|
|
92
|
-
);
|
|
93
|
-
if (generatedAgent) {
|
|
94
|
-
return { ...generatedAgent, customized: false };
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return null;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* List all agents (custom and generated)
|
|
102
|
-
*
|
|
103
|
-
* @returns {Promise<Array>} Agent list
|
|
104
|
-
*/
|
|
105
|
-
async listAgents() {
|
|
106
|
-
const agents = [];
|
|
107
|
-
|
|
108
|
-
// List custom agents
|
|
109
|
-
const customAgents = await this._listAgentsInDirectory(this.customPath, true);
|
|
110
|
-
agents.push(...customAgents);
|
|
111
|
-
|
|
112
|
-
// List generated agents (exclude if custom version exists)
|
|
113
|
-
const generatedAgents = await this._listAgentsInDirectory(this.generatedPath, false);
|
|
114
|
-
const customNames = new Set(customAgents.map(a => a.name));
|
|
115
|
-
|
|
116
|
-
for (const agent of generatedAgents) {
|
|
117
|
-
if (!customNames.has(agent.name)) {
|
|
118
|
-
agents.push(agent);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return agents;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Check if agent has been customized
|
|
127
|
-
*
|
|
128
|
-
* @param {string} name - Agent name
|
|
129
|
-
* @returns {Promise<boolean>} True if customized
|
|
130
|
-
*/
|
|
131
|
-
async isCustomized(name) {
|
|
132
|
-
const customPath = path.join(this.customPath, `${name}.md`);
|
|
133
|
-
|
|
134
|
-
try {
|
|
135
|
-
await fs.access(customPath);
|
|
136
|
-
return true;
|
|
137
|
-
} catch {
|
|
138
|
-
return false;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Move agent to custom (mark as user-modified)
|
|
144
|
-
*
|
|
145
|
-
* @param {string} name - Agent name
|
|
146
|
-
* @returns {Promise<void>}
|
|
147
|
-
*/
|
|
148
|
-
async markAsCustom(name) {
|
|
149
|
-
const generatedFile = path.join(this.generatedPath, `${name}.md`);
|
|
150
|
-
const customFile = path.join(this.customPath, `${name}.md`);
|
|
151
|
-
|
|
152
|
-
// Copy generated to custom
|
|
153
|
-
try {
|
|
154
|
-
const content = await fs.readFile(generatedFile, 'utf-8');
|
|
155
|
-
await fs.writeFile(customFile, content, 'utf-8');
|
|
156
|
-
|
|
157
|
-
// Update metadata
|
|
158
|
-
await this._updateMetadata({
|
|
159
|
-
customAgents: await this._listCustomAgents()
|
|
160
|
-
});
|
|
161
|
-
} catch (error) {
|
|
162
|
-
throw new Error(`Failed to mark ${name} as custom: ${error.message}`);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Get agent statistics
|
|
168
|
-
*
|
|
169
|
-
* @returns {Promise<Object>} Statistics
|
|
170
|
-
*/
|
|
171
|
-
async getStatistics() {
|
|
172
|
-
const customAgents = await this._listCustomAgents();
|
|
173
|
-
const generatedAgents = await this._listGeneratedAgents();
|
|
174
|
-
|
|
175
|
-
return {
|
|
176
|
-
total: customAgents.length + generatedAgents.length,
|
|
177
|
-
custom: customAgents.length,
|
|
178
|
-
generated: generatedAgents.length,
|
|
179
|
-
customNames: customAgents,
|
|
180
|
-
generatedNames: generatedAgents
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* Save individual agent
|
|
186
|
-
*
|
|
187
|
-
* @param {Object} agent - Agent data
|
|
188
|
-
* @returns {Promise<Object>} Saved agent metadata
|
|
189
|
-
* @private
|
|
190
|
-
*/
|
|
191
|
-
async _saveAgent(agent) {
|
|
192
|
-
const agentFile = path.join(this.generatedPath, `${agent.metadata.name}.md`);
|
|
193
|
-
const metadataFile = path.join(this.generatedPath, `${agent.metadata.name}.json`);
|
|
194
|
-
|
|
195
|
-
// Ensure directory exists
|
|
196
|
-
await fs.mkdir(this.generatedPath, { recursive: true });
|
|
197
|
-
|
|
198
|
-
// Write agent content
|
|
199
|
-
await atomicWrite(agentFile, agent.content);
|
|
200
|
-
|
|
201
|
-
// Write metadata
|
|
202
|
-
const metadata = {
|
|
203
|
-
...agent.metadata,
|
|
204
|
-
savedAt: new Date().toISOString(),
|
|
205
|
-
checksum: this._calculateChecksum(agent.content)
|
|
206
|
-
};
|
|
207
|
-
|
|
208
|
-
await atomicWrite(metadataFile, JSON.stringify(metadata, null, 2));
|
|
209
|
-
|
|
210
|
-
return metadata;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* Load agent from path
|
|
215
|
-
*
|
|
216
|
-
* @param {string} agentPath - Agent file path
|
|
217
|
-
* @returns {Promise<Object|null>} Agent or null
|
|
218
|
-
* @private
|
|
219
|
-
*/
|
|
220
|
-
async _loadAgentFromPath(agentPath) {
|
|
221
|
-
try {
|
|
222
|
-
const content = await fs.readFile(agentPath, 'utf-8');
|
|
223
|
-
const metadataPath = agentPath.replace('.md', '.json');
|
|
224
|
-
|
|
225
|
-
let metadata = {};
|
|
226
|
-
try {
|
|
227
|
-
const metadataContent = await fs.readFile(metadataPath, 'utf-8');
|
|
228
|
-
metadata = JSON.parse(metadataContent);
|
|
229
|
-
} catch {
|
|
230
|
-
// Metadata file doesn't exist, extract from frontmatter
|
|
231
|
-
metadata = this._extractMetadataFromContent(content);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
return {
|
|
235
|
-
name: metadata.name || path.basename(agentPath, '.md'),
|
|
236
|
-
content,
|
|
237
|
-
metadata,
|
|
238
|
-
path: agentPath
|
|
239
|
-
};
|
|
240
|
-
} catch (error) {
|
|
241
|
-
if (error.code === 'ENOENT') {
|
|
242
|
-
return null;
|
|
243
|
-
}
|
|
244
|
-
throw error;
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
/**
|
|
249
|
-
* List agents in directory
|
|
250
|
-
*
|
|
251
|
-
* @param {string} directory - Directory path
|
|
252
|
-
* @param {boolean} customized - Whether agents are customized
|
|
253
|
-
* @returns {Promise<Array>} Agent list
|
|
254
|
-
* @private
|
|
255
|
-
*/
|
|
256
|
-
async _listAgentsInDirectory(directory, customized) {
|
|
257
|
-
const agents = [];
|
|
258
|
-
|
|
259
|
-
try {
|
|
260
|
-
const files = await fs.readdir(directory);
|
|
261
|
-
|
|
262
|
-
for (const file of files) {
|
|
263
|
-
if (file.endsWith('.md')) {
|
|
264
|
-
const name = path.basename(file, '.md');
|
|
265
|
-
const filePath = path.join(directory, file);
|
|
266
|
-
const metadataPath = path.join(directory, `${name}.json`);
|
|
267
|
-
|
|
268
|
-
let metadata = {};
|
|
269
|
-
try {
|
|
270
|
-
const content = await fs.readFile(metadataPath, 'utf-8');
|
|
271
|
-
metadata = JSON.parse(content);
|
|
272
|
-
} catch {
|
|
273
|
-
// Metadata file doesn't exist
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
const stats = await fs.stat(filePath);
|
|
277
|
-
|
|
278
|
-
agents.push({
|
|
279
|
-
name,
|
|
280
|
-
customized,
|
|
281
|
-
path: filePath,
|
|
282
|
-
lastModified: stats.mtime,
|
|
283
|
-
...metadata
|
|
284
|
-
});
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
} catch (error) {
|
|
288
|
-
if (error.code !== 'ENOENT') {
|
|
289
|
-
throw error;
|
|
290
|
-
}
|
|
291
|
-
// Directory doesn't exist, return empty array
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
return agents;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
/**
|
|
298
|
-
* Preserve custom agents before regeneration
|
|
299
|
-
*
|
|
300
|
-
* @private
|
|
301
|
-
*/
|
|
302
|
-
async _preserveCustomAgents() {
|
|
303
|
-
if (!this.options.preserveCustom) {
|
|
304
|
-
return;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
// Check if any generated agents have been modified
|
|
308
|
-
try {
|
|
309
|
-
const generatedAgents = await this._listAgentsInDirectory(this.generatedPath, false);
|
|
310
|
-
|
|
311
|
-
for (const agent of generatedAgents) {
|
|
312
|
-
const content = await fs.readFile(agent.path, 'utf-8');
|
|
313
|
-
const currentChecksum = this._calculateChecksum(content);
|
|
314
|
-
|
|
315
|
-
// Check if checksum differs from metadata (agent was modified)
|
|
316
|
-
if (agent.checksum && currentChecksum !== agent.checksum) {
|
|
317
|
-
// Move to custom directory
|
|
318
|
-
const customFile = path.join(this.customPath, path.basename(agent.path));
|
|
319
|
-
await fs.copyFile(agent.path, customFile);
|
|
320
|
-
|
|
321
|
-
console.log(`Preserved modified agent: ${agent.name}`);
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
} catch (error) {
|
|
325
|
-
// Directory doesn't exist or other error, skip preservation
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
/**
|
|
330
|
-
* Clear generated directory
|
|
331
|
-
*
|
|
332
|
-
* @private
|
|
333
|
-
*/
|
|
334
|
-
async _clearGeneratedDirectory() {
|
|
335
|
-
try {
|
|
336
|
-
const files = await fs.readdir(this.generatedPath);
|
|
337
|
-
|
|
338
|
-
for (const file of files) {
|
|
339
|
-
await fs.unlink(path.join(this.generatedPath, file));
|
|
340
|
-
}
|
|
341
|
-
} catch (error) {
|
|
342
|
-
if (error.code !== 'ENOENT') {
|
|
343
|
-
throw error;
|
|
344
|
-
}
|
|
345
|
-
// Directory doesn't exist, create it
|
|
346
|
-
await fs.mkdir(this.generatedPath, { recursive: true });
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
/**
|
|
351
|
-
* List custom agent names
|
|
352
|
-
*
|
|
353
|
-
* @returns {Promise<Array>} Custom agent names
|
|
354
|
-
* @private
|
|
355
|
-
*/
|
|
356
|
-
async _listCustomAgents() {
|
|
357
|
-
try {
|
|
358
|
-
const files = await fs.readdir(this.customPath);
|
|
359
|
-
return files.filter(f => f.endsWith('.md')).map(f => path.basename(f, '.md'));
|
|
360
|
-
} catch {
|
|
361
|
-
return [];
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
/**
|
|
366
|
-
* List generated agent names
|
|
367
|
-
*
|
|
368
|
-
* @returns {Promise<Array>} Generated agent names
|
|
369
|
-
* @private
|
|
370
|
-
*/
|
|
371
|
-
async _listGeneratedAgents() {
|
|
372
|
-
try {
|
|
373
|
-
const files = await fs.readdir(this.generatedPath);
|
|
374
|
-
return files.filter(f => f.endsWith('.md')).map(f => path.basename(f, '.md'));
|
|
375
|
-
} catch {
|
|
376
|
-
return [];
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
/**
|
|
381
|
-
* Create directory structure
|
|
382
|
-
*
|
|
383
|
-
* @private
|
|
384
|
-
*/
|
|
385
|
-
async _createDirectoryStructure() {
|
|
386
|
-
const directories = [
|
|
387
|
-
this.agentsPath,
|
|
388
|
-
this.generatedPath,
|
|
389
|
-
this.customPath,
|
|
390
|
-
path.dirname(this.metadataPath)
|
|
391
|
-
];
|
|
392
|
-
|
|
393
|
-
for (const dir of directories) {
|
|
394
|
-
await fs.mkdir(dir, { recursive: true });
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
/**
|
|
399
|
-
* Load metadata
|
|
400
|
-
*
|
|
401
|
-
* @private
|
|
402
|
-
*/
|
|
403
|
-
async _loadMetadata() {
|
|
404
|
-
try {
|
|
405
|
-
const content = await fs.readFile(this.metadataPath, 'utf-8');
|
|
406
|
-
this.metadata = JSON.parse(content);
|
|
407
|
-
} catch (error) {
|
|
408
|
-
if (error.code === 'ENOENT') {
|
|
409
|
-
// Initialize with defaults
|
|
410
|
-
this.metadata = {
|
|
411
|
-
version: '1.0.0',
|
|
412
|
-
created: new Date().toISOString(),
|
|
413
|
-
lastGeneration: null,
|
|
414
|
-
generatedAgents: [],
|
|
415
|
-
customAgents: []
|
|
416
|
-
};
|
|
417
|
-
} else {
|
|
418
|
-
throw error;
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
/**
|
|
424
|
-
* Update metadata
|
|
425
|
-
*
|
|
426
|
-
* @param {Object} updates - Metadata updates
|
|
427
|
-
* @private
|
|
428
|
-
*/
|
|
429
|
-
async _updateMetadata(updates) {
|
|
430
|
-
this.metadata = {
|
|
431
|
-
...this.metadata,
|
|
432
|
-
...updates,
|
|
433
|
-
lastUpdated: new Date().toISOString()
|
|
434
|
-
};
|
|
435
|
-
|
|
436
|
-
await atomicWrite(this.metadataPath, JSON.stringify(this.metadata, null, 2));
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
/**
|
|
440
|
-
* Update .gitignore
|
|
441
|
-
*
|
|
442
|
-
* @private
|
|
443
|
-
*/
|
|
444
|
-
async _updateGitignore() {
|
|
445
|
-
if (!this.options.gitEnabled) {
|
|
446
|
-
return;
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
const gitignorePath = path.join(this.projectPath, '.gitignore');
|
|
450
|
-
const ignorePatterns = [
|
|
451
|
-
'',
|
|
452
|
-
'# agentful generated agents',
|
|
453
|
-
'.agentful/agents/generated/',
|
|
454
|
-
'.agentful/metadata.json'
|
|
455
|
-
];
|
|
456
|
-
|
|
457
|
-
try {
|
|
458
|
-
let content = '';
|
|
459
|
-
try {
|
|
460
|
-
content = await fs.readFile(gitignorePath, 'utf-8');
|
|
461
|
-
} catch {
|
|
462
|
-
// .gitignore doesn't exist
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
// Only add if not already present
|
|
466
|
-
if (!content.includes('# agentful generated agents')) {
|
|
467
|
-
content += '\n' + ignorePatterns.join('\n') + '\n';
|
|
468
|
-
await fs.writeFile(gitignorePath, content, 'utf-8');
|
|
469
|
-
}
|
|
470
|
-
} catch (error) {
|
|
471
|
-
// Ignore errors (non-git project, permissions, etc.)
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
/**
|
|
476
|
-
* Calculate checksum for content
|
|
477
|
-
*
|
|
478
|
-
* @param {string} content - Content to hash
|
|
479
|
-
* @returns {string} Checksum
|
|
480
|
-
* @private
|
|
481
|
-
*/
|
|
482
|
-
_calculateChecksum(content) {
|
|
483
|
-
return crypto.createHash('sha256').update(content).digest('hex').slice(0, 16);
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
/**
|
|
487
|
-
* Extract metadata from agent frontmatter
|
|
488
|
-
*
|
|
489
|
-
* @param {string} content - Agent content
|
|
490
|
-
* @returns {Object} Metadata
|
|
491
|
-
* @private
|
|
492
|
-
*/
|
|
493
|
-
_extractMetadataFromContent(content) {
|
|
494
|
-
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
495
|
-
if (!frontmatterMatch) {
|
|
496
|
-
return {};
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
const frontmatter = frontmatterMatch[1];
|
|
500
|
-
const metadata = {};
|
|
501
|
-
const lines = frontmatter.split('\n');
|
|
502
|
-
|
|
503
|
-
for (const line of lines) {
|
|
504
|
-
const match = line.match(/^(\w+):\s*(.+)$/);
|
|
505
|
-
if (match) {
|
|
506
|
-
const [, key, value] = match;
|
|
507
|
-
metadata[key] = value.trim();
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
return metadata;
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
export default StorageManager;
|