Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions src/tools/definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ export const TOOLS = [
},
{
name: "create_learning",
description: "Create scar, win, or pattern entry in institutional memory",
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'.",
inputSchema: {
type: "object" as const,
properties: {
Expand All @@ -181,11 +181,11 @@ export const TOOLS = [
},
title: {
type: "string",
description: "Learning title",
description: "Frame as a knowledge discovery — what we now know. Lead with the factual insight, not self-criticism.",
},
description: {
type: "string",
description: "Detailed description",
description: "Detailed description. Include the architectural/behavioral fact that makes this retrievable by domain.",
},
severity: {
type: "string",
Expand Down Expand Up @@ -895,7 +895,7 @@ export const TOOLS = [
},
{
name: "gitmem-cl",
description: "gitmem-cl (create_learning) - Create scar/win/pattern in institutional memory",
description: "gitmem-cl (create_learning) - Create scar/win/pattern. Frame as 'what we now know' — factual discovery, not self-criticism.",
inputSchema: {
type: "object" as const,
properties: {
Expand Down Expand Up @@ -1553,7 +1553,7 @@ export const TOOLS = [
},
{
name: "gm-scar",
description: "gm-scar (create_learning) - Create a scar/win/pattern in institutional memory",
description: "gm-scar (create_learning) - Create a scar/win/pattern. Frame as 'what we now know' — factual discovery, not self-criticism.",
inputSchema: {
type: "object" as const,
properties: {
Expand Down
10 changes: 8 additions & 2 deletions src/tools/record-scar-usage-batch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import { v4 as uuidv4 } from "uuid";
import * as supabase from "../services/supabase-client.js";
import { hasSupabase, getTableName } from "../services/tier.js";
import { detectAgent } from "../services/agent-detection.js";
import { getCurrentSession } from "../services/session-state.js";
import { Timer, recordMetrics, buildPerformanceData } from "../services/metrics.js";
import type {
RecordScarUsageBatchParams,
Expand Down Expand Up @@ -120,6 +122,10 @@ export async function recordScarUsageBatch(

const resolvedScars = await Promise.all(resolutionPromises);

// Auto-detect agent and session as fallbacks for entries missing them
const fallbackAgent = detectAgent().agent || null;
const fallbackSessionId = getCurrentSession()?.sessionId || null;

// Build usage records for all successfully resolved scars
const usageRecords = resolvedScars
.filter(({ scarId }) => scarId !== null)
Expand All @@ -130,8 +136,8 @@ export async function recordScarUsageBatch(
scar_id: scarId,
issue_id: entry.issue_id || null,
issue_identifier: entry.issue_identifier || null,
session_id: entry.session_id || null, // Session tracking
agent: entry.agent || null, // Agent identity
session_id: entry.session_id || fallbackSessionId,
agent: entry.agent || fallbackAgent,
surfaced_at: entry.surfaced_at,
acknowledged_at: entry.acknowledged_at || null,
referenced: entry.reference_type !== "none",
Expand Down
10 changes: 8 additions & 2 deletions src/tools/record-scar-usage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import { wrapDisplay } from "../services/display-protocol.js";
import * as supabase from "../services/supabase-client.js";
import { hasSupabase } from "../services/tier.js";
import { getStorage } from "../services/storage.js";
import { detectAgent } from "../services/agent-detection.js";
import { getCurrentSession } from "../services/session-state.js";
import {
Timer,
recordMetrics,
Expand All @@ -33,13 +35,17 @@ export async function recordScarUsage(
const metricsId = uuidv4();
const usageId = uuidv4();

// Auto-detect agent and session if not provided by caller
const resolvedAgent = params.agent || detectAgent().agent || null;
const resolvedSessionId = params.session_id || getCurrentSession()?.sessionId || null;

const usageData: Record<string, unknown> = {
id: usageId,
scar_id: params.scar_id,
issue_id: params.issue_id || null,
issue_identifier: params.issue_identifier || null,
session_id: params.session_id || null, // Session tracking
agent: params.agent || null, // Agent identity
session_id: resolvedSessionId,
agent: resolvedAgent,
surfaced_at: params.surfaced_at,
acknowledged_at: params.acknowledged_at || null,
referenced: params.reference_type !== "none",
Expand Down
22 changes: 17 additions & 5 deletions src/tools/session-start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -732,13 +732,25 @@ function writeSessionFiles(
let merged: ThreadObject[];

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