@jeffober/thatch 0.1.27 → 0.1.29
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 +24 -10
- package/artifacts/skills/thatch-code-review.md +9 -7
- package/artifacts/skills/thatch-fact-extractor.md +2 -2
- package/artifacts/skills/thatch-review-economy.md +97 -0
- package/artifacts/skills/thatch-review-followup.md +1 -1
- package/artifacts/skills/thatch-review-synthesizer.md +15 -5
- package/bin/qa-run +38 -0
- package/bin/thatch +13 -7
- package/docs/dev/README.md +76 -22
- package/docs/dev/features/README.md +33 -0
- package/docs/dev/features/behavior-engine.md +271 -0
- package/docs/dev/features/cicd.md +89 -0
- package/docs/dev/features/cli.md +81 -0
- package/docs/dev/features/compaction-recovery.md +48 -0
- package/docs/dev/features/database.md +244 -0
- package/docs/dev/features/deduplication.md +102 -0
- package/docs/dev/features/extraction.md +253 -0
- package/docs/dev/features/hygiene.md +81 -0
- package/docs/dev/features/memory-store.md +154 -0
- package/docs/dev/features/multi-host.md +228 -0
- package/docs/dev/features/nudge-pipeline.md +282 -0
- package/docs/dev/features/prediction-engine.md +253 -0
- package/docs/dev/features/qa-system.md +205 -0
- package/docs/dev/features/session-lifecycle.md +110 -0
- package/docs/dev/features/setup.md +199 -0
- package/docs/dev/features/sideband.md +104 -0
- package/docs/dev/gotchas.md +3 -3
- package/docs/dev/mcp-parity.md +7 -5
- package/docs/dev/setup-and-hooks.md +4 -4
- package/docs/dev/skills.md +10 -8
- package/docs/plans/README.md +2 -2
- package/docs/user/README.md +58 -20
- package/docs/user/behavior-engine.md +85 -0
- package/docs/user/cli.md +98 -0
- package/docs/user/code-review.md +73 -0
- package/docs/user/deduplication.md +70 -0
- package/docs/user/default-behaviors.md +75 -0
- package/docs/user/extraction.md +74 -0
- package/docs/user/hygiene.md +76 -0
- package/docs/user/memory.md +81 -0
- package/docs/user/prediction-engine.md +87 -0
- package/docs/user/setup.md +110 -0
- package/docs/user/skills.md +97 -0
- package/package.json +2 -2
- package/src/behavior.ts +120 -0
- package/src/db.ts +160 -417
- package/src/embeddings.ts +0 -41
- package/src/extraction.ts +2 -2
- package/src/git.ts +3 -1
- package/src/index.ts +66 -14
- package/src/mcp.ts +59 -2
- package/src/prediction.ts +127 -0
- package/src/prompts.ts +155 -200
- package/src/scoring-engine.ts +374 -0
- package/src/seed-behaviors.ts +339 -0
- package/src/setup.ts +2 -6
- package/src/sideband.ts +36 -8
- package/src/skills.ts +1 -0
- package/src/tool-defs.ts +235 -9
- package/src/tools.ts +11 -1
- package/src/vector-math.ts +30 -0
- package/docs/qa/README.md +0 -175
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
import { ThatchDB } from "./db";
|
|
2
|
+
import type { EmbeddingModel } from "./embeddings";
|
|
3
|
+
import pkg from "../package.json" with { type: "json" };
|
|
4
|
+
|
|
5
|
+
interface DefaultBehavior {
|
|
6
|
+
/** Identifies the behavior across releases so the seeder can find and update it. */
|
|
7
|
+
key: string;
|
|
8
|
+
situation: string;
|
|
9
|
+
behavior: string;
|
|
10
|
+
rationale: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Default behaviors seeded into the global store on first run, and updated
|
|
15
|
+
* when their content changes across releases.
|
|
16
|
+
*
|
|
17
|
+
* Each behavior carries a `key` that identifies it across versions. The
|
|
18
|
+
* key is stamped into the behavior's rationale text as `seed-key:<key>`,
|
|
19
|
+
* alongside `seed-version:<version>`. On startup, the seed searches for
|
|
20
|
+
* an existing behavior whose rationale contains the same key stamp. If
|
|
21
|
+
* the version stamps match, the behavior is up to date and skipped. If
|
|
22
|
+
* they differ (the behavior was seeded by an older release), the old
|
|
23
|
+
* behavior is deleted and the new one is created. Behaviors without a
|
|
24
|
+
* key stamp (manually codified by the user) are never touched.
|
|
25
|
+
*
|
|
26
|
+
* Key-based lookup avoids two problems that cosine-based lookup would
|
|
27
|
+
* cause: (1) seed behaviors with similar situation texts (e.g.,
|
|
28
|
+
* day-turnover-coding vs day-turnover-pr) cross the 0.85 cosine dedup
|
|
29
|
+
* threshold, so one would find the other's matcher and skip seeding;
|
|
30
|
+
* (2) a user-codified behavior that happens to match a seed situation
|
|
31
|
+
* would be found and silently deleted.
|
|
32
|
+
*
|
|
33
|
+
* Called by both the opencode plugin (src/index.ts) and the MCP server
|
|
34
|
+
* (src/mcp.ts) at startup, after DB and model initialization.
|
|
35
|
+
*/
|
|
36
|
+
const DEFAULT_BEHAVIORS: DefaultBehavior[] = [
|
|
37
|
+
{
|
|
38
|
+
key: "session-wrap-up",
|
|
39
|
+
situation:
|
|
40
|
+
"The user is wrapping up the session, ending the conversation, or " +
|
|
41
|
+
"signaling they are done working. Trigger phrases include: " +
|
|
42
|
+
"wrapping up, loose ends, clean up, merge, finish up, tidy up, " +
|
|
43
|
+
"ending the session, done for now, calling it a night, " +
|
|
44
|
+
"wrapping up for today, any loose ends, ready to merge, signing off.",
|
|
45
|
+
behavior:
|
|
46
|
+
"Before responding, check for loose ends that would be lost " +
|
|
47
|
+
"when the session ends: run git status --porcelain to surface " +
|
|
48
|
+
"uncommitted changes, check for untracked files that are not " +
|
|
49
|
+
"gitignored, check for stale artifacts or scratch files. If you " +
|
|
50
|
+
"find anything, surface it to the user before they close the " +
|
|
51
|
+
"session. Do not fix anything yourself unless asked. This is a " +
|
|
52
|
+
"read-only check.",
|
|
53
|
+
rationale:
|
|
54
|
+
"The user asks this at the end of every session. A codified rule " +
|
|
55
|
+
"that fires on wrap-up language ensures the check happens " +
|
|
56
|
+
"consistently without relying on the user remembering to ask.",
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
key: "work-finalization",
|
|
60
|
+
situation:
|
|
61
|
+
"The user is committing changes, merging a branch, closing a " +
|
|
62
|
+
"ticket, or marking a PR ready for review. The user is " +
|
|
63
|
+
"finalizing work: committing, merging, closing out, marking " +
|
|
64
|
+
"done, shipping, finishing a task, completing a milestone, " +
|
|
65
|
+
"ready to merge, pushing, tagging a release.",
|
|
66
|
+
behavior:
|
|
67
|
+
"Before responding, check whether anything needs to be saved " +
|
|
68
|
+
"before the work is finalized: run git status --porcelain " +
|
|
69
|
+
"to surface uncommitted or untracked files, check whether " +
|
|
70
|
+
"branch-scoped memories should be consolidated into an archived " +
|
|
71
|
+
"record, check whether there are follow-up tickets or TODOs " +
|
|
72
|
+
"that should be filed before the branch merges. If you find " +
|
|
73
|
+
"anything, surface it to the user. Do not fix anything yourself " +
|
|
74
|
+
"unless asked. This is a read-only check.",
|
|
75
|
+
rationale:
|
|
76
|
+
"Finalizing work (commits, merges, ticket closures) is a " +
|
|
77
|
+
"common point where durable context is lost if not saved first. " +
|
|
78
|
+
"A codified rule that fires on finalization language ensures " +
|
|
79
|
+
"the check happens consistently.",
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
key: "research-new-project",
|
|
83
|
+
situation:
|
|
84
|
+
"The user is starting research on a new project, investigating " +
|
|
85
|
+
"an unfamiliar codebase, or beginning a new ticket in an " +
|
|
86
|
+
"area they have not worked in before. Trigger phrases " +
|
|
87
|
+
"include: new project, unfamiliar codebase, new ticket, " +
|
|
88
|
+
"never seen this before, where do I start, how does this " +
|
|
89
|
+
"work, getting up to speed.",
|
|
90
|
+
behavior:
|
|
91
|
+
"Before diving into code, run a structured investigation: " +
|
|
92
|
+
"check test coverage to understand what is tested and what " +
|
|
93
|
+
"is not, disambiguate domain terms and naming conventions " +
|
|
94
|
+
"(look for naming drift, renamed concepts, or inconsistent " +
|
|
95
|
+
"terminology), look for evidence of partial migrations " +
|
|
96
|
+
"(naming, conventions, libraries, or behavioral patterns " +
|
|
97
|
+
"that changed partway through), and identify existing " +
|
|
98
|
+
"patterns before proposing changes. Load the " +
|
|
99
|
+
"thatch-code-archaeology skill for the full investigation " +
|
|
100
|
+
"procedure.",
|
|
101
|
+
rationale:
|
|
102
|
+
"Starting research without checking for existing patterns, " +
|
|
103
|
+
"partial migrations, or naming drift leads to proposing " +
|
|
104
|
+
"changes that conflict with in-progress migrations or " +
|
|
105
|
+
"established conventions. A codified rule ensures the " +
|
|
106
|
+
"investigation happens before any code changes.",
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
key: "day-turnover-coding",
|
|
110
|
+
situation:
|
|
111
|
+
"The user is continuing work from a previous day, picking " +
|
|
112
|
+
"up where they left off, or resuming a coding session. " +
|
|
113
|
+
"The session has turned over from one day to the next. " +
|
|
114
|
+
"Trigger phrases include: continuing from yesterday, " +
|
|
115
|
+
"picking up where I left off, resuming work, back at it, " +
|
|
116
|
+
"let me pick this back up, where was I.",
|
|
117
|
+
behavior:
|
|
118
|
+
"Before starting new changes, check how far origin/main " +
|
|
119
|
+
"has moved since the branch's merge-base: run " +
|
|
120
|
+
"git fetch origin, then git log --oneline " +
|
|
121
|
+
"$(git merge-base HEAD origin/main)..origin/main. If main " +
|
|
122
|
+
"has moved significantly, surface this to the user and " +
|
|
123
|
+
"suggest rebasing before starting new work. Also check " +
|
|
124
|
+
"git status for uncommitted changes from the previous " +
|
|
125
|
+
"session.",
|
|
126
|
+
rationale:
|
|
127
|
+
"When a branch sits overnight, origin/main may advance " +
|
|
128
|
+
"significantly. Starting new work on a stale base leads " +
|
|
129
|
+
"to larger merge conflicts later. A quick rebase check " +
|
|
130
|
+
"at the start of a new day prevents this.",
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
key: "day-turnover-pr",
|
|
134
|
+
situation:
|
|
135
|
+
"The user is continuing work on a branch with an open " +
|
|
136
|
+
"PR or MR, picking up a coding session that has a " +
|
|
137
|
+
"associated pull request. The session has turned over " +
|
|
138
|
+
"from one day to the next. Trigger phrases include: " +
|
|
139
|
+
"continuing the PR, picking up the branch, back on this " +
|
|
140
|
+
"PR, let me check on this, resuming the PR.",
|
|
141
|
+
behavior:
|
|
142
|
+
"Before starting new changes, check for new upstream " +
|
|
143
|
+
"review comments on the PR: run gh api repos/OWNER/REPO/" +
|
|
144
|
+
"pulls/NUMBER/comments (or gh pr view NUMBER --comments) " +
|
|
145
|
+
"and surface any comments the user has not yet seen. " +
|
|
146
|
+
"Also check git status for uncommitted changes from " +
|
|
147
|
+
"the previous session.",
|
|
148
|
+
rationale:
|
|
149
|
+
"Review comments arriving overnight can change the " +
|
|
150
|
+
"direction of the work. Surfacing them before starting " +
|
|
151
|
+
"new changes prevents wasted effort on a path the " +
|
|
152
|
+
"reviewer has already redirected.",
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
key: "day-turnover-review",
|
|
156
|
+
situation:
|
|
157
|
+
"The user is continuing a code review session, " +
|
|
158
|
+
"picking up a review that was in progress. The session " +
|
|
159
|
+
"has turned over from one day to the next. Trigger " +
|
|
160
|
+
"phrases include: continuing the review, picking up " +
|
|
161
|
+
"the review, back to reviewing, resuming the review, " +
|
|
162
|
+
"where was I on this review.",
|
|
163
|
+
behavior:
|
|
164
|
+
"Before continuing the review, check for new upstream " +
|
|
165
|
+
"comments on the PR and for responses to the user's " +
|
|
166
|
+
"previous review comments: run gh api repos/OWNER/" +
|
|
167
|
+
"REPO/pulls/NUMBER/comments (or gh pr view NUMBER " +
|
|
168
|
+
"--comments) and surface anything new. Also check " +
|
|
169
|
+
"whether the PR author has pushed new commits since " +
|
|
170
|
+
"the last review round.",
|
|
171
|
+
rationale:
|
|
172
|
+
"Review threads can evolve overnight with author " +
|
|
173
|
+
"responses and new comments from other reviewers. " +
|
|
174
|
+
"Checking before continuing prevents re-raising " +
|
|
175
|
+
"already-addressed findings or missing new context.",
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
key: "snag-dead-end",
|
|
179
|
+
situation:
|
|
180
|
+
"The user has hit a snag, dead end, red herring, or " +
|
|
181
|
+
"something unexpected during investigation or coding. " +
|
|
182
|
+
"The agent went down a path that did not pan out. " +
|
|
183
|
+
"Trigger phrases include: wait this does not make " +
|
|
184
|
+
"sense, this is a dead end, that was a red herring, " +
|
|
185
|
+
"this is not what I expected, that did not work, " +
|
|
186
|
+
"I was wrong about, turns out that is not the issue.",
|
|
187
|
+
behavior:
|
|
188
|
+
"Before moving on, save a memory about the dead end " +
|
|
189
|
+
"or red herring so future sessions do not repeat the " +
|
|
190
|
+
"same investigation. Call thatch_memory_remember " +
|
|
191
|
+
"with a label describing the dead end and content " +
|
|
192
|
+
"explaining what looked promising, why it was wrong, " +
|
|
193
|
+
"and what the actual cause or solution turned out " +
|
|
194
|
+
"to be. This is a save-only action; do not attempt " +
|
|
195
|
+
"to fix anything.",
|
|
196
|
+
rationale:
|
|
197
|
+
"Dead ends and red herrings are expensive to " +
|
|
198
|
+
"re-discover. Saving them as memories gives future " +
|
|
199
|
+
"sessions a shortcut past the same wrong path.",
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
key: "debugging-git-archaeology",
|
|
203
|
+
situation:
|
|
204
|
+
"The user is debugging an issue, investigating a bug, " +
|
|
205
|
+
"or trying to understand why something is broken. " +
|
|
206
|
+
"The agent is about to propose a fix or explanation " +
|
|
207
|
+
"without understanding the history of the code. " +
|
|
208
|
+
"Trigger phrases include: debugging, why is this " +
|
|
209
|
+
"broken, what changed, this used to work, why did " +
|
|
210
|
+
"this stop working, what is going on here, something " +
|
|
211
|
+
"is wrong but I do not know why.",
|
|
212
|
+
behavior:
|
|
213
|
+
"Before proposing a fix, do git archaeology to " +
|
|
214
|
+
"understand the history of the code being debugged. " +
|
|
215
|
+
"Run git log --oneline -- <path> for the affected " +
|
|
216
|
+
"files, then git show <commit> for any commit that " +
|
|
217
|
+
"changed the behavior in question. Look for: " +
|
|
218
|
+
"removed code that left orphaned parts behind, " +
|
|
219
|
+
"renamed functions whose old callers were not " +
|
|
220
|
+
"updated, migrations that were completed partway, " +
|
|
221
|
+
"or behavior that was intentionally removed. " +
|
|
222
|
+
"An old behavior that was removed could have left " +
|
|
223
|
+
"orphaned code causing the current bug. Load " +
|
|
224
|
+
"the thatch-code-archaeology skill for the full " +
|
|
225
|
+
"investigation procedure.",
|
|
226
|
+
rationale:
|
|
227
|
+
"Debugging without understanding history leads to " +
|
|
228
|
+
"fixing symptoms instead of root causes. A removed " +
|
|
229
|
+
"behavior that left orphaned code behind is a " +
|
|
230
|
+
"common source of mysterious bugs, and only git " +
|
|
231
|
+
"archaeology surfaces it.",
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
key: "planning-git-archaeology",
|
|
235
|
+
situation:
|
|
236
|
+
"The user is planning a change, starting a new " +
|
|
237
|
+
"ticket, or about to propose modifications to an " +
|
|
238
|
+
"existing workflow or feature. The agent is " +
|
|
239
|
+
"about to propose an approach without " +
|
|
240
|
+
"understanding the history of the feature. " +
|
|
241
|
+
"Trigger phrases include: planning a change, " +
|
|
242
|
+
"how should we approach, what if we, proposing " +
|
|
243
|
+
"a new approach, redesigning, refactoring this, " +
|
|
244
|
+
"changing how this works.",
|
|
245
|
+
behavior:
|
|
246
|
+
"Before proposing an approach, do git archaeology " +
|
|
247
|
+
"to understand why the code looks the way it " +
|
|
248
|
+
"does. Run git log --oneline -- <path> for " +
|
|
249
|
+
"the files you plan to change, then read the " +
|
|
250
|
+
"commits that established the current design. " +
|
|
251
|
+
"Look for: design decisions that look wrong " +
|
|
252
|
+
"but were correct at the time, migrations " +
|
|
253
|
+
"that were completed partway, or constraints " +
|
|
254
|
+
"that no longer apply. Understanding history " +
|
|
255
|
+
"prevents proposing changes that conflict with " +
|
|
256
|
+
"the original design intent. Load the " +
|
|
257
|
+
"thatch-code-archaeology skill for the full " +
|
|
258
|
+
"investigation procedure.",
|
|
259
|
+
rationale:
|
|
260
|
+
"Planning without history leads to proposing " +
|
|
261
|
+
"changes that fight the original design or " +
|
|
262
|
+
"reintroduce constraints that were deliberately " +
|
|
263
|
+
"removed. Git archaeology is the only way to " +
|
|
264
|
+
"distinguish a design decision from a wart.",
|
|
265
|
+
},
|
|
266
|
+
];
|
|
267
|
+
|
|
268
|
+
const SEED_KEY_TAG = "seed-key:";
|
|
269
|
+
const SEED_VERSION_TAG = "seed-version:";
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Extract the key stamp from a behavior's rationale text.
|
|
273
|
+
* Returns null if no stamp is found (behavior was not seeded by this
|
|
274
|
+
* mechanism, or was manually codified).
|
|
275
|
+
*/
|
|
276
|
+
function extractSeedKey(rationale: string | null): string | null {
|
|
277
|
+
if (!rationale) return null;
|
|
278
|
+
const match = rationale.match(/seed-key:([^\s]+)/);
|
|
279
|
+
return match ? match[1] : null;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Extract the version stamp from a behavior's rationale text.
|
|
284
|
+
* Returns null if no stamp is found (behavior was not seeded by this
|
|
285
|
+
* mechanism, or was manually codified).
|
|
286
|
+
*/
|
|
287
|
+
function extractSeedVersion(rationale: string | null): string | null {
|
|
288
|
+
if (!rationale) return null;
|
|
289
|
+
const match = rationale.match(/seed-version:([^\s]+)/);
|
|
290
|
+
return match ? match[1] : null;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Seed default behaviors into the global store. Idempotent: if a behavior
|
|
295
|
+
* with the same key stamp and version already exists, it is skipped. If
|
|
296
|
+
* the key matches but the version differs, the old behavior is deleted
|
|
297
|
+
* and re-seeded. Behaviors without a key stamp are left alone.
|
|
298
|
+
*/
|
|
299
|
+
export async function seedDefaultBehaviors(db: ThatchDB, model: EmbeddingModel): Promise<void> {
|
|
300
|
+
const currentVersion = pkg.version;
|
|
301
|
+
|
|
302
|
+
for (const { key, situation, behavior, rationale } of DEFAULT_BEHAVIORS) {
|
|
303
|
+
try {
|
|
304
|
+
const sitEmbed = await model.passageEmbed(situation);
|
|
305
|
+
const behaviorEmbed = await model.passageEmbed(behavior);
|
|
306
|
+
const stampedRationale = `${rationale} ${SEED_KEY_TAG}${key} ${SEED_VERSION_TAG}${currentVersion}`;
|
|
307
|
+
|
|
308
|
+
// Find an existing seeded behavior with this key. Lookup is by
|
|
309
|
+
// key stamp in the rationale, not by cosine similarity of the
|
|
310
|
+
// situation text. See the file-level comment for why.
|
|
311
|
+
const allBehaviors = db.listBehaviors("global");
|
|
312
|
+
const existing = allBehaviors.find((b) => extractSeedKey(b.rationale) === key);
|
|
313
|
+
|
|
314
|
+
if (existing) {
|
|
315
|
+
const storedVersion = extractSeedVersion(existing.rationale);
|
|
316
|
+
if (storedVersion === currentVersion) continue;
|
|
317
|
+
|
|
318
|
+
// Version mismatch: delete the old behavior and re-seed.
|
|
319
|
+
// Edges and provenance cascade-delete with the behavior.
|
|
320
|
+
// The matcher survives and is reused below.
|
|
321
|
+
db.deleteBehavior(existing.id);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Reuse the existing matcher if the deleted behavior had one.
|
|
325
|
+
// Otherwise find a matcher by cosine similarity (a user may have
|
|
326
|
+
// codified a behavior for the same situation, creating a matcher
|
|
327
|
+
// we can share). If none found, create a new one.
|
|
328
|
+
const matcherId = existing?.matchers[0]?.id
|
|
329
|
+
?? db.findNearestBehaviorMatcher("global", sitEmbed, 0.85)?.id
|
|
330
|
+
?? db.createBehaviorMatcher("global", situation, sitEmbed, model.name);
|
|
331
|
+
|
|
332
|
+
const behaviorId = db.createBehavior("global", behavior, stampedRationale, behaviorEmbed, model.name);
|
|
333
|
+
db.createBehaviorEdge(matcherId, behaviorId, 1.0);
|
|
334
|
+
db.addBehaviorProvenance(behaviorId, "codify", stampedRationale);
|
|
335
|
+
} catch (err) {
|
|
336
|
+
console.error(`[thatch] default behavior seed failed: ${err}`);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
package/src/setup.ts
CHANGED
|
@@ -212,10 +212,6 @@ function replaceThatchHooks(
|
|
|
212
212
|
// Skills - install to $CLAUDE_CONFIG_DIR/skills/ (or ~/.claude/skills/ by default)
|
|
213
213
|
// ---------------------------------------------------------------------------
|
|
214
214
|
|
|
215
|
-
function installClaudeSkills(skillsDir: string): SkillFile[] {
|
|
216
|
-
return installSkills(skillsDir);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
215
|
// ---------------------------------------------------------------------------
|
|
220
216
|
// Main setup entry point
|
|
221
217
|
// ---------------------------------------------------------------------------
|
|
@@ -261,7 +257,7 @@ export function setupClaudeCode(
|
|
|
261
257
|
|
|
262
258
|
appendInstructions(paths.claudeMdPath);
|
|
263
259
|
writeHooks(paths.settingsPath, thatchBin);
|
|
264
|
-
const skills =
|
|
260
|
+
const skills = installSkills(paths.skillsDir);
|
|
265
261
|
|
|
266
262
|
return {
|
|
267
263
|
mcpConfig: paths.mcpConfigPath,
|
|
@@ -410,7 +406,7 @@ export function setupCursor(
|
|
|
410
406
|
writeMcpConfig(paths.mcpConfigPath, thatchBin);
|
|
411
407
|
appendCursorInstructions(paths.agentsMdPath);
|
|
412
408
|
writeCursorHooks(paths.hooksPath, thatchBin);
|
|
413
|
-
const skills =
|
|
409
|
+
const skills = installSkills(paths.skillsDir);
|
|
414
410
|
|
|
415
411
|
return {
|
|
416
412
|
mcpConfig: paths.mcpConfigPath,
|
package/src/sideband.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { unlinkSync } from "node:fs";
|
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { createServer, connect, type Server, type Socket } from "node:net";
|
|
6
|
-
import type { ThatchDB, PredictionNudgeItem } from "./db";
|
|
6
|
+
import type { ThatchDB, PredictionNudgeItem, BehaviorNudgeItem } from "./db";
|
|
7
7
|
import type { EmbeddingModel } from "./embeddings";
|
|
8
8
|
|
|
9
9
|
/**
|
|
@@ -24,7 +24,7 @@ export interface SidebandMatch {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
interface MatchRequest {
|
|
27
|
-
method: "match" | "predictions";
|
|
27
|
+
method: "match" | "predictions" | "behaviors";
|
|
28
28
|
text: string;
|
|
29
29
|
stores: string[];
|
|
30
30
|
threshold: number;
|
|
@@ -35,6 +35,7 @@ interface MatchResponse {
|
|
|
35
35
|
ok: boolean;
|
|
36
36
|
matches?: SidebandMatch[];
|
|
37
37
|
predictions?: PredictionNudgeItem[];
|
|
38
|
+
behaviors?: BehaviorNudgeItem[];
|
|
38
39
|
error?: string;
|
|
39
40
|
}
|
|
40
41
|
|
|
@@ -45,9 +46,12 @@ interface MatchResponse {
|
|
|
45
46
|
* the ~34 MB model themselves.
|
|
46
47
|
*
|
|
47
48
|
* The protocol is newline-delimited JSON: one request per connection, one
|
|
48
|
-
* response per connection. Requests are `{"method":"match
|
|
49
|
-
* "stores":[...],"threshold":N,"limit":N}`.
|
|
50
|
-
*
|
|
49
|
+
* response per connection. Requests are `{"method":"match|predictions|
|
|
50
|
+
* behaviors","text":"...","stores":[...],"threshold":N,"limit":N}`.
|
|
51
|
+
* The `match` method searches memories; `predictions` and `behaviors`
|
|
52
|
+
* score prediction/behavior nudges. Responses are `{"ok":true,
|
|
53
|
+
* "matches":[...]}` (or `"predictions":[...]"` / `"behaviors":[...]`)
|
|
54
|
+
* or `{"ok":false,"error":"..."}`.
|
|
51
55
|
*/
|
|
52
56
|
export class SidebandServer {
|
|
53
57
|
#server: Server | null = null;
|
|
@@ -116,7 +120,7 @@ export class SidebandServer {
|
|
|
116
120
|
return;
|
|
117
121
|
}
|
|
118
122
|
|
|
119
|
-
if (req.method !== "match" && req.method !== "predictions") {
|
|
123
|
+
if (req.method !== "match" && req.method !== "predictions" && req.method !== "behaviors") {
|
|
120
124
|
this.#respond(socket, { ok: false, error: `Unknown method: ${req.method}` });
|
|
121
125
|
return;
|
|
122
126
|
}
|
|
@@ -126,12 +130,20 @@ export class SidebandServer {
|
|
|
126
130
|
|
|
127
131
|
if (req.method === "predictions") {
|
|
128
132
|
const predictions = this.#db.scorePredictionNudge(
|
|
129
|
-
req.stores, embedding, req.threshold,
|
|
133
|
+
req.stores, embedding, req.threshold, req.limit,
|
|
130
134
|
);
|
|
131
135
|
this.#respond(socket, { ok: true, predictions });
|
|
132
136
|
return;
|
|
133
137
|
}
|
|
134
138
|
|
|
139
|
+
if (req.method === "behaviors") {
|
|
140
|
+
const behaviors = this.#db.scoreBehaviorNudge(
|
|
141
|
+
req.stores, embedding, req.threshold, req.limit,
|
|
142
|
+
);
|
|
143
|
+
this.#respond(socket, { ok: true, behaviors });
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
135
147
|
const results = this.#db.search(req.stores, embedding, { limit: req.limit });
|
|
136
148
|
const matches: SidebandMatch[] = results
|
|
137
149
|
.filter((r) => r._score >= req.threshold)
|
|
@@ -186,8 +198,24 @@ export function sidebandPredictions(
|
|
|
186
198
|
.then((res) => res?.predictions ?? null);
|
|
187
199
|
}
|
|
188
200
|
|
|
201
|
+
/**
|
|
202
|
+
* Connect to the sideband server and request scored behaviors for a prompt.
|
|
203
|
+
* Returns null on any failure; callers treat null as "skip the behavior nudge."
|
|
204
|
+
*/
|
|
205
|
+
export function sidebandBehaviors(
|
|
206
|
+
socketPath: string,
|
|
207
|
+
text: string,
|
|
208
|
+
stores: string[],
|
|
209
|
+
threshold: number,
|
|
210
|
+
limit: number,
|
|
211
|
+
timeoutMs = 2000,
|
|
212
|
+
): Promise<BehaviorNudgeItem[] | null> {
|
|
213
|
+
return sidebandRequest("behaviors", socketPath, text, stores, threshold, limit, timeoutMs)
|
|
214
|
+
.then((res) => res?.behaviors ?? null);
|
|
215
|
+
}
|
|
216
|
+
|
|
189
217
|
function sidebandRequest(
|
|
190
|
-
method: "match" | "predictions",
|
|
218
|
+
method: "match" | "predictions" | "behaviors",
|
|
191
219
|
socketPath: string,
|
|
192
220
|
text: string,
|
|
193
221
|
stores: string[],
|
package/src/skills.ts
CHANGED