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
11 changes: 11 additions & 0 deletions bin/init-wizard.js
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,17 @@ function buildClaudeHooks() {
],
},
],
UserPromptSubmit: [
{
hooks: [
{
type: "command",
command: `bash ${relScripts}/auto-retrieve-hook.sh`,
timeout: 3000,
},
],
},
],
PreToolUse: [
{
matcher: "Bash",
Expand Down
31 changes: 31 additions & 0 deletions hooks/scripts/auto-retrieve-hook.sh
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,37 @@ PROMPT_LEN=${#PROMPT}

RETRIEVAL_LEVEL=""

# Priority 0: Knowledge-retrieval queries — route through gitmem, don't inject scars
# These are queries where the user wants to ACCESS institutional memory, not just
# have scars passively injected. Output a routing instruction instead of scars.
IS_KNOWLEDGE_QUERY=false

# Explicit recall/remember commands
if echo "$PROMPT_LOWER" | grep -qE '\b(recall|remember)\b.*(doc|process|tree|structure|how|what|where|our)'; then
IS_KNOWLEDGE_QUERY=true
# Process/documentation queries ("what's our process for X", "how do we usually Y")
elif echo "$PROMPT_LOWER" | grep -qE "(what('s| is) our (process|approach|pattern|method)|how do we (usually|typically|normally)|remind me (how|what|about)|what('s| is) the (process|protocol|procedure) for)"; then
IS_KNOWLEDGE_QUERY=true
# Documentation discovery ("show me the docs", "where are the scars")
elif echo "$PROMPT_LOWER" | grep -qE '\b(show me|find|where (is|are|do)).*(doc(s|umentation)?|process(es)?|pattern(s)?|decision(s)?|scar(s)?|learning(s)?)\b'; then
IS_KNOWLEDGE_QUERY=true
# Institutional knowledge queries ("what did we decide about", "what scars exist")
elif echo "$PROMPT_LOWER" | grep -qE '\b(what did we (decide|learn|document)|what scars|what learnings|past decisions about|institutional (memory|knowledge))\b'; then
IS_KNOWLEDGE_QUERY=true
# Direct "recall" as a verb/command at start of prompt
elif echo "$PROMPT_LOWER" | grep -qE '^\s*recall\b'; then
IS_KNOWLEDGE_QUERY=true
fi

if [ "$IS_KNOWLEDGE_QUERY" = "true" ]; then
cat <<HOOKJSON
{
"additionalContext": "KNOWLEDGE RETRIEVAL DETECTED. Route through GitMem FIRST:\n\n1. gitmem search (query relevant to the question) OR gitmem recall (if action-oriented)\n2. gitmem log (if browsing recent learnings)\n3. ONLY THEN fall back to filesystem reads if gitmem doesn't have the answer\n\nDo NOT start with Read/Glob/Grep on local files. Institutional memory exists precisely so we don't re-explore the filesystem every time."
}
HOOKJSON
exit 0
fi

# Priority 1: Trivial — skip retrieval entirely
if echo "$PROMPT_LOWER" | grep -qE '^(yes|no|ok|k|y|n|sure|thanks|thank you|continue|go ahead|proceed|correct|right|exactly|got it|sounds good|lgtm|looks good|done|nope|yep|yup|agreed)$'; then
exit 0
Expand Down
8 changes: 8 additions & 0 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { recordScarUsage } from "./tools/record-scar-usage.js";
import { recordScarUsageBatch } from "./tools/record-scar-usage-batch.js";
import { recall } from "./tools/recall.js";
import { confirmScars } from "./tools/confirm-scars.js";
import { reflectScars } from "./tools/reflect-scars.js";
import { saveTranscript } from "./tools/save-transcript.js";
import { getTranscript } from "./tools/get-transcript.js";
import { searchTranscripts } from "./tools/search-transcripts.js";
Expand Down Expand Up @@ -82,6 +83,7 @@ import type {
SaveTranscriptParams,
GetTranscriptParams,
ConfirmScarsParams,
ReflectScarsParams,
} from "./types/index.js";
import type { RecallParams } from "./tools/recall.js";
import type { SearchParams } from "./tools/search.js";
Expand Down Expand Up @@ -159,6 +161,11 @@ export function createServer(): Server {
case "gm-confirm":
result = await confirmScars(toolArgs as unknown as ConfirmScarsParams);
break;
case "reflect_scars":
case "gitmem-rf":
case "gm-reflect":
result = await reflectScars(toolArgs as unknown as ReflectScarsParams);
break;
case "session_start":
case "gitmem-ss":
case "gm-open":
Expand Down Expand Up @@ -269,6 +276,7 @@ export function createServer(): Server {
const commands = [
{ alias: "gitmem-r", full: "recall", description: "Check scars before taking action" },
{ alias: "gitmem-cs", full: "confirm_scars", description: "Confirm recalled scars (APPLYING/N_A/REFUTED)" },
{ alias: "gitmem-rf", full: "reflect_scars", description: "End-of-session scar reflection (OBEYED/REFUTED)" },
{ alias: "gitmem-ss", full: "session_start", description: "Initialize session with context" },
{ alias: "gitmem-sr", full: "session_refresh", description: "Refresh context for active session" },
{ alias: "gitmem-sc", full: "session_close", description: "Close session with compliance validation" },
Expand Down
2 changes: 2 additions & 0 deletions src/services/metrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export type ToolName =
| "resolve_thread"
| "create_thread"
| "confirm_scars"
| "reflect_scars"
| "cleanup_threads"
| "health";

Expand Down Expand Up @@ -106,6 +107,7 @@ export const PERFORMANCE_TARGETS: Record<ToolName, number> = {
resolve_thread: 100, // In-memory mutation + file write
create_thread: 100, // In-memory mutation + file write
confirm_scars: 500, // In-memory validation + file write
reflect_scars: 500, // In-memory validation + file write
cleanup_threads: 2000, // Fetch all threads + lifecycle computation
health: 100, // In-memory read from EffectTracker
};
Expand Down
36 changes: 34 additions & 2 deletions src/services/session-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
* This allows recall() to always assign variants even without explicit parameters.
*/

import type { SurfacedScar, ScarConfirmation, Observation, SessionChild, ThreadObject } from "../types/index.js";
import type { SurfacedScar, ScarConfirmation, ScarReflection, Observation, SessionChild, ThreadObject } from "../types/index.js";

interface SessionContext {
sessionId: string;
Expand All @@ -22,6 +22,7 @@ interface SessionContext {
startedAt: Date;
surfacedScars: SurfacedScar[]; // Track all scars surfaced during session
confirmations: ScarConfirmation[]; // Refute-or-obey confirmations for recall-surfaced scars
reflections: ScarReflection[]; // End-of-session scar reflections (OBEYED/REFUTED)
observations: Observation[]; // v2 Phase 2: Sub-agent/teammate observations
children: SessionChild[]; // v2 Phase 2: Child agent records
threads: ThreadObject[]; // : Working thread state
Expand All @@ -34,11 +35,12 @@ let currentSession: SessionContext | null = null;
* Set the current active session
* Called by session_start
*/
export function setCurrentSession(context: Omit<SessionContext, 'surfacedScars' | 'confirmations' | 'observations' | 'children' | 'threads'> & { surfacedScars?: SurfacedScar[]; observations?: Observation[]; children?: SessionChild[]; threads?: ThreadObject[] }): void {
export function setCurrentSession(context: Omit<SessionContext, 'surfacedScars' | 'confirmations' | 'reflections' | 'observations' | 'children' | 'threads'> & { surfacedScars?: SurfacedScar[]; observations?: Observation[]; children?: SessionChild[]; threads?: ThreadObject[] }): void {
currentSession = {
...context,
surfacedScars: context.surfacedScars || [],
confirmations: [],
reflections: [],
observations: context.observations || [],
children: context.children || [],
threads: context.threads || [],
Expand Down Expand Up @@ -137,6 +139,36 @@ export function getConfirmations(): ScarConfirmation[] {
return currentSession?.confirmations || [];
}

/**
* Add end-of-session scar reflections (OBEYED/REFUTED) to the current session.
* Called by reflect_scars tool after validation.
*/
export function addReflections(reflections: ScarReflection[]): void {
if (!currentSession) {
console.warn("[session-state] Cannot add reflections: no active session");
return;
}

for (const ref of reflections) {
// Replace existing reflection for same scar_id (allow re-reflection)
const idx = currentSession.reflections.findIndex(r => r.scar_id === ref.scar_id);
if (idx >= 0) {
currentSession.reflections[idx] = ref;
} else {
currentSession.reflections.push(ref);
}
}

console.error(`[session-state] Reflections tracked: ${currentSession.reflections.length} total`);
}

/**
* Get all end-of-session scar reflections for the current session.
*/
export function getReflections(): ScarReflection[] {
return currentSession?.reflections || [];
}

/**
* Check if there are recall-surfaced scars that haven't been confirmed.
* Only checks scars with source "recall" — session_start scars don't require confirmation.
Expand Down
83 changes: 81 additions & 2 deletions src/tools/definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,39 @@ export const TOOLS = [
required: ["confirmations"],
},
},
{
name: "reflect_scars",
description: "End-of-session scar reflection — the closing counterpart to confirm_scars. Mirrors CODA-1's [Scar Reflection] protocol. Call BEFORE session_close to provide evidence of how each surfaced scar was handled. OBEYED: concrete evidence of compliance (min 15 chars). REFUTED: why it didn't apply + what was done instead (min 30 chars). Session close uses reflections to set execution_successful accurately.",
inputSchema: {
type: "object" as const,
properties: {
reflections: {
type: "array",
items: {
type: "object",
properties: {
scar_id: {
type: "string",
description: "UUID of the surfaced scar (from recall or session_start)",
},
outcome: {
type: "string",
enum: ["OBEYED", "REFUTED"],
description: "OBEYED: followed the scar with evidence. REFUTED: scar didn't apply, explain why.",
},
evidence: {
type: "string",
description: "Concrete evidence of compliance (OBEYED, min 15 chars) or explanation of why scar didn't apply (REFUTED, min 30 chars).",
},
},
required: ["scar_id", "outcome", "evidence"],
},
description: "One reflection per surfaced scar.",
},
},
required: ["reflections"],
},
},
{
name: "session_start",
description: "Initialize session, detect agent, load institutional context (last session, recent decisions, open threads). Scars surface on-demand via recall(). DISPLAY: The result includes a pre-formatted 'display' field visible in the tool result. Output the display field verbatim as your response — tool results are collapsed in the CLI.",
Expand Down Expand Up @@ -812,6 +845,29 @@ export const TOOLS = [
required: ["confirmations"],
},
},
{
name: "gitmem-rf",
description: "gitmem-rf (reflect_scars) - End-of-session scar reflection (OBEYED/REFUTED with evidence)",
inputSchema: {
type: "object" as const,
properties: {
reflections: {
type: "array",
items: {
type: "object",
properties: {
scar_id: { type: "string", description: "UUID of the surfaced scar" },
outcome: { type: "string", enum: ["OBEYED", "REFUTED"], description: "Reflection outcome" },
evidence: { type: "string", description: "Evidence (OBEYED min 15 chars, REFUTED min 30 chars)" },
},
required: ["scar_id", "outcome", "evidence"],
},
description: "One reflection per surfaced scar",
},
},
required: ["reflections"],
},
},
{
name: "gitmem-ss",
description: "gitmem-ss (session_start) - Initialize session with institutional context. DISPLAY: The result includes a pre-formatted 'display' field. Output the display field verbatim as your response — tool results are collapsed in the CLI.",
Expand Down Expand Up @@ -1510,6 +1566,29 @@ export const TOOLS = [
required: ["confirmations"],
},
},
{
name: "gm-reflect",
description: "gm-reflect (reflect_scars) - End-of-session scar reflection",
inputSchema: {
type: "object" as const,
properties: {
reflections: {
type: "array",
items: {
type: "object",
properties: {
scar_id: { type: "string", description: "UUID of the surfaced scar" },
outcome: { type: "string", enum: ["OBEYED", "REFUTED"] },
evidence: { type: "string", description: "Evidence of compliance or refutation" },
},
required: ["scar_id", "outcome", "evidence"],
},
description: "One reflection per surfaced scar",
},
},
required: ["reflections"],
},
},
{
name: "gm-refresh",
description: "gm-refresh (session_refresh) - Refresh context for the active session without creating a new one. DISPLAY: The result includes a pre-formatted 'display' field. Output the display field verbatim as your response — tool results are collapsed in the CLI.",
Expand Down Expand Up @@ -2196,15 +2275,15 @@ export const TOOLS = [
*/
export const ALIAS_TOOL_NAMES = new Set([
// gitmem-* aliases
"gitmem-r", "gitmem-cs", "gitmem-ss", "gitmem-sr", "gitmem-sc",
"gitmem-r", "gitmem-cs", "gitmem-rf", "gitmem-ss", "gitmem-sr", "gitmem-sc",
"gitmem-cl", "gitmem-cd", "gitmem-rs", "gitmem-rsb",
"gitmem-st", "gitmem-gt", "gitmem-stx",
"gitmem-search", "gitmem-log", "gitmem-analyze",
"gitmem-pc", "gitmem-ao",
"gitmem-lt", "gitmem-rt", "gitmem-ct", "gitmem-ps", "gitmem-ds",
"gitmem-cleanup", "gitmem-health", "gitmem-al", "gitmem-graph",
// gm-* aliases
"gm-open", "gm-confirm", "gm-refresh", "gm-close",
"gm-open", "gm-confirm", "gm-reflect", "gm-refresh", "gm-close",
"gm-scar", "gm-search", "gm-log", "gm-analyze",
"gm-pc", "gm-absorb",
"gm-threads", "gm-resolve", "gm-thread-new", "gm-promote", "gm-dismiss",
Expand Down
Loading