diff --git a/.claude/commands/memory/compact.md b/.claude/commands/memory/compact.md index 5b86ee2a..2146bbf2 100644 --- a/.claude/commands/memory/compact.md +++ b/.claude/commands/memory/compact.md @@ -233,6 +233,16 @@ mcp__ccw-tools__core_memory({ }) ``` +Or via CLI (pipe structured text to import): + +```bash +# Write structured text to temp file, then import +echo "$structuredText" | ccw core-memory import + +# Or from a file +ccw core-memory import --file /path/to/session-memory.md +``` + **Response Format**: ```json { @@ -247,41 +257,17 @@ mcp__ccw-tools__core_memory({ After successful import, **clearly display the Recovery ID** to the user: ``` -╔══════════════════════════════════════════════════════════════╗ -║ ✓ Session Memory Saved ║ -║ ║ -║ Recovery ID: CMEM-YYYYMMDD-HHMMSS ║ -║ ║ -║ To restore this session in a new conversation: ║ -║ > Use MCP: core_memory(operation="export", id="") ║ -║ > Or CLI: ccw core-memory export --id ║ -╚══════════════════════════════════════════════════════════════╝ +╔════════════════════════════════════════════════════════════════════════════╗ +║ ✓ Session Memory Saved ║ +║ ║ +║ Recovery ID: CMEM-YYYYMMDD-HHMMSS ║ +║ ║ +║ To restore: "Please import memory " ║ +║ (MCP: core_memory export | CLI: ccw core-memory export --id ) ║ +╚════════════════════════════════════════════════════════════════════════════╝ ``` -## 7. Recovery Usage - -When starting a new session, load previous context using MCP tools: - -```javascript -// List available memories -mcp__ccw-tools__core_memory({ operation: "list" }) - -// Export and read previous session -mcp__ccw-tools__core_memory({ operation: "export", id: "CMEM-20251218-150322" }) - -// Or generate AI summary for quick context -mcp__ccw-tools__core_memory({ operation: "summary", id: "CMEM-20251218-150322" }) -``` - -Or via CLI: - -```bash -ccw core-memory list -ccw core-memory export --id CMEM-20251218-150322 -ccw core-memory summary --id CMEM-20251218-150322 -``` - -## 8. Quality Checklist +## 6. Quality Checklist Before generating: - [ ] Session ID captured if workflow session active (WFS-*) @@ -298,7 +284,7 @@ Before generating: - [ ] Known Issues separates deferred from forgotten bugs - [ ] Notes preserve debugging hypotheses if any -## 9. Path Resolution Rules +## 7. Path Resolution Rules ### Project Root Detection 1. Check current working directory from environment @@ -325,7 +311,7 @@ const toAbsolutePath = (relativePath, projectRoot) => { | Test Files | Corresponding test files for modified code | Medium | | Documentation | `README.md`, `ARCHITECTURE.md` | Low | -## 10. Plan Detection (Priority Order) +## 8. Plan Detection (Priority Order) ### Priority 1: Workflow Session (IMPL_PLAN.md) ```javascript @@ -388,7 +374,7 @@ if (inferredPlan) { } ``` -## 11. Notes +## 9. Notes - **Timing**: Execute at task completion or before context switch - **Frequency**: Once per independent task or milestone diff --git a/ccw/src/cli.ts b/ccw/src/cli.ts index db99bf32..6617f734 100644 --- a/ccw/src/cli.ts +++ b/ccw/src/cli.ts @@ -209,6 +209,13 @@ export function run(argv: string[]): void { .option('--json', 'Output as JSON') .option('--force', 'Skip confirmation') .option('--tool ', 'Tool to use for summary: gemini, qwen', 'gemini') + .option('--auto', 'Run auto-clustering') + .option('--scope ', 'Auto-cluster scope: all, recent, unclustered', 'recent') + .option('--create', 'Create new cluster') + .option('--name ', 'Cluster name') + .option('--members ', 'Cluster member IDs (comma-separated)') + .option('--status ', 'Cluster status filter') + .option('--level ', 'Context level: metadata, keyFiles, full') .action((subcommand, args, options) => coreMemoryCommand(subcommand, args, options)); program.parse(argv); diff --git a/ccw/src/core/core-memory-store.ts b/ccw/src/core/core-memory-store.ts index 0f3011f3..e67c4b3d 100644 --- a/ccw/src/core/core-memory-store.ts +++ b/ccw/src/core/core-memory-store.ts @@ -246,8 +246,8 @@ export class CoreMemoryStore { const minutes = String(now.getMinutes()).padStart(2, '0'); const seconds = String(now.getSeconds()).padStart(2, '0'); const ms = String(now.getMilliseconds()).padStart(3, '0'); - // Add random 2-digit suffix to ensure uniqueness - const random = String(Math.floor(Math.random() * 100)).padStart(2, '0'); + // Add random 4-digit suffix to ensure uniqueness (10000 combinations) + const random = String(Math.floor(Math.random() * 10000)).padStart(4, '0'); return `CLST-${year}${month}${day}-${hours}${minutes}${seconds}${ms}${random}`; } diff --git a/ccw/src/core/session-clustering-service.ts b/ccw/src/core/session-clustering-service.ts index bcd37c6f..f9ee9d78 100644 --- a/ccw/src/core/session-clustering-service.ts +++ b/ccw/src/core/session-clustering-service.ts @@ -17,8 +17,8 @@ const WEIGHTS = { intentAlignment: 0.2, }; -// Clustering threshold -const CLUSTER_THRESHOLD = 0.6; +// Clustering threshold (0.4 = moderate similarity required) +const CLUSTER_THRESHOLD = 0.4; export interface ClusteringOptions { scope?: 'all' | 'recent' | 'unclustered'; @@ -357,40 +357,60 @@ export class SessionClusteringService { * Optimized to prevent duplicate clusters by checking existing clusters first */ async autocluster(options?: ClusteringOptions): Promise { - // 1. Collect only unclustered sessions to prevent re-clustering - const sessions = await this.collectSessions({ ...options, scope: 'unclustered' }); - console.log(`[Clustering] Collected ${sessions.length} unclustered sessions`); + // 1. Collect sessions based on user-specified scope (default: 'recent') + const allSessions = await this.collectSessions(options); + console.log(`[Clustering] Collected ${allSessions.length} sessions (scope: ${options?.scope || 'recent'})`); - // 2. Update metadata cache + // 2. Filter out already-clustered sessions to prevent duplicates + const sessions = allSessions.filter(s => { + const clusters = this.coreMemoryStore.getSessionClusters(s.session_id); + return clusters.length === 0; + }); + console.log(`[Clustering] ${sessions.length} unclustered sessions after filtering`); + + // 3. Update metadata cache for (const session of sessions) { this.coreMemoryStore.upsertSessionMetadata(session); } - // 3. Calculate relevance matrix + // 4. Calculate relevance matrix const n = sessions.length; const relevanceMatrix: number[][] = Array(n).fill(0).map(() => Array(n).fill(0)); + let maxScore = 0; + let avgScore = 0; + let pairCount = 0; + for (let i = 0; i < n; i++) { for (let j = i + 1; j < n; j++) { const score = this.calculateRelevance(sessions[i], sessions[j]); relevanceMatrix[i][j] = score; relevanceMatrix[j][i] = score; + + if (score > maxScore) maxScore = score; + avgScore += score; + pairCount++; } } - // 4. Agglomerative clustering + if (pairCount > 0) { + avgScore = avgScore / pairCount; + console.log(`[Clustering] Relevance stats: max=${maxScore.toFixed(3)}, avg=${avgScore.toFixed(3)}, pairs=${pairCount}, threshold=${CLUSTER_THRESHOLD}`); + } + + // 5. Agglomerative clustering const minClusterSize = options?.minClusterSize || 2; // Early return if not enough sessions if (sessions.length < minClusterSize) { console.log('[Clustering] Not enough unclustered sessions to form new clusters'); - return { clustersCreated: 0, sessionsProcessed: sessions.length, sessionsClustered: 0 }; + return { clustersCreated: 0, sessionsProcessed: allSessions.length, sessionsClustered: 0 }; } const newPotentialClusters = this.agglomerativeClustering(sessions, relevanceMatrix, CLUSTER_THRESHOLD); console.log(`[Clustering] Generated ${newPotentialClusters.length} potential clusters`); - // 5. Process clusters: create new or merge with existing + // 6. Process clusters: create new or merge with existing let clustersCreated = 0; let clustersMerged = 0; let sessionsClustered = 0; @@ -459,11 +479,11 @@ export class SessionClusteringService { } } - console.log(`[Clustering] Summary: ${clustersCreated} created, ${clustersMerged} merged`); + console.log(`[Clustering] Summary: ${clustersCreated} created, ${clustersMerged} merged, ${allSessions.length - sessions.length} already clustered`); return { clustersCreated, - sessionsProcessed: sessions.length, + sessionsProcessed: allSessions.length, sessionsClustered }; }