claude-flow 3.48.0 → 3.50.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/.claude/.proven-config-version +1 -0
- package/.claude/helpers/hook-handler.cjs +20 -4
- package/.claude/helpers/memory.cjs +1 -1
- package/.claude/helpers/router.cjs +1 -1
- package/.claude/helpers/session.cjs +1 -1
- package/.claude/proven-config.json +42 -0
- package/.claude-plugin/marketplace.json +26 -1
- package/README.md +1 -53
- package/README.zh-CN.md +1 -53
- package/node_modules/@claude-flow/codex/package.json +1 -1
- package/node_modules/@claude-flow/security/dist/policy/engine.d.ts +2 -6
- package/node_modules/@claude-flow/security/dist/policy/engine.d.ts.map +1 -1
- package/node_modules/@claude-flow/security/dist/policy/engine.js +35 -1
- package/node_modules/@claude-flow/security/dist/policy/engine.js.map +1 -1
- package/node_modules/@claude-flow/security/dist/policy/types.d.ts +18 -0
- package/node_modules/@claude-flow/security/dist/policy/types.d.ts.map +1 -1
- package/node_modules/@claude-flow/security/package.json +1 -1
- package/package.json +2 -2
- package/v3/@claude-flow/cli/README.md +3 -53
- package/v3/@claude-flow/cli/catalog-manifest.json +4 -4
- package/v3/@claude-flow/cli/dist/src/commands/doctor.d.ts +19 -1
- package/v3/@claude-flow/cli/dist/src/commands/doctor.js +88 -9
- package/v3/@claude-flow/cli/dist/src/commands/hooks.js +7 -4
- package/v3/@claude-flow/cli/dist/src/commands/index.js +2 -0
- package/v3/@claude-flow/cli/dist/src/commands/init.js +20 -0
- package/v3/@claude-flow/cli/dist/src/commands/memory.js +30 -7
- package/v3/@claude-flow/cli/dist/src/commands/mods.d.ts +13 -0
- package/v3/@claude-flow/cli/dist/src/commands/mods.js +126 -0
- package/v3/@claude-flow/cli/dist/src/commands/plugins.js +44 -7
- package/v3/@claude-flow/cli/dist/src/commands/policy.js +5 -2
- package/v3/@claude-flow/cli/dist/src/commands/session.js +128 -21
- package/v3/@claude-flow/cli/dist/src/commands/swarm.js +11 -11
- package/v3/@claude-flow/cli/dist/src/index.js +10 -1
- package/v3/@claude-flow/cli/dist/src/init/executor.js +11 -5
- package/v3/@claude-flow/cli/dist/src/init/helper-companions.d.ts +3 -0
- package/v3/@claude-flow/cli/dist/src/init/helper-companions.js +34 -0
- package/v3/@claude-flow/cli/dist/src/init/helper-integrity.d.ts +21 -0
- package/v3/@claude-flow/cli/dist/src/init/helper-integrity.js +62 -0
- package/v3/@claude-flow/cli/dist/src/init/helper-refresh.d.ts +18 -11
- package/v3/@claude-flow/cli/dist/src/init/helper-refresh.js +56 -13
- package/v3/@claude-flow/cli/dist/src/init/helpers-generator.js +22 -10
- package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.d.ts +16 -3
- package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.js +87 -14
- package/v3/@claude-flow/cli/dist/src/mcp-tools/memory-tools.d.ts +7 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/memory-tools.js +43 -6
- package/v3/@claude-flow/cli/dist/src/mcp-tools/session-tools.d.ts +15 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/session-tools.js +236 -50
- package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.js +6 -3
- package/v3/@claude-flow/cli/dist/src/mods/claude-installs.d.ts +29 -0
- package/v3/@claude-flow/cli/dist/src/mods/claude-installs.js +83 -0
- package/v3/@claude-flow/cli/dist/src/mods/install.d.ts +64 -0
- package/v3/@claude-flow/cli/dist/src/mods/install.js +135 -0
- package/v3/@claude-flow/cli/dist/src/mods/policy-projection.d.ts +39 -0
- package/v3/@claude-flow/cli/dist/src/mods/policy-projection.js +65 -0
- package/v3/@claude-flow/cli/dist/src/mods/probe.d.ts +28 -0
- package/v3/@claude-flow/cli/dist/src/mods/probe.js +118 -0
- package/v3/@claude-flow/cli/dist/src/plugins/manager.d.ts +37 -10
- package/v3/@claude-flow/cli/dist/src/plugins/manager.js +106 -20
- package/v3/@claude-flow/cli/dist/src/plugins/trust-policy.d.ts +61 -0
- package/v3/@claude-flow/cli/dist/src/plugins/trust-policy.js +84 -0
- package/v3/@claude-flow/cli/dist/src/services/policy-runtime.d.ts +6 -0
- package/v3/@claude-flow/cli/dist/src/services/policy-runtime.js +42 -2
- package/v3/@claude-flow/cli/package.json +2 -2
|
@@ -69,17 +69,126 @@ function listSessions() {
|
|
|
69
69
|
}
|
|
70
70
|
return sessions;
|
|
71
71
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
72
|
+
/** Snapshot format written by session_save since #3573. */
|
|
73
|
+
const MEMORY_SNAPSHOT_FORMAT = 'ruflo-session-memory/2';
|
|
74
|
+
const MEMORY_PAGE_SIZE = 500;
|
|
75
|
+
async function readLiveEntries(listEntries, dbPath, encryptWrites) {
|
|
76
|
+
const rows = [];
|
|
77
|
+
// Bounded: stop on a short page, once `total` rows are in hand, or after a
|
|
78
|
+
// hard cap, so a backend that ignores `offset` cannot loop forever.
|
|
79
|
+
for (let page = 0, offset = 0; page < 10_000; page++, offset += MEMORY_PAGE_SIZE) {
|
|
80
|
+
const result = await listEntries({
|
|
81
|
+
dbPath, includeContent: true, limit: MEMORY_PAGE_SIZE, offset,
|
|
82
|
+
...(encryptWrites === undefined ? {} : { encryptWrites }),
|
|
83
|
+
});
|
|
84
|
+
if (!result.success)
|
|
85
|
+
throw new Error(result.error || `could not list ${dbPath}`);
|
|
86
|
+
rows.push(...result.entries);
|
|
87
|
+
if (result.entries.length < MEMORY_PAGE_SIZE || rows.length >= result.total)
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
return rows;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* #3573: capture the memory the user actually has.
|
|
94
|
+
*
|
|
95
|
+
* Before this, session_save read only the pre-SQLite `.claude-flow/memory/store.json`,
|
|
96
|
+
* which a current install never writes, so `--include-memory` saved nothing and
|
|
97
|
+
* reported "Memory Entries: 0". The snapshot now covers the same store `memory
|
|
98
|
+
* list` reads (resolveDbPath), plus rows that exist only in the sibling AgentDB
|
|
99
|
+
* store written by the MCP path, de-duplicated by namespace+key (default CLI
|
|
100
|
+
* writes are mirrored into AgentDB, so most rows are in both). A legacy
|
|
101
|
+
* store.json is still captured for backward compatibility.
|
|
102
|
+
*/
|
|
103
|
+
async function captureMemorySnapshot() {
|
|
104
|
+
const report = {
|
|
105
|
+
requested: true, status: 'captured', entries: 0,
|
|
106
|
+
sources: { memoryDb: 0, agentdb: 0, legacyJson: 0 },
|
|
107
|
+
};
|
|
108
|
+
const entries = {};
|
|
109
|
+
let legacy;
|
|
110
|
+
const legacyPath = join(getProjectCwd(), STORAGE_DIR, 'memory', 'store.json');
|
|
111
|
+
if (existsSync(legacyPath)) {
|
|
76
112
|
try {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
113
|
+
legacy = JSON.parse(readFileSync(legacyPath, 'utf-8'));
|
|
114
|
+
const legacyEntries = legacy.entries || {};
|
|
115
|
+
for (const [id, entry] of Object.entries(legacyEntries)) {
|
|
116
|
+
entries[id] = entry;
|
|
117
|
+
report.sources.legacyJson++;
|
|
80
118
|
}
|
|
81
119
|
}
|
|
82
|
-
catch { /*
|
|
120
|
+
catch { /* unreadable legacy file is not memory we can restore */ }
|
|
121
|
+
}
|
|
122
|
+
let sawStore = legacy !== undefined;
|
|
123
|
+
try {
|
|
124
|
+
const { listEntries, resolveDbPath } = await import('../memory/memory-initializer.js');
|
|
125
|
+
const primary = resolveDbPath();
|
|
126
|
+
const seen = new Set();
|
|
127
|
+
if (existsSync(primary)) {
|
|
128
|
+
sawStore = true;
|
|
129
|
+
for (const row of await readLiveEntries(listEntries, primary)) {
|
|
130
|
+
const id = `${row.namespace}::${row.key}`;
|
|
131
|
+
seen.add(id);
|
|
132
|
+
entries[id] = {
|
|
133
|
+
key: row.key, value: row.content ?? '', namespace: row.namespace,
|
|
134
|
+
provenanceType: row.provenanceType, source: 'memory-db',
|
|
135
|
+
};
|
|
136
|
+
report.sources.memoryDb++;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
let sibling = null;
|
|
140
|
+
try {
|
|
141
|
+
const { siblingAgentDbPath } = await import('../memory/memory-bridge.js');
|
|
142
|
+
sibling = siblingAgentDbPath(primary);
|
|
143
|
+
}
|
|
144
|
+
catch { /* no bridge, no sibling store */ }
|
|
145
|
+
if (sibling && existsSync(sibling)) {
|
|
146
|
+
sawStore = true;
|
|
147
|
+
// The AgentDB store is read by native SQLite and must stay plaintext.
|
|
148
|
+
for (const row of await readLiveEntries(listEntries, sibling, false)) {
|
|
149
|
+
const id = `${row.namespace}::${row.key}`;
|
|
150
|
+
if (seen.has(id))
|
|
151
|
+
continue;
|
|
152
|
+
seen.add(id);
|
|
153
|
+
entries[id] = {
|
|
154
|
+
key: row.key, value: row.content ?? '', namespace: row.namespace,
|
|
155
|
+
provenanceType: row.provenanceType, source: 'agentdb',
|
|
156
|
+
};
|
|
157
|
+
report.sources.agentdb++;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
catch (e) {
|
|
162
|
+
report.status = 'error';
|
|
163
|
+
report.error = e.message;
|
|
164
|
+
}
|
|
165
|
+
report.entries = Object.keys(entries).length;
|
|
166
|
+
if (report.status !== 'error' && !sawStore)
|
|
167
|
+
report.status = 'no-store';
|
|
168
|
+
if (report.entries === 0 && legacy === undefined) {
|
|
169
|
+
return { report };
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
memory: {
|
|
173
|
+
format: MEMORY_SNAPSHOT_FORMAT,
|
|
174
|
+
entries,
|
|
175
|
+
...(legacy !== undefined ? { legacy } : {}),
|
|
176
|
+
},
|
|
177
|
+
report,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
// Load related stores for session data
|
|
181
|
+
async function loadRelatedStores(options) {
|
|
182
|
+
const data = {};
|
|
183
|
+
let memoryCapture = {
|
|
184
|
+
requested: false, status: 'not-requested', entries: 0,
|
|
185
|
+
sources: { memoryDb: 0, agentdb: 0, legacyJson: 0 },
|
|
186
|
+
};
|
|
187
|
+
if (options.includeMemory) {
|
|
188
|
+
const captured = await captureMemorySnapshot();
|
|
189
|
+
memoryCapture = captured.report;
|
|
190
|
+
if (captured.memory)
|
|
191
|
+
data.memory = captured.memory;
|
|
83
192
|
}
|
|
84
193
|
if (options.includeTasks) {
|
|
85
194
|
try {
|
|
@@ -99,7 +208,92 @@ function loadRelatedStores(options) {
|
|
|
99
208
|
}
|
|
100
209
|
catch { /* ignore */ }
|
|
101
210
|
}
|
|
102
|
-
return data;
|
|
211
|
+
return { data, memoryCapture };
|
|
212
|
+
}
|
|
213
|
+
/** Count entries in a memory snapshot of either format. */
|
|
214
|
+
function countMemoryEntries(memory) {
|
|
215
|
+
if (!memory || typeof memory !== 'object')
|
|
216
|
+
return 0;
|
|
217
|
+
return Object.keys(memory.entries || {}).length;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Restore a memory snapshot into the live store and report what was actually
|
|
221
|
+
* written. #3573: the previous restore swallowed every write failure and then
|
|
222
|
+
* echoed the count recorded at save time.
|
|
223
|
+
*/
|
|
224
|
+
async function restoreMemorySnapshot(memory) {
|
|
225
|
+
const outcome = { restored: 0, failed: 0, errors: [] };
|
|
226
|
+
const isV2 = memory.format === MEMORY_SNAPSHOT_FORMAT;
|
|
227
|
+
// Legacy snapshots are the old store.json itself; v2 snapshots carry it
|
|
228
|
+
// under `legacy` only when one existed at save time.
|
|
229
|
+
const legacyJson = isV2 ? memory.legacy : memory;
|
|
230
|
+
if (legacyJson !== undefined) {
|
|
231
|
+
const memoryDir = join(getProjectCwd(), STORAGE_DIR, 'memory');
|
|
232
|
+
if (!existsSync(memoryDir))
|
|
233
|
+
mkdirRestricted(memoryDir);
|
|
234
|
+
writeFileRestricted(join(memoryDir, 'store.json'), JSON.stringify(legacyJson, null, 2));
|
|
235
|
+
}
|
|
236
|
+
const entries = memory.entries;
|
|
237
|
+
if (!entries)
|
|
238
|
+
return outcome;
|
|
239
|
+
let storeEntry;
|
|
240
|
+
let isValidProvenanceType = () => false;
|
|
241
|
+
try {
|
|
242
|
+
const mod = await import('../memory/memory-initializer.js');
|
|
243
|
+
storeEntry = mod.storeEntry;
|
|
244
|
+
try {
|
|
245
|
+
if (typeof mod.isValidProvenanceType === 'function')
|
|
246
|
+
isValidProvenanceType = mod.isValidProvenanceType;
|
|
247
|
+
}
|
|
248
|
+
catch { /* optional: without it, provenance is simply not carried over */ }
|
|
249
|
+
}
|
|
250
|
+
catch (e) {
|
|
251
|
+
outcome.failed = Object.keys(entries).length;
|
|
252
|
+
outcome.errors.push(`memory store unavailable: ${e.message}`);
|
|
253
|
+
return outcome;
|
|
254
|
+
}
|
|
255
|
+
for (const entry of Object.values(entries)) {
|
|
256
|
+
const key = entry.key || entry.id || '';
|
|
257
|
+
const value = entry.value || entry.content || '';
|
|
258
|
+
if (!key || !value) {
|
|
259
|
+
outcome.failed++;
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
try {
|
|
263
|
+
// A snapshot from another version may carry a provenance value this one
|
|
264
|
+
// rejects; drop it rather than fail an otherwise-good row.
|
|
265
|
+
const provenance = entry.provenanceType && entry.provenanceType !== 'unknown'
|
|
266
|
+
&& isValidProvenanceType(entry.provenanceType) ? entry.provenanceType : undefined;
|
|
267
|
+
const result = await storeEntry({
|
|
268
|
+
key,
|
|
269
|
+
value,
|
|
270
|
+
namespace: entry.namespace || 'restored',
|
|
271
|
+
upsert: true,
|
|
272
|
+
...(provenance ? { provenanceType: provenance } : {}),
|
|
273
|
+
});
|
|
274
|
+
if (result && result.success === false) {
|
|
275
|
+
outcome.failed++;
|
|
276
|
+
if (result.error && outcome.errors.length < 5)
|
|
277
|
+
outcome.errors.push(`${key}: ${result.error}`);
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
outcome.restored++;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
catch (e) {
|
|
284
|
+
outcome.failed++;
|
|
285
|
+
if (outcome.errors.length < 5)
|
|
286
|
+
outcome.errors.push(`${key}: ${e.message}`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return outcome;
|
|
290
|
+
}
|
|
291
|
+
/** A session record is an object that carries at least one of its defining fields. */
|
|
292
|
+
function isSessionRecordLike(value) {
|
|
293
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
294
|
+
return false;
|
|
295
|
+
const v = value;
|
|
296
|
+
return 'data' in v || 'stats' in v || 'sessionId' in v || 'savedAt' in v;
|
|
103
297
|
}
|
|
104
298
|
export const sessionTools = [
|
|
105
299
|
{
|
|
@@ -129,7 +323,7 @@ export const sessionTools = [
|
|
|
129
323
|
}
|
|
130
324
|
const sessionId = `session-${Date.now()}-${randomUUID().slice(0, 8)}`;
|
|
131
325
|
// Load related data based on options
|
|
132
|
-
const data = loadRelatedStores({
|
|
326
|
+
const { data, memoryCapture } = await loadRelatedStores({
|
|
133
327
|
includeMemory: input.includeMemory,
|
|
134
328
|
includeTasks: input.includeTasks,
|
|
135
329
|
includeAgents: input.includeAgents,
|
|
@@ -138,7 +332,7 @@ export const sessionTools = [
|
|
|
138
332
|
const stats = {
|
|
139
333
|
tasks: data.tasks ? Object.keys(data.tasks.tasks || {}).length : 0,
|
|
140
334
|
agents: data.agents ? Object.keys(data.agents.agents || {}).length : 0,
|
|
141
|
-
memoryEntries:
|
|
335
|
+
memoryEntries: countMemoryEntries(data.memory),
|
|
142
336
|
totalSize: 0,
|
|
143
337
|
};
|
|
144
338
|
const session = {
|
|
@@ -158,6 +352,7 @@ export const sessionTools = [
|
|
|
158
352
|
name: session.name,
|
|
159
353
|
savedAt: session.savedAt,
|
|
160
354
|
stats: session.stats,
|
|
355
|
+
memoryCapture,
|
|
161
356
|
path: getSessionPath(sessionId),
|
|
162
357
|
};
|
|
163
358
|
},
|
|
@@ -207,35 +402,11 @@ export const sessionTools = [
|
|
|
207
402
|
}
|
|
208
403
|
}
|
|
209
404
|
if (session) {
|
|
210
|
-
// Restore data to respective stores
|
|
211
|
-
//
|
|
405
|
+
// Restore data to respective stores. audit_1776853149979: tighten
|
|
406
|
+
// perms on the restored stores too.
|
|
407
|
+
let memoryRestore;
|
|
212
408
|
if (input.restoreMemory !== false && session.data?.memory) {
|
|
213
|
-
|
|
214
|
-
if (!existsSync(memoryDir))
|
|
215
|
-
mkdirRestricted(memoryDir);
|
|
216
|
-
writeFileRestricted(join(memoryDir, 'store.json'), JSON.stringify(session.data.memory, null, 2));
|
|
217
|
-
// Also populate active sql.js SQLite database so memory-tools can find entries
|
|
218
|
-
try {
|
|
219
|
-
const { storeEntry } = await import('../memory/memory-initializer.js');
|
|
220
|
-
const memoryData = session.data.memory;
|
|
221
|
-
if (memoryData.entries) {
|
|
222
|
-
for (const entry of Object.values(memoryData.entries)) {
|
|
223
|
-
const key = entry.key || entry.id || '';
|
|
224
|
-
const value = entry.value || entry.content || '';
|
|
225
|
-
if (key && value) {
|
|
226
|
-
await storeEntry({
|
|
227
|
-
key,
|
|
228
|
-
value,
|
|
229
|
-
namespace: entry.namespace || 'restored',
|
|
230
|
-
upsert: true,
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
catch {
|
|
237
|
-
// Legacy JSON restore is the fallback -- sql.js import may not be available
|
|
238
|
-
}
|
|
409
|
+
memoryRestore = await restoreMemorySnapshot(session.data.memory);
|
|
239
410
|
}
|
|
240
411
|
if (input.restoreTasks !== false && session.data?.tasks) {
|
|
241
412
|
const taskDir = join(getProjectCwd(), STORAGE_DIR, 'tasks');
|
|
@@ -260,6 +431,11 @@ export const sessionTools = [
|
|
|
260
431
|
},
|
|
261
432
|
restoredAt: new Date().toISOString(),
|
|
262
433
|
stats: session.stats,
|
|
434
|
+
// #3573: counts actually written, not the count recorded at save time.
|
|
435
|
+
...(memoryRestore ? {
|
|
436
|
+
memoryRestore,
|
|
437
|
+
stats: { ...session.stats, memoryEntriesRestored: memoryRestore.restored },
|
|
438
|
+
} : {}),
|
|
263
439
|
};
|
|
264
440
|
}
|
|
265
441
|
return {
|
|
@@ -484,30 +660,40 @@ export const sessionTools = [
|
|
|
484
660
|
// #1916: `ruflo session import <file>` referenced an unregistered
|
|
485
661
|
// `session_import` tool. Reads a session JSON and re-saves it locally.
|
|
486
662
|
name: 'session_import',
|
|
487
|
-
description: 'Import a session
|
|
663
|
+
description: 'Import a session (produced by session_export) into the local session store and optionally activate it. Pass either inputPath (a session JSON file) or data (the session record itself). Use when native Read is wrong because the file is a structured session record that must be re-registered (new id, stats recomputed) rather than just read. For reading the file, native Read is fine. Pair with session_export on the source.',
|
|
488
664
|
category: 'session',
|
|
489
665
|
inputSchema: {
|
|
490
666
|
type: 'object',
|
|
491
667
|
properties: {
|
|
492
|
-
inputPath: { type: 'string', description: 'Path to the session JSON file to import' },
|
|
668
|
+
inputPath: { type: 'string', description: 'Path to the session JSON file to import (or pass data)' },
|
|
669
|
+
data: { type: 'object', description: 'The session record itself, as written by session_export (or pass inputPath)' },
|
|
493
670
|
name: { type: 'string', description: 'Override the imported session name' },
|
|
494
671
|
activate: { type: 'boolean', description: 'Restore the imported session into the active stores' },
|
|
495
672
|
},
|
|
496
|
-
required: ['inputPath'],
|
|
497
673
|
},
|
|
498
674
|
handler: async (input) => {
|
|
499
|
-
const inputPath = String(input.inputPath ?? '');
|
|
500
|
-
if (!inputPath || !existsSync(inputPath))
|
|
501
|
-
return { error: `File not found: ${inputPath || '(empty)'}` };
|
|
502
675
|
let parsed;
|
|
503
|
-
|
|
504
|
-
parsed =
|
|
676
|
+
if (input.data !== undefined && input.data !== null) {
|
|
677
|
+
parsed = input.data;
|
|
678
|
+
}
|
|
679
|
+
else {
|
|
680
|
+
const inputPath = String(input.inputPath ?? '');
|
|
681
|
+
if (!inputPath)
|
|
682
|
+
return { error: 'Provide inputPath (a session JSON file) or data (a session record)' };
|
|
683
|
+
if (!existsSync(inputPath))
|
|
684
|
+
return { error: `File not found: ${inputPath}` };
|
|
685
|
+
try {
|
|
686
|
+
parsed = JSON.parse(readFileSync(inputPath, 'utf-8'));
|
|
687
|
+
}
|
|
688
|
+
catch (e) {
|
|
689
|
+
return { error: `Invalid session JSON: ${e.message}` };
|
|
690
|
+
}
|
|
505
691
|
}
|
|
506
|
-
|
|
507
|
-
return { error:
|
|
692
|
+
if (!isSessionRecordLike(parsed)) {
|
|
693
|
+
return { error: 'Not a session record: expected an object produced by session export' };
|
|
508
694
|
}
|
|
509
695
|
const newId = `session-${Date.now()}-${randomUUID().slice(0, 8)}`;
|
|
510
|
-
const stats =
|
|
696
|
+
const stats = { tasks: 0, agents: 0, memoryEntries: 0, totalSize: 0, ...(parsed.stats || {}) };
|
|
511
697
|
const session = {
|
|
512
698
|
sessionId: newId,
|
|
513
699
|
name: input.name ? String(input.name) : (parsed.name || 'imported-session'),
|
|
@@ -15,6 +15,7 @@ import { AsyncLocalStorage } from 'node:async_hooks';
|
|
|
15
15
|
import { createRequire } from 'node:module';
|
|
16
16
|
import { readFileMaybeEncrypted, writeFileAtomic, writeFileRestricted } from '../fs-secure.js';
|
|
17
17
|
import { restoreMemoryDbFromBackup } from '../services/memory-backup.js';
|
|
18
|
+
import { validateIdentifier } from '../mcp-tools/validate-input.js';
|
|
18
19
|
/**
|
|
19
20
|
* ADR-323 — typed memory provenance. Distinguishes WHO/WHAT wrote a memory
|
|
20
21
|
* entry (a user's stated claim vs an agent's own output vs a tool result vs
|
|
@@ -3392,11 +3393,13 @@ export async function withMemoryDbLock(dbPath, fn) {
|
|
|
3392
3393
|
}
|
|
3393
3394
|
}
|
|
3394
3395
|
}
|
|
3395
|
-
const NAMESPACE_PATTERN = /^[A-Za-z0-9._-]{1,128}$/;
|
|
3396
3396
|
export async function purgeNamespace(options) {
|
|
3397
3397
|
const { namespace, dbPath: customPath } = options;
|
|
3398
|
-
|
|
3399
|
-
|
|
3398
|
+
// #3570: the same validator store, import and export use, so any namespace
|
|
3399
|
+
// that can be written can also be purged (`team:alice` included).
|
|
3400
|
+
const vNs = validateIdentifier(namespace, 'namespace');
|
|
3401
|
+
if (!vNs.valid) {
|
|
3402
|
+
return { success: false, deletedCount: 0, remainingEntries: 0, error: `Invalid namespace: ${vNs.error}` };
|
|
3400
3403
|
}
|
|
3401
3404
|
const swarmDir = getMemoryRoot();
|
|
3402
3405
|
const dbPath = customPath ? path.resolve(customPath) : path.join(swarmDir, 'memory.db');
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which Claude Code binaries a shell would find, and whether the one it runs
|
|
3
|
+
* can load mods (ADR-404). Mods are on by default from Claude Code 2.1.287;
|
|
4
|
+
* from 2.1.277 they load with CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1; older
|
|
5
|
+
* builds predate them. A server-side rollout switch can still hold them off
|
|
6
|
+
* on any version (reported separately). A stale install earlier on PATH
|
|
7
|
+
* (a 2.1.107 npm-global beside a current native one, say) silently decides.
|
|
8
|
+
*/
|
|
9
|
+
export declare const MODS_DEFAULT_ON = "2.1.287";
|
|
10
|
+
export declare const MODS_FIRST = "2.1.277";
|
|
11
|
+
export interface ClaudeInstall {
|
|
12
|
+
path: string;
|
|
13
|
+
version: string | null;
|
|
14
|
+
}
|
|
15
|
+
export type VersionOf = (path: string) => string | null;
|
|
16
|
+
/** a < b for dotted numeric versions. */
|
|
17
|
+
export declare function versionLess(a: string, b: string): boolean;
|
|
18
|
+
/** `claude --version` of one binary, bounded; null when it cannot say. */
|
|
19
|
+
export declare const defaultVersionOf: VersionOf;
|
|
20
|
+
/** Every `claude` on PATH, in PATH order (the first is what runs), plus ~/.local/bin. */
|
|
21
|
+
export declare function findClaudeInstalls(env: NodeJS.ProcessEnv, home: string, versionOf?: VersionOf): ClaudeInstall[];
|
|
22
|
+
export interface InstallsFinding {
|
|
23
|
+
status: 'pass' | 'warn';
|
|
24
|
+
message: string;
|
|
25
|
+
fix?: string;
|
|
26
|
+
}
|
|
27
|
+
/** The doctor's reading of what `claude` resolves to and what else is installed. */
|
|
28
|
+
export declare function judgeInstalls(installs: readonly ClaudeInstall[], enableEnvSet: boolean): InstallsFinding;
|
|
29
|
+
//# sourceMappingURL=claude-installs.d.ts.map
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which Claude Code binaries a shell would find, and whether the one it runs
|
|
3
|
+
* can load mods (ADR-404). Mods are on by default from Claude Code 2.1.287;
|
|
4
|
+
* from 2.1.277 they load with CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1; older
|
|
5
|
+
* builds predate them. A server-side rollout switch can still hold them off
|
|
6
|
+
* on any version (reported separately). A stale install earlier on PATH
|
|
7
|
+
* (a 2.1.107 npm-global beside a current native one, say) silently decides.
|
|
8
|
+
*/
|
|
9
|
+
import { execFileSync } from 'node:child_process';
|
|
10
|
+
import { existsSync, realpathSync, statSync } from 'node:fs';
|
|
11
|
+
import { delimiter, join } from 'node:path';
|
|
12
|
+
export const MODS_DEFAULT_ON = '2.1.287';
|
|
13
|
+
export const MODS_FIRST = '2.1.277';
|
|
14
|
+
const parse = (v) => v.split('.').map((n) => Number(n));
|
|
15
|
+
/** a < b for dotted numeric versions. */
|
|
16
|
+
export function versionLess(a, b) {
|
|
17
|
+
const x = parse(a);
|
|
18
|
+
const y = parse(b);
|
|
19
|
+
for (let i = 0; i < Math.max(x.length, y.length); i++) {
|
|
20
|
+
if ((x[i] ?? 0) !== (y[i] ?? 0))
|
|
21
|
+
return (x[i] ?? 0) < (y[i] ?? 0);
|
|
22
|
+
}
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
/** `claude --version` of one binary, bounded; null when it cannot say. */
|
|
26
|
+
export const defaultVersionOf = (path) => {
|
|
27
|
+
try {
|
|
28
|
+
const out = execFileSync(path, ['--version'], { encoding: 'utf8', timeout: 5000, stdio: ['ignore', 'pipe', 'ignore'] });
|
|
29
|
+
return out.match(/\d+\.\d+\.\d+/)?.[0] ?? null;
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
/** Every `claude` on PATH, in PATH order (the first is what runs), plus ~/.local/bin. */
|
|
36
|
+
export function findClaudeInstalls(env, home, versionOf = defaultVersionOf) {
|
|
37
|
+
const names = process.platform === 'win32' ? ['claude.exe', 'claude.cmd', 'claude'] : ['claude'];
|
|
38
|
+
const dirs = [...(env.PATH ?? '').split(delimiter).filter(Boolean), join(home, '.local', 'bin')];
|
|
39
|
+
const seen = new Set();
|
|
40
|
+
const installs = [];
|
|
41
|
+
for (const dir of dirs) {
|
|
42
|
+
for (const name of names) {
|
|
43
|
+
const path = join(dir, name);
|
|
44
|
+
let real;
|
|
45
|
+
try {
|
|
46
|
+
if (!existsSync(path) || !statSync(path).isFile())
|
|
47
|
+
continue;
|
|
48
|
+
real = realpathSync(path);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (seen.has(real))
|
|
54
|
+
continue;
|
|
55
|
+
seen.add(real);
|
|
56
|
+
installs.push({ path, version: versionOf(path) });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return installs;
|
|
60
|
+
}
|
|
61
|
+
/** The doctor's reading of what `claude` resolves to and what else is installed. */
|
|
62
|
+
export function judgeInstalls(installs, enableEnvSet) {
|
|
63
|
+
if (installs.length === 0)
|
|
64
|
+
return { status: 'warn', message: 'no claude binary on PATH', fix: 'install Claude Code >= 2.1.287' };
|
|
65
|
+
const [first] = installs;
|
|
66
|
+
const others = installs.slice(1).filter((i) => i.version !== first.version);
|
|
67
|
+
const mixed = others.length
|
|
68
|
+
? `; also installed: ${others.map((i) => `${i.path} (${i.version ?? 'unknown'})`).join(', ')}. The first on PATH runs; a stale one can shadow a current one.`
|
|
69
|
+
: '';
|
|
70
|
+
const runs = `${first.path} (${first.version ?? 'version unknown'})`;
|
|
71
|
+
if (!first.version)
|
|
72
|
+
return { status: 'warn', message: `claude resolves to ${runs}${mixed}` };
|
|
73
|
+
if (!versionLess(first.version, MODS_DEFAULT_ON)) {
|
|
74
|
+
return { status: others.length ? 'warn' : 'pass', message: `claude resolves to ${runs}: mods on by default${mixed}` };
|
|
75
|
+
}
|
|
76
|
+
if (!versionLess(first.version, MODS_FIRST)) {
|
|
77
|
+
return enableEnvSet
|
|
78
|
+
? { status: others.length ? 'warn' : 'pass', message: `claude resolves to ${runs}: mods load with CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1 (set)${mixed}` }
|
|
79
|
+
: { status: 'warn', message: `claude resolves to ${runs}: below ${MODS_DEFAULT_ON}, mods need CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1 (not set)${mixed}`, fix: `upgrade to >= ${MODS_DEFAULT_ON}, or export CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1` };
|
|
80
|
+
}
|
|
81
|
+
return { status: 'warn', message: `claude resolves to ${runs}: predates mods (< ${MODS_FIRST}); classic hooks handle every event${mixed}`, fix: `upgrade to >= ${MODS_DEFAULT_ON} and remove the stale install from PATH` };
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=claude-installs.js.map
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ruflo mods install|uninstall` (ADR-404): enable the ruflo-mods plugin for
|
|
3
|
+
* one project, opt-in, and take back exactly what was added.
|
|
4
|
+
*
|
|
5
|
+
* Writes three keys of a Claude Code settings file (`.claude/settings.local.json`
|
|
6
|
+
* by default, so the early-access feature is one person's choice, not the
|
|
7
|
+
* repository's): `enabledPlugins["ruflo-mods@ruflo"]`, the `ruflo` entry of
|
|
8
|
+
* `extraKnownMarketplaces` when absent, and `env.CLAUDE_CODE_ENABLE_FUNCTION_HOOKS`.
|
|
9
|
+
* What was added is recorded in `.claude-flow/mods/install.json`, so uninstall
|
|
10
|
+
* removes those and nothing a person set themselves. Classic hooks are never
|
|
11
|
+
* touched: they stay the fallback (the mod takes an event over at runtime only).
|
|
12
|
+
*/
|
|
13
|
+
export declare const MOD_PLUGIN_ID = "ruflo-mods@ruflo";
|
|
14
|
+
export declare const MARKETPLACE_NAME = "ruflo";
|
|
15
|
+
export declare const MARKETPLACE_SOURCE: {
|
|
16
|
+
readonly source: {
|
|
17
|
+
readonly source: "github";
|
|
18
|
+
readonly repo: "ruvnet/ruflo";
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
export declare const ENABLE_ENV = "CLAUDE_CODE_ENABLE_FUNCTION_HOOKS";
|
|
22
|
+
export declare const INSTALL_RECORD: string;
|
|
23
|
+
export type Scope = 'local' | 'project';
|
|
24
|
+
export interface InstallRecord {
|
|
25
|
+
version: 1;
|
|
26
|
+
settingsFile: string;
|
|
27
|
+
installedAt: string;
|
|
28
|
+
added: {
|
|
29
|
+
plugin: boolean;
|
|
30
|
+
marketplace: boolean;
|
|
31
|
+
env: boolean;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
type Settings = Record<string, unknown> & {
|
|
35
|
+
enabledPlugins?: Record<string, unknown>;
|
|
36
|
+
extraKnownMarketplaces?: Record<string, unknown>;
|
|
37
|
+
env?: Record<string, unknown>;
|
|
38
|
+
};
|
|
39
|
+
export declare function settingsFileFor(projectRoot: string, scope: Scope): string;
|
|
40
|
+
/** Reads a settings file; absent is `{}`, anything unparseable throws (never overwritten). */
|
|
41
|
+
export declare function readSettingsFile(path: string): Settings;
|
|
42
|
+
/** The settings after install, and what install added (pure). */
|
|
43
|
+
export declare function withModEnabled(settings: Settings): {
|
|
44
|
+
next: Settings;
|
|
45
|
+
added: InstallRecord['added'];
|
|
46
|
+
};
|
|
47
|
+
/** The settings after uninstall: only what the record says install added (pure). */
|
|
48
|
+
export declare function withModRemoved(settings: Settings, added: InstallRecord['added']): Settings;
|
|
49
|
+
export interface InstallResult {
|
|
50
|
+
settingsFile: string;
|
|
51
|
+
backup?: string;
|
|
52
|
+
added: InstallRecord['added'];
|
|
53
|
+
dryRun: boolean;
|
|
54
|
+
next: Settings;
|
|
55
|
+
}
|
|
56
|
+
export declare function installMod(projectRoot: string, scope: Scope, dryRun?: boolean): InstallResult;
|
|
57
|
+
export declare function readRecord(projectRoot: string): InstallRecord | null;
|
|
58
|
+
export declare function uninstallMod(projectRoot: string, dryRun?: boolean): {
|
|
59
|
+
settingsFile?: string;
|
|
60
|
+
removed: boolean;
|
|
61
|
+
dryRun: boolean;
|
|
62
|
+
};
|
|
63
|
+
export {};
|
|
64
|
+
//# sourceMappingURL=install.d.ts.map
|