claude-flow 3.53.0 → 3.54.1
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/helpers/hook-handler.cjs +88 -6
- package/.claude/helpers/intelligence.cjs +104 -2
- package/README.md +141 -44
- package/package.json +3 -2
- package/v3/@claude-flow/cli/README.md +141 -44
- package/v3/@claude-flow/cli/catalog-manifest.json +2 -2
- package/v3/@claude-flow/cli/dist/src/blocking-stdio.d.ts +11 -0
- package/v3/@claude-flow/cli/dist/src/blocking-stdio.js +23 -0
- package/v3/@claude-flow/cli/dist/src/commands/memory.js +9 -1
- package/v3/@claude-flow/cli/dist/src/commands/session.js +11 -37
- package/v3/@claude-flow/cli/dist/src/index.js +3 -0
- package/v3/@claude-flow/cli/dist/src/init/executor.js +1 -0
- package/v3/@claude-flow/cli/dist/src/init/helpers-generator.js +65 -3
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agentdb-tools.js +13 -1
- package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.js +1 -1
- package/v3/@claude-flow/cli/dist/src/mcp-tools/session-tools.js +4 -1
- package/v3/@claude-flow/cli/dist/src/memory/embedding-q8.d.ts +19 -0
- package/v3/@claude-flow/cli/dist/src/memory/embedding-q8.js +38 -0
- package/v3/@claude-flow/cli/dist/src/memory/graph-edge-writer.js +3 -2
- package/v3/@claude-flow/cli/dist/src/memory/lucene-bm25.js +11 -19
- package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.d.ts +8 -0
- package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.js +21 -2
- package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.d.ts +5 -0
- package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.js +12 -5
- package/v3/@claude-flow/cli/dist/src/memory/shared-sqlite.d.ts +7 -0
- package/v3/@claude-flow/cli/dist/src/memory/shared-sqlite.js +45 -0
- package/v3/@claude-flow/cli/dist/src/mods/probe.d.ts +7 -0
- package/v3/@claude-flow/cli/dist/src/mods/probe.js +29 -1
- package/v3/@claude-flow/cli/dist/src/production/monitoring.d.ts +2 -0
- package/v3/@claude-flow/cli/dist/src/production/monitoring.js +8 -3
- package/v3/@claude-flow/cli/dist/src/production/rate-limiter.js +8 -5
- package/v3/@claude-flow/cli/dist/src/production/retry.js +2 -0
- package/v3/@claude-flow/cli/dist/src/services/git-workspace-identity.js +20 -3
- package/v3/@claude-flow/cli/dist/src/services/worker-queue.d.ts +1 -0
- package/v3/@claude-flow/cli/dist/src/services/worker-queue.js +9 -1
- package/v3/@claude-flow/cli/package.json +2 -2
- package/v3/@claude-flow/shared/dist/events/event-store.js +5 -2
- package/v3/@claude-flow/shared/dist/events/event-store.test.js +20 -0
- package/v3/@claude-flow/shared/package.json +1 -1
|
@@ -150,6 +150,10 @@ export const agentdbPatternStore = {
|
|
|
150
150
|
const confidence = validateScore(params.confidence, 0.8);
|
|
151
151
|
const bridge = await getBridge();
|
|
152
152
|
const result = await bridge.bridgeStorePattern({ pattern, type, confidence });
|
|
153
|
+
if (result && result.success === false) {
|
|
154
|
+
// #3691: the write was refused/not persisted — never acknowledge it.
|
|
155
|
+
return { success: false, error: `Pattern store failed: ${result.error ?? 'write not persisted'}`, controller: result.controller };
|
|
156
|
+
}
|
|
153
157
|
if (result) {
|
|
154
158
|
// #3288: `controller: 'reasoningBank'` is the ONLY label that means
|
|
155
159
|
// the healthy path ran. Every other label bridgeStorePattern can
|
|
@@ -176,12 +180,20 @@ export const agentdbPatternStore = {
|
|
|
176
180
|
const { storeEntry } = await import('../memory/memory-initializer.js');
|
|
177
181
|
const patternId = `pattern-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
178
182
|
const value = JSON.stringify({ pattern, type, confidence, _fallback: 'reasoningBank-unavailable' });
|
|
179
|
-
await storeEntry({
|
|
183
|
+
const stored = await storeEntry({
|
|
180
184
|
key: patternId,
|
|
181
185
|
value,
|
|
182
186
|
namespace: 'pattern',
|
|
183
187
|
tags: [type, 'reasoning-pattern', 'fallback'],
|
|
184
188
|
});
|
|
189
|
+
// #3691: storeEntry reports failure as {success:false}, not a throw.
|
|
190
|
+
if (!stored || stored.success !== true) {
|
|
191
|
+
return {
|
|
192
|
+
success: false,
|
|
193
|
+
error: `Pattern store failed: memory_store fallback did not persist (${stored?.error ?? 'no result'})`,
|
|
194
|
+
recommendation: 'Run agentdb_health to inspect controller registration and check that .swarm/memory.db is writable.',
|
|
195
|
+
};
|
|
196
|
+
}
|
|
185
197
|
return {
|
|
186
198
|
success: true,
|
|
187
199
|
// #3288: a caller must not have to already know to distrust a
|
|
@@ -3406,7 +3406,7 @@ export const hooksPatternStore = {
|
|
|
3406
3406
|
}
|
|
3407
3407
|
// Fallback: persist using memory-initializer store
|
|
3408
3408
|
let storeResult = { success: false };
|
|
3409
|
-
if (!reasoningResult) {
|
|
3409
|
+
if (!reasoningResult?.success) {
|
|
3410
3410
|
const storeFn = await getRealStoreFunction();
|
|
3411
3411
|
if (storeFn) {
|
|
3412
3412
|
try {
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { existsSync, readFileSync, readdirSync, unlinkSync, statSync, writeFileSync } from 'node:fs';
|
|
7
7
|
import { randomUUID } from 'node:crypto';
|
|
8
|
+
import { gunzipSync } from 'node:zlib';
|
|
8
9
|
import { join } from 'node:path';
|
|
9
10
|
import { getProjectCwd } from './types.js';
|
|
10
11
|
import { mkdirRestricted, readFileMaybeEncrypted, writeFileRestricted, } from '../fs-secure.js';
|
|
@@ -683,7 +684,9 @@ export const sessionTools = [
|
|
|
683
684
|
if (!existsSync(inputPath))
|
|
684
685
|
return { error: `File not found: ${inputPath}` };
|
|
685
686
|
try {
|
|
686
|
-
|
|
687
|
+
const bytes = readFileSync(inputPath);
|
|
688
|
+
const content = bytes[0] === 0x1f && bytes[1] === 0x8b ? gunzipSync(bytes) : bytes;
|
|
689
|
+
parsed = JSON.parse(content.toString('utf-8'));
|
|
687
690
|
}
|
|
688
691
|
catch (e) {
|
|
689
692
|
return { error: `Invalid session JSON: ${e.message}` };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ADR-472: a stored embedding as int8 + scale, for `memory list --embeddings`. Pure; no store access.
|
|
3
|
+
*/
|
|
4
|
+
/** Most embeddings `memory list --embeddings` will emit per call, and the longest vector it will encode. */
|
|
5
|
+
export declare const MAX_LIST_EMBEDDINGS = 500;
|
|
6
|
+
export declare const MAX_EMBEDDING_DIMS = 4096;
|
|
7
|
+
/** A stored embedding as int8 with one scale, base64: ~1/4 the bytes of rounded JSON floats, enough for a 2D projection or a cosine screen. */
|
|
8
|
+
export type EmbeddingQ8 = {
|
|
9
|
+
dims: number;
|
|
10
|
+
scale: number;
|
|
11
|
+
b64: string;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Read-only view of a stored embedding (a JSON array in the `embedding` column) as int8 + scale. Returns undefined for a missing,
|
|
15
|
+
* malformed, non-finite, constant-zero or over-long vector, so a caller never gets a guessed one. Each value is within scale/2 of the
|
|
16
|
+
* original (scale = max|v| / 127); cosine against the original stays above 0.9999 for unit-scale text embeddings.
|
|
17
|
+
*/
|
|
18
|
+
export declare function encodeEmbeddingQ8(stored: unknown): EmbeddingQ8 | undefined;
|
|
19
|
+
//# sourceMappingURL=embedding-q8.d.ts.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ADR-472: a stored embedding as int8 + scale, for `memory list --embeddings`. Pure; no store access.
|
|
3
|
+
*/
|
|
4
|
+
/** Most embeddings `memory list --embeddings` will emit per call, and the longest vector it will encode. */
|
|
5
|
+
export const MAX_LIST_EMBEDDINGS = 500;
|
|
6
|
+
export const MAX_EMBEDDING_DIMS = 4096;
|
|
7
|
+
/**
|
|
8
|
+
* Read-only view of a stored embedding (a JSON array in the `embedding` column) as int8 + scale. Returns undefined for a missing,
|
|
9
|
+
* malformed, non-finite, constant-zero or over-long vector, so a caller never gets a guessed one. Each value is within scale/2 of the
|
|
10
|
+
* original (scale = max|v| / 127); cosine against the original stays above 0.9999 for unit-scale text embeddings.
|
|
11
|
+
*/
|
|
12
|
+
export function encodeEmbeddingQ8(stored) {
|
|
13
|
+
if (stored === null || stored === undefined)
|
|
14
|
+
return undefined;
|
|
15
|
+
let values;
|
|
16
|
+
try {
|
|
17
|
+
values = typeof stored === 'string' ? JSON.parse(stored) : stored;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
if (!Array.isArray(values) || values.length < 2 || values.length > MAX_EMBEDDING_DIMS)
|
|
23
|
+
return undefined;
|
|
24
|
+
let max = 0;
|
|
25
|
+
for (const v of values) {
|
|
26
|
+
if (typeof v !== 'number' || !Number.isFinite(v))
|
|
27
|
+
return undefined;
|
|
28
|
+
max = Math.max(max, Math.abs(v));
|
|
29
|
+
}
|
|
30
|
+
if (max === 0)
|
|
31
|
+
return undefined;
|
|
32
|
+
const scale = max / 127;
|
|
33
|
+
const bytes = new Int8Array(values.length);
|
|
34
|
+
for (let i = 0; i < values.length; i++)
|
|
35
|
+
bytes[i] = Math.max(-127, Math.min(127, Math.round(values[i] / scale)));
|
|
36
|
+
return { dims: values.length, scale: Number(scale.toPrecision(6)), b64: Buffer.from(bytes.buffer).toString('base64') };
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=embedding-q8.js.map
|
|
@@ -34,6 +34,7 @@ import * as fs from 'fs';
|
|
|
34
34
|
import * as crypto from 'crypto';
|
|
35
35
|
import { getMemoryRoot } from './memory-initializer.js';
|
|
36
36
|
import { encodeEmbedding } from './embedding-quantization.js';
|
|
37
|
+
import { loadBetterSqlite3 } from './shared-sqlite.js';
|
|
37
38
|
// ============================================================================
|
|
38
39
|
// Lazy-cached better-sqlite3 db handle
|
|
39
40
|
// ============================================================================
|
|
@@ -152,8 +153,8 @@ export async function getBridgeDb(customDbPath, opts) {
|
|
|
152
153
|
// the try/catch below.
|
|
153
154
|
let BetterSqlite3;
|
|
154
155
|
try {
|
|
155
|
-
|
|
156
|
-
BetterSqlite3 =
|
|
156
|
+
// #3693: share AgentDB's better-sqlite3 so the idle close cannot detach its handle.
|
|
157
|
+
BetterSqlite3 = await loadBetterSqlite3();
|
|
157
158
|
}
|
|
158
159
|
catch {
|
|
159
160
|
return null;
|
|
@@ -159,34 +159,26 @@ function step3(w) {
|
|
|
159
159
|
}
|
|
160
160
|
return w;
|
|
161
161
|
}
|
|
162
|
+
// Longest suffixes first: Porter step 4 selects the longest matching suffix
|
|
163
|
+
// ONCE and applies its condition; a failed condition must not fall through to
|
|
164
|
+
// a shorter suffix (e.g. "argument": "ment" fails m>1, so "ent" is not tried).
|
|
162
165
|
const STEP4_SUFFIXES = [
|
|
163
|
-
'
|
|
164
|
-
'
|
|
166
|
+
'ement', 'ance', 'ence', 'able', 'ible', 'ment', 'ant', 'ent', 'ism',
|
|
167
|
+
'ate', 'iti', 'ous', 'ive', 'ize', 'ion', 'al', 'er', 'ic', 'ou',
|
|
165
168
|
];
|
|
166
169
|
function step4(w) {
|
|
167
170
|
for (const suf of STEP4_SUFFIXES) {
|
|
168
|
-
if (w.endsWith(suf))
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
if (suf === 'ion') {
|
|
172
|
-
const last = stem[stem.length - 1];
|
|
173
|
-
if (last === 's' || last === 't')
|
|
174
|
-
return stem;
|
|
175
|
-
}
|
|
176
|
-
else {
|
|
177
|
-
return stem;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
// ION special-case
|
|
183
|
-
if (w.endsWith('ion')) {
|
|
184
|
-
const stem = w.slice(0, -3);
|
|
171
|
+
if (!w.endsWith(suf))
|
|
172
|
+
continue;
|
|
173
|
+
const stem = w.slice(0, w.length - suf.length);
|
|
185
174
|
if (measure(stem) > 1) {
|
|
175
|
+
if (suf !== 'ion')
|
|
176
|
+
return stem;
|
|
186
177
|
const last = stem[stem.length - 1];
|
|
187
178
|
if (last === 's' || last === 't')
|
|
188
179
|
return stem;
|
|
189
180
|
}
|
|
181
|
+
return w;
|
|
190
182
|
}
|
|
191
183
|
return w;
|
|
192
184
|
}
|
|
@@ -136,6 +136,8 @@ export declare function bridgeListEntries(options: {
|
|
|
136
136
|
includeContent?: boolean;
|
|
137
137
|
/** ADR-323: restrict rows to these provenance types. */
|
|
138
138
|
provenanceFilter?: string[];
|
|
139
|
+
/** ADR-472: include each entry's embedding as int8+scale (`embeddingQ8`); at most MAX_LIST_EMBEDDINGS rows. */
|
|
140
|
+
includeEmbedding?: boolean;
|
|
139
141
|
}): Promise<{
|
|
140
142
|
success: boolean;
|
|
141
143
|
entries: {
|
|
@@ -150,6 +152,11 @@ export declare function bridgeListEntries(options: {
|
|
|
150
152
|
/** #2073: Present when `includeContent: true` was requested. */
|
|
151
153
|
content?: string;
|
|
152
154
|
provenanceType?: string;
|
|
155
|
+
embeddingQ8?: {
|
|
156
|
+
dims: number;
|
|
157
|
+
scale: number;
|
|
158
|
+
b64: string;
|
|
159
|
+
};
|
|
153
160
|
}[];
|
|
154
161
|
total: number;
|
|
155
162
|
error?: string;
|
|
@@ -346,6 +353,7 @@ export declare function bridgeStorePattern(options: {
|
|
|
346
353
|
controller: string;
|
|
347
354
|
hasEmbedding?: boolean;
|
|
348
355
|
embeddingError?: string;
|
|
356
|
+
error?: string;
|
|
349
357
|
} | null>;
|
|
350
358
|
/**
|
|
351
359
|
* Search patterns via ReasoningBank controller.
|
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
* @module v3/cli/memory-bridge
|
|
18
18
|
*/
|
|
19
19
|
import { liveMemoryRowSql } from './live-memory-row.js';
|
|
20
|
+
import { resolveAgentdbBetterSqlite3 } from './shared-sqlite.js';
|
|
21
|
+
import { encodeEmbeddingQ8, MAX_LIST_EMBEDDINGS } from './embedding-q8.js';
|
|
20
22
|
import * as path from 'path';
|
|
21
23
|
import * as crypto from 'crypto';
|
|
22
24
|
import { realpathSync } from 'node:fs';
|
|
@@ -449,7 +451,8 @@ async function getRegistry(dbPath) {
|
|
|
449
451
|
try {
|
|
450
452
|
const attestationFile = path.join(adbDir, 'dist/src/security/AttestationLog.js');
|
|
451
453
|
if (fs.existsSync(attestationFile)) {
|
|
452
|
-
|
|
454
|
+
// #3693: same better-sqlite3 copy AgentDB uses, so closing this handle cannot disturb its WAL.
|
|
455
|
+
const Database = (resolveAgentdbBetterSqlite3() ?? cjsRequire('better-sqlite3'));
|
|
453
456
|
const swarmDir = path.resolve(process.cwd(), '.swarm');
|
|
454
457
|
if (!fs.existsSync(swarmDir))
|
|
455
458
|
fs.mkdirSync(swarmDir, { recursive: true });
|
|
@@ -1430,7 +1433,7 @@ export async function bridgeListEntries(options) {
|
|
|
1430
1433
|
ORDER BY updated_at DESC
|
|
1431
1434
|
LIMIT ? OFFSET ?
|
|
1432
1435
|
`);
|
|
1433
|
-
const rows = stmt.all(...filterParams, limit, offset);
|
|
1436
|
+
const rows = stmt.all(...filterParams, options.includeEmbedding ? Math.min(limit, MAX_LIST_EMBEDDINGS) : limit, offset);
|
|
1434
1437
|
for (const row of rows) {
|
|
1435
1438
|
const entry = {
|
|
1436
1439
|
// #2073: don't truncate id when content is requested — callers
|
|
@@ -1448,6 +1451,11 @@ export async function bridgeListEntries(options) {
|
|
|
1448
1451
|
if (options.includeContent) {
|
|
1449
1452
|
entry.content = row.content || '';
|
|
1450
1453
|
}
|
|
1454
|
+
if (options.includeEmbedding) {
|
|
1455
|
+
const q8 = encodeEmbeddingQ8(row.embedding);
|
|
1456
|
+
if (q8)
|
|
1457
|
+
entry.embeddingQ8 = q8;
|
|
1458
|
+
}
|
|
1451
1459
|
entries.push(entry);
|
|
1452
1460
|
}
|
|
1453
1461
|
}
|
|
@@ -2105,6 +2113,17 @@ export async function bridgeStorePattern(options) {
|
|
|
2105
2113
|
});
|
|
2106
2114
|
if (!result)
|
|
2107
2115
|
return null;
|
|
2116
|
+
// #3691: bridgeStoreEntry reports data-level failures (MutationGuard
|
|
2117
|
+
// rejection, nothing written) as a truthy {success:false}. Do not turn
|
|
2118
|
+
// that into a success receipt — refuse at this boundary.
|
|
2119
|
+
if (!result.success) {
|
|
2120
|
+
return {
|
|
2121
|
+
success: false,
|
|
2122
|
+
patternId: '',
|
|
2123
|
+
controller: 'bridge-fallback',
|
|
2124
|
+
error: result.error ?? 'pattern write was not persisted',
|
|
2125
|
+
};
|
|
2126
|
+
}
|
|
2108
2127
|
// Add to HNSW index for fast semantic search (bridgeStoreEntry stores SQL only)
|
|
2109
2128
|
if (result.rawEmbedding) {
|
|
2110
2129
|
try {
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @module v3/cli/memory-initializer
|
|
10
10
|
*/
|
|
11
|
+
import { type EmbeddingQ8 } from './embedding-q8.js';
|
|
11
12
|
/**
|
|
12
13
|
* ADR-323 — typed memory provenance. Distinguishes WHO/WHAT wrote a memory
|
|
13
14
|
* entry (a user's stated claim vs an agent's own output vs a tool result vs
|
|
@@ -504,6 +505,8 @@ export declare function listEntries(options: {
|
|
|
504
505
|
includeContent?: boolean;
|
|
505
506
|
/** ADR-323: restrict rows to these provenance types. */
|
|
506
507
|
provenanceFilter?: string[];
|
|
508
|
+
/** ADR-472: include each entry's embedding as int8+scale (`embeddingQ8`); at most MAX_LIST_EMBEDDINGS rows. Read-only. */
|
|
509
|
+
includeEmbedding?: boolean;
|
|
507
510
|
}): Promise<{
|
|
508
511
|
success: boolean;
|
|
509
512
|
entries: {
|
|
@@ -518,6 +521,8 @@ export declare function listEntries(options: {
|
|
|
518
521
|
/** #2073: Present when `includeContent: true` was requested. */
|
|
519
522
|
content?: string;
|
|
520
523
|
provenanceType?: string;
|
|
524
|
+
/** ADR-472: present when `includeEmbedding: true` and the row has a valid stored vector. */
|
|
525
|
+
embeddingQ8?: EmbeddingQ8;
|
|
521
526
|
}[];
|
|
522
527
|
total: number;
|
|
523
528
|
error?: string;
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @module v3/cli/memory-initializer
|
|
10
10
|
*/
|
|
11
|
+
import { loadBetterSqlite3 } from './shared-sqlite.js';
|
|
11
12
|
import { liveMemoryRowSql } from './live-memory-row.js';
|
|
13
|
+
import { encodeEmbeddingQ8, MAX_LIST_EMBEDDINGS } from './embedding-q8.js';
|
|
12
14
|
import * as fs from 'fs';
|
|
13
15
|
import * as path from 'path';
|
|
14
16
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
@@ -1407,8 +1409,8 @@ export async function recoverMemoryDatabase(dbPath, opts = {}) {
|
|
|
1407
1409
|
try {
|
|
1408
1410
|
// Module name behind a variable so TS does not statically resolve the
|
|
1409
1411
|
// optional native dep's types at build time (CI may not install them).
|
|
1410
|
-
|
|
1411
|
-
Database =
|
|
1412
|
+
// #3693: same better-sqlite3 identity as AgentDB (see shared-sqlite.ts).
|
|
1413
|
+
Database = await loadBetterSqlite3();
|
|
1412
1414
|
}
|
|
1413
1415
|
catch {
|
|
1414
1416
|
return await restoreFromBackup('no-native');
|
|
@@ -1556,8 +1558,8 @@ export async function repairVectorIndexes(dbPath, opts = {}) {
|
|
|
1556
1558
|
try {
|
|
1557
1559
|
// Module name behind a variable so TS does not statically resolve the
|
|
1558
1560
|
// optional native dep's types at build time (CI may not install them).
|
|
1559
|
-
|
|
1560
|
-
Database =
|
|
1561
|
+
// #3693: same better-sqlite3 identity as AgentDB (see shared-sqlite.ts).
|
|
1562
|
+
Database = await loadBetterSqlite3();
|
|
1561
1563
|
}
|
|
1562
1564
|
catch {
|
|
1563
1565
|
// Native module absent (e.g. WASM-only host). Statusline fix still covers
|
|
@@ -3018,7 +3020,7 @@ export async function listEntries(options) {
|
|
|
3018
3020
|
const countResult = countRows.length > 0 ? [{ values: countRows }] : [];
|
|
3019
3021
|
const total = countResult[0]?.values?.[0]?.[0] || 0;
|
|
3020
3022
|
// Get entries
|
|
3021
|
-
const safeLimit = parseInt(String(limit), 10) || 100;
|
|
3023
|
+
const safeLimit = Math.min(parseInt(String(limit), 10) || 100, options.includeEmbedding ? MAX_LIST_EMBEDDINGS : Number.MAX_SAFE_INTEGER);
|
|
3022
3024
|
const safeOffset = parseInt(String(offset), 10) || 0;
|
|
3023
3025
|
// #2120 — same NULL-as-active acceptance as the count above.
|
|
3024
3026
|
const listStmt = db.prepare(`SELECT id, key, namespace, content, embedding, access_count, created_at, updated_at, provenance_type
|
|
@@ -3051,6 +3053,11 @@ export async function listEntries(options) {
|
|
|
3051
3053
|
if (options.includeContent) {
|
|
3052
3054
|
entry.content = content || '';
|
|
3053
3055
|
}
|
|
3056
|
+
if (options.includeEmbedding) {
|
|
3057
|
+
const q8 = encodeEmbeddingQ8(embedding);
|
|
3058
|
+
if (q8)
|
|
3059
|
+
entry.embeddingQ8 = q8;
|
|
3060
|
+
}
|
|
3054
3061
|
entries.push(entry);
|
|
3055
3062
|
}
|
|
3056
3063
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
type DbCtor = new (path: string, opts?: Record<string, unknown>) => any;
|
|
2
|
+
/** Test seam: pass `from` to resolve relative to another entry file. */
|
|
3
|
+
export declare function resolveAgentdbBetterSqlite3(from?: string): DbCtor | null;
|
|
4
|
+
/** Constructor shared with AgentDB when possible, else the CLI's own copy. */
|
|
5
|
+
export declare function loadBetterSqlite3(): Promise<DbCtor>;
|
|
6
|
+
export {};
|
|
7
|
+
//# sourceMappingURL=shared-sqlite.d.ts.map
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #3693 — one better-sqlite3 identity per process.
|
|
3
|
+
*
|
|
4
|
+
* AgentDB (via @claude-flow/memory) owns a live native handle on memory.db. If
|
|
5
|
+
* the CLI opens its own handle through a DIFFERENT installed copy of
|
|
6
|
+
* better-sqlite3 (a nested `agentdb/node_modules/better-sqlite3` next to a
|
|
7
|
+
* hoisted one is the common layout), the two copies each statically link their
|
|
8
|
+
* own SQLite and keep separate per-process file bookkeeping. Closing the CLI's
|
|
9
|
+
* handle (graph-edge idle release: wal_checkpoint(TRUNCATE) + close) then looks
|
|
10
|
+
* like "last connection" to that copy and deletes the -wal/-shm sidecars out
|
|
11
|
+
* from under AgentDB's still-open handle.
|
|
12
|
+
*
|
|
13
|
+
* Resolve the constructor from the same place AgentDB resolves it, so both
|
|
14
|
+
* share one SQLite instance (and its connection bookkeeping). Falls back to the
|
|
15
|
+
* CLI's own copy when agentdb cannot be resolved.
|
|
16
|
+
*/
|
|
17
|
+
import { createRequire } from 'node:module';
|
|
18
|
+
let cached;
|
|
19
|
+
/** Test seam: pass `from` to resolve relative to another entry file. */
|
|
20
|
+
export function resolveAgentdbBetterSqlite3(from) {
|
|
21
|
+
if (!from && cached !== undefined)
|
|
22
|
+
return cached;
|
|
23
|
+
let found = null;
|
|
24
|
+
try {
|
|
25
|
+
const own = createRequire(import.meta.url);
|
|
26
|
+
const agentdbEntry = from ?? own.resolve('agentdb');
|
|
27
|
+
const mod = createRequire(agentdbEntry)('better-sqlite3');
|
|
28
|
+
found = (mod?.default ?? mod);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
found = null;
|
|
32
|
+
}
|
|
33
|
+
if (!from)
|
|
34
|
+
cached = found;
|
|
35
|
+
return found;
|
|
36
|
+
}
|
|
37
|
+
/** Constructor shared with AgentDB when possible, else the CLI's own copy. */
|
|
38
|
+
export async function loadBetterSqlite3() {
|
|
39
|
+
const shared = resolveAgentdbBetterSqlite3();
|
|
40
|
+
if (shared)
|
|
41
|
+
return shared;
|
|
42
|
+
const mod = 'better-sqlite3';
|
|
43
|
+
return (await import(mod)).default;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=shared-sqlite.js.map
|
|
@@ -29,5 +29,12 @@ export interface ProbeInputs {
|
|
|
29
29
|
/** Reads for the plugin-resolution findings; the real filesystem when absent. */
|
|
30
30
|
fs?: ReadFs;
|
|
31
31
|
}
|
|
32
|
+
declare const LEGACY_EVENTS: {
|
|
33
|
+
readonly route: "UserPromptSubmit";
|
|
34
|
+
readonly 'post-edit': "PostToolUse";
|
|
35
|
+
};
|
|
36
|
+
/** Which of route/post-edit the settings run through the CLI's own `hooks` command, in any of the files. */
|
|
37
|
+
export declare function legacyCliHooks(files: readonly string[]): Array<keyof typeof LEGACY_EVENTS>;
|
|
32
38
|
export declare function probeMods(inputs: ProbeInputs): Finding[];
|
|
39
|
+
export {};
|
|
33
40
|
//# sourceMappingURL=probe.d.ts.map
|
|
@@ -33,6 +33,26 @@ export function managedSettingsPath(os = platform()) {
|
|
|
33
33
|
return 'C:\\ProgramData\\ClaudeCode\\managed-settings.json';
|
|
34
34
|
return '/etc/claude-code/managed-settings.json';
|
|
35
35
|
}
|
|
36
|
+
const LEGACY_EVENTS = { route: 'UserPromptSubmit', 'post-edit': 'PostToolUse' };
|
|
37
|
+
/** Which of route/post-edit the settings run through the CLI's own `hooks` command, in any of the files. */
|
|
38
|
+
export function legacyCliHooks(files) {
|
|
39
|
+
const commands = (event) => files.flatMap((f) => {
|
|
40
|
+
let groups;
|
|
41
|
+
try {
|
|
42
|
+
groups = get(readSettingsFile(f), 'hooks', event);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
return Array.isArray(groups)
|
|
48
|
+
? groups.flatMap((g) => (Array.isArray(g?.hooks) ? g.hooks.map((h) => h?.command).filter((c) => typeof c === 'string') : []))
|
|
49
|
+
: [];
|
|
50
|
+
});
|
|
51
|
+
return Object.keys(LEGACY_EVENTS).filter((sub) => {
|
|
52
|
+
const word = new RegExp(`(?:^|[\\s/"'])(?:@claude-flow/cli|claude-flow|ruflo)(?:@[\\w.-]+)?["']?\\s+hooks\\s+${sub}(?![\\w-])`);
|
|
53
|
+
return commands(LEGACY_EVENTS[sub]).some((c) => word.test(c));
|
|
54
|
+
});
|
|
55
|
+
}
|
|
36
56
|
export function probeMods(inputs) {
|
|
37
57
|
const root = resolve(inputs.projectRoot);
|
|
38
58
|
const home = inputs.home ?? homedir();
|
|
@@ -104,6 +124,14 @@ export function probeMods(inputs) {
|
|
|
104
124
|
findings.push(managedOnly !== undefined && managedOnly !== false
|
|
105
125
|
? { name: 'managed policy', status: 'warn', message: 'allowManagedModsOnly is set: Claude Code refuses user mods; classic hooks stay in charge' }
|
|
106
126
|
: { name: 'managed policy', status: 'pass', message: managed === undefined ? 'no managed settings' : 'user mods allowed (allowManagedModsOnly not set)' });
|
|
127
|
+
// 4a. Settings that still run the CLI's own `hooks route|post-edit` (what init
|
|
128
|
+
// wrote before hook-handler.cjs). That command never reads the handshake, so
|
|
129
|
+
// the mod stands down for those events (plugins/ruflo-mods/hooks/ownership.ts,
|
|
130
|
+
// legacyCliConfigured: keep the two patterns in step).
|
|
131
|
+
const legacy = legacyCliHooks(files);
|
|
132
|
+
if (legacy.length) {
|
|
133
|
+
findings.push({ name: 'legacy CLI hooks', status: 'warn', message: `settings run the CLI's own \`hooks ${legacy.join('` and `hooks ')}\`: the mod stands down for ${legacy.join(' and ')}, which that command keeps`, fix: 'ruflo init upgrade --settings (regenerate the settings hooks)' });
|
|
134
|
+
}
|
|
107
135
|
// 4. Every classic helper a hook could run honours the handshake (the mod's
|
|
108
136
|
// own rule, plugins/ruflo-mods/hooks/session.ts); else the mod stands down.
|
|
109
137
|
const helpers = [...new Set([join(root, '.claude', 'helpers', 'hook-handler.cjs'), join(home, '.claude', 'helpers', 'hook-handler.cjs')])].filter(existsSync);
|
|
@@ -116,7 +144,7 @@ export function probeMods(inputs) {
|
|
|
116
144
|
}
|
|
117
145
|
});
|
|
118
146
|
findings.push(helpers.length === 0
|
|
119
|
-
? { name: 'classic handshake', status: 'pass', message: 'no hook-handler.cjs: the mod owns route and post-edit outright' }
|
|
147
|
+
? { name: 'classic handshake', status: 'pass', message: legacy.length ? 'no hook-handler.cjs (see legacy CLI hooks)' : 'no hook-handler.cjs: the mod owns route and post-edit outright' }
|
|
120
148
|
: stale.length === 0
|
|
121
149
|
? { name: 'classic handshake', status: 'pass', message: `${helpers.join(', ')} hand route and post-edit to the mod while it runs` }
|
|
122
150
|
: { name: 'classic handshake', status: 'warn', message: `${stale.join(', ')} predate${stale.length === 1 ? 's' : ''} the handshake: the mod stands down and classic hooks keep route/post-edit`, fix: 'ruflo init --upgrade (refresh helpers in the project and in ~/.claude)' });
|
|
@@ -243,6 +243,7 @@ export class MonitoringHooks {
|
|
|
243
243
|
* Get metrics for a specific name
|
|
244
244
|
*/
|
|
245
245
|
getMetrics(name, since) {
|
|
246
|
+
this.expireMetrics();
|
|
246
247
|
let filtered = this.metrics.filter(m => m.name === name);
|
|
247
248
|
if (since) {
|
|
248
249
|
filtered = filtered.filter(m => m.timestamp >= since);
|
|
@@ -253,6 +254,7 @@ export class MonitoringHooks {
|
|
|
253
254
|
* Get all metrics summary
|
|
254
255
|
*/
|
|
255
256
|
getMetricsSummary() {
|
|
257
|
+
this.expireMetrics();
|
|
256
258
|
const summary = {};
|
|
257
259
|
for (const metric of this.metrics) {
|
|
258
260
|
if (!summary[metric.name]) {
|
|
@@ -320,11 +322,14 @@ export class MonitoringHooks {
|
|
|
320
322
|
}
|
|
321
323
|
}
|
|
322
324
|
}
|
|
325
|
+
/** Drop metrics older than retentionMs (also applied on reads, #3855). */
|
|
326
|
+
expireMetrics() {
|
|
327
|
+
const cutoff = Date.now() - this.config.retentionMs;
|
|
328
|
+
this.metrics = this.metrics.filter(m => m.timestamp > cutoff);
|
|
329
|
+
}
|
|
323
330
|
cleanupMetrics() {
|
|
324
|
-
const now = Date.now();
|
|
325
|
-
const cutoff = now - this.config.retentionMs;
|
|
326
331
|
// Remove old metrics
|
|
327
|
-
this.
|
|
332
|
+
this.expireMetrics();
|
|
328
333
|
// Limit total metrics
|
|
329
334
|
if (this.metrics.length > this.config.maxMetrics) {
|
|
330
335
|
this.metrics = this.metrics.slice(-this.config.maxMetrics);
|
|
@@ -41,12 +41,15 @@ export class RateLimiter {
|
|
|
41
41
|
const limits = this.getLimits(operation);
|
|
42
42
|
const now = Date.now();
|
|
43
43
|
// Get or create bucket
|
|
44
|
-
const
|
|
44
|
+
const trackedUser = userId && this.config.perUserLimits ? userId : undefined;
|
|
45
|
+
const bucketKey = trackedUser
|
|
45
46
|
? `${operation}:${userId}`
|
|
46
47
|
: `global:${operation}`;
|
|
47
48
|
let bucket = this.buckets.get(bucketKey);
|
|
48
49
|
if (!bucket) {
|
|
49
50
|
bucket = this.createBucket();
|
|
51
|
+
bucket.operation = operation;
|
|
52
|
+
bucket.userId = trackedUser;
|
|
50
53
|
this.buckets.set(bucketKey, bucket);
|
|
51
54
|
this.cleanupBuckets();
|
|
52
55
|
}
|
|
@@ -133,10 +136,10 @@ export class RateLimiter {
|
|
|
133
136
|
getStats() {
|
|
134
137
|
const operationCounts = new Map();
|
|
135
138
|
const users = new Set();
|
|
136
|
-
for (const
|
|
137
|
-
const
|
|
138
|
-
if (userId)
|
|
139
|
-
users.add(userId);
|
|
139
|
+
for (const bucket of this.buckets.values()) {
|
|
140
|
+
const operation = bucket.operation;
|
|
141
|
+
if (bucket.userId)
|
|
142
|
+
users.add(bucket.userId);
|
|
140
143
|
const current = operationCounts.get(operation) || 0;
|
|
141
144
|
operationCounts.set(operation, current + bucket.requests.length);
|
|
142
145
|
}
|
|
@@ -54,6 +54,8 @@ function calculateDelay(attempt, config, strategy = 'exponential') {
|
|
|
54
54
|
const jitterRange = delay * config.jitter;
|
|
55
55
|
delay += (Math.random() - 0.5) * 2 * jitterRange;
|
|
56
56
|
}
|
|
57
|
+
// Enforce the configured maximum on the final (jittered) delay
|
|
58
|
+
delay = Math.min(delay, config.maxDelayMs);
|
|
57
59
|
return Math.round(Math.max(0, delay));
|
|
58
60
|
}
|
|
59
61
|
/**
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
import { createHash } from 'crypto';
|
|
25
25
|
import { resolve } from 'path';
|
|
26
26
|
import * as fs from 'fs';
|
|
27
|
-
import { safeGitTextSync } from '@claude-flow/security/safe-git';
|
|
27
|
+
import { safeGitBufferSync, safeGitTextSync } from '@claude-flow/security/safe-git';
|
|
28
28
|
const GIT_TIMEOUT_MS = 3000;
|
|
29
29
|
function git(cwd, ...args) {
|
|
30
30
|
try {
|
|
@@ -37,6 +37,23 @@ function git(cwd, ...args) {
|
|
|
37
37
|
return null;
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Path-valued git output: git terminates the record with a single LF, so strip
|
|
42
|
+
* only that. A full trim() would corrupt valid paths ending in whitespace.
|
|
43
|
+
*/
|
|
44
|
+
function gitPath(cwd, ...args) {
|
|
45
|
+
try {
|
|
46
|
+
const out = safeGitBufferSync(cwd, args, {
|
|
47
|
+
timeoutMs: GIT_TIMEOUT_MS,
|
|
48
|
+
stderr: 'ignore',
|
|
49
|
+
}).toString('utf8');
|
|
50
|
+
const rec = out.endsWith('\n') ? out.slice(0, -1) : out;
|
|
51
|
+
return rec.length > 0 ? rec : null;
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
40
57
|
function sha256(input) {
|
|
41
58
|
return createHash('sha256').update(input).digest('hex');
|
|
42
59
|
}
|
|
@@ -50,7 +67,7 @@ export function resolveGitWorkspaceIdentity(dir) {
|
|
|
50
67
|
const resolved = resolve(dir);
|
|
51
68
|
let stable = identityCache.get(resolved);
|
|
52
69
|
if (!stable) {
|
|
53
|
-
const worktreeRoot =
|
|
70
|
+
const worktreeRoot = gitPath(resolved, 'rev-parse', '--show-toplevel');
|
|
54
71
|
if (!worktreeRoot) {
|
|
55
72
|
stable = {
|
|
56
73
|
worktreeRoot: resolved,
|
|
@@ -63,7 +80,7 @@ export function resolveGitWorkspaceIdentity(dir) {
|
|
|
63
80
|
else {
|
|
64
81
|
// --git-common-dir may be relative to the worktree root (git < 2.31
|
|
65
82
|
// and some invocation contexts) — resolve against it.
|
|
66
|
-
const rawCommon =
|
|
83
|
+
const rawCommon = gitPath(worktreeRoot, 'rev-parse', '--git-common-dir') ?? '.git';
|
|
67
84
|
const commonGitDir = resolve(worktreeRoot, rawCommon);
|
|
68
85
|
stable = {
|
|
69
86
|
worktreeRoot,
|