claude-brain 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/VERSION CHANGED
@@ -1 +1 @@
1
- 0.12.0
1
+ 0.14.0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-brain",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "description": "Local development assistant bridging Obsidian vaults with Claude Code via MCP",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -32,6 +32,30 @@ export async function runInit() {
32
32
  }
33
33
  console.log()
34
34
 
35
+ // Phase 23b: Validate project directory
36
+ const { existsSync: dirExists } = await import('fs')
37
+ const homeDir = process.env.HOME || process.env.USERPROFILE || ''
38
+ if (repoPath === homeDir) {
39
+ console.log(warningText(' This is your home directory — not a project root.'))
40
+ console.log(dimText(' Run this inside a project directory, or pass a path: claude-brain init /path/to/project'))
41
+ console.log()
42
+ return
43
+ }
44
+
45
+ const hasGit = dirExists(path.join(repoPath, '.git'))
46
+ const hasPackageJson = dirExists(path.join(repoPath, 'package.json'))
47
+ const hasCargoToml = dirExists(path.join(repoPath, 'Cargo.toml'))
48
+ const hasPyproject = dirExists(path.join(repoPath, 'pyproject.toml'))
49
+ const hasGoMod = dirExists(path.join(repoPath, 'go.mod'))
50
+ const isProject = hasGit || hasPackageJson || hasCargoToml || hasPyproject || hasGoMod
51
+
52
+ if (!isProject && !force) {
53
+ console.log(warningText(' No project markers found (no .git, package.json, Cargo.toml, etc.)'))
54
+ console.log(dimText(' Use --force to scan anyway, or run inside a project directory.'))
55
+ console.log()
56
+ return
57
+ }
58
+
35
59
  // Scan the repo
36
60
  const context = await withSpinner('Scanning repository', () => scanRepo(repoPath))
37
61
 
@@ -18,6 +18,17 @@ export async function runServe() {
18
18
  // Auto-initialize home directory on first run
19
19
  ensureHomeDirectory()
20
20
 
21
+ // Auto-install Claude Code hooks (idempotent, non-fatal)
22
+ try {
23
+ const { installHooks } = await import('@/hooks/installer')
24
+ const hookResult = installHooks()
25
+ if (hookResult.message !== 'Hooks already installed') {
26
+ console.error(`[claude-brain] ${hookResult.message}`)
27
+ }
28
+ } catch (error) {
29
+ console.error(`[claude-brain] Hook auto-install skipped: ${error instanceof Error ? error.message : 'unknown error'}`)
30
+ }
31
+
21
32
  if (process.env.NODE_ENV !== 'production') {
22
33
  console.error(BANNER)
23
34
  }
@@ -3,7 +3,7 @@ import type { PartialConfig } from './schema'
3
3
  /** Default configuration values for Claude Brain */
4
4
  export const defaultConfig: PartialConfig = {
5
5
  serverName: 'claude-brain',
6
- serverVersion: '0.13.0',
6
+ serverVersion: '0.14.0',
7
7
  logLevel: 'info',
8
8
  logFilePath: './logs/claude-brain.log',
9
9
  dbPath: './data/memory.db',
@@ -284,7 +284,7 @@ export const ConfigSchema = z.object({
284
284
  serverName: z.string().default('claude-brain'),
285
285
 
286
286
  /** Server version in semver format */
287
- serverVersion: z.string().regex(/^\d+\.\d+\.\d+$/, 'Version must be semver format').default('0.13.0'),
287
+ serverVersion: z.string().regex(/^\d+\.\d+\.\d+$/, 'Version must be semver format').default('0.14.0'),
288
288
 
289
289
  /** Logging level */
290
290
  logLevel: LogLevelSchema.default('info'),
@@ -9,13 +9,15 @@ import type { CapturedKnowledge } from './types'
9
9
 
10
10
  export async function handleGitCapture(): Promise<void> {
11
11
  // Parse positional args: project, branch, message, files, port
12
- const [, , , project, branch, message, filesStr, portStr] = process.argv
12
+ const [, , , rawProject, branch, message, filesStr, portStr] = process.argv
13
13
 
14
- if (!project || !message) {
14
+ if (!rawProject || !message) {
15
15
  process.exit(0)
16
16
  return
17
17
  }
18
18
 
19
+ // Phase 23b: Resolve project name — strip scoped package prefix, validate
20
+ const project = resolveProjectName(rawProject)
19
21
  const port = parseInt(portStr || process.env.CLAUDE_BRAIN_PORT || '3000', 10)
20
22
  const files = filesStr ? filesStr.split(',').filter(Boolean) : []
21
23
 
@@ -60,6 +62,19 @@ export async function handleGitCapture(): Promise<void> {
60
62
  process.exit(0)
61
63
  }
62
64
 
65
+ /**
66
+ * Phase 23b: Resolve project name from raw input.
67
+ * Strips npm scoped prefixes (@scope/name → name) and normalizes.
68
+ */
69
+ function resolveProjectName(raw: string): string {
70
+ let name = raw.trim()
71
+ // Strip npm scoped prefix: @scope/name → name
72
+ if (name.startsWith('@') && name.includes('/')) {
73
+ name = name.split('/').pop() || name
74
+ }
75
+ return name || raw
76
+ }
77
+
63
78
  /**
64
79
  * Detect technologies from file extensions.
65
80
  */
@@ -25,7 +25,17 @@ function buildHookScript(): string {
25
25
  ' BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)',
26
26
  ' MESSAGE=$(git log -1 --pretty=%B 2>/dev/null)',
27
27
  ' FILES=$(git diff-tree --no-commit-id --name-only -r HEAD 2>/dev/null | tr "\\n" "," | sed "s/,$//")',
28
- ' PROJECT=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)")',
28
+ ' REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)',
29
+ ' # Phase 23b: Smart project name detection (package.json > init-context > basename)',
30
+ ' if [ -f "$REPO_ROOT/package.json" ]; then',
31
+ ' PROJECT=$(grep -m1 \'"name"\' "$REPO_ROOT/package.json" 2>/dev/null | sed \'s/.*"name"[[:space:]]*:[[:space:]]*"\\([^"]*\\)".*/\\1/\' | sed \'s/@[^/]*\\///\')',
32
+ ' fi',
33
+ ' if [ -z "$PROJECT" ] && [ -f "$REPO_ROOT/.claude-brain/init-context.md" ]; then',
34
+ ' PROJECT=$(head -1 "$REPO_ROOT/.claude-brain/init-context.md" 2>/dev/null | sed \'s/^Project "\\([^"]*\\)".*/\\1/\')',
35
+ ' fi',
36
+ ' if [ -z "$PROJECT" ]; then',
37
+ ' PROJECT=$(basename "$REPO_ROOT")',
38
+ ' fi',
29
39
  ' PORT=${CLAUDE_BRAIN_PORT:-3000}',
30
40
  ' claude-brain git-capture "$PROJECT" "$BRANCH" "$MESSAGE" "$FILES" "$PORT" 2>/dev/null',
31
41
  ') &',
@@ -88,7 +88,12 @@ const PROGRESS_PHRASES = [
88
88
  'built', 'created', 'added', 'fixed', 'resolved',
89
89
  'shipped', 'deployed', 'merged', 'released',
90
90
  "i'm working on", 'currently working', 'making progress',
91
- 'just did', 'just finished'
91
+ 'just did', 'just finished',
92
+ // Phase 23b: present-tense statement patterns
93
+ "i'm implementing", "i'm building", "i'm adding",
94
+ "i'm fixing", "i'm refactoring", "i'm setting up",
95
+ "i'm migrating", "i'm updating", "i'm creating",
96
+ 'started implementing', 'started building', 'started adding'
92
97
  ]
93
98
 
94
99
  // Comparison indicators
@@ -156,10 +161,13 @@ const LIST_PHRASES = [
156
161
  // Update/correct existing memory
157
162
  const UPDATE_PHRASES = [
158
163
  'actually,', 'actually ', 'correction:', 'update that',
159
- 'i changed my mind', 'change that to', 'instead of what i said',
164
+ 'i changed my mind', 'changed my mind', 'change that to', 'instead of what i said',
160
165
  'supersedes', 'override', 'revise that', 'amend that',
161
166
  'that should be', 'update the decision', 'modify that',
162
- 'replace that with', 'no wait', 'scratch that, use'
167
+ 'replace that with', 'no wait', 'scratch that, use',
168
+ // Phase 23b: additional update triggers
169
+ 'switch to', 'instead use', 'use instead', 'no longer using',
170
+ 'moved to', 'migrated to', 'replaced with'
163
171
  ]
164
172
 
165
173
  // Delete/forget memory
@@ -23,6 +23,7 @@ import {
23
23
  getPhase12Service,
24
24
  getKnowledgeGraphService,
25
25
  getEpisodeService,
26
+ getSessionTracker,
26
27
  isServicesInitialized
27
28
  } from '@/server/services'
28
29
  import { timed } from '@/utils/timing'
@@ -251,6 +252,7 @@ export class BrainRouter {
251
252
  return this.servicesNotReady()
252
253
  }
253
254
 
255
+ const effectiveProject = project || DEFAULT_PROJECT
254
256
  const query = entities.topic || message
255
257
  const tiers: TierResults[] = []
256
258
  const hasTemporal = classification?.secondary.includes('exploration') ||
@@ -258,7 +260,7 @@ export class BrainRouter {
258
260
 
259
261
  // Use temporal search if temporal signals detected
260
262
  if (hasTemporal) {
261
- const { results } = await this.searchEngine.temporalSearch(query, { project, limit: 5 })
263
+ const { results } = await this.searchEngine.temporalSearch(query, { project: effectiveProject, limit: 5 })
262
264
  if (results.length > 0) {
263
265
  tiers.push({
264
266
  label: 'Memories',
@@ -273,7 +275,7 @@ export class BrainRouter {
273
275
  } else {
274
276
  // Standard enhanced search
275
277
  const searchResults = await this.searchEngine.enhancedSearch(query, {
276
- project,
278
+ project: effectiveProject,
277
279
  limit: 5,
278
280
  minSimilarity: 0.3
279
281
  })
@@ -291,7 +293,7 @@ export class BrainRouter {
291
293
  }
292
294
 
293
295
  // Also search patterns
294
- const patternResults = await this.searchEngine.searchPatterns(query, { project, limit: 3 })
296
+ const patternResults = await this.searchEngine.searchPatterns(query, { project: effectiveProject, limit: 3 })
295
297
  if (patternResults.length > 0) {
296
298
  tiers.push({
297
299
  label: 'Patterns',
@@ -304,7 +306,7 @@ export class BrainRouter {
304
306
  })
305
307
  }
306
308
 
307
- return this.responseFilter.synthesize(tiers, message, project)
309
+ return this.responseFilter.synthesize(tiers, message, effectiveProject)
308
310
  }
309
311
 
310
312
  /**
@@ -811,6 +813,7 @@ export class BrainRouter {
811
813
  return this.servicesNotReady()
812
814
  }
813
815
 
816
+ const effectiveProject = project || DEFAULT_PROJECT
814
817
  const query = entities.topic || message
815
818
  const tiers: TierResults[] = []
816
819
  const hasTemporal = classification.secondary.includes('exploration') ||
@@ -822,26 +825,26 @@ export class BrainRouter {
822
825
  // Main search — temporal or standard
823
826
  let searchResults: NormalizedResult[]
824
827
  if (hasTemporal) {
825
- const { results } = await this.searchEngine.temporalSearch(query, { project, limit: 5 })
828
+ const { results } = await this.searchEngine.temporalSearch(query, { project: effectiveProject, limit: 5 })
826
829
  searchResults = results
827
830
  } else if (isComplex) {
828
831
  // C7: Try multi-hop chain retrieval
829
- const chainResult = await this.searchEngine.chainSearch(query, { project })
832
+ const chainResult = await this.searchEngine.chainSearch(query, { project: effectiveProject })
830
833
  if (chainResult?.allResults?.length) {
831
834
  searchResults = chainResult.allResults.map((r: any) => ({
832
835
  id: r.id || '',
833
836
  content: r.content || '',
834
837
  score: r.similarity || 0,
835
838
  source: 'decision' as const,
836
- project: r.metadata?.project || project || '',
839
+ project: r.metadata?.project || effectiveProject || '',
837
840
  date: r.metadata?.created_at || '',
838
841
  metadata: r.metadata || {}
839
842
  }))
840
843
  } else {
841
- searchResults = await this.searchEngine.enhancedSearch(query, { project, limit: 5 })
844
+ searchResults = await this.searchEngine.enhancedSearch(query, { project: effectiveProject, limit: 5 })
842
845
  }
843
846
  } else {
844
- searchResults = await this.searchEngine.enhancedSearch(query, { project, limit: 5 })
847
+ searchResults = await this.searchEngine.enhancedSearch(query, { project: effectiveProject, limit: 5 })
845
848
  }
846
849
 
847
850
  if (searchResults.length > 0) {
@@ -858,8 +861,8 @@ export class BrainRouter {
858
861
 
859
862
  // Parallel: patterns + corrections + graph
860
863
  const [patternResults, correctionResults, graphResults] = await Promise.all([
861
- this.searchEngine.searchPatterns(query, { project, limit: 3 }),
862
- this.searchEngine.searchCorrections(query, { project, limit: 3 }),
864
+ this.searchEngine.searchPatterns(query, { project: effectiveProject, limit: 3 }),
865
+ this.searchEngine.searchCorrections(query, { project: effectiveProject, limit: 3 }),
863
866
  // C11: KnowledgeGraph enrichment for all questions
864
867
  this.searchEngine.searchGraph(query, 5)
865
868
  ])
@@ -924,10 +927,33 @@ export class BrainRouter {
924
927
  }
925
928
  }
926
929
 
930
+ // Phase 23b Fix 5: Session recall — "what have we discussed" queries session tracker
931
+ if (this.isSessionRecallQuery(message)) {
932
+ try {
933
+ const tracker = getSessionTracker()
934
+ if (tracker) {
935
+ const stats = tracker.getStats()
936
+ if (stats.totalItems > 0) {
937
+ tiers.push({
938
+ label: 'Current Session',
939
+ results: [{
940
+ content: `Active sessions: ${stats.activeSessions}, items tracked: ${stats.totalItems}`,
941
+ score: 0.95,
942
+ source: 'Session Tracker',
943
+ metadata: {}
944
+ }]
945
+ })
946
+ }
947
+ }
948
+ } catch {
949
+ // Session tracker not available
950
+ }
951
+ }
952
+
927
953
  // Register with episode
928
- this.registerEpisodeMessage(message, project, 'question')
954
+ this.registerEpisodeMessage(message, effectiveProject, 'question')
929
955
 
930
- return this.responseFilter.synthesize(tiers, message, project)
956
+ return this.responseFilter.synthesize(tiers, message, effectiveProject)
931
957
  }
932
958
 
933
959
  /**
@@ -945,6 +971,7 @@ export class BrainRouter {
945
971
  return this.servicesNotReady()
946
972
  }
947
973
 
974
+ const effectiveProject = project || DEFAULT_PROJECT
948
975
  const query = entities.topic || message
949
976
  const lower = message.toLowerCase()
950
977
  const tiers: TierResults[] = []
@@ -952,7 +979,7 @@ export class BrainRouter {
952
979
  // C6: Timeline queries
953
980
  if (lower.includes('timeline') || lower.includes('chronological') || lower.includes('history of')) {
954
981
  const timeline = await this.searchEngine.buildTimeline({
955
- project,
982
+ project: effectiveProject,
956
983
  topic: query,
957
984
  limit: 20
958
985
  })
@@ -972,7 +999,7 @@ export class BrainRouter {
972
999
 
973
1000
  // C6: Evolution queries
974
1001
  if (lower.includes('evolution') || lower.includes('how has') || lower.includes('changed') || lower.includes('evolved')) {
975
- const evolution = await this.searchEngine.analyzeEvolution(query, { project })
1002
+ const evolution = await this.searchEngine.analyzeEvolution(query, { project: effectiveProject })
976
1003
  if (evolution?.timeline?.length) {
977
1004
  const parts = []
978
1005
  parts.push(`**Stability:** ${evolution.stability || 'unknown'}`)
@@ -995,7 +1022,7 @@ export class BrainRouter {
995
1022
 
996
1023
  // C6: Trend queries
997
1024
  if (lower.includes('trend') || lower.includes('emerging') || lower.includes('declining')) {
998
- const trends = await this.searchEngine.detectTrends({ project })
1025
+ const trends = await this.searchEngine.detectTrends({ project: effectiveProject })
999
1026
  if (trends?.topTrends?.length) {
1000
1027
  tiers.push({
1001
1028
  label: 'Trends',
@@ -1048,7 +1075,7 @@ export class BrainRouter {
1048
1075
 
1049
1076
  // Also do a basic memory search as fallback
1050
1077
  const searchResults = await this.searchEngine.enhancedSearch(query, {
1051
- project,
1078
+ project: effectiveProject,
1052
1079
  limit: 5,
1053
1080
  minSimilarity: 0.2
1054
1081
  })
@@ -1064,7 +1091,7 @@ export class BrainRouter {
1064
1091
  })
1065
1092
  }
1066
1093
 
1067
- return this.responseFilter.synthesize(tiers, message, project, 'analyzed')
1094
+ return this.responseFilter.synthesize(tiers, message, effectiveProject, 'analyzed')
1068
1095
  }
1069
1096
 
1070
1097
  private async handleComparison(
@@ -1076,12 +1103,13 @@ export class BrainRouter {
1076
1103
  return this.servicesNotReady()
1077
1104
  }
1078
1105
 
1106
+ const effectiveProject = project || DEFAULT_PROJECT
1079
1107
  const query = entities.topic || message
1080
1108
  const tiers: TierResults[] = []
1081
1109
 
1082
1110
  // Search for related decisions
1083
1111
  const searchResults = await this.searchEngine.enhancedSearch(query, {
1084
- project,
1112
+ project: effectiveProject,
1085
1113
  limit: 5,
1086
1114
  minSimilarity: 0.2
1087
1115
  })
@@ -1111,7 +1139,7 @@ export class BrainRouter {
1111
1139
  })
1112
1140
  }
1113
1141
 
1114
- return this.responseFilter.synthesize(tiers, message, project, 'analyzed')
1142
+ return this.responseFilter.synthesize(tiers, message, effectiveProject, 'analyzed')
1115
1143
  }
1116
1144
 
1117
1145
  // ===== Helpers =====
@@ -1199,6 +1227,30 @@ export class BrainRouter {
1199
1227
  }
1200
1228
  }
1201
1229
 
1230
+ /**
1231
+ * Phase 23b: Detect session recall queries ("what have we discussed", "this session", etc.)
1232
+ */
1233
+ private isSessionRecallQuery(message: string): boolean {
1234
+ const lower = message.toLowerCase()
1235
+ const SESSION_RECALL_PHRASES = [
1236
+ 'what have we discussed',
1237
+ 'what did we discuss',
1238
+ 'what have we talked about',
1239
+ 'what did we talk about',
1240
+ 'this session',
1241
+ 'current session',
1242
+ 'session so far',
1243
+ 'what have we done',
1244
+ 'what did we do',
1245
+ 'session summary',
1246
+ 'summarize this session',
1247
+ 'recap this session',
1248
+ 'what happened this session',
1249
+ 'what have we covered'
1250
+ ]
1251
+ return SESSION_RECALL_PHRASES.some(p => lower.includes(p))
1252
+ }
1253
+
1202
1254
  /**
1203
1255
  * C7: Detect complex multi-part questions
1204
1256
  */
@@ -39,6 +39,17 @@ export class SearchEngine {
39
39
  this.logger = logger.child({ component: 'search-engine' })
40
40
  }
41
41
 
42
+ /**
43
+ * Sort results so same-project entries come first (safety net for cross-project noise).
44
+ */
45
+ private prioritizeProject(results: NormalizedResult[], project?: string): NormalizedResult[] {
46
+ if (!project || results.length === 0) return results
47
+ const sameProject = results.filter(r => r.project === project)
48
+ const otherProject = results.filter(r => r.project !== project)
49
+ if (sameProject.length > 0) return [...sameProject, ...otherProject]
50
+ return results
51
+ }
52
+
42
53
  /**
43
54
  * Enhanced search — uses hybrid retrieval pipeline when available,
44
55
  * falls back to plain searchRaw().
@@ -88,7 +99,7 @@ export class SearchEngine {
88
99
  )
89
100
 
90
101
  if (hybridResults.length > 0) {
91
- const normalized = hybridResults.map((r: any) => ({
102
+ const normalized = this.prioritizeProject(hybridResults.map((r: any) => ({
92
103
  id: r.id || '',
93
104
  content: r.content || r.document || '',
94
105
  score: r.score || r.similarity || 0,
@@ -96,7 +107,7 @@ export class SearchEngine {
96
107
  project: r.metadata?.project || options?.project || '',
97
108
  date: r.metadata?.created_at || '',
98
109
  metadata: r.metadata || {}
99
- }))
110
+ })), options?.project)
100
111
 
101
112
  // Cache results
102
113
  if (cache) {
@@ -135,7 +146,7 @@ export class SearchEngine {
135
146
  SEARCH_TIMEOUT,
136
147
  []
137
148
  )
138
- const normalized = normalizeSearchResults(rawResults)
149
+ const normalized = this.prioritizeProject(normalizeSearchResults(rawResults), options?.project)
139
150
 
140
151
  // Cache results
141
152
  if (cache && cacheKey) {
@@ -7,6 +7,7 @@ import { Hono } from 'hono'
7
7
  import type { Logger } from 'pino'
8
8
  import type { Config } from '@/config'
9
9
  import { getMemoryService, getVaultService, isServicesInitialized } from '@/server/services'
10
+ import { ResourceProvider } from '@/server/providers/resources'
10
11
  import type { MemoryManager } from '@/memory'
11
12
  import type { CapturedKnowledge, HookStats } from '@/hooks/types'
12
13
  import { SmartDeduplicator } from '@/hooks/deduplicator'
@@ -81,6 +82,9 @@ export class HttpApiServer {
81
82
  this.app.post('/api/hooks/ingest', (c) => this.handleHookIngest(c))
82
83
  this.app.post('/api/hooks/session-end', (c) => this.handleHookSessionEnd(c))
83
84
  this.app.get('/api/hooks/status', () => this.handleHookStatus())
85
+
86
+ // Phase 23b: Expose brain://context/auto via HTTP for testability
87
+ this.app.get('/api/context/auto', () => this.handleContextAuto())
84
88
  }
85
89
 
86
90
  private async handleListProjects(): Promise<Response> {
@@ -458,6 +462,23 @@ export class HttpApiServer {
458
462
  }
459
463
  }
460
464
 
465
+ // ─── Phase 23b: Context Auto Endpoint ────────────────────
466
+
467
+ private async handleContextAuto(): Promise<Response> {
468
+ try {
469
+ const resourceProvider = new ResourceProvider(this.logger)
470
+ const result = await resourceProvider.readResource('brain://context/auto')
471
+ const text = result?.contents?.[0]?.text || ''
472
+ return Response.json({ success: true, data: { content: text } })
473
+ } catch (error) {
474
+ this.logger.error({ error }, 'Failed to get auto context')
475
+ return Response.json(
476
+ { success: false, error: 'Failed to get auto context' },
477
+ { status: 500 }
478
+ )
479
+ }
480
+ }
481
+
461
482
  // ─── Phase 17: Hook Endpoints ────────────────────────────
462
483
 
463
484
  private async handleHookIngest(c: any): Promise<Response> {
@@ -564,7 +585,7 @@ export class HttpApiServer {
564
585
  ...this.hookStats,
565
586
  activeSessions: sessionStats?.activeSessions ?? 0,
566
587
  sessionItems: sessionStats?.totalItems ?? 0,
567
- hooksEnabled: this.config.hooks?.enabled ?? false,
588
+ hooksEnabled: this.config.hooks?.enabled ?? true,
568
589
  },
569
590
  })
570
591
  }
@@ -609,6 +630,16 @@ export class HttpApiServer {
609
630
  break
610
631
 
611
632
  case 'progress': {
633
+ // Store in ChromaDB so it's searchable via brain tool
634
+ await memoryService.rememberDecision(
635
+ project,
636
+ `Captured via hook: ${knowledge.metadata.action || knowledge.metadata.source || 'progress'}`,
637
+ knowledge.content,
638
+ `Auto-captured from ${knowledge.source}`,
639
+ { tags: knowledge.technologies }
640
+ )
641
+
642
+ // Also update vault progress if project exists
612
643
  const vaultService = getVaultService()
613
644
  if (vaultService) {
614
645
  try {