@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.
- package/bin/bootspring.js +5 -0
- package/cli/org.js +474 -0
- package/cli/preseed/index.js +16 -0
- package/cli/preseed/interactive.js +143 -0
- package/cli/preseed/templates.js +227 -0
- package/cli/preseed.js +9 -301
- package/cli/seed/builders/ai-context-builder.js +85 -0
- package/cli/seed/builders/index.js +13 -0
- package/cli/seed/builders/seed-builder.js +272 -0
- package/cli/seed/extractors/content-extractors.js +383 -0
- package/cli/seed/extractors/index.js +47 -0
- package/cli/seed/extractors/metadata-extractors.js +167 -0
- package/cli/seed/extractors/section-extractor.js +54 -0
- package/cli/seed/extractors/stack-extractors.js +228 -0
- package/cli/seed/index.js +18 -0
- package/cli/seed/utils/folder-structure.js +84 -0
- package/cli/seed/utils/index.js +11 -0
- package/cli/seed.js +23 -1074
- package/core/api-client.js +77 -0
- package/core/entitlements.js +36 -0
- package/core/organizations.js +223 -0
- package/core/policies.js +51 -6
- package/core/policy-matrix.js +303 -0
- package/core/project-context.js +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.js +3220 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/context-McpJQa_2.d.ts +5710 -0
- package/dist/core/index.d.ts +635 -0
- package/dist/core/index.js +2593 -0
- package/dist/core/index.js.map +1 -0
- package/dist/index-QqbeEiDm.d.ts +857 -0
- package/dist/index-UiYCgwiH.d.ts +174 -0
- package/dist/index.d.ts +453 -0
- package/dist/index.js +44228 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp/index.js +41173 -0
- package/dist/mcp/index.js.map +1 -0
- package/generators/index.ts +82 -0
- package/intelligence/orchestrator/config/failure-signatures.js +48 -0
- package/intelligence/orchestrator/config/index.js +23 -0
- package/intelligence/orchestrator/config/pack-lifecycle.js +262 -0
- package/intelligence/orchestrator/config/phases.js +111 -0
- package/intelligence/orchestrator/config/remediation.js +150 -0
- package/intelligence/orchestrator/config/workflows.js +168 -0
- package/intelligence/orchestrator/core/index.js +16 -0
- package/intelligence/orchestrator/core/state-manager.js +88 -0
- package/intelligence/orchestrator/core/telemetry.js +24 -0
- package/intelligence/orchestrator/index.js +17 -0
- package/intelligence/orchestrator.js +17 -512
- package/mcp/contracts/mcp-contract.v1.json +1 -1
- package/package.json +16 -3
- package/src/cli/agent.ts +703 -0
- package/src/cli/analyze.ts +640 -0
- package/src/cli/audit.ts +707 -0
- package/src/cli/auth.ts +930 -0
- package/src/cli/billing.ts +364 -0
- package/src/cli/build.ts +1089 -0
- package/src/cli/business.ts +508 -0
- package/src/cli/checkpoint-utils.ts +236 -0
- package/src/cli/checkpoint.ts +757 -0
- package/src/cli/cloud-sync.ts +534 -0
- package/src/cli/content.ts +273 -0
- package/src/cli/context.ts +667 -0
- package/src/cli/dashboard.ts +133 -0
- package/src/cli/deploy.ts +704 -0
- package/src/cli/doctor.ts +480 -0
- package/src/cli/fundraise.ts +494 -0
- package/src/cli/generate.ts +346 -0
- package/src/cli/github-cmd.ts +566 -0
- package/src/cli/health.ts +599 -0
- package/src/cli/index.ts +113 -0
- package/src/cli/init.ts +838 -0
- package/src/cli/legal.ts +495 -0
- package/src/cli/log.ts +316 -0
- package/src/cli/loop.ts +1660 -0
- package/src/cli/manager.ts +878 -0
- package/src/cli/mcp.ts +275 -0
- package/src/cli/memory.ts +346 -0
- package/src/cli/metrics.ts +590 -0
- package/src/cli/monitor.ts +960 -0
- package/src/cli/mvp.ts +662 -0
- package/src/cli/onboard.ts +663 -0
- package/src/cli/orchestrator.ts +622 -0
- package/src/cli/plugin.ts +483 -0
- package/src/cli/prd.ts +671 -0
- package/src/cli/preseed-start.ts +1633 -0
- package/src/cli/preseed.ts +2434 -0
- package/src/cli/project.ts +526 -0
- package/src/cli/quality.ts +885 -0
- package/src/cli/security.ts +1079 -0
- package/src/cli/seed.ts +1224 -0
- package/src/cli/skill.ts +537 -0
- package/src/cli/suggest.ts +1225 -0
- package/src/cli/switch.ts +518 -0
- package/src/cli/task.ts +780 -0
- package/src/cli/telemetry.ts +172 -0
- package/src/cli/todo.ts +627 -0
- package/src/cli/types.ts +15 -0
- package/src/cli/update.ts +334 -0
- package/src/cli/visualize.ts +609 -0
- package/src/cli/watch.ts +895 -0
- package/src/cli/workspace.ts +709 -0
- package/src/core/action-recorder.ts +673 -0
- package/src/core/analyze-workflow.ts +1453 -0
- package/src/core/api-client.ts +1120 -0
- package/src/core/audit-workflow.ts +1681 -0
- package/src/core/auth.ts +471 -0
- package/src/core/build-orchestrator.ts +509 -0
- package/src/core/build-state.ts +621 -0
- package/src/core/checkpoint-engine.ts +482 -0
- package/src/core/config.ts +1285 -0
- package/src/core/context-loader.ts +694 -0
- package/src/core/context.ts +410 -0
- package/src/core/deploy-workflow.ts +1085 -0
- package/src/core/entitlements.ts +322 -0
- package/src/core/github-sync.ts +720 -0
- package/src/core/index.ts +981 -0
- package/src/core/ingest.ts +1186 -0
- package/src/core/metrics-engine.ts +886 -0
- package/src/core/mvp.ts +847 -0
- package/src/core/onboard-workflow.ts +1293 -0
- package/src/core/policies.ts +81 -0
- package/src/core/preseed-workflow.ts +1163 -0
- package/src/core/preseed.ts +1826 -0
- package/src/core/project-context.ts +380 -0
- package/src/core/project-state.ts +699 -0
- package/src/core/r2-sync.ts +691 -0
- package/src/core/scaffold.ts +1715 -0
- package/src/core/session.ts +286 -0
- package/src/core/task-extractor.ts +799 -0
- package/src/core/telemetry.ts +371 -0
- package/src/core/tier-enforcement.ts +737 -0
- package/src/core/utils.ts +437 -0
- package/src/index.ts +29 -0
- package/src/intelligence/agent-collab.ts +2376 -0
- package/src/intelligence/auto-suggest.ts +713 -0
- package/src/intelligence/content-gen.ts +1351 -0
- package/src/intelligence/cross-project.ts +1692 -0
- package/src/intelligence/git-memory.ts +529 -0
- package/src/intelligence/index.ts +318 -0
- package/src/intelligence/orchestrator.ts +534 -0
- package/src/intelligence/prd.ts +466 -0
- package/src/intelligence/recommendations.ts +982 -0
- package/src/intelligence/workflow-composer.ts +1472 -0
- package/src/mcp/capabilities.ts +233 -0
- package/src/mcp/index.ts +37 -0
- package/src/mcp/registry.ts +1268 -0
- package/src/mcp/response-formatter.ts +797 -0
- package/src/mcp/server.ts +240 -0
- package/src/types/agent.ts +69 -0
- package/src/types/config.ts +86 -0
- package/src/types/context.ts +77 -0
- package/src/types/index.ts +53 -0
- package/src/types/mcp.ts +91 -0
- package/src/types/skills.ts +47 -0
- package/src/types/workflow.ts +155 -0
- 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
|
+
}
|