@girardmedia/bootspring 2.0.21 → 2.0.23

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.
Files changed (159) hide show
  1. package/bin/bootspring.js +5 -0
  2. package/cli/org.js +474 -0
  3. package/cli/preseed/index.js +16 -0
  4. package/cli/preseed/interactive.js +143 -0
  5. package/cli/preseed/templates.js +227 -0
  6. package/cli/preseed.js +9 -301
  7. package/cli/seed/builders/ai-context-builder.js +85 -0
  8. package/cli/seed/builders/index.js +13 -0
  9. package/cli/seed/builders/seed-builder.js +272 -0
  10. package/cli/seed/extractors/content-extractors.js +383 -0
  11. package/cli/seed/extractors/index.js +47 -0
  12. package/cli/seed/extractors/metadata-extractors.js +167 -0
  13. package/cli/seed/extractors/section-extractor.js +54 -0
  14. package/cli/seed/extractors/stack-extractors.js +228 -0
  15. package/cli/seed/index.js +18 -0
  16. package/cli/seed/utils/folder-structure.js +84 -0
  17. package/cli/seed/utils/index.js +11 -0
  18. package/cli/seed.js +23 -1074
  19. package/core/api-client.js +77 -0
  20. package/core/entitlements.js +36 -0
  21. package/core/organizations.js +223 -0
  22. package/core/policies.js +51 -6
  23. package/core/policy-matrix.js +303 -0
  24. package/core/project-context.js +1 -0
  25. package/dist/cli/index.d.ts +3 -0
  26. package/dist/cli/index.js +3220 -0
  27. package/dist/cli/index.js.map +1 -0
  28. package/dist/context-McpJQa_2.d.ts +5710 -0
  29. package/dist/core/index.d.ts +635 -0
  30. package/dist/core/index.js +2593 -0
  31. package/dist/core/index.js.map +1 -0
  32. package/dist/index-QqbeEiDm.d.ts +857 -0
  33. package/dist/index-UiYCgwiH.d.ts +174 -0
  34. package/dist/index.d.ts +453 -0
  35. package/dist/index.js +44228 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/mcp/index.d.ts +1 -0
  38. package/dist/mcp/index.js +41173 -0
  39. package/dist/mcp/index.js.map +1 -0
  40. package/generators/index.ts +82 -0
  41. package/intelligence/orchestrator/config/failure-signatures.js +48 -0
  42. package/intelligence/orchestrator/config/index.js +23 -0
  43. package/intelligence/orchestrator/config/pack-lifecycle.js +262 -0
  44. package/intelligence/orchestrator/config/phases.js +111 -0
  45. package/intelligence/orchestrator/config/remediation.js +150 -0
  46. package/intelligence/orchestrator/config/workflows.js +168 -0
  47. package/intelligence/orchestrator/core/index.js +16 -0
  48. package/intelligence/orchestrator/core/state-manager.js +88 -0
  49. package/intelligence/orchestrator/core/telemetry.js +24 -0
  50. package/intelligence/orchestrator/index.js +17 -0
  51. package/intelligence/orchestrator.js +17 -512
  52. package/mcp/contracts/mcp-contract.v1.json +1 -1
  53. package/package.json +16 -3
  54. package/src/cli/agent.ts +703 -0
  55. package/src/cli/analyze.ts +640 -0
  56. package/src/cli/audit.ts +707 -0
  57. package/src/cli/auth.ts +930 -0
  58. package/src/cli/billing.ts +364 -0
  59. package/src/cli/build.ts +1089 -0
  60. package/src/cli/business.ts +508 -0
  61. package/src/cli/checkpoint-utils.ts +236 -0
  62. package/src/cli/checkpoint.ts +757 -0
  63. package/src/cli/cloud-sync.ts +534 -0
  64. package/src/cli/content.ts +273 -0
  65. package/src/cli/context.ts +667 -0
  66. package/src/cli/dashboard.ts +133 -0
  67. package/src/cli/deploy.ts +704 -0
  68. package/src/cli/doctor.ts +480 -0
  69. package/src/cli/fundraise.ts +494 -0
  70. package/src/cli/generate.ts +346 -0
  71. package/src/cli/github-cmd.ts +566 -0
  72. package/src/cli/health.ts +599 -0
  73. package/src/cli/index.ts +113 -0
  74. package/src/cli/init.ts +838 -0
  75. package/src/cli/legal.ts +495 -0
  76. package/src/cli/log.ts +316 -0
  77. package/src/cli/loop.ts +1660 -0
  78. package/src/cli/manager.ts +878 -0
  79. package/src/cli/mcp.ts +275 -0
  80. package/src/cli/memory.ts +346 -0
  81. package/src/cli/metrics.ts +590 -0
  82. package/src/cli/monitor.ts +960 -0
  83. package/src/cli/mvp.ts +662 -0
  84. package/src/cli/onboard.ts +663 -0
  85. package/src/cli/orchestrator.ts +622 -0
  86. package/src/cli/plugin.ts +483 -0
  87. package/src/cli/prd.ts +671 -0
  88. package/src/cli/preseed-start.ts +1633 -0
  89. package/src/cli/preseed.ts +2434 -0
  90. package/src/cli/project.ts +526 -0
  91. package/src/cli/quality.ts +885 -0
  92. package/src/cli/security.ts +1079 -0
  93. package/src/cli/seed.ts +1224 -0
  94. package/src/cli/skill.ts +537 -0
  95. package/src/cli/suggest.ts +1225 -0
  96. package/src/cli/switch.ts +518 -0
  97. package/src/cli/task.ts +780 -0
  98. package/src/cli/telemetry.ts +172 -0
  99. package/src/cli/todo.ts +627 -0
  100. package/src/cli/types.ts +15 -0
  101. package/src/cli/update.ts +334 -0
  102. package/src/cli/visualize.ts +609 -0
  103. package/src/cli/watch.ts +895 -0
  104. package/src/cli/workspace.ts +709 -0
  105. package/src/core/action-recorder.ts +673 -0
  106. package/src/core/analyze-workflow.ts +1453 -0
  107. package/src/core/api-client.ts +1120 -0
  108. package/src/core/audit-workflow.ts +1681 -0
  109. package/src/core/auth.ts +471 -0
  110. package/src/core/build-orchestrator.ts +509 -0
  111. package/src/core/build-state.ts +621 -0
  112. package/src/core/checkpoint-engine.ts +482 -0
  113. package/src/core/config.ts +1285 -0
  114. package/src/core/context-loader.ts +694 -0
  115. package/src/core/context.ts +410 -0
  116. package/src/core/deploy-workflow.ts +1085 -0
  117. package/src/core/entitlements.ts +322 -0
  118. package/src/core/github-sync.ts +720 -0
  119. package/src/core/index.ts +981 -0
  120. package/src/core/ingest.ts +1186 -0
  121. package/src/core/metrics-engine.ts +886 -0
  122. package/src/core/mvp.ts +847 -0
  123. package/src/core/onboard-workflow.ts +1293 -0
  124. package/src/core/policies.ts +81 -0
  125. package/src/core/preseed-workflow.ts +1163 -0
  126. package/src/core/preseed.ts +1826 -0
  127. package/src/core/project-context.ts +380 -0
  128. package/src/core/project-state.ts +699 -0
  129. package/src/core/r2-sync.ts +691 -0
  130. package/src/core/scaffold.ts +1715 -0
  131. package/src/core/session.ts +286 -0
  132. package/src/core/task-extractor.ts +799 -0
  133. package/src/core/telemetry.ts +371 -0
  134. package/src/core/tier-enforcement.ts +737 -0
  135. package/src/core/utils.ts +437 -0
  136. package/src/index.ts +29 -0
  137. package/src/intelligence/agent-collab.ts +2376 -0
  138. package/src/intelligence/auto-suggest.ts +713 -0
  139. package/src/intelligence/content-gen.ts +1351 -0
  140. package/src/intelligence/cross-project.ts +1692 -0
  141. package/src/intelligence/git-memory.ts +529 -0
  142. package/src/intelligence/index.ts +318 -0
  143. package/src/intelligence/orchestrator.ts +534 -0
  144. package/src/intelligence/prd.ts +466 -0
  145. package/src/intelligence/recommendations.ts +982 -0
  146. package/src/intelligence/workflow-composer.ts +1472 -0
  147. package/src/mcp/capabilities.ts +233 -0
  148. package/src/mcp/index.ts +37 -0
  149. package/src/mcp/registry.ts +1268 -0
  150. package/src/mcp/response-formatter.ts +797 -0
  151. package/src/mcp/server.ts +240 -0
  152. package/src/types/agent.ts +69 -0
  153. package/src/types/config.ts +86 -0
  154. package/src/types/context.ts +77 -0
  155. package/src/types/index.ts +53 -0
  156. package/src/types/mcp.ts +91 -0
  157. package/src/types/skills.ts +47 -0
  158. package/src/types/workflow.ts +155 -0
  159. package/generators/index.js +0 -18
@@ -0,0 +1,673 @@
1
+ /**
2
+ * Bootspring Action Recorder
3
+ * Automatically records actions and creates .md files for reference
4
+ *
5
+ * Action Types:
6
+ * - agents: Agent invocation logs
7
+ * - code: Code generation logs
8
+ * - decisions: Decision records
9
+ * - fixes: Bug fix documentation
10
+ * - business: Business action logs
11
+ * - meetings: Meeting notes
12
+ *
13
+ * @package bootspring
14
+ * @module core/action-recorder
15
+ */
16
+
17
+ import * as fs from 'fs';
18
+ import * as path from 'path';
19
+
20
+ // Module interfaces
21
+ interface Config {
22
+ _projectRoot: string;
23
+ }
24
+
25
+ interface ConfigModule {
26
+ load: () => Config | null;
27
+ }
28
+
29
+ interface Utils {
30
+ formatDate: () => string;
31
+ slugify: (text: string) => string;
32
+ truncate: (text: string, maxLength: number) => string;
33
+ writeFile: (path: string, content: string) => boolean;
34
+ readFile: (path: string) => string | null;
35
+ fileExists: (path: string) => boolean;
36
+ ensureDir: (dir: string) => void;
37
+ formatRelativeTime: (date: Date) => string;
38
+ }
39
+
40
+ interface ContextLoader {
41
+ ensureDirectories: (projectRoot: string) => void;
42
+ }
43
+
44
+ export interface Action {
45
+ type: string;
46
+ agent?: string | undefined;
47
+ title?: string | undefined;
48
+ input?: string | undefined;
49
+ output?: string | undefined;
50
+ files?: string[] | undefined;
51
+ context?: string[] | undefined;
52
+ metadata?: Record<string, unknown> | undefined;
53
+ // Decision-specific
54
+ options?: string[] | undefined;
55
+ rationale?: string | undefined;
56
+ // Fix-specific
57
+ problem?: string | undefined;
58
+ solution?: string | undefined;
59
+ prevention?: string | undefined;
60
+ }
61
+
62
+ export interface RecordResult {
63
+ success: boolean;
64
+ path: string;
65
+ filename: string;
66
+ relativePath: string;
67
+ }
68
+
69
+ export interface RecordOptions {
70
+ config?: Config | undefined;
71
+ }
72
+
73
+ export interface ListOptions {
74
+ config?: Config | undefined;
75
+ type?: string | undefined;
76
+ limit?: number | undefined;
77
+ offset?: number | undefined;
78
+ }
79
+
80
+ export interface SearchOptions {
81
+ config?: Config | undefined;
82
+ types?: string[] | undefined;
83
+ limit?: number | undefined;
84
+ }
85
+
86
+ export interface IndexEntry {
87
+ type: string;
88
+ filename: string;
89
+ timestamp: string;
90
+ agent?: string | undefined;
91
+ title?: string | undefined;
92
+ files?: string[] | undefined;
93
+ }
94
+
95
+ export interface LogIndex {
96
+ version: string;
97
+ entries: IndexEntry[];
98
+ lastUpdated?: string | undefined;
99
+ }
100
+
101
+ export interface SearchResult {
102
+ type: string;
103
+ file: string;
104
+ path: string;
105
+ matches: { line: number; preview: string }[];
106
+ }
107
+
108
+ export interface LogEntry {
109
+ type: string;
110
+ filename: string;
111
+ path: string;
112
+ relativePath: string;
113
+ content: string;
114
+ metadata: {
115
+ size: number;
116
+ modified: Date;
117
+ modifiedRelative: string;
118
+ };
119
+ }
120
+
121
+ export interface RelevantLog extends LogEntry {
122
+ type: string;
123
+ keyword?: string | undefined;
124
+ }
125
+
126
+ export interface ExportResult {
127
+ success: boolean;
128
+ count: number;
129
+ outputDir: string;
130
+ }
131
+
132
+ export interface ClearResult {
133
+ success: boolean;
134
+ deleted: number;
135
+ }
136
+
137
+ // Lazy-loaded modules
138
+ let _config: ConfigModule | null = null;
139
+ let _utils: Utils | null = null;
140
+ let _contextLoader: ContextLoader | null = null;
141
+
142
+ function getConfig(): ConfigModule {
143
+ if (!_config) {
144
+ _config = require('./config') as ConfigModule;
145
+ }
146
+ return _config;
147
+ }
148
+
149
+ function getUtils(): Utils {
150
+ if (!_utils) {
151
+ _utils = require('./utils') as Utils;
152
+ }
153
+ return _utils;
154
+ }
155
+
156
+ function getContextLoader(): ContextLoader {
157
+ if (!_contextLoader) {
158
+ _contextLoader = require('./context-loader') as ContextLoader;
159
+ }
160
+ return _contextLoader;
161
+ }
162
+
163
+ /**
164
+ * Action types and their log directories
165
+ */
166
+ export const ACTION_TYPES: Record<string, string> = {
167
+ agent: 'agents',
168
+ code: 'code',
169
+ decision: 'decisions',
170
+ fix: 'fixes',
171
+ business: 'business',
172
+ meeting: 'meetings'
173
+ };
174
+
175
+ /**
176
+ * Get the logs directory path
177
+ */
178
+ export function getLogsDir(projectRoot: string, actionType: string | null = null): string {
179
+ const baseDir = path.join(projectRoot, '.bootspring', 'logs');
180
+ if (actionType && ACTION_TYPES[actionType]) {
181
+ return path.join(baseDir, ACTION_TYPES[actionType]!);
182
+ }
183
+ return baseDir;
184
+ }
185
+
186
+ /**
187
+ * Generate a unique log filename
188
+ */
189
+ export function generateFilename(actionType: string, identifier: string | null = null): string {
190
+ const utils = getUtils();
191
+ const date = utils.formatDate();
192
+ const timePart = new Date().toISOString().split('T')[1];
193
+ const time = timePart ? timePart.slice(0, 8).replace(/:/g, '-') : '00-00-00';
194
+ const id = identifier ? `-${utils.slugify(identifier)}` : '';
195
+ return `${date}-${time}${id}.md`;
196
+ }
197
+
198
+ /**
199
+ * Generate markdown content for an action
200
+ */
201
+ function generateMarkdown(action: Action): string {
202
+ const timestamp = new Date().toISOString();
203
+ const lines: string[] = [];
204
+
205
+ // Header
206
+ const title = action.title || `${action.type} Action`;
207
+ lines.push(`# ${title}`);
208
+ lines.push('');
209
+ lines.push(`**Date**: ${timestamp}`);
210
+ lines.push(`**Type**: ${action.type}`);
211
+
212
+ if (action.agent) {
213
+ lines.push(`**Agent**: ${action.agent}`);
214
+ }
215
+
216
+ lines.push('');
217
+
218
+ // Input section
219
+ if (action.input) {
220
+ lines.push('## Input');
221
+ lines.push('');
222
+ lines.push(action.input);
223
+ lines.push('');
224
+ }
225
+
226
+ // Output section
227
+ if (action.output) {
228
+ lines.push('## Output');
229
+ lines.push('');
230
+ lines.push(action.output);
231
+ lines.push('');
232
+ }
233
+
234
+ // Files affected
235
+ if (action.files && action.files.length > 0) {
236
+ lines.push('## Files Affected');
237
+ lines.push('');
238
+ for (const file of action.files) {
239
+ lines.push(`- ${file}`);
240
+ }
241
+ lines.push('');
242
+ }
243
+
244
+ // Context used
245
+ if (action.context && action.context.length > 0) {
246
+ lines.push('## Context Used');
247
+ lines.push('');
248
+ for (const ctx of action.context) {
249
+ lines.push(`- ${ctx}`);
250
+ }
251
+ lines.push('');
252
+ }
253
+
254
+ // Decision-specific sections
255
+ if (action.type === 'decision') {
256
+ if (action.options) {
257
+ lines.push('## Options Considered');
258
+ lines.push('');
259
+ for (const opt of action.options) {
260
+ lines.push(`- ${opt}`);
261
+ }
262
+ lines.push('');
263
+ }
264
+
265
+ if (action.rationale) {
266
+ lines.push('## Rationale');
267
+ lines.push('');
268
+ lines.push(action.rationale);
269
+ lines.push('');
270
+ }
271
+ }
272
+
273
+ // Fix-specific sections
274
+ if (action.type === 'fix') {
275
+ if (action.problem) {
276
+ lines.push('## Problem');
277
+ lines.push('');
278
+ lines.push(action.problem);
279
+ lines.push('');
280
+ }
281
+
282
+ if (action.solution) {
283
+ lines.push('## Solution');
284
+ lines.push('');
285
+ lines.push(action.solution);
286
+ lines.push('');
287
+ }
288
+
289
+ if (action.prevention) {
290
+ lines.push('## Prevention');
291
+ lines.push('');
292
+ lines.push(action.prevention);
293
+ lines.push('');
294
+ }
295
+ }
296
+
297
+ // Metadata
298
+ if (action.metadata && Object.keys(action.metadata).length > 0) {
299
+ lines.push('## Metadata');
300
+ lines.push('');
301
+ lines.push('```json');
302
+ lines.push(JSON.stringify(action.metadata, null, 2));
303
+ lines.push('```');
304
+ lines.push('');
305
+ }
306
+
307
+ // Footer
308
+ lines.push('---');
309
+ lines.push('');
310
+ lines.push('*Automatically recorded by Bootspring*');
311
+
312
+ return lines.join('\n');
313
+ }
314
+
315
+ /**
316
+ * Update the logs index
317
+ */
318
+ function updateIndex(projectRoot: string, entry: IndexEntry): void {
319
+ const utils = getUtils();
320
+ const indexPath = path.join(projectRoot, '.bootspring', 'logs', 'index.json');
321
+
322
+ let index: LogIndex = { version: '1.0', entries: [] };
323
+
324
+ // Load existing index
325
+ if (utils.fileExists(indexPath)) {
326
+ try {
327
+ const content = utils.readFile(indexPath);
328
+ if (content) {
329
+ index = JSON.parse(content) as LogIndex;
330
+ }
331
+ } catch {
332
+ // Keep default index
333
+ }
334
+ }
335
+
336
+ // Add new entry
337
+ index.entries.unshift(entry);
338
+
339
+ // Limit to last 1000 entries
340
+ if (index.entries.length > 1000) {
341
+ index.entries = index.entries.slice(0, 1000);
342
+ }
343
+
344
+ index.lastUpdated = new Date().toISOString();
345
+
346
+ // Save index
347
+ utils.writeFile(indexPath, JSON.stringify(index, null, 2));
348
+ }
349
+
350
+ /**
351
+ * Record an action
352
+ */
353
+ export function record(action: Action, options: RecordOptions = {}): RecordResult {
354
+ const config = getConfig();
355
+ const utils = getUtils();
356
+ const contextLoader = getContextLoader();
357
+
358
+ const cfg = options.config || config.load();
359
+ const projectRoot = cfg?._projectRoot || process.cwd();
360
+
361
+ // Ensure directories exist
362
+ contextLoader.ensureDirectories(projectRoot);
363
+
364
+ const actionType = action.type || 'agent';
365
+ const logsDir = getLogsDir(projectRoot, actionType);
366
+ const identifier = action.agent || action.title || actionType;
367
+ const filename = generateFilename(actionType, identifier);
368
+ const filepath = path.join(logsDir, filename);
369
+
370
+ // Generate markdown content
371
+ const content = generateMarkdown(action);
372
+
373
+ // Write the log file
374
+ const success = utils.writeFile(filepath, content);
375
+
376
+ if (success) {
377
+ // Update the logs index
378
+ updateIndex(projectRoot, {
379
+ type: actionType,
380
+ filename,
381
+ timestamp: new Date().toISOString(),
382
+ agent: action.agent,
383
+ title: action.title,
384
+ files: action.files
385
+ });
386
+ }
387
+
388
+ return {
389
+ success,
390
+ path: filepath,
391
+ filename,
392
+ relativePath: path.relative(projectRoot, filepath)
393
+ };
394
+ }
395
+
396
+ /**
397
+ * List recent actions
398
+ */
399
+ export function list(options: ListOptions = {}): IndexEntry[] {
400
+ const config = getConfig();
401
+ const utils = getUtils();
402
+
403
+ const cfg = options.config || config.load();
404
+ const projectRoot = cfg?._projectRoot || process.cwd();
405
+ const limit = options.limit || 20;
406
+ const offset = options.offset || 0;
407
+ const filterType = options.type;
408
+
409
+ const indexPath = path.join(projectRoot, '.bootspring', 'logs', 'index.json');
410
+
411
+ if (!utils.fileExists(indexPath)) {
412
+ return [];
413
+ }
414
+
415
+ try {
416
+ const content = utils.readFile(indexPath);
417
+ if (!content) return [];
418
+
419
+ const index = JSON.parse(content) as LogIndex;
420
+ let entries = index.entries || [];
421
+
422
+ // Filter by type
423
+ if (filterType) {
424
+ entries = entries.filter(e => e.type === filterType);
425
+ }
426
+
427
+ // Apply pagination
428
+ return entries.slice(offset, offset + limit);
429
+ } catch {
430
+ return [];
431
+ }
432
+ }
433
+
434
+ /**
435
+ * Search action logs
436
+ */
437
+ export function search(query: string, options: SearchOptions = {}): SearchResult[] {
438
+ const config = getConfig();
439
+ const utils = getUtils();
440
+
441
+ const cfg = options.config || config.load();
442
+ const projectRoot = cfg?._projectRoot || process.cwd();
443
+ const types = options.types || Object.keys(ACTION_TYPES);
444
+ const limit = options.limit || 20;
445
+ const results: SearchResult[] = [];
446
+ const queryLower = query.toLowerCase();
447
+
448
+ for (const type of types) {
449
+ const logsDir = getLogsDir(projectRoot, type);
450
+
451
+ if (!utils.fileExists(logsDir)) continue;
452
+
453
+ try {
454
+ const files = fs.readdirSync(logsDir)
455
+ .filter(f => f.endsWith('.md'))
456
+ .sort()
457
+ .reverse(); // Most recent first
458
+
459
+ for (const file of files) {
460
+ const filepath = path.join(logsDir, file);
461
+ const content = utils.readFile(filepath);
462
+
463
+ if (content && content.toLowerCase().includes(queryLower)) {
464
+ const lines = content.split('\n');
465
+ const matchingLines = lines
466
+ .map((line, i) => ({ line, num: i + 1 }))
467
+ .filter(l => l.line.toLowerCase().includes(queryLower))
468
+ .slice(0, 3);
469
+
470
+ results.push({
471
+ type,
472
+ file,
473
+ path: filepath,
474
+ matches: matchingLines.map(m => ({
475
+ line: m.num,
476
+ preview: utils.truncate(m.line.trim(), 100)
477
+ }))
478
+ });
479
+
480
+ if (results.length >= limit) break;
481
+ }
482
+ }
483
+ } catch {
484
+ // Directory read error, skip
485
+ }
486
+
487
+ if (results.length >= limit) break;
488
+ }
489
+
490
+ return results;
491
+ }
492
+
493
+ /**
494
+ * Get a specific log entry
495
+ */
496
+ export function get(filename: string, options: RecordOptions = {}): LogEntry | null {
497
+ const config = getConfig();
498
+ const utils = getUtils();
499
+
500
+ const cfg = options.config || config.load();
501
+ const projectRoot = cfg?._projectRoot || process.cwd();
502
+
503
+ // Search in all log directories
504
+ for (const type of Object.keys(ACTION_TYPES)) {
505
+ const logsDir = getLogsDir(projectRoot, type);
506
+ const filepath = path.join(logsDir, filename);
507
+
508
+ if (utils.fileExists(filepath)) {
509
+ const content = utils.readFile(filepath);
510
+ const stats = fs.statSync(filepath);
511
+
512
+ return {
513
+ type,
514
+ filename,
515
+ path: filepath,
516
+ relativePath: path.relative(projectRoot, filepath),
517
+ content: content || '',
518
+ metadata: {
519
+ size: stats.size,
520
+ modified: stats.mtime,
521
+ modifiedRelative: utils.formatRelativeTime(stats.mtime)
522
+ }
523
+ };
524
+ }
525
+ }
526
+
527
+ return null;
528
+ }
529
+
530
+ /**
531
+ * Get relevant past actions for an agent
532
+ */
533
+ export function getRelevantForAgent(
534
+ agentName: string,
535
+ options: { config?: Config; keywords?: string[]; limit?: number } = {}
536
+ ): RelevantLog[] {
537
+ const config = getConfig();
538
+ const cfg = options.config || config.load();
539
+ const keywords = options.keywords || [];
540
+ const limit = options.limit || 5;
541
+
542
+ const results: RelevantLog[] = [];
543
+
544
+ // First, get agent's own past invocations
545
+ const agentLogs = list({
546
+ config: cfg || undefined,
547
+ type: 'agent',
548
+ limit: 10
549
+ }).filter(e => e.agent === agentName);
550
+
551
+ for (const entry of agentLogs.slice(0, 3)) {
552
+ const log = get(entry.filename, { config: cfg || undefined });
553
+ if (log) {
554
+ results.push({
555
+ ...log,
556
+ type: 'previous_invocation'
557
+ });
558
+ }
559
+ }
560
+
561
+ // Then, search for keyword-relevant actions
562
+ if (keywords.length > 0) {
563
+ for (const keyword of keywords) {
564
+ const matches = search(keyword, {
565
+ config: cfg || undefined,
566
+ types: ['code', 'decisions', 'fixes'],
567
+ limit: 3
568
+ });
569
+
570
+ for (const match of matches) {
571
+ if (!results.find(r => r.path === match.path)) {
572
+ const log = get(match.file, { config: cfg || undefined });
573
+ if (log) {
574
+ results.push({
575
+ ...log,
576
+ type: 'keyword_match',
577
+ keyword
578
+ });
579
+ }
580
+ }
581
+ }
582
+
583
+ if (results.length >= limit) break;
584
+ }
585
+ }
586
+
587
+ return results.slice(0, limit);
588
+ }
589
+
590
+ /**
591
+ * Export logs to a directory
592
+ */
593
+ export function exportLogs(
594
+ outputDir: string,
595
+ options: { config?: Config; types?: string[] } = {}
596
+ ): ExportResult {
597
+ const config = getConfig();
598
+ const utils = getUtils();
599
+
600
+ const cfg = options.config || config.load();
601
+ const projectRoot = cfg?._projectRoot || process.cwd();
602
+ const types = options.types || Object.keys(ACTION_TYPES);
603
+ let count = 0;
604
+
605
+ utils.ensureDir(outputDir);
606
+
607
+ for (const type of types) {
608
+ const logsDir = getLogsDir(projectRoot, type);
609
+
610
+ if (!utils.fileExists(logsDir)) continue;
611
+
612
+ const typeDir = path.join(outputDir, ACTION_TYPES[type]!);
613
+ utils.ensureDir(typeDir);
614
+
615
+ try {
616
+ const files = fs.readdirSync(logsDir).filter(f => f.endsWith('.md'));
617
+
618
+ for (const file of files) {
619
+ const src = path.join(logsDir, file);
620
+ const dest = path.join(typeDir, file);
621
+ fs.copyFileSync(src, dest);
622
+ count++;
623
+ }
624
+ } catch {
625
+ // Directory read error, skip
626
+ }
627
+ }
628
+
629
+ return { success: true, count, outputDir };
630
+ }
631
+
632
+ /**
633
+ * Clear old logs
634
+ */
635
+ export function clearOld(
636
+ options: { config?: Config; olderThanDays?: number; types?: string[] } = {}
637
+ ): ClearResult {
638
+ const config = getConfig();
639
+ const utils = getUtils();
640
+
641
+ const cfg = options.config || config.load();
642
+ const projectRoot = cfg?._projectRoot || process.cwd();
643
+ const olderThanDays = options.olderThanDays || 30;
644
+ const types = options.types || Object.keys(ACTION_TYPES);
645
+ let deleted = 0;
646
+
647
+ const cutoffDate = new Date();
648
+ cutoffDate.setDate(cutoffDate.getDate() - olderThanDays);
649
+
650
+ for (const type of types) {
651
+ const logsDir = getLogsDir(projectRoot, type);
652
+
653
+ if (!utils.fileExists(logsDir)) continue;
654
+
655
+ try {
656
+ const files = fs.readdirSync(logsDir).filter(f => f.endsWith('.md'));
657
+
658
+ for (const file of files) {
659
+ const filepath = path.join(logsDir, file);
660
+ const stats = fs.statSync(filepath);
661
+
662
+ if (stats.mtime < cutoffDate) {
663
+ fs.unlinkSync(filepath);
664
+ deleted++;
665
+ }
666
+ }
667
+ } catch {
668
+ // Directory read error, skip
669
+ }
670
+ }
671
+
672
+ return { success: true, deleted };
673
+ }