Skip to content

Commit 2ce2009

Browse files
authored
Merge pull request #5 from gitmem-dev/feature/multi-client-distribution
feat: multi-client distribution + thread sync fix
2 parents 007347f + 5ef4582 commit 2ce2009

File tree

4 files changed

+38
-14
lines changed

4 files changed

+38
-14
lines changed

src/tools/definitions.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ export const TOOLS = [
170170
},
171171
{
172172
name: "create_learning",
173-
description: "Create scar, win, or pattern entry in institutional memory",
173+
description: "Create scar, win, or pattern entry in institutional memory. Frame as 'what we now know' — lead with the factual/architectural discovery, not what went wrong. Good: 'Fine-grained PATs are scoped to one resource owner'. Bad: 'Should have checked PAT type first'.",
174174
inputSchema: {
175175
type: "object" as const,
176176
properties: {
@@ -181,11 +181,11 @@ export const TOOLS = [
181181
},
182182
title: {
183183
type: "string",
184-
description: "Learning title",
184+
description: "Frame as a knowledge discovery — what we now know. Lead with the factual insight, not self-criticism.",
185185
},
186186
description: {
187187
type: "string",
188-
description: "Detailed description",
188+
description: "Detailed description. Include the architectural/behavioral fact that makes this retrievable by domain.",
189189
},
190190
severity: {
191191
type: "string",
@@ -895,7 +895,7 @@ export const TOOLS = [
895895
},
896896
{
897897
name: "gitmem-cl",
898-
description: "gitmem-cl (create_learning) - Create scar/win/pattern in institutional memory",
898+
description: "gitmem-cl (create_learning) - Create scar/win/pattern. Frame as 'what we now know' — factual discovery, not self-criticism.",
899899
inputSchema: {
900900
type: "object" as const,
901901
properties: {
@@ -1553,7 +1553,7 @@ export const TOOLS = [
15531553
},
15541554
{
15551555
name: "gm-scar",
1556-
description: "gm-scar (create_learning) - Create a scar/win/pattern in institutional memory",
1556+
description: "gm-scar (create_learning) - Create a scar/win/pattern. Frame as 'what we now know' — factual discovery, not self-criticism.",
15571557
inputSchema: {
15581558
type: "object" as const,
15591559
properties: {

src/tools/record-scar-usage-batch.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
import { v4 as uuidv4 } from "uuid";
77
import * as supabase from "../services/supabase-client.js";
88
import { hasSupabase, getTableName } from "../services/tier.js";
9+
import { detectAgent } from "../services/agent-detection.js";
10+
import { getCurrentSession } from "../services/session-state.js";
911
import { Timer, recordMetrics, buildPerformanceData } from "../services/metrics.js";
1012
import type {
1113
RecordScarUsageBatchParams,
@@ -120,6 +122,10 @@ export async function recordScarUsageBatch(
120122

121123
const resolvedScars = await Promise.all(resolutionPromises);
122124

125+
// Auto-detect agent and session as fallbacks for entries missing them
126+
const fallbackAgent = detectAgent().agent || null;
127+
const fallbackSessionId = getCurrentSession()?.sessionId || null;
128+
123129
// Build usage records for all successfully resolved scars
124130
const usageRecords = resolvedScars
125131
.filter(({ scarId }) => scarId !== null)
@@ -130,8 +136,8 @@ export async function recordScarUsageBatch(
130136
scar_id: scarId,
131137
issue_id: entry.issue_id || null,
132138
issue_identifier: entry.issue_identifier || null,
133-
session_id: entry.session_id || null, // Session tracking
134-
agent: entry.agent || null, // Agent identity
139+
session_id: entry.session_id || fallbackSessionId,
140+
agent: entry.agent || fallbackAgent,
135141
surfaced_at: entry.surfaced_at,
136142
acknowledged_at: entry.acknowledged_at || null,
137143
referenced: entry.reference_type !== "none",

src/tools/record-scar-usage.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import { wrapDisplay } from "../services/display-protocol.js";
1212
import * as supabase from "../services/supabase-client.js";
1313
import { hasSupabase } from "../services/tier.js";
1414
import { getStorage } from "../services/storage.js";
15+
import { detectAgent } from "../services/agent-detection.js";
16+
import { getCurrentSession } from "../services/session-state.js";
1517
import {
1618
Timer,
1719
recordMetrics,
@@ -33,13 +35,17 @@ export async function recordScarUsage(
3335
const metricsId = uuidv4();
3436
const usageId = uuidv4();
3537

38+
// Auto-detect agent and session if not provided by caller
39+
const resolvedAgent = params.agent || detectAgent().agent || null;
40+
const resolvedSessionId = params.session_id || getCurrentSession()?.sessionId || null;
41+
3642
const usageData: Record<string, unknown> = {
3743
id: usageId,
3844
scar_id: params.scar_id,
3945
issue_id: params.issue_id || null,
4046
issue_identifier: params.issue_identifier || null,
41-
session_id: params.session_id || null, // Session tracking
42-
agent: params.agent || null, // Agent identity
47+
session_id: resolvedSessionId,
48+
agent: resolvedAgent,
4349
surfaced_at: params.surfaced_at,
4450
acknowledged_at: params.acknowledged_at || null,
4551
referenced: params.reference_type !== "none",

src/tools/session-start.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -732,13 +732,25 @@ function writeSessionFiles(
732732
let merged: ThreadObject[];
733733

734734
if (supabaseAuthoritative) {
735-
// Supabase is source of truth — use its threads, but preserve any local-only threads
736-
// (threads in the file that don't exist in the Supabase set, e.g. created via create_thread
737-
// mid-session but not yet synced to Supabase by session_close).
735+
// Supabase is source of truth — use its threads, but preserve local-only threads
736+
// that were created recently (within 24h) and have a valid ID. These are likely
737+
// mid-session threads not yet synced by session_close. Older local-only threads
738+
// are stale — they were resolved/archived in Supabase but linger in the file
739+
// because the NOT-IN query excludes them, making them look "local-only".
738740
const supabaseIds = new Set(threads.map(t => t.id));
739-
const localOnlyThreads = existingFileThreads.filter(t => !supabaseIds.has(t.id));
741+
const cutoff = Date.now() - 24 * 60 * 60 * 1000;
742+
const localOnlyThreads = existingFileThreads.filter(t => {
743+
if (supabaseIds.has(t.id)) return false; // exists in Supabase active set
744+
if (!t.id) return false; // no ID = malformed, drop
745+
const created = t.created_at ? new Date(t.created_at).getTime() : 0;
746+
return created > cutoff; // only keep if created within last 24h
747+
});
748+
const dropped = existingFileThreads.filter(t => !supabaseIds.has(t.id)).length - localOnlyThreads.length;
740749
if (localOnlyThreads.length > 0) {
741-
console.error(`[session_start] Preserving ${localOnlyThreads.length} local-only threads not yet in Supabase`);
750+
console.error(`[session_start] Preserving ${localOnlyThreads.length} recent local-only threads not yet in Supabase`);
751+
}
752+
if (dropped > 0) {
753+
console.error(`[session_start] Dropped ${dropped} stale local-only threads (resolved/archived in Supabase)`);
742754
}
743755
merged = deduplicateThreadList([...threads, ...localOnlyThreads]);
744756
} else {

0 commit comments

Comments
 (0)