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.
- package/.claude-plugin/plugin.json +1 -1
- package/bin/dude-claude.js +42 -27
- package/hooks/auto-persist-plan.js +10 -3
- package/hooks/auto-persist.js +10 -3
- package/hooks/auto-retrieve.js +10 -3
- package/package.json +1 -1
- package/skills/issues/SKILL.md +50 -61
- package/skills/projects/SKILL.md +36 -76
- package/skills/specifications/SKILL.md +54 -60
- package/src/db-libsql.js +42 -8
- package/src/db-sqlite-vec.js +3 -3
- package/src/server.js +5 -3
package/bin/dude-claude.js
CHANGED
|
@@ -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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
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
|
|
40
|
+
const db = await dbPromise;
|
|
38
41
|
const text = `${title} ${body}`.trim();
|
|
39
|
-
const embedding = await embed(text);
|
|
40
42
|
|
|
41
|
-
|
|
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,
|
package/hooks/auto-persist.js
CHANGED
|
@@ -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
|
|
40
|
+
const db = await dbPromise;
|
|
38
41
|
const text = `${title} ${body}`.trim();
|
|
39
|
-
const embedding = await embed(text);
|
|
40
42
|
|
|
41
|
-
|
|
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,
|
package/hooks/auto-retrieve.js
CHANGED
|
@@ -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
|
|
25
|
-
|
|
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
package/skills/issues/SKILL.md
CHANGED
|
@@ -10,74 +10,73 @@ Track bugs, tasks, and blockers via the `dude:` MCP tools.
|
|
|
10
10
|
## Quick Start
|
|
11
11
|
|
|
12
12
|
```
|
|
13
|
-
dude:
|
|
14
|
-
dude:
|
|
15
|
-
dude:search { "
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
- `
|
|
27
|
-
- `
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
+
```
|
|
34
|
+
dude:get_record { "id": 42 }
|
|
35
|
+
```
|
|
33
36
|
|
|
34
37
|
**Parameters:**
|
|
35
|
-
- `
|
|
38
|
+
- `id` (required): Record ID (integer)
|
|
36
39
|
|
|
37
40
|
### Creating Issues
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
- `
|
|
44
|
-
- `
|
|
45
|
-
- `
|
|
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
|
-
|
|
55
|
+
### Updating Issues
|
|
56
|
+
Provide the `id` of an existing record to update it:
|
|
48
57
|
```
|
|
49
|
-
dude:
|
|
50
|
-
"
|
|
51
|
-
"
|
|
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
|
-
|
|
67
|
+
Set status to `"resolved"`:
|
|
68
|
+
```
|
|
69
|
+
dude:upsert_record { "id": 42, "kind": "issue", "title": "...", "status": "resolved" }
|
|
74
70
|
```
|
|
75
|
-
|
|
71
|
+
|
|
72
|
+
To reopen:
|
|
73
|
+
```
|
|
74
|
+
dude:upsert_record { "id": 42, "kind": "issue", "title": "...", "status": "open" }
|
|
76
75
|
```
|
|
77
76
|
|
|
78
|
-
|
|
77
|
+
### Deleting Issues
|
|
79
78
|
```
|
|
80
|
-
dude:
|
|
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
|
-
"
|
|
90
|
-
"
|
|
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
|
-
- `
|
|
97
|
-
- `
|
|
98
|
-
- `
|
|
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
|
|
120
|
-
- **dude:specifications
|
|
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
|
package/skills/projects/SKILL.md
CHANGED
|
@@ -1,115 +1,75 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: projects
|
|
3
|
-
description: "Manage development projects using the dude MCP server. List,
|
|
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
|
-
|
|
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
|
|
14
|
-
dude:
|
|
15
|
-
dude:search { "
|
|
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:
|
|
24
|
+
dude:list_projects
|
|
67
25
|
```
|
|
68
26
|
|
|
69
|
-
|
|
27
|
+
Returns all known projects with their IDs, names, and timestamps.
|
|
28
|
+
|
|
29
|
+
### Viewing Project Records
|
|
70
30
|
```
|
|
71
|
-
dude:
|
|
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
|
-
|
|
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
|
-
###
|
|
41
|
+
### Searching Within a Project
|
|
77
42
|
```
|
|
78
43
|
dude:search {
|
|
79
|
-
"query": "authentication
|
|
80
|
-
"
|
|
81
|
-
"limit":
|
|
44
|
+
"query": "authentication flow",
|
|
45
|
+
"project": "my-org/my-repo",
|
|
46
|
+
"limit": 10
|
|
82
47
|
}
|
|
83
48
|
```
|
|
84
49
|
|
|
85
|
-
|
|
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
|
-
|
|
93
|
-
```
|
|
94
|
-
dude:search_text { "query": "auth" }
|
|
95
|
-
```
|
|
52
|
+
## How Projects Work
|
|
96
53
|
|
|
97
|
-
**
|
|
98
|
-
- `
|
|
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`
|
|
104
|
-
2. `dude:
|
|
105
|
-
3.
|
|
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
|
-
###
|
|
66
|
+
### Exploring Across Projects
|
|
108
67
|
```
|
|
109
|
-
dude:
|
|
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
|
|
115
|
-
- **dude:specifications
|
|
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:
|
|
14
|
-
dude:
|
|
15
|
-
dude:
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
- `
|
|
27
|
-
- `
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
36
|
+
```
|
|
37
|
+
dude:get_record { "id": 42 }
|
|
38
|
+
```
|
|
33
39
|
|
|
34
40
|
**Parameters:**
|
|
35
|
-
- `
|
|
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:
|
|
50
|
-
"
|
|
51
|
-
"
|
|
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:
|
|
55
|
-
"
|
|
56
|
-
"
|
|
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
|
-
- `
|
|
67
|
-
- `
|
|
68
|
-
- `
|
|
69
|
-
- `
|
|
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
|
-
###
|
|
72
|
-
|
|
64
|
+
### Updating Specifications
|
|
65
|
+
Provide the `id` of an existing record to update it:
|
|
73
66
|
```
|
|
74
|
-
dude:
|
|
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
|
-
|
|
75
|
+
### Archiving Specifications
|
|
76
|
+
Set status to `"archived"` to mark as deprecated:
|
|
78
77
|
```
|
|
79
|
-
dude:
|
|
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
|
-
"
|
|
89
|
-
"
|
|
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
|
-
- `
|
|
96
|
-
- `
|
|
97
|
-
- `
|
|
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
|
|
120
|
-
- **dude:issues
|
|
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
|
-
|
|
41
|
-
|
|
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 =
|
|
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,
|
|
362
|
-
const
|
|
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 ===
|
|
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 };
|
package/src/db-sqlite-vec.js
CHANGED
|
@@ -203,8 +203,8 @@ export class SqliteVecAdapter extends DbAdapter {
|
|
|
203
203
|
return tx();
|
|
204
204
|
}
|
|
205
205
|
|
|
206
|
-
async search(embedding, { kind,
|
|
207
|
-
const
|
|
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 ===
|
|
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
|
|
54
|
-
|
|
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,
|