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/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
- const KIROO_DIR = '.kiroo';
5
- const INTERACTIONS_DIR = join(KIROO_DIR, 'interactions');
6
- const SNAPSHOTS_DIR = join(KIROO_DIR, 'snapshots');
7
- const ENV_FILE = join(KIROO_DIR, 'env.json');
8
-
9
- export function ensureKirooDir() {
10
- if (!existsSync(KIROO_DIR)) {
11
- mkdirSync(KIROO_DIR);
12
- }
13
- if (!existsSync(INTERACTIONS_DIR)) {
14
- mkdirSync(INTERACTIONS_DIR);
15
- }
16
- if (!existsSync(SNAPSHOTS_DIR)) {
17
- mkdirSync(SNAPSHOTS_DIR);
18
- }
19
- if (!existsSync(ENV_FILE)) {
20
- writeFileSync(ENV_FILE, JSON.stringify({ current: 'default', environments: { default: {} } }, null, 2));
21
- }
22
-
23
- const GITIGNORE_FILE = join(KIROO_DIR, '.gitignore');
24
- if (!existsSync(GITIGNORE_FILE)) {
25
- writeFileSync(GITIGNORE_FILE, 'env.json\n');
26
- }
27
- }
28
-
29
- export async function saveInteraction(interaction) {
30
- ensureKirooDir();
31
-
32
- const timestamp = new Date().toISOString();
33
- const id = timestamp.replace(/[:.]/g, '-');
34
-
35
- const interactionData = {
36
- id,
37
- timestamp,
38
- request: {
39
- method: interaction.method,
40
- url: interaction.url,
41
- headers: interaction.headers,
42
- body: interaction.body,
43
- },
44
- response: interaction.response,
45
- metadata: {
46
- duration_ms: interaction.duration,
47
- saves: interaction.saves || [], // Variables saved from this response
48
- uses: interaction.uses || [], // Variables used in this request
49
- },
50
- };
51
-
52
- const filename = `${id}.json`;
53
- const filepath = join(INTERACTIONS_DIR, filename);
54
-
55
- writeFileSync(filepath, JSON.stringify(interactionData, null, 2));
56
-
57
- return id;
58
- }
59
-
60
- export function loadInteraction(id) {
61
- const filepath = join(INTERACTIONS_DIR, `${id}.json`);
62
-
63
- if (!existsSync(filepath)) {
64
- throw new Error(`Interaction not found: ${id}`);
65
- }
66
-
67
- const data = readFileSync(filepath, 'utf8');
68
- return JSON.parse(data);
69
- }
70
-
71
- export function getAllInteractions() {
72
- ensureKirooDir();
73
-
74
- if (!existsSync(INTERACTIONS_DIR)) {
75
- return [];
76
- }
77
-
78
- const files = readdirSync(INTERACTIONS_DIR)
79
- .filter(f => f.endsWith('.json'))
80
- .sort()
81
- .reverse(); // Most recent first
82
-
83
- return files.map(f => {
84
- const filepath = join(INTERACTIONS_DIR, f);
85
- const data = readFileSync(filepath, 'utf8');
86
- return JSON.parse(data);
87
- });
88
- }
89
-
90
- export function saveSnapshotData(tag, data) {
91
- ensureKirooDir();
92
-
93
- const filename = `${tag}.json`;
94
- const filepath = join(SNAPSHOTS_DIR, filename);
95
-
96
- writeFileSync(filepath, JSON.stringify(data, null, 2));
97
- }
98
-
99
- export function loadSnapshotData(tag) {
100
- const filepath = join(SNAPSHOTS_DIR, `${tag}.json`);
101
-
102
- if (!existsSync(filepath)) {
103
- throw new Error(`Snapshot not found: ${tag}`);
104
- }
105
-
106
- const data = readFileSync(filepath, 'utf8');
107
- return JSON.parse(data);
108
- }
109
-
110
- export function getAllSnapshots() {
111
- ensureKirooDir();
112
-
113
- if (!existsSync(SNAPSHOTS_DIR)) {
114
- return [];
115
- }
116
-
117
- return readdirSync(SNAPSHOTS_DIR)
118
- .filter(f => f.endsWith('.json'))
119
- .map(f => f.replace('.json', ''));
120
- }
121
-
122
- export function clearAllInteractions() {
123
- ensureKirooDir();
124
- if (existsSync(INTERACTIONS_DIR)) {
125
- const files = readdirSync(INTERACTIONS_DIR);
126
- files.forEach(f => {
127
- const filepath = join(INTERACTIONS_DIR, f);
128
- rmSync(filepath, { force: true });
129
- });
130
- }
131
- }
132
-
133
- export function loadEnv() {
134
- ensureKirooDir();
135
- const data = readFileSync(ENV_FILE, 'utf8');
136
- return JSON.parse(data);
137
- }
138
-
139
- export function saveEnv(data) {
140
- ensureKirooDir();
141
- writeFileSync(ENV_FILE, JSON.stringify(data, null, 2));
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
+ }