chore: remove over-engineering findings from repo-wide ponytail audit (wave 1)

Cuts verified dead code, duplication, and speculative abstractions across
14 disjoint areas of the tree: sqlite layer, worker HTTP routes, search
pipeline, server, chroma sync, viewer UI, npx-cli, MCP server, shared/utils,
telemetry/infra, integrations, scripts, tests, and stale plans/evals docs.

Also unifies the Chroma search pipeline onto SearchOrchestrator/strategies
and ports dual-project (merged_into_project) scoping plus dateRange
filtering into ChromaSearchStrategy, which corpus builds need but had
silently lost.

Full audit trail: 65-agent verification workflow wf_160a7862-1a6, 14-agent
execution wf_c7e48c0f-164, 6-agent fixup wf_865f86a4-c6b.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Alex Newman
2026-07-02 20:49:27 -07:00
parent 39dd77d9e3
commit 2a0a407340
254 changed files with 2152 additions and 24886 deletions
+22 -30
View File
@@ -7,7 +7,7 @@ import * as path from "path";
import * as os from "os";
import * as readline from "readline";
import { exec } from "child_process";
import { promisify } from "util";
import { promisify, parseArgs } from "util";
const execAsync = promisify(exec);
@@ -18,36 +18,28 @@ interface CliArgs {
help: boolean;
}
function parseArgs(): CliArgs {
const args = process.argv.slice(2);
const parsed: CliArgs = {
verbose: false,
noLogs: false,
help: false,
};
function parseCliArgs(): CliArgs {
try {
const { values } = parseArgs({
args: process.argv.slice(2),
options: {
help: { type: "boolean", short: "h", default: false },
verbose: { type: "boolean", short: "v", default: false },
"no-logs": { type: "boolean", default: false },
output: { type: "string", short: "o" },
},
});
for (let i = 0; i < args.length; i++) {
const arg = args[i];
switch (arg) {
case "-h":
case "--help":
parsed.help = true;
break;
case "-v":
case "--verbose":
parsed.verbose = true;
break;
case "--no-logs":
parsed.noLogs = true;
break;
case "-o":
case "--output":
parsed.output = args[++i];
break;
}
return {
output: values.output,
verbose: values.verbose,
noLogs: values["no-logs"],
help: values.help,
};
} catch (error) {
console.error(error instanceof Error ? error.message : String(error));
process.exit(1);
}
return parsed;
}
function printHelp(): void {
@@ -127,7 +119,7 @@ async function promptMultiline(prompt: string): Promise<string> {
}
async function main() {
const args = parseArgs();
const args = parseCliArgs();
if (args.help) {
printHelp();
-207
View File
@@ -1,207 +0,0 @@
#!/usr/bin/env bun
import { Database } from 'bun:sqlite';
import { homedir } from 'os';
import { join } from 'path';
const DB_PATH = join(homedir(), '.claude-mem', 'claude-mem.db');
const TIME_WINDOW_MODES = {
strict: 5, // 5 seconds - only exact duplicates from same batch
normal: 60, // 60 seconds - duplicates within same minute
aggressive: 0, // 0 = ignore time entirely, match on session+text+type only
};
interface DuplicateGroup {
memory_session_id: string;
title: string;
type: string;
epoch_bucket: number;
count: number;
ids: number[];
keep_id: number;
delete_ids: number[];
}
interface ObservationRow {
id: number;
memory_session_id: string;
title: string | null;
subtitle: string | null;
narrative: string | null;
type: string;
created_at_epoch: number;
}
function main() {
const dryRun = !process.argv.includes('--execute');
const aggressive = process.argv.includes('--aggressive');
const strict = process.argv.includes('--strict');
let windowMode: keyof typeof TIME_WINDOW_MODES = 'normal';
if (aggressive) windowMode = 'aggressive';
if (strict) windowMode = 'strict';
const batchWindowSeconds = TIME_WINDOW_MODES[windowMode];
console.log('='.repeat(60));
console.log('Claude-Mem Duplicate Observation Cleanup');
console.log('='.repeat(60));
console.log(`Mode: ${dryRun ? 'DRY RUN (use --execute to delete)' : 'EXECUTE'}`);
console.log(`Database: ${DB_PATH}`);
console.log(`Time window: ${windowMode} (${batchWindowSeconds === 0 ? 'ignore time' : batchWindowSeconds + ' seconds'})`);
console.log('');
console.log('Options:');
console.log(' --execute Actually delete duplicates (default: dry run)');
console.log(' --strict 5-second window (exact batch duplicates only)');
console.log(' --aggressive Ignore time, match on session+text+type only');
console.log('');
const db = dryRun
? new Database(DB_PATH, { readonly: true })
: new Database(DB_PATH);
const totalCount = db.prepare('SELECT COUNT(*) as count FROM observations').get() as { count: number };
console.log(`Total observations in database: ${totalCount.count}`);
const observations = db.prepare(`
SELECT
id,
memory_session_id,
title,
subtitle,
narrative,
type,
created_at_epoch
FROM observations
ORDER BY memory_session_id, title, type, created_at_epoch
`).all() as ObservationRow[];
console.log(`Analyzing ${observations.length} observations for duplicates...`);
console.log('');
const groups = new Map<string, ObservationRow[]>();
for (const obs of observations) {
if (obs.title === null) continue;
const contentKey = `${obs.title}|${obs.subtitle || ''}|${obs.narrative || ''}`;
let fingerprint: string;
if (batchWindowSeconds === 0) {
fingerprint = `${obs.memory_session_id}|${obs.type}|${contentKey}`;
} else {
const epochBucket = Math.floor(obs.created_at_epoch / batchWindowSeconds);
fingerprint = `${obs.memory_session_id}|${obs.type}|${epochBucket}|${contentKey}`;
}
if (!groups.has(fingerprint)) {
groups.set(fingerprint, []);
}
groups.get(fingerprint)!.push(obs);
}
const duplicateGroups: DuplicateGroup[] = [];
for (const [fingerprint, rows] of groups) {
if (rows.length > 1) {
rows.sort((a, b) => a.id - b.id);
const keepId = rows[0].id;
const deleteIds = rows.slice(1).map(r => r.id);
if (deleteIds.length >= rows.length) {
throw new Error(`SAFETY VIOLATION: Would delete all ${rows.length} copies! Aborting.`);
}
if (!deleteIds.every(id => id !== keepId)) {
throw new Error(`SAFETY VIOLATION: Delete list contains keep_id ${keepId}! Aborting.`);
}
const title = rows[0].title || '';
duplicateGroups.push({
memory_session_id: rows[0].memory_session_id,
title: title.substring(0, 100) + (title.length > 100 ? '...' : ''),
type: rows[0].type,
epoch_bucket: batchWindowSeconds > 0 ? Math.floor(rows[0].created_at_epoch / batchWindowSeconds) : 0,
count: rows.length,
ids: rows.map(r => r.id),
keep_id: keepId,
delete_ids: deleteIds,
});
}
}
if (duplicateGroups.length === 0) {
console.log('No duplicate observations found!');
db.close();
return;
}
const totalDuplicates = duplicateGroups.reduce((sum, g) => sum + g.delete_ids.length, 0);
const affectedSessions = new Set(duplicateGroups.map(g => g.memory_session_id)).size;
console.log('DUPLICATE ANALYSIS:');
console.log('-'.repeat(60));
console.log(`Duplicate groups found: ${duplicateGroups.length}`);
console.log(`Total duplicates to remove: ${totalDuplicates}`);
console.log(`Affected sessions: ${affectedSessions}`);
console.log(`Observations after cleanup: ${totalCount.count - totalDuplicates}`);
console.log('');
console.log('SAMPLE DUPLICATES (first 10 groups):');
console.log('-'.repeat(60));
for (const group of duplicateGroups.slice(0, 10)) {
console.log(`Session: ${group.memory_session_id.substring(0, 20)}...`);
console.log(`Type: ${group.type}`);
console.log(`Count: ${group.count} copies (keeping id=${group.keep_id}, deleting ${group.delete_ids.length})`);
console.log(`Title: "${group.title}"`);
console.log('');
}
if (duplicateGroups.length > 10) {
console.log(`... and ${duplicateGroups.length - 10} more groups`);
console.log('');
}
if (!dryRun) {
console.log('EXECUTING DELETION...');
console.log('-'.repeat(60));
const allDeleteIds = duplicateGroups.flatMap(g => g.delete_ids);
const BATCH_SIZE = 500;
let deleted = 0;
db.exec('BEGIN TRANSACTION');
try {
for (let i = 0; i < allDeleteIds.length; i += BATCH_SIZE) {
const batch = allDeleteIds.slice(i, i + BATCH_SIZE);
const placeholders = batch.map(() => '?').join(',');
const stmt = db.prepare(`DELETE FROM observations WHERE id IN (${placeholders})`);
const result = stmt.run(...batch);
deleted += result.changes;
console.log(`Deleted batch ${Math.floor(i / BATCH_SIZE) + 1}: ${result.changes} observations`);
}
db.exec('COMMIT');
console.log('');
console.log(`Successfully deleted ${deleted} duplicate observations!`);
const finalCount = db.prepare('SELECT COUNT(*) as count FROM observations').get() as { count: number };
console.log(`Final observation count: ${finalCount.count}`);
} catch (error) {
db.exec('ROLLBACK');
console.error('Error during deletion, rolled back:', error);
process.exit(1);
}
} else {
console.log('DRY RUN COMPLETE');
console.log('-'.repeat(60));
console.log('No changes were made. Run with --execute to delete duplicates.');
}
db.close();
}
main();
-162
View File
@@ -1,162 +0,0 @@
#!/usr/bin/env bun
import { Database } from 'bun:sqlite';
import { homedir } from 'os';
import { join, basename, dirname } from 'path';
import { existsSync, copyFileSync } from 'fs';
import { spawnSync } from 'child_process';
const DB_PATH = join(homedir(), '.claude-mem', 'claude-mem.db');
const APPLY = process.argv.includes('--apply');
type Classification =
| { kind: 'main'; project: string }
| { kind: 'worktree'; project: string; parent: string }
| { kind: 'skip'; reason: string };
function git(cwd: string, args: string[]): string | null {
const r = spawnSync('git', ['-C', cwd, ...args], { encoding: 'utf8' });
if (r.status !== 0) {
const stderr = (r.stderr ?? '').trim();
if (stderr && !/not a git repository/i.test(stderr)) {
console.error(`git ${args.join(' ')} failed in ${cwd}: ${stderr}`);
}
return null;
}
return r.stdout.trim();
}
function classify(cwd: string): Classification {
if (!existsSync(cwd)) return { kind: 'skip', reason: 'cwd-missing' };
const gitDir = git(cwd, ['rev-parse', '--absolute-git-dir']);
if (!gitDir) return { kind: 'skip', reason: 'not-a-git-repo' };
const commonDir = git(cwd, ['rev-parse', '--path-format=absolute', '--git-common-dir']);
if (!commonDir) return { kind: 'skip', reason: 'no-common-dir' };
const toplevel = git(cwd, ['rev-parse', '--show-toplevel']);
if (!toplevel) return { kind: 'skip', reason: 'no-toplevel' };
const leaf = basename(toplevel);
if (gitDir === commonDir) {
return { kind: 'main', project: leaf };
}
const parentRepoDir = commonDir.endsWith('/.git')
? dirname(commonDir)
: commonDir.replace(/\.git$/, '');
const parent = basename(parentRepoDir);
return { kind: 'worktree', project: `${parent}/${leaf}`, parent };
}
function main() {
if (!existsSync(DB_PATH)) {
console.error(`DB not found at ${DB_PATH}`);
process.exit(1);
}
if (APPLY) {
const backup = `${DB_PATH}.bak-cwd-remap-${Date.now()}`;
copyFileSync(DB_PATH, backup);
console.log(`Backup created: ${backup}`);
}
const db = new Database(DB_PATH);
const cwdRows = db.prepare(`
SELECT cwd, COUNT(*) AS messages
FROM pending_messages
WHERE cwd IS NOT NULL AND cwd != ''
GROUP BY cwd
`).all() as Array<{ cwd: string; messages: number }>;
console.log(`Classifying ${cwdRows.length} distinct cwds via git...`);
const byCwd = new Map<string, Classification>();
const counts = { main: 0, worktree: 0, skip: 0 };
for (const { cwd } of cwdRows) {
const c = classify(cwd);
byCwd.set(cwd, c);
counts[c.kind]++;
}
console.log(` main=${counts.main} worktree=${counts.worktree} skip=${counts.skip}`);
const skipped = [...byCwd.entries()].filter(([, c]) => c.kind === 'skip') as Array<[string, Extract<Classification, { kind: 'skip' }>]>;
if (skipped.length) {
console.log('\nSkipped cwds:');
for (const [cwd, c] of skipped) console.log(` [${c.reason}] ${cwd}`);
}
const sessionRows = db.prepare(`
SELECT s.id AS session_id, s.memory_session_id, s.content_session_id, s.project AS old_project, p.cwd
FROM sdk_sessions s
JOIN pending_messages p ON p.content_session_id = s.content_session_id
WHERE p.cwd IS NOT NULL AND p.cwd != ''
AND p.id = (
SELECT MIN(p2.id) FROM pending_messages p2
WHERE p2.content_session_id = s.content_session_id
AND p2.cwd IS NOT NULL AND p2.cwd != ''
)
`).all() as Array<{ session_id: number; memory_session_id: string | null; content_session_id: string; old_project: string; cwd: string }>;
type Target = { sessionId: number; memorySessionId: string | null; contentSessionId: string; oldProject: string; newProject: string; cwd: string };
const perSession = new Map<number, Target>();
for (const r of sessionRows) {
const c = byCwd.get(r.cwd);
if (!c || c.kind === 'skip') continue;
perSession.set(r.session_id, {
sessionId: r.session_id,
memorySessionId: r.memory_session_id,
contentSessionId: r.content_session_id,
oldProject: r.old_project,
newProject: c.project,
cwd: r.cwd,
});
}
const targets = [...perSession.values()].filter(t => t.oldProject !== t.newProject);
console.log(`\nSessions linked to a classified cwd: ${perSession.size}`);
console.log(`Sessions whose project would change: ${targets.length}`);
const summary = new Map<string, number>();
for (const t of targets) {
const key = `${t.oldProject}${t.newProject}`;
summary.set(key, (summary.get(key) ?? 0) + 1);
}
const rows = [...summary.entries()]
.map(([mapping, n]) => ({ mapping, sessions: n }))
.sort((a, b) => b.sessions - a.sessions);
console.log('\nTop mappings:');
console.table(rows.slice(0, 30));
if (rows.length > 30) console.log(` …and ${rows.length - 30} more mappings`);
if (!APPLY) {
console.log('\nDry-run only. Re-run with --apply to perform UPDATEs.');
db.close();
return;
}
const updSession = db.prepare('UPDATE sdk_sessions SET project = ? WHERE id = ?');
const updObs = db.prepare('UPDATE observations SET project = ? WHERE memory_session_id = ?');
const updSum = db.prepare('UPDATE session_summaries SET project = ? WHERE memory_session_id = ?');
let sessionN = 0, obsN = 0, sumN = 0;
const tx = db.transaction(() => {
for (const t of targets) {
sessionN += updSession.run(t.newProject, t.sessionId).changes;
if (t.memorySessionId) {
obsN += updObs.run(t.newProject, t.memorySessionId).changes;
sumN += updSum.run(t.newProject, t.memorySessionId).changes;
}
}
});
tx();
console.log(`\nApplied. sessions=${sessionN} observations=${obsN} session_summaries=${sumN}`);
db.close();
}
main();
-309
View File
@@ -1,309 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
WORKER_URL="http://localhost:37777"
CORPUS_NAME="e2e-test-knowledge-agent"
PASS_COUNT=0
FAIL_COUNT=0
LOG_FILE="${HOME}/.claude-mem/logs/e2e-knowledge-agents-$(date +%Y%m%d-%H%M%S).log"
log() { echo "[$(date +%H:%M:%S)] $*" | tee -a "$LOG_FILE"; }
pass() { PASS_COUNT=$((PASS_COUNT + 1)); log "PASS: $1"; }
fail() { FAIL_COUNT=$((FAIL_COUNT + 1)); log "FAIL: $1$2"; }
assert_http_status() {
local description="$1" expected_status="$2" actual_status="$3"
if [[ "$actual_status" == "$expected_status" ]]; then
pass "$description (HTTP $actual_status)"
else
fail "$description" "expected HTTP $expected_status, got $actual_status"
fi
}
assert_json_field() {
local description="$1" json="$2" field="$3" expected="$4"
local actual
actual=$(echo "$json" | jq -r "$field" 2>/dev/null || echo "PARSE_ERROR")
if [[ "$actual" == "$expected" ]]; then
pass "$description ($field=$actual)"
else
fail "$description" "expected $field=$expected, got $actual"
fi
}
assert_json_field_not_empty() {
local description="$1" json="$2" field="$3"
local actual
actual=$(echo "$json" | jq -r "$field" 2>/dev/null || echo "")
if [[ -n "$actual" && "$actual" != "null" && "$actual" != "" ]]; then
pass "$description ($field is present)"
else
fail "$description" "$field is empty or null"
fi
}
assert_json_field_numeric_gt() {
local description="$1" json="$2" field="$3" min_value="$4"
local actual
actual=$(echo "$json" | jq -r "$field" 2>/dev/null || echo "0")
if [[ "$actual" -gt "$min_value" ]] 2>/dev/null; then
pass "$description ($field=$actual > $min_value)"
else
fail "$description" "expected $field > $min_value, got $actual"
fi
}
curl_get() {
curl -sS --connect-timeout 5 --max-time 30 -w '\n%{http_code}' "$WORKER_URL$1" 2>/dev/null || printf '\n000'
}
curl_post() {
local path="$1" body="$2" max_time="${3:-30}"
curl -sS --connect-timeout 5 --max-time "$max_time" -w '\n%{http_code}' -X POST "$WORKER_URL$path" \
-H 'Content-Type: application/json' \
-d "$body" 2>/dev/null || printf '\n000'
}
curl_delete() {
curl -sS --connect-timeout 5 --max-time 30 -w '\n%{http_code}' -X DELETE "$WORKER_URL$1" 2>/dev/null || printf '\n000'
}
extract_body_and_status() {
local response="$1"
RESPONSE_BODY=$(echo "$response" | sed '$d')
RESPONSE_STATUS=$(echo "$response" | tail -1)
}
cleanup_test_corpus() {
log "Cleaning up test corpus '$CORPUS_NAME'..."
curl -s -X DELETE "$WORKER_URL/api/corpus/$CORPUS_NAME" > /dev/null 2>&1 || true
}
test_worker_health() {
log "=== Test: Worker Health ==="
local response
response=$(curl_get "/api/health")
extract_body_and_status "$response"
assert_http_status "Worker health check" "200" "$RESPONSE_STATUS"
}
test_worker_readiness() {
log "=== Test: Worker Readiness ==="
local response
response=$(curl_get "/api/readiness")
extract_body_and_status "$response"
assert_http_status "Worker readiness check" "200" "$RESPONSE_STATUS"
}
test_build_corpus() {
log "=== Test: Build Corpus ==="
local response
response=$(curl_post "/api/corpus" "{
\"name\": \"$CORPUS_NAME\",
\"description\": \"E2E test corpus for knowledge agents\",
\"query\": \"architecture\",
\"limit\": 20
}")
extract_body_and_status "$response"
assert_http_status "Build corpus" "200" "$RESPONSE_STATUS"
assert_json_field "Build corpus name" "$RESPONSE_BODY" ".name" "$CORPUS_NAME"
assert_json_field_not_empty "Build corpus description" "$RESPONSE_BODY" ".description"
assert_json_field_not_empty "Build corpus stats" "$RESPONSE_BODY" ".stats.observation_count"
log "Build response: $(echo "$RESPONSE_BODY" | jq -c '{name, stats: .stats}' 2>/dev/null)"
}
test_list_corpora() {
log "=== Test: List Corpora ==="
local response
response=$(curl_get "/api/corpus")
extract_body_and_status "$response"
assert_http_status "List corpora" "200" "$RESPONSE_STATUS"
local found
found=$(echo "$RESPONSE_BODY" | jq -r ".[] | select(.name == \"$CORPUS_NAME\") | .name" 2>/dev/null)
if [[ "$found" == "$CORPUS_NAME" ]]; then
pass "Test corpus found in list"
else
fail "Test corpus in list" "corpus '$CORPUS_NAME' not found"
fi
}
test_get_corpus() {
log "=== Test: Get Corpus ==="
local response
response=$(curl_get "/api/corpus/$CORPUS_NAME")
extract_body_and_status "$response"
assert_http_status "Get corpus" "200" "$RESPONSE_STATUS"
assert_json_field "Get corpus name" "$RESPONSE_BODY" ".name" "$CORPUS_NAME"
assert_json_field "Get corpus session_id (pre-prime)" "$RESPONSE_BODY" ".session_id" "null"
}
test_get_corpus_404() {
log "=== Test: Get Nonexistent Corpus ==="
local response
response=$(curl_get "/api/corpus/nonexistent-corpus-that-does-not-exist")
extract_body_and_status "$response"
assert_http_status "Get nonexistent corpus returns 404" "404" "$RESPONSE_STATUS"
}
test_prime_corpus() {
log "=== Test: Prime Corpus ==="
log " (This may take 30-120 seconds — Agent SDK session is being created...)"
local response
response=$(curl_post "/api/corpus/$CORPUS_NAME/prime" '{}' 300)
extract_body_and_status "$response"
assert_http_status "Prime corpus" "200" "$RESPONSE_STATUS"
assert_json_field_not_empty "Prime returns session_id" "$RESPONSE_BODY" ".session_id"
assert_json_field "Prime returns corpus name" "$RESPONSE_BODY" ".name" "$CORPUS_NAME"
log "Prime response: $(echo "$RESPONSE_BODY" | jq -c '{name, session_id: (.session_id | .[0:20] + "...")}' 2>/dev/null)"
}
test_query_corpus() {
log "=== Test: Query Corpus ==="
local response
response=$(curl_post "/api/corpus/$CORPUS_NAME/query" '{"question": "What are the main topics and themes in this knowledge base? Give a brief summary."}' 300)
extract_body_and_status "$response"
assert_http_status "Query corpus" "200" "$RESPONSE_STATUS"
assert_json_field_not_empty "Query returns answer" "$RESPONSE_BODY" ".answer"
assert_json_field_not_empty "Query returns session_id" "$RESPONSE_BODY" ".session_id"
local answer_length
answer_length=$(echo "$RESPONSE_BODY" | jq -r '.answer | length' 2>/dev/null || echo "0")
if [[ "$answer_length" -gt 50 ]]; then
pass "Query answer is substantive (${answer_length} chars)"
else
fail "Query answer length" "expected > 50 chars, got $answer_length"
fi
log "Query answer preview: $(echo "$RESPONSE_BODY" | jq -r '.answer' 2>/dev/null | head -3)"
}
test_query_without_prime() {
log "=== Test: Query Unprimed Corpus ==="
curl_post "/api/corpus" "{\"name\": \"e2e-unprimed-test\", \"limit\": 5}" > /dev/null 2>&1
local response
response=$(curl_post "/api/corpus/e2e-unprimed-test/query" '{"question": "test"}' 30)
extract_body_and_status "$response"
if [[ "$RESPONSE_STATUS" != "200" ]] || echo "$RESPONSE_BODY" | jq -r '.error' 2>/dev/null | grep -qi "prime\|session"; then
pass "Query unprimed corpus correctly rejected"
else
fail "Query unprimed corpus" "expected error about priming, got HTTP $RESPONSE_STATUS"
fi
curl -s -X DELETE "$WORKER_URL/api/corpus/e2e-unprimed-test" > /dev/null 2>&1 || true
}
test_reprime_corpus() {
log "=== Test: Reprime Corpus ==="
log " (Creating fresh session...)"
local old_response old_session_id
old_response=$(curl_get "/api/corpus/$CORPUS_NAME")
extract_body_and_status "$old_response"
old_session_id=$(echo "$RESPONSE_BODY" | jq -r '.session_id' 2>/dev/null)
local response
response=$(curl_post "/api/corpus/$CORPUS_NAME/reprime" '{}' 300)
extract_body_and_status "$response"
assert_http_status "Reprime corpus" "200" "$RESPONSE_STATUS"
assert_json_field_not_empty "Reprime returns session_id" "$RESPONSE_BODY" ".session_id"
local new_session_id
new_session_id=$(echo "$RESPONSE_BODY" | jq -r '.session_id' 2>/dev/null)
if [[ "$new_session_id" != "$old_session_id" ]]; then
pass "Reprime created new session (different session_id)"
else
fail "Reprime session_id" "expected new session_id, got same as before"
fi
}
test_query_after_reprime() {
log "=== Test: Query After Reprime ==="
local response
response=$(curl_post "/api/corpus/$CORPUS_NAME/query" '{"question": "List the types of observations in this knowledge base."}' 300)
extract_body_and_status "$response"
assert_http_status "Query after reprime" "200" "$RESPONSE_STATUS"
assert_json_field_not_empty "Answer after reprime" "$RESPONSE_BODY" ".answer"
log "Post-reprime answer preview: $(echo "$RESPONSE_BODY" | jq -r '.answer' 2>/dev/null | head -3)"
}
test_rebuild_corpus() {
log "=== Test: Rebuild Corpus ==="
local response
response=$(curl_post "/api/corpus/$CORPUS_NAME/rebuild" '{}' 60)
extract_body_and_status "$response"
assert_http_status "Rebuild corpus" "200" "$RESPONSE_STATUS"
assert_json_field "Rebuild returns name" "$RESPONSE_BODY" ".name" "$CORPUS_NAME"
assert_json_field_not_empty "Rebuild returns stats" "$RESPONSE_BODY" ".stats.observation_count"
}
test_delete_corpus() {
log "=== Test: Delete Corpus ==="
local response
response=$(curl_delete "/api/corpus/$CORPUS_NAME")
extract_body_and_status "$response"
assert_http_status "Delete corpus" "200" "$RESPONSE_STATUS"
local verify_response
verify_response=$(curl_get "/api/corpus/$CORPUS_NAME")
extract_body_and_status "$verify_response"
assert_http_status "Deleted corpus returns 404" "404" "$RESPONSE_STATUS"
}
test_delete_nonexistent() {
log "=== Test: Delete Nonexistent Corpus ==="
local response
response=$(curl_delete "/api/corpus/nonexistent-corpus-that-does-not-exist")
extract_body_and_status "$response"
assert_http_status "Delete nonexistent returns 404" "404" "$RESPONSE_STATUS"
}
main() {
mkdir -p "$(dirname "$LOG_FILE")"
log "======================================================"
log " Knowledge Agents E2E Test"
log " $(date)"
log "======================================================"
log ""
cleanup_test_corpus
test_worker_health
test_worker_readiness
log ""
test_build_corpus
test_list_corpora
test_get_corpus
test_get_corpus_404
log ""
test_prime_corpus
test_query_corpus
test_query_without_prime
log ""
test_reprime_corpus
test_query_after_reprime
log ""
test_rebuild_corpus
test_delete_corpus
test_delete_nonexistent
log ""
local total=$((PASS_COUNT + FAIL_COUNT))
log "======================================================"
log " RESULTS: $PASS_COUNT/$total passed, $FAIL_COUNT failed"
log "======================================================"
if [[ "$FAIL_COUNT" -gt 0 ]]; then
log " STATUS: FAILED"
log " Log: $LOG_FILE"
exit 1
else
log " STATUS: ALL PASSED"
log " Log: $LOG_FILE"
exit 0
fi
}
main "$@"
-208
View File
@@ -1,208 +0,0 @@
#!/usr/bin/env bun
import Database from 'bun:sqlite';
import { resolve } from 'path';
const DB_PATH = resolve(process.env.HOME!, '.claude-mem/claude-mem.db');
const BAD_WINDOW_START = 1766623500000;
const BAD_WINDOW_END = 1766626260000;
interface AffectedObservation {
id: number;
memory_session_id: string;
created_at_epoch: number;
title: string;
}
interface SessionMapping {
session_db_id: number;
memory_session_id: string;
}
interface TimestampFix {
observation_id: number;
observation_title: string;
wrong_timestamp: number;
correct_timestamp: number;
session_db_id: number;
pending_message_id: number;
}
function formatTimestamp(epoch: number): string {
return new Date(epoch).toLocaleString('en-US', {
timeZone: 'America/Los_Angeles',
year: 'numeric',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
}
function main() {
const args = process.argv.slice(2);
const dryRun = args.includes('--dry-run');
const autoYes = args.includes('--yes') || args.includes('-y');
console.log('🔍 Analyzing corrupted observation timestamps...\n');
if (dryRun) {
console.log('🏃 DRY RUN MODE - No changes will be made\n');
}
const db = new Database(DB_PATH);
try {
console.log('Step 1: Finding observations created during bad window...');
const affectedObs = db.query<AffectedObservation, []>(`
SELECT id, memory_session_id, created_at_epoch, title
FROM observations
WHERE created_at_epoch >= ${BAD_WINDOW_START}
AND created_at_epoch <= ${BAD_WINDOW_END}
ORDER BY id
`).all();
console.log(`Found ${affectedObs.length} observations in bad window\n`);
if (affectedObs.length === 0) {
console.log('✅ No affected observations found!');
return;
}
console.log('Step 2: Matching observations to session start times...');
const fixes: TimestampFix[] = [];
interface ObsWithSession {
obs_id: number;
obs_title: string;
obs_created: number;
session_started: number;
memory_session_id: string;
}
const obsWithSessions = db.query<ObsWithSession, []>(`
SELECT
o.id as obs_id,
o.title as obs_title,
o.created_at_epoch as obs_created,
s.started_at_epoch as session_started,
s.memory_session_id
FROM observations o
JOIN sdk_sessions s ON o.memory_session_id = s.memory_session_id
WHERE o.created_at_epoch >= ${BAD_WINDOW_START}
AND o.created_at_epoch <= ${BAD_WINDOW_END}
AND s.started_at_epoch < ${BAD_WINDOW_START}
ORDER BY o.id
`).all();
for (const row of obsWithSessions) {
fixes.push({
observation_id: row.obs_id,
observation_title: row.obs_title || '(no title)',
wrong_timestamp: row.obs_created,
correct_timestamp: row.session_started,
session_db_id: 0, // Not needed for this approach
pending_message_id: 0
});
}
console.log(`Identified ${fixes.length} observations to fix\n`);
console.log('═══════════════════════════════════════════════════════════════════════');
console.log('PROPOSED FIXES:');
console.log('═══════════════════════════════════════════════════════════════════════\n');
for (const fix of fixes) {
const daysDiff = Math.round((fix.wrong_timestamp - fix.correct_timestamp) / (1000 * 60 * 60 * 24));
console.log(`Observation #${fix.observation_id}: ${fix.observation_title}`);
console.log(` ❌ Wrong: ${formatTimestamp(fix.wrong_timestamp)}`);
console.log(` ✅ Correct: ${formatTimestamp(fix.correct_timestamp)}`);
console.log(` 📅 Off by ${daysDiff} days\n`);
}
console.log('═══════════════════════════════════════════════════════════════════════');
console.log(`Ready to fix ${fixes.length} observations.`);
if (dryRun) {
console.log('\n🏃 DRY RUN COMPLETE - No changes made.');
console.log('Run without --dry-run flag to apply fixes.\n');
db.close();
return;
}
if (autoYes) {
console.log('Auto-confirming with --yes flag...\n');
applyFixes(db, fixes);
return;
}
console.log('Apply these fixes? (y/n): ');
const stdin = Bun.stdin.stream();
const reader = stdin.getReader();
reader.read().then(({ value }) => {
const response = new TextDecoder().decode(value).trim().toLowerCase();
if (response === 'y' || response === 'yes') {
applyFixes(db, fixes);
} else {
console.log('\n❌ Fixes cancelled. No changes made.');
db.close();
}
});
} catch (error) {
console.error('❌ Error:', error);
db.close();
process.exit(1);
}
}
function applyFixes(db: Database, fixes: TimestampFix[]) {
console.log('\n🔧 Applying fixes...\n');
const updateStmt = db.prepare(`
UPDATE observations
SET created_at_epoch = ?,
created_at = datetime(?/1000, 'unixepoch')
WHERE id = ?
`);
let successCount = 0;
let errorCount = 0;
for (const fix of fixes) {
try {
updateStmt.run(
fix.correct_timestamp,
fix.correct_timestamp,
fix.observation_id
);
successCount++;
console.log(`✅ Fixed observation #${fix.observation_id}`);
} catch (error) {
errorCount++;
console.error(`❌ Failed to fix observation #${fix.observation_id}:`, error);
}
}
console.log('\n═══════════════════════════════════════════════════════════════════════');
console.log('RESULTS:');
console.log('═══════════════════════════════════════════════════════════════════════');
console.log(`✅ Successfully fixed: ${successCount}`);
console.log(`❌ Failed: ${errorCount}`);
console.log(`📊 Total processed: ${fixes.length}\n`);
if (successCount > 0) {
console.log('🎉 Timestamp corruption has been repaired!');
console.log('💡 Next steps:');
console.log(' 1. Verify the fixes with: bun scripts/verify-timestamp-fix.ts');
console.log(' 2. Consider re-enabling orphan processing if timestamp fix is working\n');
}
db.close();
}
main();
-143
View File
@@ -1,143 +0,0 @@
#!/usr/bin/env node
import { Jimp } from 'jimp';
import { writeFileSync, readdirSync, existsSync } from 'fs';
import { deflateRawSync } from 'zlib';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const repoRoot = join(__dirname, '..');
const FRAMES_DIR = process.env.FRAMES_DIR || '/tmp/cmem-banner-frames';
const OUT = join(repoRoot, 'src/npx-cli/banner-frames.ts');
const COLS = 128;
const VIDEO_ROWS = Math.round(COLS * (9 / 16) / 2);
const ROWS = VIDEO_ROWS;
const TOP_PAD = 0;
const BOTTOM_PAD = 0;
const RAMP = ' .·~+=*x%$@#';
const BLACK_FLOOR = 50;
const WHITE_CEIL = 160;
const HALO_MIN = 70;
const HALO_MAX = 175;
function rasterize(img, gridW, gridH) {
const resized = img.clone().resize({ w: gridW, h: gridH });
const data = resized.bitmap.data;
const density = new Float32Array(gridW * gridH);
for (let cy = 0; cy < gridH; cy++) {
for (let cx = 0; cx < gridW; cx++) {
const idx = (cy * gridW + cx) * 4;
const r = data[idx], g = data[idx + 1], b = data[idx + 2];
density[cy * gridW + cx] = 0.2126 * r + 0.7152 * g + 0.0722 * b;
}
}
return density;
}
function densityToChar(d) {
if (d <= BLACK_FLOOR) return ' ';
const range = WHITE_CEIL - BLACK_FLOOR;
const norm = Math.min(1, (d - BLACK_FLOOR) / range);
const t = Math.pow(norm, 1.3);
const idx = Math.min(RAMP.length - 1, Math.max(1, Math.round(t * (RAMP.length - 1))));
return RAMP[idx];
}
function renderASCII(density, w, h) {
const lines = [];
for (let y = 0; y < h; y++) {
let line = '';
let inSpan = false;
for (let x = 0; x < w; x++) {
const i = y * w + x;
const d = density[i];
const ch = densityToChar(d);
const wantSpan = d > HALO_MIN && d < HALO_MAX && ch !== ' ';
if (wantSpan && !inSpan) { line += '<span>'; inSpan = true; }
if (!wantSpan && inSpan) { line += '</span>'; inSpan = false; }
line += ch;
}
if (inSpan) line += '</span>';
lines.push(line);
}
return lines.join('\n');
}
async function main() {
if (!existsSync(FRAMES_DIR)) {
throw new Error(`Frames directory not found: ${FRAMES_DIR}\n` +
`Run: ffmpeg -y -i <video> -vf "scale=320:180" ${FRAMES_DIR}/frame_%04d.png`);
}
const files = readdirSync(FRAMES_DIR)
.filter((f) => f.endsWith('.png'))
.sort();
if (files.length === 0) {
throw new Error(`No PNG frames found in ${FRAMES_DIR}`);
}
const blankLine = ' '.repeat(COLS);
const topPadding = Array(TOP_PAD).fill(blankLine).join('\n');
const bottomPadding = Array(BOTTOM_PAD).fill(blankLine).join('\n');
const frameStrings = [];
for (let i = 0; i < files.length; i++) {
const img = await Jimp.read(join(FRAMES_DIR, files[i]));
const density = rasterize(img, COLS, VIDEO_ROWS);
const body = renderASCII(density, COLS, VIDEO_ROWS);
const padded = [topPadding, body, bottomPadding].filter(Boolean).join('\n');
frameStrings.push(padded);
if ((i + 1) % 32 === 0 || i === files.length - 1) {
process.stdout.write(` rasterized ${i + 1}/${files.length}\r`);
}
}
process.stdout.write('\n');
const joined = frameStrings.join('\x01');
const compressed = deflateRawSync(Buffer.from(joined, 'utf8'), { level: 9 });
const b64 = compressed.toString('base64');
const FRAME_DELAY = 22;
const ts = `// @strip-comments-keep — auto-generated, do not edit by hand.
// Source: scripts/generate-banner-frames.mjs (webm video → ASCII via luminance ramp).
// Frames are gzip-deflated, base64-encoded, separated by \\x01.
export interface BannerData {
/** Base64-encoded raw deflate of all frames joined by \\x01 */
compressed: string;
frameCount: number;
width: number;
height: number;
/** Milliseconds per frame */
frameDelay: number;
}
export const BANNER: BannerData = {
compressed: ${JSON.stringify(b64)},
frameCount: ${files.length},
width: ${COLS},
height: ${ROWS},
frameDelay: ${FRAME_DELAY},
};
`;
writeFileSync(OUT, ts);
console.log(`✓ Generated ${files.length} ASCII frames at ${COLS}×${ROWS}`);
console.log(` Raw size: ${joined.length} bytes`);
console.log(` Compressed: ${compressed.length} bytes (${((compressed.length / joined.length) * 100).toFixed(1)}%)`);
console.log(` Base64: ${b64.length} bytes`);
console.log(` Written to: ${OUT}`);
if (process.env.PREVIEW) {
console.log('\n--- final frame preview ---');
console.log(frameStrings[frameStrings.length - 1].replace(/<\/?span>/g, ''));
}
}
main().catch((err) => {
console.error(err);
process.exit(1);
});
-135
View File
@@ -1,135 +0,0 @@
#!/usr/bin/env bun
import Database from 'bun:sqlite';
import { resolve } from 'path';
const DB_PATH = resolve(process.env.HOME!, '.claude-mem/claude-mem.db');
function formatTimestamp(epoch: number): string {
return new Date(epoch).toLocaleString('en-US', {
timeZone: 'America/Los_Angeles',
year: 'numeric',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
}
function main() {
console.log('🔍 Investigating timestamp situation...\n');
const db = new Database(DB_PATH);
try {
console.log('Check 1: All observations created on Dec 24, 2025...');
// Computed at runtime to avoid drift; note that Date.UTC returns the
// UTC midnight epoch — this script formats with America/Los_Angeles, so
// the boundaries are on UTC days, not Pacific days. That's intentional
// here: we want a stable epoch window the SQL can compare against.
const dec24Start = Date.UTC(2025, 11, 24);
const dec24End = Date.UTC(2025, 11, 25);
const dec24Obs = db.query(`
SELECT id, memory_session_id, created_at_epoch, title
FROM observations
WHERE created_at_epoch >= ${dec24Start}
AND created_at_epoch < ${dec24End}
ORDER BY created_at_epoch
LIMIT 100
`).all();
console.log(`Found ${dec24Obs.length} observations on Dec 24:\n`);
for (const obs of dec24Obs.slice(0, 20)) {
console.log(` #${obs.id}: ${formatTimestamp(obs.created_at_epoch)} - ${obs.title || '(no title)'}`);
}
if (dec24Obs.length > 20) {
console.log(` ... and ${dec24Obs.length - 20} more`);
}
console.log();
console.log('Check 2: Observations from Dec 17-20, 2025...');
const dec17Start = Date.UTC(2025, 11, 17);
const dec21Start = Date.UTC(2025, 11, 21);
const oldObs = db.query(`
SELECT id, memory_session_id, created_at_epoch, title
FROM observations
WHERE created_at_epoch >= ${dec17Start}
AND created_at_epoch < ${dec21Start}
ORDER BY created_at_epoch
LIMIT 100
`).all();
console.log(`Found ${oldObs.length} observations from Dec 17-20:\n`);
for (const obs of oldObs.slice(0, 20)) {
console.log(` #${obs.id}: ${formatTimestamp(obs.created_at_epoch)} - ${obs.title || '(no title)'}`);
}
if (oldObs.length > 20) {
console.log(` ... and ${oldObs.length - 20} more`);
}
console.log();
console.log('Check 3: Pending messages status...');
const statusCounts = db.query(`
SELECT status, COUNT(*) as count
FROM pending_messages
GROUP BY status
`).all();
console.log('Pending message counts by status:');
for (const row of statusCounts) {
console.log(` ${row.status}: ${row.count}`);
}
console.log();
console.log('Check 4: Pending messages from Dec 17-20...');
const oldMessages = db.query(`
SELECT id, session_db_id, tool_name, status, created_at_epoch, completed_at_epoch
FROM pending_messages
WHERE created_at_epoch >= ${dec17Start}
AND created_at_epoch < ${dec21Start}
ORDER BY created_at_epoch
LIMIT 50
`).all();
console.log(`Found ${oldMessages.length} pending messages from Dec 17-20:\n`);
for (const msg of oldMessages.slice(0, 20)) {
const completedAt = msg.completed_at_epoch ? formatTimestamp(msg.completed_at_epoch) : 'N/A';
console.log(` #${msg.id}: ${msg.tool_name} - Status: ${msg.status}`);
console.log(` Created: ${formatTimestamp(msg.created_at_epoch)}`);
console.log(` Completed: ${completedAt}\n`);
}
if (oldMessages.length > 20) {
console.log(` ... and ${oldMessages.length - 20} more`);
}
console.log('Check 5: Recently completed pending messages...');
const recentCompleted = db.query(`
SELECT id, session_db_id, tool_name, status, created_at_epoch, completed_at_epoch
FROM pending_messages
WHERE completed_at_epoch IS NOT NULL
ORDER BY completed_at_epoch DESC
LIMIT 20
`).all();
console.log(`Most recent completed pending messages:\n`);
for (const msg of recentCompleted) {
const createdAt = formatTimestamp(msg.created_at_epoch);
const completedAt = formatTimestamp(msg.completed_at_epoch);
const lag = Math.round((msg.completed_at_epoch - msg.created_at_epoch) / 1000);
console.log(` #${msg.id}: ${msg.tool_name} (${msg.status})`);
console.log(` Created: ${createdAt}`);
console.log(` Completed: ${completedAt} (${lag}s later)\n`);
}
} catch (error) {
console.error('❌ Error:', error);
process.exit(1);
} finally {
db.close();
}
}
main();
-158
View File
@@ -1,158 +0,0 @@
#!/usr/bin/env node
import { exec } from 'child_process';
import { promisify } from 'util';
import fs from 'fs';
import readline from 'readline';
const execAsync = promisify(exec);
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
const question = (query) => new Promise((resolve) => rl.question(query, resolve));
async function publish() {
try {
console.log('📦 Claude-mem Marketplace Release Tool\n');
console.log('🔍 Checking git status...');
const { stdout: gitStatus } = await execAsync('git status --porcelain');
if (gitStatus.trim()) {
console.log('⚠️ Uncommitted changes detected:');
console.log(gitStatus);
const proceed = await question('\nContinue anyway? (y/N) ');
if (proceed.toLowerCase() !== 'y') {
console.log('Aborted.');
rl.close();
process.exit(0);
}
} else {
console.log('✓ Working directory clean');
}
const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf-8'));
const currentVersion = packageJson.version;
console.log(`\n📌 Current version: ${currentVersion}`);
console.log('\nVersion bump type:');
console.log(' 1. patch (x.x.X) - Bug fixes');
console.log(' 2. minor (x.X.0) - New features');
console.log(' 3. major (X.0.0) - Breaking changes');
console.log(' 4. custom - Enter version manually');
const bumpType = await question('\nSelect bump type (1-4): ');
let newVersion;
switch (bumpType.trim()) {
case '1':
newVersion = bumpVersion(currentVersion, 'patch');
break;
case '2':
newVersion = bumpVersion(currentVersion, 'minor');
break;
case '3':
newVersion = bumpVersion(currentVersion, 'major');
break;
case '4':
newVersion = await question('Enter version: ');
if (!isValidVersion(newVersion)) {
throw new Error('Invalid version format. Use semver (e.g., 1.2.3)');
}
break;
default:
throw new Error('Invalid selection');
}
console.log(`\n🎯 New version: ${newVersion}`);
const confirm = await question('\nProceed with publish? (y/N) ');
if (confirm.toLowerCase() !== 'y') {
console.log('Aborted.');
rl.close();
process.exit(0);
}
console.log('\n📝 Updating package.json and marketplace.json...');
packageJson.version = newVersion;
fs.writeFileSync('package.json', JSON.stringify(packageJson, null, 2) + '\n');
const marketplaceJson = JSON.parse(fs.readFileSync('.claude-plugin/marketplace.json', 'utf-8'));
marketplaceJson.plugins[0].version = newVersion;
fs.writeFileSync('.claude-plugin/marketplace.json', JSON.stringify(marketplaceJson, null, 2) + '\n');
console.log('✓ Versions updated in both files');
console.log('\n🔨 Building hooks...');
await execAsync('npm run build');
console.log('✓ Build complete');
if (packageJson.scripts?.test) {
console.log('\n🧪 Running tests...');
try {
await execAsync('npm test');
console.log('✓ Tests passed');
} catch (error) {
console.error('❌ Tests failed:', error.message);
const continueAnyway = await question('\nPublish anyway? (y/N) ');
if (continueAnyway.toLowerCase() !== 'y') {
console.log('Aborted.');
rl.close();
process.exit(1);
}
}
}
console.log('\n📌 Creating git commit and tag...');
await execAsync('git add package.json .claude-plugin/marketplace.json plugin/');
await execAsync(`git commit -m "chore: Release v${newVersion}
Marketplace release for Claude Code plugin
https://github.com/thedotmack/claude-mem"`);
await execAsync(`git tag v${newVersion}`);
console.log(`✓ Created commit and tag v${newVersion}`);
console.log('\n⬆ Pushing to git...');
await execAsync('git push');
await execAsync('git push --tags');
console.log('✓ Pushed to git');
console.log(`\n✅ Successfully released v${newVersion}! 🎉`);
console.log(`\n🏷️ Tag: https://github.com/thedotmack/claude-mem/releases/tag/v${newVersion}`);
console.log(`📦 Marketplace will sync from this tag automatically`);
} catch (error) {
console.error('\n❌ Release failed:', error.message);
if (error.stderr) {
console.error('\nError details:', error.stderr);
}
process.exit(1);
} finally {
rl.close();
}
}
function bumpVersion(version, type) {
const parts = version.split('.').map(Number);
switch (type) {
case 'patch':
parts[2]++;
break;
case 'minor':
parts[1]++;
parts[2] = 0;
break;
case 'major':
parts[0]++;
parts[1] = 0;
parts[2] = 0;
break;
}
return parts.join('.');
}
function isValidVersion(version) {
return /^\d+\.\d+\.\d+(-[a-zA-Z0-9.-]+)?$/.test(version);
}
publish();
+25 -18
View File
@@ -9,6 +9,7 @@ import { parse as parse5Parse, parseFragment as parse5ParseFragment } from 'pars
import { readFileSync, writeFileSync, statSync } from 'node:fs';
import { execSync } from 'node:child_process';
import { extname, basename, join } from 'node:path';
import { parseArgs } from 'node:util';
interface CliOptions {
root: string;
@@ -17,27 +18,33 @@ interface CliOptions {
verbose: boolean;
}
function parseArgs(argv: string[]): CliOptions {
let root = process.cwd();
let check = false;
let dryRun = false;
let verbose = false;
for (const arg of argv.slice(2)) {
if (arg === '--check') check = true;
else if (arg === '--dry-run') dryRun = true;
else if (arg === '--verbose' || arg === '-v') verbose = true;
else if (arg === '--help' || arg === '-h') {
function parseCliArgs(argv: string[]): CliOptions {
try {
const { values, positionals } = parseArgs({
args: argv.slice(2),
allowPositionals: true,
options: {
check: { type: 'boolean', default: false },
'dry-run': { type: 'boolean', default: false },
verbose: { type: 'boolean', short: 'v', default: false },
help: { type: 'boolean', short: 'h', default: false },
},
});
if (values.help) {
printHelp();
process.exit(0);
} else if (!arg.startsWith('-')) {
root = arg;
} else {
console.error(`Unknown flag: ${arg}`);
printHelp();
process.exit(2);
}
return {
root: positionals[0] ?? process.cwd(),
check: values.check,
dryRun: values['dry-run'],
verbose: values.verbose,
};
} catch (e) {
console.error((e as Error).message);
printHelp();
process.exit(2);
}
return { root, check, dryRun, verbose };
}
function printHelp(): void {
@@ -422,7 +429,7 @@ function processFile(absPath: string, relPath: string, stats: Stats, opts: CliOp
}
function main(): void {
const opts = parseArgs(process.argv);
const opts = parseCliArgs(process.argv);
const stats: Stats = {
changed: 0,
-56
View File
@@ -1,56 +0,0 @@
#!/bin/bash
set -e
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Resolve SOURCE_DIR relative to this script so it works regardless of cwd.
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SOURCE_DIR="$SCRIPT_DIR/../plugin/"
DEST_DIR="$HOME/.claude/plugins/marketplaces/thedotmack/plugin/"
print_status() {
echo -e "${GREEN}[INFO]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
if [ ! -d "$SOURCE_DIR" ]; then
print_error "Source directory '$SOURCE_DIR' does not exist!"
exit 1
fi
if [ ! -d "$DEST_DIR" ]; then
print_warning "Destination directory '$DEST_DIR' does not exist. Creating it..."
mkdir -p "$DEST_DIR"
fi
print_status "Syncing plugin folder to marketplace..."
print_status "Source: $SOURCE_DIR"
print_status "Destination: $DEST_DIR"
if [ "$1" = "--dry-run" ] || [ "$1" = "-n" ]; then
print_status "Dry run - showing what would be synced:"
rsync -av --delete --dry-run "$SOURCE_DIR" "$DEST_DIR"
exit 0
fi
if rsync -av --delete "$SOURCE_DIR" "$DEST_DIR"; then
print_status "✅ Plugin folder synced successfully!"
else
print_error "❌ Sync failed!"
exit 1
fi
echo ""
print_status "Sync complete. Files are now synchronized."
print_status "You can run '$0 --dry-run' to preview changes before syncing."
+40 -70
View File
@@ -1,5 +1,6 @@
#!/usr/bin/env bun
import { parseArgs } from "node:util";
import { translateReadme, SUPPORTED_LANGUAGES } from "./index.ts";
interface CliArgs {
@@ -116,81 +117,50 @@ function printLanguages(): void {
console.log("");
}
function parseArgs(argv: string[]): CliArgs {
const args: CliArgs = {
source: "",
languages: [],
preserveCode: true,
verbose: false,
force: false,
useExisting: false,
help: false,
listLanguages: false,
};
function parseCliArgs(argv: string[]): CliArgs {
try {
const { values, positionals } = parseArgs({
args: argv.slice(2),
allowPositionals: true,
options: {
help: { type: "boolean", short: "h", default: false },
"list-languages": { type: "boolean", default: false },
verbose: { type: "boolean", short: "v", default: false },
force: { type: "boolean", short: "f", default: false },
"use-existing": { type: "boolean", default: false },
"no-preserve-code": { type: "boolean", default: false },
output: { type: "string", short: "o" },
pattern: { type: "string", short: "p" },
model: { type: "string", short: "m" },
"max-budget": { type: "string" },
},
});
const positional: string[] = [];
let i = 2;
while (i < argv.length) {
const arg = argv[i];
switch (arg) {
case "-h":
case "--help":
args.help = true;
break;
case "--list-languages":
args.listLanguages = true;
break;
case "-v":
case "--verbose":
args.verbose = true;
break;
case "-f":
case "--force":
args.force = true;
break;
case "--use-existing":
args.useExisting = true;
break;
case "--no-preserve-code":
args.preserveCode = false;
break;
case "-o":
case "--output":
args.outputDir = argv[++i];
break;
case "-p":
case "--pattern":
args.pattern = argv[++i];
break;
case "-m":
case "--model":
args.model = argv[++i];
break;
case "--max-budget":
args.maxBudget = parseFloat(argv[++i]);
break;
default:
if (arg.startsWith("-")) {
console.error(`Unknown option: ${arg}`);
process.exit(1);
}
positional.push(arg);
}
i++;
return {
source: positionals[0] ?? "",
languages: positionals.slice(1),
outputDir: values.output,
pattern: values.pattern,
preserveCode: !values["no-preserve-code"],
model: values.model,
maxBudget:
values["max-budget"] !== undefined
? parseFloat(values["max-budget"])
: undefined,
verbose: values.verbose,
force: values.force,
useExisting: values["use-existing"],
help: values.help,
listLanguages: values["list-languages"],
};
} catch (error) {
console.error(error instanceof Error ? error.message : String(error));
process.exit(1);
}
if (positional.length > 0) {
args.source = positional[0];
args.languages = positional.slice(1);
}
return args;
}
async function main(): Promise<void> {
const args = parseArgs(process.argv);
const args = parseCliArgs(process.argv);
if (args.help) {
printHelp();
-127
View File
@@ -1,127 +0,0 @@
import { translateReadme, TranslationJobResult, SUPPORTED_LANGUAGES } from "./index.js";
async function translateToCommonLanguages(): Promise<void> {
const result = await translateReadme({
source: "./README.md",
languages: ["es", "fr", "de", "ja", "zh"],
verbose: true,
});
console.log(`Translated to ${result.successful} languages`);
}
async function fullI18nSetup(): Promise<void> {
const result = await translateReadme({
source: "./README.md",
languages: ["es", "fr", "de", "it", "pt", "ja", "ko", "zh", "ru", "ar"],
outputDir: "./docs/i18n",
pattern: "README.{lang}.md",
preserveCode: true,
model: "sonnet",
maxBudgetUsd: 5.0, // Cap spending at $5
verbose: true,
});
for (const r of result.results) {
if (!r.success) {
console.error(`Failed to translate to ${r.language}: ${r.error}`);
}
}
}
async function buildScriptIntegration(): Promise<number> {
try {
const result = await translateReadme({
source: process.env.README_PATH || "./README.md",
languages: (process.env.TRANSLATE_LANGS || "es,fr,de").split(","),
outputDir: process.env.I18N_OUTPUT || "./i18n",
verbose: process.env.CI !== "true", // Quiet in CI
});
return result.failed > 0 ? 1 : 0;
} catch (error) {
console.error("Translation failed:", error);
return 1;
}
}
async function batchTranslation(): Promise<void> {
const readmes = [
"./README.md",
"./packages/core/README.md",
"./packages/cli/README.md",
];
const languages = ["es", "fr", "de"];
for (const readme of readmes) {
console.log(`\nProcessing: ${readme}`);
await translateReadme({
source: readme,
languages,
verbose: true,
});
}
}
async function docsiteSetup(): Promise<void> {
await translateReadme({
source: "./README.md",
languages: ["es", "fr", "de", "ja", "zh"],
outputDir: "./docs",
pattern: "README.{lang}.md",
verbose: true,
});
}
async function cicdTranslation(): Promise<void> {
const isRelease = process.env.GITHUB_REF === "refs/heads/main";
const isManualTrigger = process.env.GITHUB_EVENT_NAME === "workflow_dispatch";
if (!isRelease && !isManualTrigger) {
console.log("Skipping translation - not a release build");
return;
}
const result = await translateReadme({
source: "./README.md",
languages: ["es", "fr", "de", "ja", "ko", "zh", "pt-br"],
outputDir: "./dist/i18n",
maxBudgetUsd: 10.0,
verbose: true,
});
if (process.env.GITHUB_STEP_SUMMARY) {
const summary = `
## Translation Summary
- Successful: ${result.successful}
- Failed: ${result.failed}
- 💰 Cost: $${result.totalCostUsd.toFixed(4)}
`;
console.log(summary);
}
}
const example = process.argv[2];
switch (example) {
case "simple":
translateToCommonLanguages();
break;
case "full":
fullI18nSetup();
break;
case "batch":
batchTranslation();
break;
case "docs":
docsiteSetup();
break;
case "ci":
cicdTranslation();
break;
default:
console.log("Available examples: simple, full, batch, docs, ci");
console.log("\nSupported languages:", SUPPORTED_LANGUAGES.join(", "));
}
-139
View File
@@ -1,139 +0,0 @@
#!/usr/bin/env bun
import Database from 'bun:sqlite';
import { resolve } from 'path';
const DB_PATH = resolve(process.env.HOME!, '.claude-mem/claude-mem.db');
function formatTimestamp(epoch: number): string {
return new Date(epoch).toLocaleString('en-US', {
timeZone: 'America/Los_Angeles',
year: 'numeric',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
}
function main() {
console.log('🔍 Validating timestamp logic for backlog processing...\n');
const db = new Database(DB_PATH);
try {
const pendingStats = db.query(`
SELECT
status,
COUNT(*) as count,
MIN(created_at_epoch) as earliest,
MAX(created_at_epoch) as latest
FROM pending_messages
GROUP BY status
ORDER BY status
`).all();
console.log('Pending Messages Status:\n');
for (const stat of pendingStats) {
console.log(`${stat.status}: ${stat.count} messages`);
if (stat.earliest && stat.latest) {
console.log(` Created: ${formatTimestamp(stat.earliest)} to ${formatTimestamp(stat.latest)}`);
}
}
console.log();
const pendingWithSessions = db.query(`
SELECT
pm.id,
pm.session_db_id,
pm.tool_name,
pm.created_at_epoch as msg_created,
pm.status,
s.memory_session_id,
s.started_at_epoch as session_started,
s.project
FROM pending_messages pm
LEFT JOIN sdk_sessions s ON pm.session_db_id = s.id
WHERE pm.status IN ('pending', 'processing')
ORDER BY pm.created_at_epoch
LIMIT 10
`).all();
if (pendingWithSessions.length === 0) {
console.log('✅ No pending messages - all caught up!\n');
db.close();
return;
}
console.log(`Sample of ${pendingWithSessions.length} pending messages:\n`);
console.log('═══════════════════════════════════════════════════════════════════════');
for (const msg of pendingWithSessions) {
console.log(`\nPending Message #${msg.id}: ${msg.tool_name} (${msg.status})`);
console.log(` Created: ${formatTimestamp(msg.msg_created)}`);
if (msg.session_started) {
console.log(` Session started: ${formatTimestamp(msg.session_started)}`);
console.log(` Project: ${msg.project}`);
const ageDays = Math.round((Date.now() - msg.msg_created) / (1000 * 60 * 60 * 24));
if (msg.msg_created < msg.session_started) {
console.log(` ⚠️ WARNING: Message created BEFORE session! This is impossible.`);
} else if (ageDays > 0) {
console.log(` 📅 Message is ${ageDays} days old`);
console.log(` ✅ Would use original timestamp: ${formatTimestamp(msg.msg_created)}`);
} else {
console.log(` ✅ Recent message, would use original timestamp: ${formatTimestamp(msg.msg_created)}`);
}
} else {
console.log(` ⚠️ No session found for session_db_id ${msg.session_db_id}`);
}
}
console.log('\n═══════════════════════════════════════════════════════════════════════');
console.log('\nTimestamp Logic Validation:\n');
console.log('✅ Code Flow:');
console.log(' 1. SessionManager.yieldNextMessage() tracks earliestPendingTimestamp');
console.log(' 2. ClaudeProvider captures originalTimestamp before processing');
console.log(' 3. processSDKResponse passes originalTimestamp to storeObservation/storeSummary');
console.log(' 4. SessionStore uses overrideTimestampEpoch ?? Date.now()');
console.log(' 5. earliestPendingTimestamp reset after batch completes\n');
console.log('✅ Expected Behavior:');
console.log(' - New messages: get current timestamp');
console.log(' - Backlog messages: get original created_at_epoch');
console.log(' - Observations match their source message timestamps\n');
const stuckMessages = db.query(`
SELECT
session_db_id,
COUNT(*) as count,
MIN(created_at_epoch) as earliest,
MAX(created_at_epoch) as latest
FROM pending_messages
WHERE status = 'processing'
GROUP BY session_db_id
ORDER BY count DESC
`).all();
if (stuckMessages.length > 0) {
console.log('⚠️ Stuck Messages (status=processing):\n');
for (const stuck of stuckMessages) {
const ageDays = Math.round((Date.now() - stuck.earliest) / (1000 * 60 * 60 * 24));
console.log(` Session ${stuck.session_db_id}: ${stuck.count} messages`);
console.log(` Stuck for ${ageDays} days (${formatTimestamp(stuck.earliest)})`);
}
console.log('\n 💡 These will be processed with original timestamps when orphan processing is enabled\n');
}
} catch (error) {
console.error('❌ Error:', error);
process.exit(1);
} finally {
db.close();
}
}
main();
-118
View File
@@ -1,118 +0,0 @@
#!/usr/bin/env bun
import Database from 'bun:sqlite';
import { resolve } from 'path';
const DB_PATH = resolve(process.env.HOME!, '.claude-mem/claude-mem.db');
const BAD_WINDOW_START = 1766623500000;
const BAD_WINDOW_END = 1766626260000;
const ORIGINAL_WINDOW_START = 1765914000000;
const ORIGINAL_WINDOW_END = 1766613600000;
interface Observation {
id: number;
memory_session_id: string;
created_at_epoch: number;
created_at: string;
title: string;
}
function formatTimestamp(epoch: number): string {
return new Date(epoch).toLocaleString('en-US', {
timeZone: 'America/Los_Angeles',
year: 'numeric',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
}
function main() {
console.log('🔍 Verifying timestamp fix...\n');
const db = new Database(DB_PATH);
try {
console.log('Check 1: Looking for observations still in bad window (Dec 24 19:45-20:31)...');
const badWindowObs = db.query<Observation, []>(`
SELECT id, memory_session_id, created_at_epoch, created_at, title
FROM observations
WHERE created_at_epoch >= ${BAD_WINDOW_START}
AND created_at_epoch <= ${BAD_WINDOW_END}
ORDER BY id
`).all();
if (badWindowObs.length === 0) {
console.log('✅ No observations found in bad window - GOOD!\n');
} else {
console.log(`⚠️ Found ${badWindowObs.length} observations still in bad window:\n`);
for (const obs of badWindowObs) {
console.log(` Observation #${obs.id}: ${obs.title || '(no title)'}`);
console.log(` Timestamp: ${formatTimestamp(obs.created_at_epoch)}`);
console.log(` Session: ${obs.memory_session_id}\n`);
}
}
console.log('Check 2: Counting observations in original window (Dec 17-20)...');
const originalWindowObs = db.query<{ count: number }, []>(`
SELECT COUNT(*) as count
FROM observations
WHERE created_at_epoch >= ${ORIGINAL_WINDOW_START}
AND created_at_epoch <= ${ORIGINAL_WINDOW_END}
`).get();
console.log(`Found ${originalWindowObs?.count || 0} observations in Dec 17-20 window`);
console.log('(These should be the corrected observations)\n');
console.log('Check 3: Session distribution of corrected observations...');
const sessionDist = db.query<{ memory_session_id: string; count: number }, []>(`
SELECT memory_session_id, COUNT(*) as count
FROM observations
WHERE created_at_epoch >= ${ORIGINAL_WINDOW_START}
AND created_at_epoch <= ${ORIGINAL_WINDOW_END}
GROUP BY memory_session_id
ORDER BY count DESC
`).all();
if (sessionDist.length > 0) {
console.log(`Observations distributed across ${sessionDist.length} sessions:\n`);
for (const dist of sessionDist.slice(0, 10)) {
console.log(` ${dist.memory_session_id}: ${dist.count} observations`);
}
if (sessionDist.length > 10) {
console.log(` ... and ${sessionDist.length - 10} more sessions`);
}
console.log();
}
console.log('═══════════════════════════════════════════════════════════════════════');
console.log('VERIFICATION SUMMARY:');
console.log('═══════════════════════════════════════════════════════════════════════\n');
if (badWindowObs.length === 0 && (originalWindowObs?.count || 0) > 0) {
console.log('✅ SUCCESS: Timestamp fix appears to be working correctly!');
console.log(` - No observations remain in bad window (Dec 24 19:45-20:31)`);
console.log(` - ${originalWindowObs?.count} observations restored to Dec 17-20`);
console.log('\n💡 Safe to re-enable orphan processing in worker-service.ts\n');
} else if (badWindowObs.length > 0) {
console.log('⚠️ WARNING: Some observations still have incorrect timestamps!');
console.log(` - ${badWindowObs.length} observations still in bad window`);
console.log(' - Run fix-corrupted-timestamps.ts again or investigate manually\n');
} else {
console.log(' No corrupted observations detected');
console.log(' - Either already fixed or corruption never occurred\n');
}
} catch (error) {
console.error('❌ Error:', error);
process.exit(1);
} finally {
db.close();
}
}
main();
-15
View File
@@ -1,15 +0,0 @@
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const os = require('os');
const chromaDir = path.join(os.homedir(), '.claude-mem', 'chroma');
if (fs.existsSync(chromaDir)) {
const before = fs.readdirSync(chromaDir);
console.log(`Wiping ${chromaDir} (${before.length} items)...`);
fs.rmSync(chromaDir, { recursive: true, force: true });
console.log('Done. Chroma will rebuild from SQLite on next worker restart.');
} else {
console.log('Chroma directory does not exist, nothing to wipe.');
}