dude-claude-plugin 2026.2.19 → 2026.2.21

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dude",
3
- "version": "2026.2.19",
3
+ "version": "2026.2.21",
4
4
  "description": "Ultra-minimal RAG and cross-project memory for Claude CLI",
5
5
  "author": {
6
6
  "name": "Fingerskier"
@@ -1,32 +1,43 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
+
3
+ process.on('uncaughtException', (err) => {
4
+ console.error(`[dude] Fatal error: ${err.message}`);
5
+ process.exit(1);
6
+ });
7
+
8
+ process.on('unhandledRejection', (err) => {
9
+ console.error(`[dude] Unhandled rejection: ${err?.message || err}`);
10
+ process.exit(1);
11
+ });
2
12
 
3
13
  const command = process.argv[2] || 'mcp';
4
14
 
5
- switch (command) {
6
- case 'mcp': {
7
- const { startServer } = await import('../src/server.js');
8
- await startServer();
9
- break;
10
- }
11
- case 'serve': {
12
- const { startWebServer } = await import('../src/web.js');
13
- await startWebServer();
14
- break;
15
- }
16
- case 'auto-retrieve': {
17
- await import('../hooks/auto-retrieve.js');
18
- break;
19
- }
20
- case 'auto-persist': {
21
- await import('../hooks/auto-persist.js');
22
- break;
23
- }
24
- case 'auto-persist-plan': {
25
- await import('../hooks/auto-persist-plan.js');
26
- break;
27
- }
28
- default:
29
- console.error(`Usage: dude-claude [mcp|serve|auto-retrieve|auto-persist|auto-persist-plan]
15
+ try {
16
+ switch (command) {
17
+ case 'mcp': {
18
+ const { startServer } = await import('../src/server.js');
19
+ await startServer();
20
+ break;
21
+ }
22
+ case 'serve': {
23
+ const { startWebServer } = await import('../src/web.js');
24
+ await startWebServer();
25
+ break;
26
+ }
27
+ case 'auto-retrieve': {
28
+ await import('../hooks/auto-retrieve.js');
29
+ break;
30
+ }
31
+ case 'auto-persist': {
32
+ await import('../hooks/auto-persist.js');
33
+ break;
34
+ }
35
+ case 'auto-persist-plan': {
36
+ await import('../hooks/auto-persist-plan.js');
37
+ break;
38
+ }
39
+ default:
40
+ console.error(`Usage: dude-claude [mcp|serve|auto-retrieve|auto-persist|auto-persist-plan]
30
41
 
31
42
  Commands:
32
43
  mcp Start the MCP stdio server (default)
@@ -34,5 +45,9 @@ Commands:
34
45
  auto-retrieve Run the auto-retrieve hook (reads prompt from stdin)
35
46
  auto-persist Run the auto-persist utility (reads classification JSON from stdin)
36
47
  auto-persist-plan Run the auto-persist-plan utility (reads classification JSON from stdin)`);
37
- process.exit(1);
48
+ process.exit(1);
49
+ }
50
+ } catch (err) {
51
+ console.error(`[dude] Startup failed: ${err.message}`);
52
+ process.exit(1);
38
53
  }
@@ -10,6 +10,9 @@ import { embed } from '../src/embed.js';
10
10
  import { initDb } from '../src/db.js';
11
11
 
12
12
  try {
13
+ // Start DB init early — runs in parallel with stdin reading
14
+ const dbPromise = initDb();
15
+
13
16
  const chunks = [];
14
17
  for await (const chunk of process.stdin) {
15
18
  chunks.push(chunk);
@@ -34,11 +37,15 @@ try {
34
37
  const body = input.body || '';
35
38
  const status = input.status || 'open';
36
39
 
37
- const db = await initDb();
40
+ const db = await dbPromise;
38
41
  const text = `${title} ${body}`.trim();
39
- const embedding = await embed(text);
40
42
 
41
- const project = await db.getCurrentProject();
43
+ // Parallelize embedding + project lookup
44
+ const [embedding, project] = await Promise.all([
45
+ embed(text),
46
+ db.getCurrentProject(),
47
+ ]);
48
+
42
49
  const record = await db.upsert(
43
50
  {
44
51
  projectId: project.id,
@@ -10,6 +10,9 @@ import { embed } from '../src/embed.js';
10
10
  import { initDb } from '../src/db.js';
11
11
 
12
12
  try {
13
+ // Start DB init early — runs in parallel with stdin reading
14
+ const dbPromise = initDb();
15
+
13
16
  const chunks = [];
14
17
  for await (const chunk of process.stdin) {
15
18
  chunks.push(chunk);
@@ -34,11 +37,15 @@ try {
34
37
  const body = input.body || '';
35
38
  const status = input.status || 'open';
36
39
 
37
- const db = await initDb();
40
+ const db = await dbPromise;
38
41
  const text = `${title} ${body}`.trim();
39
- const embedding = await embed(text);
40
42
 
41
- const project = await db.getCurrentProject();
43
+ // Parallelize embedding + project lookup
44
+ const [embedding, project] = await Promise.all([
45
+ embed(text),
46
+ db.getCurrentProject(),
47
+ ]);
48
+
42
49
  const record = await db.upsert(
43
50
  {
44
51
  projectId: project.id,
@@ -10,6 +10,9 @@ import { embed } from '../src/embed.js';
10
10
  import { initDb } from '../src/db.js';
11
11
 
12
12
  try {
13
+ // Start DB init early — runs in parallel with stdin reading
14
+ const dbPromise = initDb();
15
+
13
16
  const chunks = [];
14
17
  for await (const chunk of process.stdin) {
15
18
  chunks.push(chunk);
@@ -21,8 +24,13 @@ try {
21
24
  process.exit(0);
22
25
  }
23
26
 
24
- const db = await initDb();
25
- const project = await db.getCurrentProject();
27
+ const db = await dbPromise;
28
+
29
+ // Parallelize embedding + project lookup
30
+ const [embedding, project] = await Promise.all([
31
+ embed(prompt),
32
+ db.getCurrentProject(),
33
+ ]);
26
34
 
27
35
  // 1) Project identification
28
36
  process.stdout.write(`[dude] Project: ${project.name} (id=${project.id})\n`);
@@ -39,7 +47,6 @@ try {
39
47
  }
40
48
 
41
49
  // 3) Semantic search
42
- const embedding = await embed(prompt);
43
50
  const limit = Number(process.env.DUDE_CONTEXT_LIMIT) || 5;
44
51
  const results = await db.search(embedding, { limit });
45
52
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dude-claude-plugin",
3
- "version": "2026.2.19",
3
+ "version": "2026.2.21",
4
4
  "description": "Ultra-minimal RAG and cross-project memory for Claude CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -10,74 +10,73 @@ Track bugs, tasks, and blockers via the `dude:` MCP tools.
10
10
  ## Quick Start
11
11
 
12
12
  ```
13
- dude:list_issues { "projectUuid": "..." } - List project issues
14
- dude:create_issue { "project_uuid": "...", "text": "BUG: ..." }
15
- dude:search { "entityTypes": ["issue"] } - Find issues
13
+ dude:list_records { "kind": "issue" } - List all issues
14
+ dude:upsert_record { "kind": "issue", "title": "BUG: ..." } - Create issue
15
+ dude:search { "query": "...", "kind": "issue" } - Find issues
16
16
  ```
17
17
 
18
18
  ## Issue Operations
19
19
 
20
20
  ### Listing Issues
21
- | Tool | Description |
22
- |------|-------------|
23
- | `dude:list_issues` | List issues for a project |
21
+ ```
22
+ dude:list_records { "kind": "issue" }
23
+ dude:list_records { "kind": "issue", "status": "open" }
24
+ dude:list_records { "kind": "issue", "project": "my-org/my-repo" }
25
+ ```
24
26
 
25
27
  **Parameters:**
26
- - `projectUuid` (required): Project UUID
27
- - `parentUuid` (optional): Filter to children of parent issue
28
+ - `kind` set to `"issue"` to filter to issues only
29
+ - `status` (optional) `"open"`, `"resolved"`, `"archived"`, or `"all"`
30
+ - `project` (optional) — project name, or `"*"` for all projects
28
31
 
29
32
  ### Getting Issue Details
30
- | Tool | Description |
31
- |------|-------------|
32
- | `dude:get_issue` | Get single issue details |
33
+ ```
34
+ dude:get_record { "id": 42 }
35
+ ```
33
36
 
34
37
  **Parameters:**
35
- - `uuid` (required): Issue UUID
38
+ - `id` (required): Record ID (integer)
36
39
 
37
40
  ### Creating Issues
38
- | Tool | Description |
39
- |------|-------------|
40
- | `dude:create_issue` | Create new issue |
41
+ ```
42
+ dude:upsert_record {
43
+ "kind": "issue",
44
+ "title": "BUG: Load cell readings drift after 2 hours",
45
+ "body": "Detailed description of the problem..."
46
+ }
47
+ ```
41
48
 
42
49
  **Parameters:**
43
- - `project_uuid` (required): Project UUID
44
- - `text` (required): Issue description
45
- - `parent_issue_uuid` (optional): Parent issue for nesting
50
+ - `kind` (required): `"issue"`
51
+ - `title` (required): Short summary (use prefixes below)
52
+ - `body` (optional): Full description
53
+ - `status` (optional): Defaults to `"open"`
46
54
 
47
- **Examples:**
55
+ ### Updating Issues
56
+ Provide the `id` of an existing record to update it:
48
57
  ```
49
- dude:create_issue {
50
- "project_uuid": "...",
51
- "text": "BUG: Load cell readings drift after 2 hours"
52
- }
53
-
54
- dude:create_issue {
55
- "project_uuid": "...",
56
- "text": "TASK: Implement user authentication",
57
- "parent_issue_uuid": "parent-task-uuid"
58
+ dude:upsert_record {
59
+ "id": 42,
60
+ "kind": "issue",
61
+ "title": "BUG: Load cell readings drift after 2 hours - found root cause",
62
+ "status": "resolved"
58
63
  }
59
64
  ```
60
65
 
61
- ### Updating Issues
62
- | Tool | Description |
63
- |------|-------------|
64
- | `dude:update_issue` | Update existing issue |
65
-
66
- **Parameters:**
67
- - `uuid` (required): Issue UUID
68
- - `text` (optional): New description
69
- - `parent_issue_uuid` (optional, nullable): New parent (null for top-level)
70
- - `complete` (optional): Set completion status (1 = complete, 0 = incomplete)
71
-
72
66
  ### Completing Issues
73
- To mark an issue as complete:
67
+ Set status to `"resolved"`:
68
+ ```
69
+ dude:upsert_record { "id": 42, "kind": "issue", "title": "...", "status": "resolved" }
74
70
  ```
75
- dude:update_issue { "uuid": "...", "complete": 1 }
71
+
72
+ To reopen:
73
+ ```
74
+ dude:upsert_record { "id": 42, "kind": "issue", "title": "...", "status": "open" }
76
75
  ```
77
76
 
78
- To reopen an issue:
77
+ ### Deleting Issues
79
78
  ```
80
- dude:update_issue { "uuid": "...", "complete": 0 }
79
+ dude:delete_record { "id": 42 }
81
80
  ```
82
81
 
83
82
  ## Search for Issues
@@ -86,25 +85,17 @@ dude:update_issue { "uuid": "...", "complete": 0 }
86
85
  ```
87
86
  dude:search {
88
87
  "query": "memory leak in worker thread",
89
- "entityTypes": ["issue"],
90
- "projectUuid": "optional-project-uuid"
88
+ "kind": "issue",
89
+ "project": "my-org/my-repo",
90
+ "limit": 10
91
91
  }
92
92
  ```
93
93
 
94
94
  **Parameters:**
95
95
  - `query` (required): Natural language search query
96
- - `limit` (optional): Max results (default: 10)
97
- - `threshold` (optional): Min similarity 0-1 (default: 0.3)
98
- - `entityTypes` (optional): Filter to `["issue"]`
99
- - `projectUuid` (optional): Scope to specific project
100
-
101
- ### Keyword Search
102
- ```
103
- dude:search_text { "query": "BUG" }
104
- ```
105
-
106
- **Parameters:**
107
- - `query` (required): Text to search for
96
+ - `kind` (optional): Set to `"issue"` to filter results
97
+ - `project` (optional): Project name to boost, or `"*"` for equal weight
98
+ - `limit` (optional): Max results (default 5)
108
99
 
109
100
  ## Issue Conventions
110
101
 
@@ -116,7 +107,5 @@ Use prefixes to categorize issues:
116
107
 
117
108
  ## Related Skills
118
109
 
119
- - **dude:projects**: Manage projects and get full project context
120
- - **dude:specifications**: Document requirements and architecture
121
-
122
- **Tip:** Use `dude:get_project_context` (from dude:projects) to see all issues for a project at once.
110
+ - **dude:projects** List and explore projects
111
+ - **dude:specifications** Document requirements and architecture
@@ -1,115 +1,75 @@
1
1
  ---
2
2
  name: projects
3
- description: "Manage development projects using the dude MCP server. List, create, update projects. Get full project context with issues and specifications. Search for projects. Use when working with project organization, project hierarchies, starting work on a codebase, or needing project-level context."
3
+ description: "Manage development projects using the dude MCP server. List projects, view records by project, search across projects. Projects are auto-detected from git no manual creation needed. Use when exploring project organization, starting work on a codebase, or needing project-level context."
4
4
  ---
5
5
 
6
6
  # Dude Projects - Project Management
7
7
 
8
- Manage development projects via the `dude:` MCP tools.
8
+ Explore development projects via the `dude:` MCP tools.
9
+
10
+ Projects are **auto-detected** from the git repository (remote origin or directory name). There is no need to manually create projects — they are created automatically when records are added.
9
11
 
10
12
  ## Quick Start
11
13
 
12
14
  ```
13
- dude:list_projects - List all projects
14
- dude:get_project_context - Full project with issues/specs
15
- dude:search { "entityTypes": ["project"] } - Find projects
15
+ dude:list_projects - List all known projects
16
+ dude:list_records { "project": "org/repo" } - Records for a project
17
+ dude:search { "query": "...", "project": "org/repo" } - Search with project boost
16
18
  ```
17
19
 
18
20
  ## Project Operations
19
21
 
20
22
  ### Listing Projects
21
- | Tool | Description |
22
- |------|-------------|
23
- | `dude:list_projects` | List all projects or filter by parent |
24
-
25
- **Parameters:**
26
- - `parentUuid` (optional): Filter to children of parent project
27
-
28
- ### Getting Project Details
29
- | Tool | Description |
30
- |------|-------------|
31
- | `dude:get_project` | Get single project details |
32
- | `dude:get_project_context` | Get project with ALL issues and specs |
33
-
34
- **get_project Parameters:**
35
- - `uuid` (required): Project UUID
36
-
37
- **get_project_context Parameters:**
38
- - `uuid` (required): Project UUID
39
- - `includeSubprojects` (optional): Include child projects (default: false)
40
-
41
- ### Creating Projects
42
- | Tool | Description |
43
- |------|-------------|
44
- | `dude:create_project` | Create new project |
45
-
46
- **Parameters:**
47
- - `name` (required): Project name
48
- - `directory` (optional): Project directory path
49
- - `parent_project_uuid` (optional): Parent project for nesting
50
-
51
- ### Updating Projects
52
- | Tool | Description |
53
- |------|-------------|
54
- | `dude:update_project` | Update existing project |
55
-
56
- **Parameters:**
57
- - `uuid` (required): Project UUID
58
- - `name` (optional): New name
59
- - `directory` (optional): New directory path
60
- - `parent_project_uuid` (optional, nullable): New parent (null for top-level)
61
- - `active` (optional): Set active status (1 = active, 0 = inactive)
62
-
63
- ### Archiving Projects
64
- To archive a project (soft delete), set the `active` flag to 0:
65
23
  ```
66
- dude:update_project { "uuid": "...", "active": 0 }
24
+ dude:list_projects
67
25
  ```
68
26
 
69
- To reactivate:
27
+ Returns all known projects with their IDs, names, and timestamps.
28
+
29
+ ### Viewing Project Records
70
30
  ```
71
- dude:update_project { "uuid": "...", "active": 1 }
31
+ dude:list_records { "project": "my-org/my-repo" }
32
+ dude:list_records { "project": "my-org/my-repo", "kind": "issue", "status": "open" }
33
+ dude:list_records { "project": "*" }
72
34
  ```
73
35
 
74
- ## Search for Projects
36
+ **Parameters:**
37
+ - `project` — project name (e.g. `"fingerskier/dude-claude-plugin"`), or `"*"` for all projects
38
+ - `kind` (optional) — `"issue"`, `"spec"`, `"arch"`, `"update"`, `"test"`, or `"all"`
39
+ - `status` (optional) — `"open"`, `"resolved"`, `"archived"`, `"active"`, `"inactive"`, or `"all"`
75
40
 
76
- ### Semantic Search
41
+ ### Searching Within a Project
77
42
  ```
78
43
  dude:search {
79
- "query": "authentication service",
80
- "entityTypes": ["project"],
81
- "limit": 5
44
+ "query": "authentication flow",
45
+ "project": "my-org/my-repo",
46
+ "limit": 10
82
47
  }
83
48
  ```
84
49
 
85
- **Parameters:**
86
- - `query` (required): Natural language search query
87
- - `limit` (optional): Max results (default: 10)
88
- - `threshold` (optional): Min similarity 0-1 (default: 0.3)
89
- - `entityTypes` (optional): Filter to `["project"]`
90
- - `projectUuid` (optional): Scope to specific project
50
+ The `project` parameter boosts results from that project in similarity ranking.
91
51
 
92
- ### Keyword Search
93
- ```
94
- dude:search_text { "query": "auth" }
95
- ```
52
+ ## How Projects Work
96
53
 
97
- **Parameters:**
98
- - `query` (required): Text to search for
54
+ - **Auto-detection**: When the MCP server starts, it detects the project from `git remote get-url origin` (falls back to the directory basename)
55
+ - **Format**: Projects are named like `org/repo` (e.g. `fingerskier/dude-claude-plugin`)
56
+ - **Current project**: Records are automatically associated with the current project when created
57
+ - **Cross-project**: Use `project: "*"` to list/search across all projects
99
58
 
100
59
  ## Common Workflows
101
60
 
102
61
  ### Starting Work on a Codebase
103
- 1. `dude:list_projects` - Find the project UUID
104
- 2. `dude:get_project_context` - Load full context
105
- 3. Begin coding with awareness of existing issues/specs
62
+ 1. `dude:list_projects` See what's tracked
63
+ 2. `dude:list_records { "kind": "issue", "status": "open" }` Open issues for current project
64
+ 3. `dude:list_records { "kind": "spec" }` Existing specifications
106
65
 
107
- ### Organizing Projects
66
+ ### Exploring Across Projects
108
67
  ```
109
- dude:create_project { "name": "Frontend", "parent_project_uuid": "parent-uuid" }
68
+ dude:list_records { "project": "*", "kind": "arch" }
69
+ dude:search { "query": "database migration", "project": "*" }
110
70
  ```
111
71
 
112
72
  ## Related Skills
113
73
 
114
- - **dude:issues**: Create and manage issues within projects
115
- - **dude:specifications**: Create and manage specifications within projects
74
+ - **dude:issues** Create and manage issues within projects
75
+ - **dude:specifications** Create and manage specifications within projects
@@ -7,76 +7,80 @@ description: "Document specifications using the dude MCP server. List, create, u
7
7
 
8
8
  Document requirements and architecture via the `dude:` MCP tools.
9
9
 
10
+ Specs and architecture decisions are stored as records with kind `"spec"` or `"arch"`.
11
+
10
12
  ## Quick Start
11
13
 
12
14
  ```
13
- dude:list_specifications { "projectUuid": "..." } - List specs
14
- dude:create_specification { "project_uuid": "...", "text": "..." }
15
- dude:search { "entityTypes": ["specification"] } - Find specs
15
+ dude:list_records { "kind": "spec" } - List specifications
16
+ dude:list_records { "kind": "arch" } - List architecture decisions
17
+ dude:upsert_record { "kind": "spec", "title": "API: POST /users ..." } - Create spec
18
+ dude:search { "query": "...", "kind": "spec" } - Find specs
16
19
  ```
17
20
 
18
21
  ## Specification Operations
19
22
 
20
23
  ### Listing Specifications
21
- | Tool | Description |
22
- |------|-------------|
23
- | `dude:list_specifications` | List specs for a project |
24
+ ```
25
+ dude:list_records { "kind": "spec" }
26
+ dude:list_records { "kind": "arch" }
27
+ dude:list_records { "kind": "spec", "status": "open" }
28
+ ```
24
29
 
25
30
  **Parameters:**
26
- - `projectUuid` (required): Project UUID
27
- - `parentUuid` (optional): Filter to children of parent spec
31
+ - `kind` `"spec"` for specifications, `"arch"` for architecture decisions
32
+ - `status` (optional) `"open"`, `"resolved"`, `"archived"`, or `"all"`
33
+ - `project` (optional) — project name, or `"*"` for all projects
28
34
 
29
35
  ### Getting Specification Details
30
- | Tool | Description |
31
- |------|-------------|
32
- | `dude:get_specification` | Get single spec details |
36
+ ```
37
+ dude:get_record { "id": 42 }
38
+ ```
33
39
 
34
40
  **Parameters:**
35
- - `uuid` (required): Specification UUID
41
+ - `id` (required): Record ID (integer)
36
42
 
37
43
  ### Creating Specifications
38
- | Tool | Description |
39
- |------|-------------|
40
- | `dude:create_specification` | Create new specification |
41
-
42
- **Parameters:**
43
- - `project_uuid` (required): Project UUID
44
- - `text` (required): Specification content
45
- - `parent_specification_uuid` (optional): Parent spec for nesting
46
-
47
- **Examples:**
48
44
  ```
49
- dude:create_specification {
50
- "project_uuid": "...",
51
- "text": "AUTH: JWT tokens with 24h expiry. Refresh handled in authMiddleware.js"
45
+ dude:upsert_record {
46
+ "kind": "spec",
47
+ "title": "AUTH: JWT tokens with 24h expiry",
48
+ "body": "Refresh handled in authMiddleware.js. Tokens are RS256 signed."
52
49
  }
53
50
 
54
- dude:create_specification {
55
- "project_uuid": "...",
56
- "text": "API: POST /users returns 201 with user object on success"
51
+ dude:upsert_record {
52
+ "kind": "arch",
53
+ "title": "ARCH: Use libsql for local+cloud hybrid storage",
54
+ "body": "Local SQLite file with optional Turso cloud sync."
57
55
  }
58
56
  ```
59
57
 
60
- ### Updating Specifications
61
- | Tool | Description |
62
- |------|-------------|
63
- | `dude:update_specification` | Update existing spec |
64
-
65
58
  **Parameters:**
66
- - `uuid` (required): Specification UUID
67
- - `text` (optional): New content
68
- - `parent_specification_uuid` (optional, nullable): New parent (null for top-level)
69
- - `valid` (optional): Set validity status (1 = valid, 0 = invalid/deprecated)
59
+ - `kind` (required): `"spec"` or `"arch"`
60
+ - `title` (required): Short summary (use prefixes below)
61
+ - `body` (optional): Full description
62
+ - `status` (optional): Defaults to `"open"`
70
63
 
71
- ### Invalidating Specifications
72
- To mark a specification as invalid/deprecated:
64
+ ### Updating Specifications
65
+ Provide the `id` of an existing record to update it:
73
66
  ```
74
- dude:update_specification { "uuid": "...", "valid": 0 }
67
+ dude:upsert_record {
68
+ "id": 42,
69
+ "kind": "spec",
70
+ "title": "AUTH: JWT tokens with 1h expiry (changed from 24h)",
71
+ "body": "Updated based on security review..."
72
+ }
75
73
  ```
76
74
 
77
- To restore validity:
75
+ ### Archiving Specifications
76
+ Set status to `"archived"` to mark as deprecated:
78
77
  ```
79
- dude:update_specification { "uuid": "...", "valid": 1 }
78
+ dude:upsert_record { "id": 42, "kind": "spec", "title": "...", "status": "archived" }
79
+ ```
80
+
81
+ ### Deleting Specifications
82
+ ```
83
+ dude:delete_record { "id": 42 }
80
84
  ```
81
85
 
82
86
  ## Search for Specifications
@@ -85,25 +89,17 @@ dude:update_specification { "uuid": "...", "valid": 1 }
85
89
  ```
86
90
  dude:search {
87
91
  "query": "authentication flow JWT tokens",
88
- "entityTypes": ["specification"],
89
- "projectUuid": "optional-project-uuid"
92
+ "kind": "spec",
93
+ "project": "my-org/my-repo",
94
+ "limit": 10
90
95
  }
91
96
  ```
92
97
 
93
98
  **Parameters:**
94
99
  - `query` (required): Natural language search query
95
- - `limit` (optional): Max results (default: 10)
96
- - `threshold` (optional): Min similarity 0-1 (default: 0.3)
97
- - `entityTypes` (optional): Filter to `["specification"]`
98
- - `projectUuid` (optional): Scope to specific project
99
-
100
- ### Keyword Search
101
- ```
102
- dude:search_text { "query": "API" }
103
- ```
104
-
105
- **Parameters:**
106
- - `query` (required): Text to search for
100
+ - `kind` (optional): `"spec"` or `"arch"` to filter results
101
+ - `project` (optional): Project name to boost, or `"*"` for equal weight
102
+ - `limit` (optional): Max results (default 5)
107
103
 
108
104
  ## Specification Conventions
109
105
 
@@ -116,7 +112,5 @@ Use prefixes to categorize:
116
112
 
117
113
  ## Related Skills
118
114
 
119
- - **dude:projects**: Manage projects and get full project context
120
- - **dude:issues**: Track bugs and tasks
121
-
122
- **Tip:** Use `dude:get_project_context` (from dude:projects) to see all specs for a project at once.
115
+ - **dude:projects** List and explore projects
116
+ - **dude:issues** Track bugs and tasks
package/src/db-libsql.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { createClient } from '@libsql/client';
2
2
  import { execSync } from 'node:child_process';
3
- import { existsSync, mkdirSync } from 'node:fs';
3
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
4
4
  import { join, basename } from 'node:path';
5
5
  import { homedir } from 'node:os';
6
6
  import { DbAdapter } from './db-adapter.js';
@@ -36,17 +36,23 @@ export class LibsqlAdapter extends DbAdapter {
36
36
  if (this._hasSyncConfig()) {
37
37
  try {
38
38
  this.db = this._createClient(); // tries sync-enabled client
39
+ await this._runSchema();
39
40
  } catch (err) {
40
- console.error(`[dude] Cloud sync connection failed: ${err.message}`);
41
- console.error('[dude] Falling back to local-only mode.');
41
+ // Close the bad client if it was created
42
+ if (this.db) try { this.db.close(); } catch {}
42
43
  this._syncError = err.message;
44
+ this._logToFile(`Cloud sync failed: ${err.message}\n${err.stack}`);
45
+ console.error(`[dude] Cloud sync failed: ${err.message}`);
46
+ console.error('[dude] Falling back to local-only mode. Details in ~/.dude-claude/dude.log');
47
+ // Recreate with local-only client
43
48
  this.db = this._createLocalOnlyClient();
49
+ await this._runSchema();
44
50
  }
45
51
  } else {
46
52
  this.db = this._createLocalOnlyClient();
53
+ await this._runSchema();
47
54
  }
48
55
 
49
- await this._runSchema();
50
56
  const projectName = this._detectProject();
51
57
  this.currentProject = await this._upsertProject(projectName);
52
58
  await this._migrateProjectNames(projectName);
@@ -66,6 +72,14 @@ export class LibsqlAdapter extends DbAdapter {
66
72
  }
67
73
  }
68
74
 
75
+ _logToFile(message) {
76
+ try {
77
+ const logPath = join(DATA_DIR, 'dude.log');
78
+ const timestamp = new Date().toISOString();
79
+ appendFileSync(logPath, `[${timestamp}] ${message}\n`);
80
+ } catch { /* never let logging crash the server */ }
81
+ }
82
+
69
83
  _hasSyncConfig() {
70
84
  return !!(this.config.syncUrl || process.env.DUDE_TURSO_URL);
71
85
  }
@@ -170,6 +184,20 @@ export class LibsqlAdapter extends DbAdapter {
170
184
  }
171
185
 
172
186
  _detectProject() {
187
+ const cwd = process.cwd();
188
+ const cacheFile = join(DATA_DIR, '.project-cache.json');
189
+ const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
190
+
191
+ // Check cache first
192
+ try {
193
+ if (existsSync(cacheFile)) {
194
+ const cache = JSON.parse(readFileSync(cacheFile, 'utf8'));
195
+ if (cache.cwd === cwd && (Date.now() - cache.timestamp) < CACHE_TTL) {
196
+ return cache.name;
197
+ }
198
+ }
199
+ } catch { /* cache miss — fall through to detection */ }
200
+
173
201
  let name;
174
202
  try {
175
203
  const toplevel = execSync('git rev-parse --show-toplevel', {
@@ -193,8 +221,14 @@ export class LibsqlAdapter extends DbAdapter {
193
221
  // No remote — keep basename
194
222
  }
195
223
  } catch {
196
- name = process.cwd();
224
+ name = cwd;
197
225
  }
226
+
227
+ // Write cache
228
+ try {
229
+ writeFileSync(cacheFile, JSON.stringify({ cwd, name, timestamp: Date.now() }));
230
+ } catch { /* never let caching crash the server */ }
231
+
198
232
  return name;
199
233
  }
200
234
 
@@ -358,8 +392,8 @@ export class LibsqlAdapter extends DbAdapter {
358
392
  return result.rowsAffected > 0;
359
393
  }
360
394
 
361
- async search(embedding, { kind, projectId, limit = 5 } = {}) {
362
- const curProject = await this.getCurrentProject();
395
+ async search(embedding, { kind, project, limit = 5 } = {}) {
396
+ const boostProject = project === '*' ? null : (project || (await this.getCurrentProject()).name);
363
397
  const embJson = this._embeddingToJson(embedding);
364
398
  const candidateLimit = Math.floor(limit * 3);
365
399
 
@@ -382,7 +416,7 @@ export class LibsqlAdapter extends DbAdapter {
382
416
 
383
417
  let rows = result.rows.map(row => {
384
418
  const similarity = this._computeSimilarity(embedding, row.embedding);
385
- const boosted = (row.project === curProject.name)
419
+ const boosted = (boostProject && row.project === boostProject)
386
420
  ? Math.min(1.0, similarity + 0.1)
387
421
  : similarity;
388
422
  return { ...row, similarity: boosted };
@@ -203,8 +203,8 @@ export class SqliteVecAdapter extends DbAdapter {
203
203
  return tx();
204
204
  }
205
205
 
206
- async search(embedding, { kind, projectId, limit = 5 } = {}) {
207
- const curProject = await this.getCurrentProject();
206
+ async search(embedding, { kind, project, limit = 5 } = {}) {
207
+ const boostProject = project === '*' ? null : (project || (await this.getCurrentProject()).name);
208
208
 
209
209
  let sql = `
210
210
  SELECT
@@ -232,7 +232,7 @@ export class SqliteVecAdapter extends DbAdapter {
232
232
  // Convert cosine distance to similarity and apply project boost
233
233
  rows = rows.map(row => {
234
234
  let similarity = 1 - row.distance;
235
- if (row.project === curProject.name) {
235
+ if (boostProject && row.project === boostProject) {
236
236
  similarity = Math.min(1.0, similarity + 0.1);
237
237
  }
238
238
  return { ...row, similarity };
package/src/server.js CHANGED
@@ -25,7 +25,7 @@ export async function startServer() {
25
25
  async ({ query, kind, project, limit }) => {
26
26
  try {
27
27
  const embedding = await embed(query);
28
- const results = await db.search(embedding, { kind, limit });
28
+ const results = await db.search(embedding, { kind, project, limit });
29
29
  return {
30
30
  content: [{ type: 'text', text: JSON.stringify(results, null, 2) }],
31
31
  };
@@ -50,8 +50,10 @@ export async function startServer() {
50
50
  async ({ id, kind, title, body, status }) => {
51
51
  try {
52
52
  const text = `${title} ${body || ''}`.trim();
53
- const embedding = await embed(text);
54
- const project = await db.getCurrentProject();
53
+ const [embedding, project] = await Promise.all([
54
+ embed(text),
55
+ db.getCurrentProject(),
56
+ ]);
55
57
  const record = await db.upsert(
56
58
  { id, projectId: project.id, kind, title, body: body || '', status: status || 'open' },
57
59
  embedding,