@mrdrmccoy/xerj-opencode-plugin 0.4.5 → 0.4.6
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/package.json +1 -1
- package/src/hooks/session.ts +1 -0
- package/src/index/watcher.ts +12 -1
- package/src/search/rank.ts +20 -0
- package/src/search/service.ts +10 -2
package/package.json
CHANGED
package/src/hooks/session.ts
CHANGED
package/src/index/watcher.ts
CHANGED
|
@@ -50,6 +50,10 @@ const STDERR_TAIL_LINES = 20;
|
|
|
50
50
|
// The engine refuses a second watcher for the same journal and says so.
|
|
51
51
|
const CONTENTION_MARKER = "already owns";
|
|
52
52
|
const CONTENTION_RETRY_MS = 5_000;
|
|
53
|
+
// A watcher that survives this long is watching, even before its first run
|
|
54
|
+
// document lands: an initial pass over a large corpus takes minutes, and the
|
|
55
|
+
// status would otherwise read "starting" the whole time.
|
|
56
|
+
const WATCH_SETTLE_MS = 3_000;
|
|
53
57
|
|
|
54
58
|
export class CorpusService {
|
|
55
59
|
readonly layout: CorpusLayout;
|
|
@@ -66,6 +70,7 @@ export class CorpusService {
|
|
|
66
70
|
private stopped = false;
|
|
67
71
|
private spawning = false;
|
|
68
72
|
private lastStderr = "";
|
|
73
|
+
private spawnedAt = 0;
|
|
69
74
|
|
|
70
75
|
constructor(private readonly options: CorpusServiceOptions) {
|
|
71
76
|
this.layout = corpusLayout(options.projectRoot, options.prefix, options.dataRoot);
|
|
@@ -128,8 +133,13 @@ export class CorpusService {
|
|
|
128
133
|
}
|
|
129
134
|
|
|
130
135
|
snapshot(): CorpusSnapshot {
|
|
136
|
+
const settled =
|
|
137
|
+
this.state === "starting" &&
|
|
138
|
+
this.child !== null &&
|
|
139
|
+
Date.now() - this.spawnedAt >= WATCH_SETTLE_MS;
|
|
140
|
+
|
|
131
141
|
return {
|
|
132
|
-
state: this.state,
|
|
142
|
+
state: settled ? "watching" : this.state,
|
|
133
143
|
lastRun: this.lastRun,
|
|
134
144
|
restarts: this.restarts,
|
|
135
145
|
runs: this.runs,
|
|
@@ -165,6 +175,7 @@ export class CorpusService {
|
|
|
165
175
|
this.child = child;
|
|
166
176
|
this.spawning = false;
|
|
167
177
|
this.lastStderr = "";
|
|
178
|
+
this.spawnedAt = Date.now();
|
|
168
179
|
if (watch) this.state = "starting";
|
|
169
180
|
|
|
170
181
|
this.attachOutput(child);
|
package/src/search/rank.ts
CHANGED
|
@@ -59,3 +59,23 @@ export function demoteGenerated(
|
|
|
59
59
|
})
|
|
60
60
|
.sort((left, right) => right.score - left.score);
|
|
61
61
|
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Drops generated files the query does not name. Injection shares one small
|
|
65
|
+
* budget across a whole turn, and a lockfile passage matches on dependency
|
|
66
|
+
* names that have nothing to do with the question, so it can crowd out the
|
|
67
|
+
* source that answers it. Tool searches keep them: the caller asked directly.
|
|
68
|
+
*/
|
|
69
|
+
export function dropGenerated(
|
|
70
|
+
hits: ReadonlyArray<ParsedHit>,
|
|
71
|
+
query: string,
|
|
72
|
+
): ParsedHit[] {
|
|
73
|
+
const named = query.toLowerCase();
|
|
74
|
+
|
|
75
|
+
return hits.filter((hit) => {
|
|
76
|
+
if (hit.path === null) return true;
|
|
77
|
+
if (!isGeneratedPath(hit.path)) return true;
|
|
78
|
+
const basename = hit.path.toLowerCase().split("/").pop() ?? "";
|
|
79
|
+
return named.includes(basename);
|
|
80
|
+
});
|
|
81
|
+
}
|
package/src/search/service.ts
CHANGED
|
@@ -14,7 +14,7 @@ import type { Logger } from "../log.ts";
|
|
|
14
14
|
import { XerjClient } from "../node/client.ts";
|
|
15
15
|
import type { BrainSpec } from "../paths.ts";
|
|
16
16
|
import type { BrainGraph } from "./brains.ts";
|
|
17
|
-
import { demoteGenerated } from "./rank.ts";
|
|
17
|
+
import { demoteGenerated, dropGenerated } from "./rank.ts";
|
|
18
18
|
|
|
19
19
|
export interface SearchHit {
|
|
20
20
|
readonly index: string;
|
|
@@ -36,7 +36,14 @@ export interface SearchResult {
|
|
|
36
36
|
readonly narrowingBytes: number;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Where the results go. Injection shares a small budget across a whole turn,
|
|
41
|
+
* so it is stricter than an explicit tool call about generated files.
|
|
42
|
+
*/
|
|
43
|
+
export type SearchSurface = "context" | "tool";
|
|
44
|
+
|
|
39
45
|
export interface SearchOptions {
|
|
46
|
+
readonly surface?: SearchSurface;
|
|
40
47
|
readonly k?: number;
|
|
41
48
|
readonly maxTokens?: number;
|
|
42
49
|
readonly signal?: AbortSignal;
|
|
@@ -132,7 +139,8 @@ export class SearchService {
|
|
|
132
139
|
|
|
133
140
|
const strategy = semantic ? "hybrid" : "lexical";
|
|
134
141
|
const fused = semantic ? fuse([lexical.hits, semantic.hits], k) : lexical.hits.slice(0, k);
|
|
135
|
-
const
|
|
142
|
+
const ranked = demoteGenerated(this.locateBrainPaths(fused), trimmed);
|
|
143
|
+
const located = options.surface === "context" ? dropGenerated(ranked, trimmed) : ranked;
|
|
136
144
|
const resolved = await this.resolveLines(located, trimmed);
|
|
137
145
|
const hits = dropShadowedWholeFiles(
|
|
138
146
|
resolved,
|