kiroo 0.8.0 → 0.9.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 +368 -293
- package/bin/kiroo.js +361 -288
- package/package.json +2 -1
- package/src/analyze.js +550 -0
- package/src/config.js +109 -0
- package/src/deterministic.js +22 -0
- package/src/env.js +31 -3
- package/src/executor.js +17 -0
- package/src/export.js +503 -93
- package/src/init.js +80 -48
- package/src/lingo.js +32 -36
- package/src/proxy.js +120 -0
- package/src/replay.js +5 -4
- package/src/sanitizer.js +100 -0
- package/src/snapshot.js +76 -19
- package/src/storage.js +223 -142
package/src/storage.js
CHANGED
|
@@ -1,142 +1,223 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync, rmSync } from 'fs';
|
|
2
|
-
import { join } from 'path';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export function
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if (!existsSync(
|
|
103
|
-
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
return
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
1
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync, rmSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { sanitizeInteractionRecord } from './sanitizer.js';
|
|
4
|
+
import { loadKirooConfig } from './config.js';
|
|
5
|
+
import { stableJSONStringify } from './deterministic.js';
|
|
6
|
+
|
|
7
|
+
const KIROO_DIR = '.kiroo';
|
|
8
|
+
const INTERACTIONS_DIR = join(KIROO_DIR, 'interactions');
|
|
9
|
+
const SNAPSHOTS_DIR = join(KIROO_DIR, 'snapshots');
|
|
10
|
+
const ENV_FILE = join(KIROO_DIR, 'env.json');
|
|
11
|
+
|
|
12
|
+
function getPersistenceSettings() {
|
|
13
|
+
const config = loadKirooConfig();
|
|
14
|
+
const redaction = config.settings?.redaction || {};
|
|
15
|
+
const determinism = config.settings?.determinism || {};
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
redactEnabled: redaction.enabled !== false,
|
|
19
|
+
redactedValue: redaction.redactedValue || '<REDACTED>',
|
|
20
|
+
sortKeys: determinism.sortKeys !== false
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function stringifyForPersistence(value, sortKeysEnabled) {
|
|
25
|
+
if (sortKeysEnabled) {
|
|
26
|
+
return stableJSONStringify(value, 2);
|
|
27
|
+
}
|
|
28
|
+
return JSON.stringify(value, null, 2);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function ensureKirooDir() {
|
|
32
|
+
if (!existsSync(KIROO_DIR)) {
|
|
33
|
+
mkdirSync(KIROO_DIR);
|
|
34
|
+
}
|
|
35
|
+
if (!existsSync(INTERACTIONS_DIR)) {
|
|
36
|
+
mkdirSync(INTERACTIONS_DIR);
|
|
37
|
+
}
|
|
38
|
+
if (!existsSync(SNAPSHOTS_DIR)) {
|
|
39
|
+
mkdirSync(SNAPSHOTS_DIR);
|
|
40
|
+
}
|
|
41
|
+
if (!existsSync(ENV_FILE)) {
|
|
42
|
+
writeFileSync(ENV_FILE, JSON.stringify({ current: 'default', environments: { default: {} } }, null, 2));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const GITIGNORE_FILE = join(KIROO_DIR, '.gitignore');
|
|
46
|
+
if (!existsSync(GITIGNORE_FILE)) {
|
|
47
|
+
writeFileSync(GITIGNORE_FILE, 'env.json\n');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export async function saveInteraction(interaction) {
|
|
52
|
+
ensureKirooDir();
|
|
53
|
+
const settings = getPersistenceSettings();
|
|
54
|
+
|
|
55
|
+
const timestamp = new Date().toISOString();
|
|
56
|
+
const id = timestamp.replace(/[:.]/g, '-');
|
|
57
|
+
|
|
58
|
+
const interactionData = {
|
|
59
|
+
id,
|
|
60
|
+
timestamp,
|
|
61
|
+
request: {
|
|
62
|
+
method: interaction.method,
|
|
63
|
+
url: interaction.url,
|
|
64
|
+
headers: interaction.headers,
|
|
65
|
+
body: interaction.body,
|
|
66
|
+
},
|
|
67
|
+
response: interaction.response,
|
|
68
|
+
metadata: {
|
|
69
|
+
duration_ms: interaction.duration,
|
|
70
|
+
saves: interaction.saves || [], // Variables saved from this response
|
|
71
|
+
uses: interaction.uses || [], // Variables used in this request
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const sanitizedInteractionData = sanitizeInteractionRecord(interactionData, {
|
|
76
|
+
enabled: settings.redactEnabled,
|
|
77
|
+
redactedValue: settings.redactedValue
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const filename = `${id}.json`;
|
|
81
|
+
const filepath = join(INTERACTIONS_DIR, filename);
|
|
82
|
+
|
|
83
|
+
writeFileSync(filepath, stringifyForPersistence(sanitizedInteractionData, settings.sortKeys));
|
|
84
|
+
|
|
85
|
+
return id;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function loadInteraction(id) {
|
|
89
|
+
const filepath = join(INTERACTIONS_DIR, `${id}.json`);
|
|
90
|
+
|
|
91
|
+
if (!existsSync(filepath)) {
|
|
92
|
+
throw new Error(`Interaction not found: ${id}`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const data = readFileSync(filepath, 'utf8');
|
|
96
|
+
return JSON.parse(data);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function getAllInteractions() {
|
|
100
|
+
ensureKirooDir();
|
|
101
|
+
|
|
102
|
+
if (!existsSync(INTERACTIONS_DIR)) {
|
|
103
|
+
return [];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const files = readdirSync(INTERACTIONS_DIR)
|
|
107
|
+
.filter(f => f.endsWith('.json'))
|
|
108
|
+
.sort()
|
|
109
|
+
.reverse(); // Most recent first
|
|
110
|
+
|
|
111
|
+
return files.map(f => {
|
|
112
|
+
const filepath = join(INTERACTIONS_DIR, f);
|
|
113
|
+
const data = readFileSync(filepath, 'utf8');
|
|
114
|
+
return JSON.parse(data);
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function saveSnapshotData(tag, data) {
|
|
119
|
+
ensureKirooDir();
|
|
120
|
+
const settings = getPersistenceSettings();
|
|
121
|
+
|
|
122
|
+
const filename = `${tag}.json`;
|
|
123
|
+
const filepath = join(SNAPSHOTS_DIR, filename);
|
|
124
|
+
const sanitizedSnapshot = sanitizeInteractionRecord(data, {
|
|
125
|
+
enabled: settings.redactEnabled,
|
|
126
|
+
redactedValue: settings.redactedValue
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
writeFileSync(filepath, stringifyForPersistence(sanitizedSnapshot, settings.sortKeys));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function loadSnapshotData(tag) {
|
|
133
|
+
const filepath = join(SNAPSHOTS_DIR, `${tag}.json`);
|
|
134
|
+
|
|
135
|
+
if (!existsSync(filepath)) {
|
|
136
|
+
throw new Error(`Snapshot not found: ${tag}`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const data = readFileSync(filepath, 'utf8');
|
|
140
|
+
return JSON.parse(data);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function getAllSnapshots() {
|
|
144
|
+
ensureKirooDir();
|
|
145
|
+
|
|
146
|
+
if (!existsSync(SNAPSHOTS_DIR)) {
|
|
147
|
+
return [];
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return readdirSync(SNAPSHOTS_DIR)
|
|
151
|
+
.filter(f => f.endsWith('.json'))
|
|
152
|
+
.sort((a, b) => a.localeCompare(b))
|
|
153
|
+
.map(f => f.replace('.json', ''));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function clearAllInteractions() {
|
|
157
|
+
ensureKirooDir();
|
|
158
|
+
if (existsSync(INTERACTIONS_DIR)) {
|
|
159
|
+
const files = readdirSync(INTERACTIONS_DIR);
|
|
160
|
+
files.forEach(f => {
|
|
161
|
+
const filepath = join(INTERACTIONS_DIR, f);
|
|
162
|
+
rmSync(filepath, { force: true });
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function scrubDirectory(directoryPath, { dryRun = false } = {}) {
|
|
168
|
+
const settings = getPersistenceSettings();
|
|
169
|
+
|
|
170
|
+
if (!existsSync(directoryPath)) {
|
|
171
|
+
return { scanned: 0, updated: 0 };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const files = readdirSync(directoryPath).filter((f) => f.endsWith('.json'));
|
|
175
|
+
let updated = 0;
|
|
176
|
+
|
|
177
|
+
for (const fileName of files) {
|
|
178
|
+
const filePath = join(directoryPath, fileName);
|
|
179
|
+
const originalRaw = readFileSync(filePath, 'utf8');
|
|
180
|
+
const originalData = JSON.parse(originalRaw);
|
|
181
|
+
const sanitizedData = sanitizeInteractionRecord(originalData, {
|
|
182
|
+
enabled: settings.redactEnabled,
|
|
183
|
+
redactedValue: settings.redactedValue
|
|
184
|
+
});
|
|
185
|
+
const sanitizedRaw = stringifyForPersistence(sanitizedData, settings.sortKeys);
|
|
186
|
+
|
|
187
|
+
if (originalRaw !== sanitizedRaw) {
|
|
188
|
+
updated += 1;
|
|
189
|
+
if (!dryRun) {
|
|
190
|
+
writeFileSync(filePath, sanitizedRaw);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return { scanned: files.length, updated };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function scrubStoredData(options = {}) {
|
|
199
|
+
ensureKirooDir();
|
|
200
|
+
const { dryRun = false } = options;
|
|
201
|
+
|
|
202
|
+
const interactions = scrubDirectory(INTERACTIONS_DIR, { dryRun });
|
|
203
|
+
const snapshots = scrubDirectory(SNAPSHOTS_DIR, { dryRun });
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
interactions,
|
|
207
|
+
snapshots,
|
|
208
|
+
totalUpdated: interactions.updated + snapshots.updated,
|
|
209
|
+
dryRun,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export function loadEnv() {
|
|
214
|
+
ensureKirooDir();
|
|
215
|
+
const data = readFileSync(ENV_FILE, 'utf8');
|
|
216
|
+
return JSON.parse(data);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export function saveEnv(data) {
|
|
220
|
+
ensureKirooDir();
|
|
221
|
+
const settings = getPersistenceSettings();
|
|
222
|
+
writeFileSync(ENV_FILE, stringifyForPersistence(data, settings.sortKeys));
|
|
223
|
+
}
|