opencodekit 0.14.6 ā 0.15.1
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/README.md +2 -2
- package/dist/index.js +435 -57
- package/dist/template/.opencode/.env.example +1 -0
- package/dist/template/.opencode/AGENTS.md +13 -24
- package/dist/template/.opencode/README.md +8 -119
- package/dist/template/.opencode/agent/explore.md +2 -3
- package/dist/template/.opencode/agent/general.md +56 -0
- package/dist/template/.opencode/agent/plan.md +54 -0
- package/dist/template/.opencode/agent/scout.md +15 -5
- package/dist/template/.opencode/command/analyze-project.md +2 -2
- package/dist/template/.opencode/command/brainstorm.md +1 -1
- package/dist/template/.opencode/command/design-audit.md +4 -5
- package/dist/template/.opencode/command/design.md +4 -13
- package/dist/template/.opencode/command/generate-pattern.md +2 -9
- package/dist/template/.opencode/command/implement.md +4 -4
- package/dist/template/.opencode/command/init.md +1 -1
- package/dist/template/.opencode/command/new-feature.md +2 -3
- package/dist/template/.opencode/command/plan.md +1 -1
- package/dist/template/.opencode/command/pr.md +0 -1
- package/dist/template/.opencode/command/research.md +20 -6
- package/dist/template/.opencode/command/restore-image.md +1 -9
- package/dist/template/.opencode/command/revert-feature.md +1 -1
- package/dist/template/.opencode/command/review-codebase.md +4 -4
- package/dist/template/.opencode/command/status.md +1 -2
- package/dist/template/.opencode/command/summarize.md +1 -2
- package/dist/template/.opencode/command/triage.md +4 -32
- package/dist/template/.opencode/dcp.jsonc +68 -68
- package/dist/template/.opencode/memory/_templates/README.md +35 -0
- package/dist/template/.opencode/memory/_templates/project/architecture.md +60 -0
- package/dist/template/.opencode/memory/_templates/project/commands.md +72 -0
- package/dist/template/.opencode/memory/_templates/project/conventions.md +68 -0
- package/dist/template/.opencode/memory/_templates/project/gotchas.md +41 -0
- package/dist/template/.opencode/memory/beads-workflow.md +30 -29
- package/dist/template/.opencode/memory/project/architecture.md +31 -50
- package/dist/template/.opencode/memory/project/commands.md +41 -22
- package/dist/template/.opencode/memory/project/conventions.md +39 -177
- package/dist/template/.opencode/memory/project/gotchas.md +21 -177
- package/dist/template/.opencode/memory/user.example.md +5 -0
- package/dist/template/.opencode/opencode.json +628 -579
- package/dist/template/.opencode/package.json +18 -21
- package/dist/template/.opencode/plugin/compaction.ts +79 -85
- package/dist/template/.opencode/plugin/env-ctx.ts +19 -19
- package/dist/template/.opencode/plugin/lib/notify.ts +41 -45
- package/dist/template/.opencode/plugin/lsp.ts +197 -200
- package/dist/template/.opencode/plugin/memory.ts +14 -112
- package/dist/template/.opencode/plugin/package.json +5 -5
- package/dist/template/.opencode/plugin/sessions.ts +1 -1
- package/dist/template/.opencode/plugin/skill-mcp.ts +486 -521
- package/dist/template/.opencode/plugin/truncator.ts +47 -50
- package/dist/template/.opencode/plugin/tsconfig.json +14 -14
- package/dist/template/.opencode/skill/chrome-devtools/mcp.json +17 -17
- package/dist/template/.opencode/skill/condition-based-waiting/SKILL.md +17 -12
- package/dist/template/.opencode/skill/condition-based-waiting/example.ts +63 -69
- package/dist/template/.opencode/skill/defense-in-depth/SKILL.md +14 -8
- package/dist/template/.opencode/skill/dispatching-parallel-agents/SKILL.md +14 -3
- package/dist/template/.opencode/skill/playwright/mcp.json +14 -14
- package/dist/template/.opencode/skill/receiving-code-review/SKILL.md +21 -8
- package/dist/template/.opencode/skill/requesting-code-review/review.md +14 -0
- package/dist/template/.opencode/skill/root-cause-tracing/SKILL.md +18 -4
- package/dist/template/.opencode/skill/source-code-research/SKILL.md +9 -7
- package/dist/template/.opencode/skill/test-driven-development/SKILL.md +49 -32
- package/dist/template/.opencode/skill/testing-anti-patterns/SKILL.md +40 -22
- package/dist/template/.opencode/skill/testing-skills-with-subagents/SKILL.md +46 -26
- package/dist/template/.opencode/skill/tool-priority/SKILL.md +117 -44
- package/dist/template/.opencode/skill/v0/SKILL.md +1 -7
- package/dist/template/.opencode/skill/verification-before-completion/SKILL.md +27 -19
- package/dist/template/.opencode/skill/writing-skills/anthropic-best-practices.md +171 -148
- package/dist/template/.opencode/skill/writing-skills/persuasion-principles.md +39 -6
- package/dist/template/.opencode/tool/memory-read.ts +44 -56
- package/dist/template/.opencode/tool/memory-search.ts +8 -291
- package/dist/template/.opencode/tool/memory-update.ts +47 -51
- package/dist/template/.opencode/tool/observation.ts +6 -180
- package/dist/template/.opencode/tsconfig.json +19 -19
- package/package.json +19 -15
- package/dist/template/.opencode/.background-tasks.json +0 -114
- package/dist/template/.opencode/.ralph-state.json +0 -12
- package/dist/template/.opencode/agent/build.md +0 -327
- package/dist/template/.opencode/agent/ninja.md +0 -351
- package/dist/template/.opencode/agent/planner.md +0 -281
- package/dist/template/.opencode/agent/rush.md +0 -223
- package/dist/template/.opencode/memory/handoffs/README.md +0 -83
- package/dist/template/.opencode/memory/observations/.gitkeep +0 -0
- package/dist/template/.opencode/memory/observations/2026-01-09-pattern-ampcode-mcp-json-includetools-pattern.md +0 -42
- package/dist/template/.opencode/memory/vector_db/memories.lance/_transactions/0-0d25ba80-ba3b-4209-9046-b45d6093b4da.txn +0 -0
- package/dist/template/.opencode/memory/vector_db/memories.lance/_versions/1.manifest +0 -0
- package/dist/template/.opencode/memory/vector_db/memories.lance/data/1111100101010101011010004a9ef34df6b29f36a9a53a2892.lance +0 -0
- package/dist/template/.opencode/tool/ast-grep.ts +0 -245
- package/dist/template/.opencode/tool/background.ts +0 -509
- package/dist/template/.opencode/tool/bd-inbox.ts +0 -110
- package/dist/template/.opencode/tool/bd-msg.ts +0 -62
- package/dist/template/.opencode/tool/bd-release.ts +0 -71
- package/dist/template/.opencode/tool/bd-reserve.ts +0 -121
- package/dist/template/.opencode/tool/memory-embed.ts +0 -183
- package/dist/template/.opencode/tool/memory-index.ts +0 -769
- package/dist/template/.opencode/tool/repo-map.ts +0 -451
|
@@ -9,142 +9,171 @@ LLMs respond to the same persuasion principles as humans. Understanding this psy
|
|
|
9
9
|
## The Seven Principles
|
|
10
10
|
|
|
11
11
|
### 1. Authority
|
|
12
|
+
|
|
12
13
|
**What it is:** Deference to expertise, credentials, or official sources.
|
|
13
14
|
|
|
14
15
|
**How it works in skills:**
|
|
16
|
+
|
|
15
17
|
- Imperative language: "YOU MUST", "Never", "Always"
|
|
16
18
|
- Non-negotiable framing: "No exceptions"
|
|
17
19
|
- Eliminates decision fatigue and rationalization
|
|
18
20
|
|
|
19
21
|
**When to use:**
|
|
22
|
+
|
|
20
23
|
- Discipline-enforcing skills (TDD, verification requirements)
|
|
21
24
|
- Safety-critical practices
|
|
22
25
|
- Established best practices
|
|
23
26
|
|
|
24
27
|
**Example:**
|
|
28
|
+
|
|
25
29
|
```markdown
|
|
26
30
|
ā
Write code before test? Delete it. Start over. No exceptions.
|
|
27
31
|
ā Consider writing tests first when feasible.
|
|
28
32
|
```
|
|
29
33
|
|
|
30
34
|
### 2. Commitment
|
|
35
|
+
|
|
31
36
|
**What it is:** Consistency with prior actions, statements, or public declarations.
|
|
32
37
|
|
|
33
38
|
**How it works in skills:**
|
|
39
|
+
|
|
34
40
|
- Require announcements: "Announce skill usage"
|
|
35
41
|
- Force explicit choices: "Choose A, B, or C"
|
|
36
42
|
- Use tracking: TodoWrite for checklists
|
|
37
43
|
|
|
38
44
|
**When to use:**
|
|
45
|
+
|
|
39
46
|
- Ensuring skills are actually followed
|
|
40
47
|
- Multi-step processes
|
|
41
48
|
- Accountability mechanisms
|
|
42
49
|
|
|
43
50
|
**Example:**
|
|
51
|
+
|
|
44
52
|
```markdown
|
|
45
53
|
ā
When you find a skill, you MUST announce: "I'm using [Skill Name]"
|
|
46
54
|
ā Consider letting your partner know which skill you're using.
|
|
47
55
|
```
|
|
48
56
|
|
|
49
57
|
### 3. Scarcity
|
|
58
|
+
|
|
50
59
|
**What it is:** Urgency from time limits or limited availability.
|
|
51
60
|
|
|
52
61
|
**How it works in skills:**
|
|
62
|
+
|
|
53
63
|
- Time-bound requirements: "Before proceeding"
|
|
54
64
|
- Sequential dependencies: "Immediately after X"
|
|
55
65
|
- Prevents procrastination
|
|
56
66
|
|
|
57
67
|
**When to use:**
|
|
68
|
+
|
|
58
69
|
- Immediate verification requirements
|
|
59
70
|
- Time-sensitive workflows
|
|
60
71
|
- Preventing "I'll do it later"
|
|
61
72
|
|
|
62
73
|
**Example:**
|
|
74
|
+
|
|
63
75
|
```markdown
|
|
64
76
|
ā
After completing a task, IMMEDIATELY request code review before proceeding.
|
|
65
77
|
ā You can review code when convenient.
|
|
66
78
|
```
|
|
67
79
|
|
|
68
80
|
### 4. Social Proof
|
|
81
|
+
|
|
69
82
|
**What it is:** Conformity to what others do or what's considered normal.
|
|
70
83
|
|
|
71
84
|
**How it works in skills:**
|
|
85
|
+
|
|
72
86
|
- Universal patterns: "Every time", "Always"
|
|
73
87
|
- Failure modes: "X without Y = failure"
|
|
74
88
|
- Establishes norms
|
|
75
89
|
|
|
76
90
|
**When to use:**
|
|
91
|
+
|
|
77
92
|
- Documenting universal practices
|
|
78
93
|
- Warning about common failures
|
|
79
94
|
- Reinforcing standards
|
|
80
95
|
|
|
81
96
|
**Example:**
|
|
97
|
+
|
|
82
98
|
```markdown
|
|
83
99
|
ā
Checklists without TodoWrite tracking = steps get skipped. Every time.
|
|
84
100
|
ā Some people find TodoWrite helpful for checklists.
|
|
85
101
|
```
|
|
86
102
|
|
|
87
103
|
### 5. Unity
|
|
104
|
+
|
|
88
105
|
**What it is:** Shared identity, "we-ness", in-group belonging.
|
|
89
106
|
|
|
90
107
|
**How it works in skills:**
|
|
108
|
+
|
|
91
109
|
- Collaborative language: "our codebase", "we're colleagues"
|
|
92
110
|
- Shared goals: "we both want quality"
|
|
93
111
|
|
|
94
112
|
**When to use:**
|
|
113
|
+
|
|
95
114
|
- Collaborative workflows
|
|
96
115
|
- Establishing team culture
|
|
97
116
|
- Non-hierarchical practices
|
|
98
117
|
|
|
99
118
|
**Example:**
|
|
119
|
+
|
|
100
120
|
```markdown
|
|
101
121
|
ā
We're colleagues working together. I need your honest technical judgment.
|
|
102
122
|
ā You should probably tell me if I'm wrong.
|
|
103
123
|
```
|
|
104
124
|
|
|
105
125
|
### 6. Reciprocity
|
|
126
|
+
|
|
106
127
|
**What it is:** Obligation to return benefits received.
|
|
107
128
|
|
|
108
129
|
**How it works:**
|
|
130
|
+
|
|
109
131
|
- Use sparingly - can feel manipulative
|
|
110
132
|
- Rarely needed in skills
|
|
111
133
|
|
|
112
134
|
**When to avoid:**
|
|
135
|
+
|
|
113
136
|
- Almost always (other principles more effective)
|
|
114
137
|
|
|
115
138
|
### 7. Liking
|
|
139
|
+
|
|
116
140
|
**What it is:** Preference for cooperating with those we like.
|
|
117
141
|
|
|
118
142
|
**How it works:**
|
|
143
|
+
|
|
119
144
|
- **DON'T USE for compliance**
|
|
120
145
|
- Conflicts with honest feedback culture
|
|
121
146
|
- Creates sycophancy
|
|
122
147
|
|
|
123
148
|
**When to avoid:**
|
|
149
|
+
|
|
124
150
|
- Always for discipline enforcement
|
|
125
151
|
|
|
126
152
|
## Principle Combinations by Skill Type
|
|
127
153
|
|
|
128
|
-
| Skill Type
|
|
129
|
-
|
|
154
|
+
| Skill Type | Use | Avoid |
|
|
155
|
+
| -------------------- | ------------------------------------- | ------------------- |
|
|
130
156
|
| Discipline-enforcing | Authority + Commitment + Social Proof | Liking, Reciprocity |
|
|
131
|
-
| Guidance/technique
|
|
132
|
-
| Collaborative
|
|
133
|
-
| Reference
|
|
157
|
+
| Guidance/technique | Moderate Authority + Unity | Heavy authority |
|
|
158
|
+
| Collaborative | Unity + Commitment | Authority, Liking |
|
|
159
|
+
| Reference | Clarity only | All persuasion |
|
|
134
160
|
|
|
135
161
|
## Why This Works: The Psychology
|
|
136
162
|
|
|
137
163
|
**Bright-line rules reduce rationalization:**
|
|
164
|
+
|
|
138
165
|
- "YOU MUST" removes decision fatigue
|
|
139
166
|
- Absolute language eliminates "is this an exception?" questions
|
|
140
167
|
- Explicit anti-rationalization counters close specific loopholes
|
|
141
168
|
|
|
142
169
|
**Implementation intentions create automatic behavior:**
|
|
170
|
+
|
|
143
171
|
- Clear triggers + required actions = automatic execution
|
|
144
172
|
- "When X, do Y" more effective than "generally do Y"
|
|
145
173
|
- Reduces cognitive load on compliance
|
|
146
174
|
|
|
147
175
|
**LLMs are parahuman:**
|
|
176
|
+
|
|
148
177
|
- Trained on human text containing these patterns
|
|
149
178
|
- Authority language precedes compliance in training data
|
|
150
179
|
- Commitment sequences (statement ā action) frequently modeled
|
|
@@ -153,11 +182,13 @@ LLMs respond to the same persuasion principles as humans. Understanding this psy
|
|
|
153
182
|
## Ethical Use
|
|
154
183
|
|
|
155
184
|
**Legitimate:**
|
|
185
|
+
|
|
156
186
|
- Ensuring critical practices are followed
|
|
157
187
|
- Creating effective documentation
|
|
158
188
|
- Preventing predictable failures
|
|
159
189
|
|
|
160
190
|
**Illegitimate:**
|
|
191
|
+
|
|
161
192
|
- Manipulating for personal gain
|
|
162
193
|
- Creating false urgency
|
|
163
194
|
- Guilt-based compliance
|
|
@@ -166,11 +197,13 @@ LLMs respond to the same persuasion principles as humans. Understanding this psy
|
|
|
166
197
|
|
|
167
198
|
## Research Citations
|
|
168
199
|
|
|
169
|
-
**Cialdini, R. B. (2021).**
|
|
200
|
+
**Cialdini, R. B. (2021).** _Influence: The Psychology of Persuasion (New and Expanded)._ Harper Business.
|
|
201
|
+
|
|
170
202
|
- Seven principles of persuasion
|
|
171
203
|
- Empirical foundation for influence research
|
|
172
204
|
|
|
173
205
|
**Meincke, L., Shapiro, D., Duckworth, A. L., Mollick, E., Mollick, L., & Cialdini, R. (2025).** Call Me A Jerk: Persuading AI to Comply with Objectionable Requests. University of Pennsylvania.
|
|
206
|
+
|
|
174
207
|
- Tested 7 principles with N=28,000 LLM conversations
|
|
175
208
|
- Compliance increased 33% ā 72% with persuasion techniques
|
|
176
209
|
- Authority, commitment, scarcity most effective
|
|
@@ -3,64 +3,52 @@ import path from "node:path";
|
|
|
3
3
|
import { tool } from "@opencode-ai/plugin";
|
|
4
4
|
|
|
5
5
|
export default tool({
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
6
|
+
description:
|
|
7
|
+
"Read memory files for persistent cross-session context. Returns current project state, learnings, and active tasks. Supports subdirectories (e.g., 'research/opencode-sessions').",
|
|
8
|
+
args: {
|
|
9
|
+
file: tool.schema
|
|
10
|
+
.string()
|
|
11
|
+
.optional()
|
|
12
|
+
.describe(
|
|
13
|
+
"Memory file to read: handoffs/YYYY-MM-DD-phase, research/YYYY-MM-DD-topic, _templates/task-prd, _templates/task-spec, _templates/task-review, _templates/research, _templates/handoff",
|
|
14
|
+
),
|
|
15
|
+
},
|
|
16
|
+
execute: async (args: { file?: string }) => {
|
|
17
|
+
const fileName = args.file || "memory";
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
// Normalize: strip .md extension if present
|
|
20
|
+
const normalizedFile = fileName.replace(/\.md$/i, "");
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
`${normalizedFile}.md`,
|
|
29
|
-
),
|
|
30
|
-
path.join(
|
|
31
|
-
process.cwd(),
|
|
32
|
-
".config/opencode/memory",
|
|
33
|
-
`${normalizedFile}.md`,
|
|
34
|
-
),
|
|
35
|
-
];
|
|
22
|
+
// Location priority: project > global > legacy
|
|
23
|
+
const locations = [
|
|
24
|
+
path.join(process.cwd(), ".opencode/memory", `${normalizedFile}.md`),
|
|
25
|
+
path.join(process.env.HOME || "", ".config/opencode/memory", `${normalizedFile}.md`),
|
|
26
|
+
path.join(process.cwd(), ".config/opencode/memory", `${normalizedFile}.md`),
|
|
27
|
+
];
|
|
36
28
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
return `Error reading memory from ${filePath}: ${error.message}`;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
29
|
+
// Try each location in order
|
|
30
|
+
for (const filePath of locations) {
|
|
31
|
+
try {
|
|
32
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
33
|
+
const locationLabel = filePath.includes(".opencode/memory")
|
|
34
|
+
? "project"
|
|
35
|
+
: filePath.includes(process.env.HOME || "")
|
|
36
|
+
? "global"
|
|
37
|
+
: "legacy";
|
|
38
|
+
return `[Read from ${locationLabel}: ${filePath}]\n\n${content}`;
|
|
39
|
+
} catch (error) {
|
|
40
|
+
// Continue to next location if file not found
|
|
41
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
// Other errors should be reported
|
|
45
|
+
if (error instanceof Error) {
|
|
46
|
+
return `Error reading memory from ${filePath}: ${error.message}`;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
62
50
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
51
|
+
// No file found in any location
|
|
52
|
+
return `Memory file '${normalizedFile}.md' not found in any location.\nSearched:\n- ${locations.join("\n- ")}\n\nStructure:\n- handoffs/YYYY-MM-DD-phase (phase transitions)\n- research/YYYY-MM-DD-topic (research findings)\n- _templates/ (prd, spec, review, research, handoff)`;
|
|
53
|
+
},
|
|
66
54
|
});
|
|
@@ -1,82 +1,10 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { tool } from "@opencode-ai/plugin";
|
|
4
|
-
import { searchVectorStore } from "./memory-index";
|
|
5
4
|
|
|
6
5
|
interface SearchResult {
|
|
7
6
|
file: string;
|
|
8
7
|
matches: { line: number; content: string }[];
|
|
9
|
-
score?: number;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
interface SemanticResult {
|
|
13
|
-
file: string;
|
|
14
|
-
title: string;
|
|
15
|
-
preview: string;
|
|
16
|
-
type: string;
|
|
17
|
-
score?: number;
|
|
18
|
-
confidence?: string;
|
|
19
|
-
age_days?: number;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// Confidence decay factor based on age (Graphiti-inspired)
|
|
23
|
-
// Older observations with lower confidence rank lower
|
|
24
|
-
function applyConfidenceDecay(
|
|
25
|
-
results: SemanticResult[],
|
|
26
|
-
contents: Map<string, string>,
|
|
27
|
-
): SemanticResult[] {
|
|
28
|
-
const now = Date.now();
|
|
29
|
-
|
|
30
|
-
return results.map((result) => {
|
|
31
|
-
const content = contents.get(result.file) || "";
|
|
32
|
-
|
|
33
|
-
// Extract metadata from YAML frontmatter
|
|
34
|
-
const createdMatch = content.match(/created:\s*(.+)/);
|
|
35
|
-
const confidenceMatch = content.match(/confidence:\s*(\w+)/);
|
|
36
|
-
const validUntilMatch = content.match(/valid_until:\s*(.+)/);
|
|
37
|
-
const supersededByMatch = content.match(/superseded_by:\s*(.+)/);
|
|
38
|
-
|
|
39
|
-
// Check if superseded (should rank very low)
|
|
40
|
-
if (supersededByMatch && supersededByMatch[1] !== "null") {
|
|
41
|
-
return { ...result, score: (result.score || 1) * 0.1 };
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Check if expired
|
|
45
|
-
if (validUntilMatch && validUntilMatch[1] !== "null") {
|
|
46
|
-
const validUntil = new Date(validUntilMatch[1]).getTime();
|
|
47
|
-
if (validUntil < now) {
|
|
48
|
-
return { ...result, score: (result.score || 1) * 0.2 };
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Calculate age in days
|
|
53
|
-
let ageDays = 0;
|
|
54
|
-
if (createdMatch) {
|
|
55
|
-
const created = new Date(createdMatch[1]).getTime();
|
|
56
|
-
ageDays = Math.floor((now - created) / (1000 * 60 * 60 * 24));
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Confidence multiplier
|
|
60
|
-
const confidenceMultiplier: Record<string, number> = {
|
|
61
|
-
high: 1.0,
|
|
62
|
-
medium: 0.8,
|
|
63
|
-
low: 0.6,
|
|
64
|
-
};
|
|
65
|
-
const confidence = confidenceMatch?.[1] || "high";
|
|
66
|
-
const confMult = confidenceMultiplier[confidence] || 1.0;
|
|
67
|
-
|
|
68
|
-
// Age decay: lose 5% per 30 days, minimum 50%
|
|
69
|
-
const ageDecay = Math.max(0.5, 1 - (ageDays / 30) * 0.05);
|
|
70
|
-
|
|
71
|
-
const finalScore = (result.score || 1) * confMult * ageDecay;
|
|
72
|
-
|
|
73
|
-
return {
|
|
74
|
-
...result,
|
|
75
|
-
score: finalScore,
|
|
76
|
-
confidence,
|
|
77
|
-
age_days: ageDays,
|
|
78
|
-
};
|
|
79
|
-
});
|
|
80
8
|
}
|
|
81
9
|
|
|
82
10
|
async function searchDirectory(
|
|
@@ -125,7 +53,7 @@ async function searchDirectory(
|
|
|
125
53
|
async function keywordSearch(
|
|
126
54
|
query: string,
|
|
127
55
|
type: string | undefined,
|
|
128
|
-
|
|
56
|
+
_limit: number,
|
|
129
57
|
): Promise<SearchResult[]> {
|
|
130
58
|
const memoryDir = path.join(process.cwd(), ".opencode/memory");
|
|
131
59
|
const beadsDir = path.join(process.cwd(), ".beads/artifacts");
|
|
@@ -171,58 +99,16 @@ async function keywordSearch(
|
|
|
171
99
|
return results;
|
|
172
100
|
}
|
|
173
101
|
|
|
174
|
-
async function semanticSearch(
|
|
175
|
-
query: string,
|
|
176
|
-
type: string | undefined,
|
|
177
|
-
limit: number,
|
|
178
|
-
): Promise<SemanticResult[]> {
|
|
179
|
-
const typeMap: Record<string, string> = {
|
|
180
|
-
handoffs: "handoff",
|
|
181
|
-
observations: "observation",
|
|
182
|
-
beads: "bead",
|
|
183
|
-
project: "project",
|
|
184
|
-
templates: "template",
|
|
185
|
-
};
|
|
186
|
-
|
|
187
|
-
const fileType = type && type !== "all" ? typeMap[type] : undefined;
|
|
188
|
-
const docs = await searchVectorStore(query, limit * 2, fileType); // Fetch extra for decay filtering
|
|
189
|
-
|
|
190
|
-
// Build content map for decay calculation
|
|
191
|
-
const contents = new Map<string, string>();
|
|
192
|
-
for (const doc of docs) {
|
|
193
|
-
try {
|
|
194
|
-
const content = await fs.readFile(doc.file_path, "utf-8");
|
|
195
|
-
contents.set(doc.file_path, content);
|
|
196
|
-
} catch {
|
|
197
|
-
// File not found, skip
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
const results: SemanticResult[] = docs.map((doc) => ({
|
|
202
|
-
file: doc.file_path,
|
|
203
|
-
title: doc.title,
|
|
204
|
-
preview: doc.content_preview,
|
|
205
|
-
type: doc.file_type,
|
|
206
|
-
score: 1.0,
|
|
207
|
-
}));
|
|
208
|
-
|
|
209
|
-
// Apply confidence decay and re-sort
|
|
210
|
-
const decayedResults = applyConfidenceDecay(results, contents);
|
|
211
|
-
decayedResults.sort((a, b) => (b.score || 0) - (a.score || 0));
|
|
212
|
-
|
|
213
|
-
return decayedResults.slice(0, limit);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
102
|
function formatKeywordResults(
|
|
217
103
|
query: string,
|
|
218
104
|
results: SearchResult[],
|
|
219
105
|
limit: number,
|
|
220
106
|
): string {
|
|
221
107
|
if (results.length === 0) {
|
|
222
|
-
return `No
|
|
108
|
+
return `No matches found for "${query}".`;
|
|
223
109
|
}
|
|
224
110
|
|
|
225
|
-
let output = `#
|
|
111
|
+
let output = `# Search: "${query}"\n\n`;
|
|
226
112
|
output += `Found ${results.length} file(s) with matches.\n\n`;
|
|
227
113
|
|
|
228
114
|
for (const result of results) {
|
|
@@ -234,138 +120,6 @@ function formatKeywordResults(
|
|
|
234
120
|
if (result.matches.length > limit) {
|
|
235
121
|
output += `- ... and ${result.matches.length - limit} more matches\n`;
|
|
236
122
|
}
|
|
237
|
-
// Add LSP navigation hint for code files
|
|
238
|
-
if (
|
|
239
|
-
result.file.endsWith(".ts") ||
|
|
240
|
-
result.file.endsWith(".tsx") ||
|
|
241
|
-
result.file.endsWith(".js") ||
|
|
242
|
-
result.file.endsWith(".jsx") ||
|
|
243
|
-
result.file.endsWith(".py") ||
|
|
244
|
-
result.file.endsWith(".go") ||
|
|
245
|
-
result.file.endsWith(".rs")
|
|
246
|
-
) {
|
|
247
|
-
const lineNum = result.matches[0]?.line;
|
|
248
|
-
if (lineNum) {
|
|
249
|
-
output += "\nš **LSP Nudge:**\n";
|
|
250
|
-
output += ` \`lsp_lsp_goto_definition({ filePath: "${result.file}", line: ${lineNum}, character: 1 })\`\n`;
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
output += "\n";
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
return output;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
function formatSemanticResults(
|
|
260
|
-
query: string,
|
|
261
|
-
results: SemanticResult[],
|
|
262
|
-
): string {
|
|
263
|
-
if (results.length === 0) {
|
|
264
|
-
return `No semantic matches found for "${query}".\n\nTip: Run 'vector-store rebuild' to index memory files first.`;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
let output = `# Semantic Search: "${query}"\n\n`;
|
|
268
|
-
output += `Found ${results.length} similar document(s).\n\n`;
|
|
269
|
-
|
|
270
|
-
const confidenceIcons: Record<string, string> = {
|
|
271
|
-
high: "š¢",
|
|
272
|
-
medium: "š”",
|
|
273
|
-
low: "š“",
|
|
274
|
-
};
|
|
275
|
-
|
|
276
|
-
for (const result of results) {
|
|
277
|
-
output += `## ${result.title}\n\n`;
|
|
278
|
-
output += `**File:** \`${result.file}\`\n`;
|
|
279
|
-
output += `**Type:** ${result.type}`;
|
|
280
|
-
if (result.confidence) {
|
|
281
|
-
const icon = confidenceIcons[result.confidence] || "";
|
|
282
|
-
output += ` | **Confidence:** ${icon} ${result.confidence}`;
|
|
283
|
-
}
|
|
284
|
-
if (result.age_days !== undefined && result.age_days > 0) {
|
|
285
|
-
output += ` | **Age:** ${result.age_days}d`;
|
|
286
|
-
}
|
|
287
|
-
output += "\n\n";
|
|
288
|
-
output += `${result.preview}...\n\n`;
|
|
289
|
-
|
|
290
|
-
// Add LSP navigation for code files
|
|
291
|
-
if (
|
|
292
|
-
result.file.endsWith(".ts") ||
|
|
293
|
-
result.file.endsWith(".tsx") ||
|
|
294
|
-
result.file.endsWith(".js") ||
|
|
295
|
-
result.file.endsWith(".jsx") ||
|
|
296
|
-
result.file.endsWith(".py") ||
|
|
297
|
-
result.file.endsWith(".go") ||
|
|
298
|
-
result.file.endsWith(".rs")
|
|
299
|
-
) {
|
|
300
|
-
output += "\nš **LSP Nudge:**\n";
|
|
301
|
-
output += ` Get symbols: \`lsp_lsp_document_symbols({ filePath: "${result.file}" })\`\n`;
|
|
302
|
-
output += ` Find references: \`lsp_lsp_find_references({ filePath: "${result.file}", line: 1, character: 1 })\`\n`;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
output += "---\n\n";
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
return output;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
function formatHybridResults(
|
|
312
|
-
query: string,
|
|
313
|
-
keywordResults: SearchResult[],
|
|
314
|
-
semanticResults: SemanticResult[],
|
|
315
|
-
limit: number,
|
|
316
|
-
): string {
|
|
317
|
-
let output = `# Hybrid Search: "${query}"\n\n`;
|
|
318
|
-
|
|
319
|
-
// Semantic results first (conceptual matches)
|
|
320
|
-
output += `## Semantic Matches (${semanticResults.length})\n\n`;
|
|
321
|
-
if (semanticResults.length === 0) {
|
|
322
|
-
output += `_No semantic matches. Run 'vector-store rebuild' to enable._\n\n`;
|
|
323
|
-
} else {
|
|
324
|
-
for (const result of semanticResults.slice(0, limit)) {
|
|
325
|
-
output += `- **${result.title}** (\`${result.file}\`)\n`;
|
|
326
|
-
output += ` ${result.preview.substring(0, 100)}...\n`;
|
|
327
|
-
// Add LSP hint for code files
|
|
328
|
-
if (
|
|
329
|
-
result.file.endsWith(".ts") ||
|
|
330
|
-
result.file.endsWith(".tsx") ||
|
|
331
|
-
result.file.endsWith(".js") ||
|
|
332
|
-
result.file.endsWith(".jsx") ||
|
|
333
|
-
result.file.endsWith(".py") ||
|
|
334
|
-
result.file.endsWith(".go") ||
|
|
335
|
-
result.file.endsWith(".rs")
|
|
336
|
-
) {
|
|
337
|
-
output += ` š **LSP Nudge:** \`lsp_lsp_document_symbols({ filePath: "${result.file}" })\`\n`;
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
output += "\n";
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// Keyword results (exact matches)
|
|
344
|
-
output += `## Keyword Matches (${keywordResults.length})\n\n`;
|
|
345
|
-
if (keywordResults.length === 0) {
|
|
346
|
-
output += "_No exact keyword matches._\n\n";
|
|
347
|
-
} else {
|
|
348
|
-
for (const result of keywordResults.slice(0, limit)) {
|
|
349
|
-
output += `- **${result.file}**\n`;
|
|
350
|
-
for (const match of result.matches.slice(0, 2)) {
|
|
351
|
-
output += ` - Line ${match.line}: ${match.content.substring(0, 80)}...\n`;
|
|
352
|
-
}
|
|
353
|
-
// Add LSP hint for code files
|
|
354
|
-
if (
|
|
355
|
-
result.file.endsWith(".ts") ||
|
|
356
|
-
result.file.endsWith(".tsx") ||
|
|
357
|
-
result.file.endsWith(".js") ||
|
|
358
|
-
result.file.endsWith(".jsx") ||
|
|
359
|
-
result.file.endsWith(".py") ||
|
|
360
|
-
result.file.endsWith(".go") ||
|
|
361
|
-
result.file.endsWith(".rs")
|
|
362
|
-
) {
|
|
363
|
-
const lineNum = result.matches[0]?.line;
|
|
364
|
-
if (lineNum) {
|
|
365
|
-
output += ` š **LSP Nudge:** \`lsp_lsp_goto_definition({ filePath: "${result.file}", line: ${lineNum}, character: 1 })\`\n`;
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
123
|
output += "\n";
|
|
370
124
|
}
|
|
371
125
|
|
|
@@ -374,19 +128,11 @@ function formatHybridResults(
|
|
|
374
128
|
|
|
375
129
|
export default tool({
|
|
376
130
|
description:
|
|
377
|
-
"Search across all memory files using keywords
|
|
131
|
+
"Search across all memory files using keywords. Returns matching files with context. Useful for finding past decisions, research, or handoffs.",
|
|
378
132
|
args: {
|
|
379
133
|
query: tool.schema
|
|
380
134
|
.string()
|
|
381
|
-
.describe(
|
|
382
|
-
"Search query: keywords, regex pattern, or natural language for semantic search",
|
|
383
|
-
),
|
|
384
|
-
mode: tool.schema
|
|
385
|
-
.enum(["keyword", "semantic", "hybrid"])
|
|
386
|
-
.optional()
|
|
387
|
-
.describe(
|
|
388
|
-
"Search mode: 'keyword' (default, regex matching), 'semantic' (vector similarity), 'hybrid' (both)",
|
|
389
|
-
),
|
|
135
|
+
.describe("Search query: keywords or regex pattern"),
|
|
390
136
|
type: tool.schema
|
|
391
137
|
.string()
|
|
392
138
|
.optional()
|
|
@@ -395,38 +141,9 @@ export default tool({
|
|
|
395
141
|
),
|
|
396
142
|
limit: tool.schema.number().optional().describe("Max results (default: 5)"),
|
|
397
143
|
},
|
|
398
|
-
execute: async (args: {
|
|
399
|
-
query: string;
|
|
400
|
-
mode?: "keyword" | "semantic" | "hybrid";
|
|
401
|
-
type?: string;
|
|
402
|
-
limit?: number;
|
|
403
|
-
}) => {
|
|
404
|
-
const mode = args.mode || "keyword";
|
|
144
|
+
execute: async (args: { query: string; type?: string; limit?: number }) => {
|
|
405
145
|
const limit = args.limit || 5;
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
const results = await keywordSearch(args.query, args.type, limit);
|
|
409
|
-
return formatKeywordResults(args.query, results, limit);
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
if (mode === "semantic") {
|
|
413
|
-
const results = await semanticSearch(args.query, args.type, limit);
|
|
414
|
-
return formatSemanticResults(args.query, results);
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
if (mode === "hybrid") {
|
|
418
|
-
const [keywordResults, semanticResults] = await Promise.all([
|
|
419
|
-
keywordSearch(args.query, args.type, limit),
|
|
420
|
-
semanticSearch(args.query, args.type, limit),
|
|
421
|
-
]);
|
|
422
|
-
return formatHybridResults(
|
|
423
|
-
args.query,
|
|
424
|
-
keywordResults,
|
|
425
|
-
semanticResults,
|
|
426
|
-
limit,
|
|
427
|
-
);
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
return "Unknown search mode";
|
|
146
|
+
const results = await keywordSearch(args.query, args.type, limit);
|
|
147
|
+
return formatKeywordResults(args.query, results, limit);
|
|
431
148
|
},
|
|
432
149
|
});
|