diff --git a/cmd/tacit/main.go b/cmd/tacit/main.go index c6a6e08..168aedb 100644 --- a/cmd/tacit/main.go +++ b/cmd/tacit/main.go @@ -94,7 +94,7 @@ Usage: tacit status Check daemon status tacit update Update tacit to the latest version tacit list [duration] List knowledge entries (default: 24h) - tacit search Search knowledge entries by pattern + tacit search [--duration ] Search knowledge entries by pattern tacit get ... Print the full content of one or more knowledge entries tacit config view Show current configuration tacit config edit Open configuration in a text editor @@ -832,25 +832,54 @@ func cmdList() { // cmdSearch searches the knowledge base for entries matching a pattern. func cmdSearch() { - if len(os.Args) < 3 { - fmt.Fprintf(os.Stderr, "Usage: tacit search \n") + // Parse args: tacit search [--duration ] + args := os.Args[2:] + var since time.Time + var patternArgs []string + + for i := 0; i < len(args); i++ { + if args[i] == "--duration" && i+1 < len(args) { + d, err := parseDuration(args[i+1]) + if err != nil { + fmt.Fprintf(os.Stderr, "Invalid duration %q: %v\n", args[i+1], err) + fmt.Fprintf(os.Stderr, "Examples: 1h, 30m, 24h, 1d, 7d, 2w\n") + os.Exit(1) + } + since = time.Now().Add(-d) + i++ // skip duration value + } else { + patternArgs = append(patternArgs, args[i]) + } + } + + if len(patternArgs) == 0 { + fmt.Fprintf(os.Stderr, "Usage: tacit search [--duration ] \n") + fmt.Fprintf(os.Stderr, "Examples: tacit search meeting, tacit search --duration 1h meeting\n") os.Exit(1) } - pattern := os.Args[2] + pattern := patternArgs[0] baseDir := config.BaseDir() - results, err := search.Search(baseDir, pattern) + results, err := search.Search(baseDir, pattern, since) if err != nil { log.Fatalf("Search failed: %v", err) } if len(results) == 0 { - fmt.Printf("No results found for %q.\n", pattern) + if !since.IsZero() { + fmt.Printf("No results found for %q in the last %s.\n", pattern, formatDuration(time.Since(since))) + } else { + fmt.Printf("No results found for %q.\n", pattern) + } return } - fmt.Printf("Found %d result(s) for %q:\n\n", len(results), pattern) + if !since.IsZero() { + fmt.Printf("Found %d result(s) for %q in the last %s:\n\n", len(results), pattern, formatDuration(time.Since(since))) + } else { + fmt.Printf("Found %d result(s) for %q:\n\n", len(results), pattern) + } for _, r := range results { fmt.Printf("[%s] %s / %s\n", r.CreatedAt.Format("2006-01-02 15:04:05"), r.Category, r.Title) fmt.Printf(" File: %s\n", r.FilePath) diff --git a/pkg/search/search.go b/pkg/search/search.go index f2a9ad3..4915d81 100644 --- a/pkg/search/search.go +++ b/pkg/search/search.go @@ -15,6 +15,7 @@ import ( "sort" "strings" "sync" + "time" "github.com/sangmin7648/tacit/pkg/storage" ) @@ -82,8 +83,9 @@ func isFrontmatterLine(s string) bool { } // Search searches the knowledge base at baseDir for pattern (case-insensitive). +// If since is non-zero, only entries created after that time are included. // Returns results sorted by relevance score descending. -func Search(baseDir, pattern string) ([]*SearchResult, error) { +func Search(baseDir, pattern string, since time.Time) ([]*SearchResult, error) { rg, err := extractRg() if err != nil { return nil, err @@ -152,6 +154,10 @@ func Search(baseDir, pattern string) ([]*SearchResult, error) { continue } + if !since.IsZero() && !entry.CreatedAt.After(since) { + continue + } + score := len(re.FindAllString(entry.Title, -1)) * 10 score += len(re.FindAllString(entry.Category, -1)) * 5 score += len(re.FindAllString(entry.Summary, -1)) * 3 diff --git a/skills/tacit.knowledge/SKILL.md b/skills/tacit.knowledge/SKILL.md index 777b012..00aed86 100644 --- a/skills/tacit.knowledge/SKILL.md +++ b/skills/tacit.knowledge/SKILL.md @@ -13,9 +13,9 @@ You are retrieving relevant knowledge from multiple sources: the local tacit kno ## Available tacit Commands ``` -tacit list [duration] — List entries created within duration (default: 1h). Supports: 30m, 1h, 24h, 1d, 7d, 2w -tacit search — Full-text search across all entries (title + summary + content) -tacit get — Print full content of a specific entry +tacit list [duration] — List entries created within duration (default: 1h). Supports: 30m, 1h, 24h, 1d, 7d, 2w +tacit search [--duration ] — Full-text search across all entries (title + summary + content). Optional --duration limits to entries created within that window. +tacit get — Print full content of a specific entry ``` ## Process @@ -37,7 +37,7 @@ Launch one sub-agent per source, all in parallel using the Agent tool. Each sub- **tacit sub-agent** — retrieves from local knowledge base: 1. Run `tacit list ` to get recent entries -2. Extract 2–4 keywords from the user's prompt and run `tacit search ` for each +2. Extract 2–4 keywords from the user's prompt and run `tacit search ` for each. If a time window was identified, pass `--duration ` to narrow results (e.g. `tacit search --duration 1h `) 3. Merge all unique file paths, prioritizing entries that appear in both list and search results 4. Fetch full content: `tacit get ...` 5. Return structured results: `{ source: "tacit", items: [{ title, file_path, date, category, summary, content }] }` @@ -70,4 +70,5 @@ Synthesize all retrieved knowledge into a direct answer to the user's prompt: - tacit categories and content are primarily in Korean - Do NOT fabricate knowledge entries — only reference what actually exists - `tacit search` supports regex-compatible patterns — you can use `tacit search "키워드1\|키워드2"` to search multiple terms at once +- `tacit search --duration` accepts the same units as `tacit list`: `30m`, `1h`, `24h`, `1d`, `7d`, `2w` - When launching external source sub-agents, pass the user's original prompt and extracted keywords so each sub-agent can independently determine the best query strategy for its source