@wrongstack/tools 0.296.4 → 0.298.0
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/dist/_spawn-stream.d.ts +2 -0
- package/dist/_util.d.ts +29 -1
- package/dist/audit.js +128 -65
- package/dist/bash.js +20 -4
- package/dist/batch-tool-use.js +70 -7
- package/dist/browser/index.js +31 -1
- package/dist/builtin.js +883 -351
- package/dist/codebase-index/index.js +95 -16
- package/dist/codebase-index/project-server-protocol.d.ts +26 -0
- package/dist/codebase-index/project-server.js +76 -16
- package/dist/codebase-index/worker.js +36 -13
- package/dist/codebase-index/writer-search-helpers.d.ts +16 -0
- package/dist/codebase-index/writer.d.ts +13 -1
- package/dist/design.d.ts +1 -1
- package/dist/design.js +4 -3
- package/dist/diff.js +11 -1
- package/dist/e2e.js +9 -6
- package/dist/edit.js +9 -6
- package/dist/exec.js +562 -516
- package/dist/format.js +131 -65
- package/dist/git.js +22 -2
- package/dist/glob.js +11 -5
- package/dist/grep.js +160 -104
- package/dist/index.js +834 -290
- package/dist/install.js +135 -68
- package/dist/json.js +64 -18
- package/dist/kanban-board-inputs.d.ts +21 -4
- package/dist/kanban.js +20 -5
- package/dist/languages/index.js +143 -71
- package/dist/lint.js +128 -65
- package/dist/next-steps.d.ts +23 -0
- package/dist/next-steps.js +4 -0
- package/dist/outdated.js +101 -26
- package/dist/pack.js +883 -351
- package/dist/patch.js +11 -1
- package/dist/plan.js +32 -10
- package/dist/process-registry.d.ts +9 -0
- package/dist/process-registry.js +15 -0
- package/dist/ps-slash.js +15 -0
- package/dist/read.js +104 -23
- package/dist/replace.js +11 -3
- package/dist/scaffold.js +9 -6
- package/dist/session-kanban.js +21 -5
- package/dist/task.js +3 -1
- package/dist/test.js +128 -65
- package/dist/todo.js +3 -1
- package/dist/tool-tier.js +883 -351
- package/dist/tool-use.d.ts +1 -1
- package/dist/tool-use.js +28 -4
- package/dist/tree.d.ts +1 -0
- package/dist/tree.js +44 -8
- package/dist/typecheck.js +128 -65
- package/dist/write.js +9 -6
- package/package.json +5 -4
- package/dist/_concurrency.d.ts.map +0 -1
- package/dist/_danger-detect.d.ts.map +0 -1
- package/dist/_edit-match.d.ts.map +0 -1
- package/dist/_env.d.ts.map +0 -1
- package/dist/_fetch-guard.d.ts.map +0 -1
- package/dist/_output-spool.d.ts.map +0 -1
- package/dist/_redact-command.d.ts.map +0 -1
- package/dist/_regex.d.ts.map +0 -1
- package/dist/_session-shell.d.ts.map +0 -1
- package/dist/_shell-pick.d.ts.map +0 -1
- package/dist/_spawn-stream.d.ts.map +0 -1
- package/dist/_syntax-check.d.ts.map +0 -1
- package/dist/_util.d.ts.map +0 -1
- package/dist/_win32-resolve.d.ts.map +0 -1
- package/dist/audit.d.ts.map +0 -1
- package/dist/audit.js.map +0 -7
- package/dist/auto-proceed-loop-guard.d.ts.map +0 -1
- package/dist/auto-proceed-loop-guard.js.map +0 -7
- package/dist/bash-kill-guard.d.ts.map +0 -1
- package/dist/bash.d.ts.map +0 -1
- package/dist/bash.js.map +0 -7
- package/dist/batch-tool-use.d.ts.map +0 -1
- package/dist/batch-tool-use.js.map +0 -7
- package/dist/browser/artifacts.d.ts.map +0 -1
- package/dist/browser/index.d.ts.map +0 -1
- package/dist/browser/index.js.map +0 -7
- package/dist/browser/manager.d.ts.map +0 -1
- package/dist/browser/network-guard-proxy.d.ts.map +0 -1
- package/dist/browser/security.d.ts.map +0 -1
- package/dist/browser/tools.d.ts.map +0 -1
- package/dist/browser/types.d.ts.map +0 -1
- package/dist/builtin.d.ts.map +0 -1
- package/dist/builtin.js.map +0 -7
- package/dist/circuit-breaker.d.ts.map +0 -1
- package/dist/circuit-breaker.js.map +0 -7
- package/dist/codebase-index/background-indexer.d.ts.map +0 -1
- package/dist/codebase-index/bm25.d.ts.map +0 -1
- package/dist/codebase-index/circuit-breaker.d.ts.map +0 -1
- package/dist/codebase-index/codebase-index-tool.d.ts.map +0 -1
- package/dist/codebase-index/codebase-search-tool.d.ts.map +0 -1
- package/dist/codebase-index/codebase-stats-tool.d.ts.map +0 -1
- package/dist/codebase-index/dead-code-scan.d.ts.map +0 -1
- package/dist/codebase-index/generic-parser.d.ts.map +0 -1
- package/dist/codebase-index/gitignore.d.ts.map +0 -1
- package/dist/codebase-index/go-parser.d.ts.map +0 -1
- package/dist/codebase-index/index-service.d.ts.map +0 -1
- package/dist/codebase-index/index.d.ts.map +0 -1
- package/dist/codebase-index/index.js.map +0 -7
- package/dist/codebase-index/indexer.d.ts.map +0 -1
- package/dist/codebase-index/json-parser.d.ts.map +0 -1
- package/dist/codebase-index/languages.d.ts.map +0 -1
- package/dist/codebase-index/lsp-kind.d.ts.map +0 -1
- package/dist/codebase-index/parser-dispatch.d.ts.map +0 -1
- package/dist/codebase-index/project-server-cache.d.ts.map +0 -1
- package/dist/codebase-index/project-server-client.d.ts.map +0 -1
- package/dist/codebase-index/project-server-endpoint.d.ts.map +0 -1
- package/dist/codebase-index/project-server-protocol.d.ts.map +0 -1
- package/dist/codebase-index/project-server.d.ts.map +0 -1
- package/dist/codebase-index/project-server.js.map +0 -7
- package/dist/codebase-index/py-parser.d.ts.map +0 -1
- package/dist/codebase-index/refs-extractor.d.ts.map +0 -1
- package/dist/codebase-index/rs-parser.d.ts.map +0 -1
- package/dist/codebase-index/schema.d.ts.map +0 -1
- package/dist/codebase-index/spawn-gate.d.ts.map +0 -1
- package/dist/codebase-index/sqlite-runtime.d.ts.map +0 -1
- package/dist/codebase-index/ts-parser.d.ts.map +0 -1
- package/dist/codebase-index/worker-protocol.d.ts.map +0 -1
- package/dist/codebase-index/worker.d.ts.map +0 -1
- package/dist/codebase-index/worker.js.map +0 -7
- package/dist/codebase-index/writer-admin.d.ts.map +0 -1
- package/dist/codebase-index/writer-bulk-insert.d.ts.map +0 -1
- package/dist/codebase-index/writer-graph-helpers.d.ts.map +0 -1
- package/dist/codebase-index/writer-graph-reader.d.ts.map +0 -1
- package/dist/codebase-index/writer-helpers.d.ts.map +0 -1
- package/dist/codebase-index/writer-pragmas.d.ts.map +0 -1
- package/dist/codebase-index/writer-ref-mapper.d.ts.map +0 -1
- package/dist/codebase-index/writer-schema.d.ts.map +0 -1
- package/dist/codebase-index/writer-search-helpers.d.ts.map +0 -1
- package/dist/codebase-index/writer-store-pool.d.ts.map +0 -1
- package/dist/codebase-index/writer.d.ts.map +0 -1
- package/dist/codebase-index/yaml-parser.d.ts.map +0 -1
- package/dist/design.d.ts.map +0 -1
- package/dist/design.js.map +0 -7
- package/dist/diff.d.ts.map +0 -1
- package/dist/diff.js.map +0 -7
- package/dist/document.d.ts.map +0 -1
- package/dist/document.js.map +0 -7
- package/dist/e2e.d.ts.map +0 -1
- package/dist/e2e.js.map +0 -7
- package/dist/edit.d.ts.map +0 -1
- package/dist/edit.js.map +0 -7
- package/dist/exec-kill-guard.d.ts.map +0 -1
- package/dist/exec.d.ts.map +0 -1
- package/dist/exec.js.map +0 -7
- package/dist/fetch.d.ts.map +0 -1
- package/dist/fetch.js.map +0 -7
- package/dist/format.d.ts.map +0 -1
- package/dist/format.js.map +0 -7
- package/dist/git.d.ts.map +0 -1
- package/dist/git.js.map +0 -7
- package/dist/glob.d.ts.map +0 -1
- package/dist/glob.js.map +0 -7
- package/dist/grep.d.ts.map +0 -1
- package/dist/grep.js.map +0 -7
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -7
- package/dist/install.d.ts.map +0 -1
- package/dist/install.js.map +0 -7
- package/dist/json.d.ts.map +0 -1
- package/dist/json.js.map +0 -7
- package/dist/kanban-board-inputs.d.ts.map +0 -1
- package/dist/kanban-decomposition-actions.d.ts.map +0 -1
- package/dist/kanban-detail-actions.d.ts.map +0 -1
- package/dist/kanban-evidence-bridge.d.ts.map +0 -1
- package/dist/kanban-presence.d.ts.map +0 -1
- package/dist/kanban-split-task-handler.d.ts.map +0 -1
- package/dist/kanban-task-inputs.d.ts.map +0 -1
- package/dist/kanban-tool-results.d.ts.map +0 -1
- package/dist/kanban-tool-schema.d.ts.map +0 -1
- package/dist/kanban-tool-types.d.ts.map +0 -1
- package/dist/kanban.d.ts.map +0 -1
- package/dist/kanban.js.map +0 -7
- package/dist/languages/detect.d.ts.map +0 -1
- package/dist/languages/diagnostics.d.ts.map +0 -1
- package/dist/languages/execute-tool.d.ts.map +0 -1
- package/dist/languages/execute.d.ts.map +0 -1
- package/dist/languages/index.d.ts.map +0 -1
- package/dist/languages/index.js.map +0 -7
- package/dist/languages/legacy-bridge.d.ts.map +0 -1
- package/dist/languages/package-tool.d.ts.map +0 -1
- package/dist/languages/plan.d.ts.map +0 -1
- package/dist/languages/profile-helpers.d.ts.map +0 -1
- package/dist/languages/profiles/additional.d.ts.map +0 -1
- package/dist/languages/profiles/primary.d.ts.map +0 -1
- package/dist/languages/registry.d.ts.map +0 -1
- package/dist/languages/tool.d.ts.map +0 -1
- package/dist/languages/types.d.ts.map +0 -1
- package/dist/lint.d.ts.map +0 -1
- package/dist/lint.js.map +0 -7
- package/dist/logs.d.ts.map +0 -1
- package/dist/logs.js.map +0 -7
- package/dist/memory.d.ts.map +0 -1
- package/dist/memory.js.map +0 -7
- package/dist/mode.d.ts.map +0 -1
- package/dist/mode.js.map +0 -7
- package/dist/next-steps.d.ts.map +0 -1
- package/dist/next-steps.js.map +0 -7
- package/dist/outdated.d.ts.map +0 -1
- package/dist/outdated.js.map +0 -7
- package/dist/pack.d.ts.map +0 -1
- package/dist/pack.js.map +0 -7
- package/dist/patch.d.ts.map +0 -1
- package/dist/patch.js.map +0 -7
- package/dist/plan.d.ts.map +0 -1
- package/dist/plan.js.map +0 -7
- package/dist/process-guardian.d.ts.map +0 -1
- package/dist/process-registry-persistent.d.ts.map +0 -1
- package/dist/process-registry.d.ts.map +0 -1
- package/dist/process-registry.js.map +0 -7
- package/dist/ps-slash.d.ts.map +0 -1
- package/dist/ps-slash.js.map +0 -7
- package/dist/read.d.ts.map +0 -1
- package/dist/read.js.map +0 -7
- package/dist/replace.d.ts.map +0 -1
- package/dist/replace.js.map +0 -7
- package/dist/scaffold.d.ts.map +0 -1
- package/dist/scaffold.js.map +0 -7
- package/dist/search.d.ts.map +0 -1
- package/dist/search.js.map +0 -7
- package/dist/session-kanban.d.ts.map +0 -1
- package/dist/session-kanban.js.map +0 -7
- package/dist/set-working-dir.d.ts.map +0 -1
- package/dist/skill.d.ts.map +0 -1
- package/dist/skill.js.map +0 -7
- package/dist/spawn-background.d.ts.map +0 -1
- package/dist/task.d.ts.map +0 -1
- package/dist/task.js.map +0 -7
- package/dist/test.d.ts.map +0 -1
- package/dist/test.js.map +0 -7
- package/dist/todo.d.ts.map +0 -1
- package/dist/todo.js.map +0 -7
- package/dist/tool-diff.d.ts.map +0 -1
- package/dist/tool-diff.js.map +0 -7
- package/dist/tool-help.d.ts.map +0 -1
- package/dist/tool-help.js.map +0 -7
- package/dist/tool-icon-map.d.ts.map +0 -1
- package/dist/tool-icons.d.ts.map +0 -1
- package/dist/tool-icons.js.map +0 -7
- package/dist/tool-search.d.ts.map +0 -1
- package/dist/tool-search.js.map +0 -7
- package/dist/tool-summary.d.ts.map +0 -1
- package/dist/tool-summary.js.map +0 -7
- package/dist/tool-tier.d.ts.map +0 -1
- package/dist/tool-tier.js.map +0 -7
- package/dist/tool-use.d.ts.map +0 -1
- package/dist/tool-use.js.map +0 -7
- package/dist/tree.d.ts.map +0 -1
- package/dist/tree.js.map +0 -7
- package/dist/typecheck.d.ts.map +0 -1
- package/dist/typecheck.js.map +0 -7
- package/dist/win32.d.ts.map +0 -1
- package/dist/win32.js.map +0 -7
- package/dist/write.d.ts.map +0 -1
- package/dist/write.js.map +0 -7
|
@@ -2875,6 +2875,7 @@ var REFS_INDEX_SQL = [
|
|
|
2875
2875
|
var SYMBOLS_FTS_SQL = "CREATE VIRTUAL TABLE IF NOT EXISTS symbols_fts USING fts5(text, tokenize = 'unicode61')";
|
|
2876
2876
|
|
|
2877
2877
|
// src/codebase-index/writer-search-helpers.ts
|
|
2878
|
+
var SEARCH_CANDIDATE_SCAN_CAP = 5e3;
|
|
2878
2879
|
function normalizeSearchLimit(limit) {
|
|
2879
2880
|
return typeof limit === "number" && Number.isFinite(limit) ? Math.max(0, Math.trunc(limit)) : void 0;
|
|
2880
2881
|
}
|
|
@@ -2904,8 +2905,8 @@ function buildWriterSearchWhere(query, filter) {
|
|
|
2904
2905
|
}
|
|
2905
2906
|
if (query.trim()) {
|
|
2906
2907
|
const tokens = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
2907
|
-
conditions.push(`(${tokens.map(() => "text LIKE ?").join(" OR ")})`);
|
|
2908
|
-
for (const token of tokens) values.push(`%${token}%`);
|
|
2908
|
+
conditions.push(`(${tokens.map(() => "text LIKE ? ESCAPE '\\'").join(" OR ")})`);
|
|
2909
|
+
for (const token of tokens) values.push(`%${escapeLike(token)}%`);
|
|
2909
2910
|
}
|
|
2910
2911
|
return { where: conditions.length ? `WHERE ${conditions.join(" AND ")}` : "", values };
|
|
2911
2912
|
}
|
|
@@ -3014,6 +3015,7 @@ var StorePool = class {
|
|
|
3014
3015
|
|
|
3015
3016
|
// src/codebase-index/writer.ts
|
|
3016
3017
|
var DB_FILE2 = "index.db";
|
|
3018
|
+
var MAX_STATEMENT_CACHE = 128;
|
|
3017
3019
|
var IndexStore = class _IndexStore {
|
|
3018
3020
|
db;
|
|
3019
3021
|
/** Absolute path to this project's index directory. */
|
|
@@ -3051,12 +3053,31 @@ var IndexStore = class _IndexStore {
|
|
|
3051
3053
|
// Dirty on open so the first getOrBuildBm25() rebuilds against current rows;
|
|
3052
3054
|
// an empty or pre-existing corpus makes a stale IDF table meaningless.
|
|
3053
3055
|
bm25Dirty = true;
|
|
3054
|
-
/**
|
|
3056
|
+
/**
|
|
3057
|
+
* Prepare-once helper: compile `sql` on first use, reuse thereafter.
|
|
3058
|
+
*
|
|
3059
|
+
* Bounded LRU rather than an open Map. The cache is keyed by SQL TEXT, and
|
|
3060
|
+
* the fallback search builder emits one `text LIKE ?` clause per query token
|
|
3061
|
+
* — so the SQL varies with the token count and a stream of differently-sized
|
|
3062
|
+
* queries grew the cache without limit. Sage's store already bounds its
|
|
3063
|
+
* equivalent at 128 (WS-096).
|
|
3064
|
+
*
|
|
3065
|
+
* Re-inserting on a hit keeps the hot fixed-SQL statements (upsertFile,
|
|
3066
|
+
* insertRefs, …) at the young end, so a burst of one-off search SQL evicts
|
|
3067
|
+
* itself rather than the reindex hot path.
|
|
3068
|
+
*/
|
|
3055
3069
|
stmt(sql) {
|
|
3056
|
-
|
|
3057
|
-
if (
|
|
3058
|
-
|
|
3059
|
-
this.stmtCache.set(sql,
|
|
3070
|
+
const cached = this.stmtCache.get(sql);
|
|
3071
|
+
if (cached !== void 0) {
|
|
3072
|
+
this.stmtCache.delete(sql);
|
|
3073
|
+
this.stmtCache.set(sql, cached);
|
|
3074
|
+
return cached;
|
|
3075
|
+
}
|
|
3076
|
+
const s = this.db.prepare(sql);
|
|
3077
|
+
this.stmtCache.set(sql, s);
|
|
3078
|
+
if (this.stmtCache.size > MAX_STATEMENT_CACHE) {
|
|
3079
|
+
const oldest = this.stmtCache.keys().next();
|
|
3080
|
+
if (!oldest.done) this.stmtCache.delete(oldest.value);
|
|
3060
3081
|
}
|
|
3061
3082
|
return s;
|
|
3062
3083
|
}
|
|
@@ -3329,7 +3350,7 @@ var IndexStore = class _IndexStore {
|
|
|
3329
3350
|
const { where, values } = built;
|
|
3330
3351
|
const limit = normalizeSearchLimit(opts?.limit);
|
|
3331
3352
|
const limitSql = limit !== void 0 ? " LIMIT ?" : "";
|
|
3332
|
-
const sql = `SELECT id, lang, kind, name, file, line, col, signature, doc_comment
|
|
3353
|
+
const sql = `SELECT id, lang, kind, name, file, line, col, signature, doc_comment FROM symbols ${where}${limitSql}`;
|
|
3333
3354
|
const binds = limit !== void 0 ? [...values, limit] : values;
|
|
3334
3355
|
const rows = this.stmt(sql).all(
|
|
3335
3356
|
...binds
|
|
@@ -3458,11 +3479,13 @@ var IndexStore = class _IndexStore {
|
|
|
3458
3479
|
/** Legacy ranked path: LIKE candidates + in-process BM25 + JS snippets. */
|
|
3459
3480
|
searchRankedFallback(query, filter, limit) {
|
|
3460
3481
|
if (!query.trim()) {
|
|
3461
|
-
const
|
|
3462
|
-
if (
|
|
3463
|
-
return { results: this.search(query, filter, { limit }), total };
|
|
3482
|
+
const total2 = this.countSearch(query, filter);
|
|
3483
|
+
if (total2 === 0) return { results: [], total: 0 };
|
|
3484
|
+
return { results: this.search(query, filter, { limit }), total: total2 };
|
|
3464
3485
|
}
|
|
3465
|
-
const
|
|
3486
|
+
const total = this.countSearch(query, filter);
|
|
3487
|
+
if (total === 0) return { results: [], total: 0 };
|
|
3488
|
+
const candidates = this.search(query, filter, { limit: SEARCH_CANDIDATE_SCAN_CAP });
|
|
3466
3489
|
if (candidates.length === 0) return { results: [], total: 0 };
|
|
3467
3490
|
const candidateById = new Map(candidates.map((c) => [c.id, c]));
|
|
3468
3491
|
const bm25 = this.getOrBuildBm25();
|
|
@@ -3488,7 +3511,7 @@ var IndexStore = class _IndexStore {
|
|
|
3488
3511
|
const c = expectDefined(candidateById.get(id));
|
|
3489
3512
|
return { ...c, score, snippet: bm25.extractSnippet(id, qTokens) };
|
|
3490
3513
|
});
|
|
3491
|
-
return { results, total
|
|
3514
|
+
return { results, total };
|
|
3492
3515
|
}
|
|
3493
3516
|
getAllIndexable() {
|
|
3494
3517
|
return getAllIndexableWithStatement((sql) => this.stmt(sql));
|
|
@@ -4082,6 +4105,8 @@ var ProjectServerConnection = class {
|
|
|
4082
4105
|
connectResolve = null;
|
|
4083
4106
|
connectReject = null;
|
|
4084
4107
|
nextId = 1;
|
|
4108
|
+
/** WS-027: read from the owner-only metadata file — see currentAuthToken. */
|
|
4109
|
+
authToken;
|
|
4085
4110
|
pending = /* @__PURE__ */ new Map();
|
|
4086
4111
|
transition(status, options = {}) {
|
|
4087
4112
|
const previous = connectionStates.get(this.endpoint);
|
|
@@ -4102,6 +4127,10 @@ var ProjectServerConnection = class {
|
|
|
4102
4127
|
isConnected() {
|
|
4103
4128
|
return this.socket !== null && !this.socket.destroyed && this.info !== null;
|
|
4104
4129
|
}
|
|
4130
|
+
/** Safe LRU candidate: no request, connect, or health probe is in flight. */
|
|
4131
|
+
isEvictable() {
|
|
4132
|
+
return this.pending.size === 0 && this.connecting === null && this.healthCheck === null;
|
|
4133
|
+
}
|
|
4105
4134
|
async checkHealth(spawnIfMissing = false, timeoutMs = SERVER_HEALTH_TIMEOUT_MS) {
|
|
4106
4135
|
await this.ensureConnected(spawnIfMissing);
|
|
4107
4136
|
if (this.healthCheck) return this.healthCheck;
|
|
@@ -4214,6 +4243,31 @@ var ProjectServerConnection = class {
|
|
|
4214
4243
|
this.transition("offline");
|
|
4215
4244
|
maybeStopHeartbeatLoop();
|
|
4216
4245
|
}
|
|
4246
|
+
/**
|
|
4247
|
+
* WS-027: the per-process token, read from the daemon's owner-only metadata
|
|
4248
|
+
* file — never from the `hello` frame, which the daemon sends to every
|
|
4249
|
+
* socket that connects (the mistake WS-028 found in the SAGE daemon).
|
|
4250
|
+
*
|
|
4251
|
+
* Read lazily and re-read while unknown: the daemon starts listening before
|
|
4252
|
+
* it writes metadata (endpoint ownership has to be won first), and a
|
|
4253
|
+
* respawned daemon mints a new token, so a cached wrong one would be sticky.
|
|
4254
|
+
*/
|
|
4255
|
+
currentAuthToken() {
|
|
4256
|
+
if (this.authToken === void 0) {
|
|
4257
|
+
try {
|
|
4258
|
+
const raw = fs4.readFileSync(
|
|
4259
|
+
projectIndexServerMetadataPath(this.projectRoot, this.indexDir),
|
|
4260
|
+
"utf8"
|
|
4261
|
+
);
|
|
4262
|
+
const parsed = JSON.parse(raw);
|
|
4263
|
+
if (typeof parsed.authToken === "string" && parsed.authToken.length > 0) {
|
|
4264
|
+
this.authToken = parsed.authToken;
|
|
4265
|
+
}
|
|
4266
|
+
} catch {
|
|
4267
|
+
}
|
|
4268
|
+
}
|
|
4269
|
+
return this.authToken;
|
|
4270
|
+
}
|
|
4217
4271
|
request(message, options) {
|
|
4218
4272
|
const socket = this.socket;
|
|
4219
4273
|
if (!socket || socket.destroyed) {
|
|
@@ -4257,7 +4311,7 @@ var ProjectServerConnection = class {
|
|
|
4257
4311
|
return;
|
|
4258
4312
|
}
|
|
4259
4313
|
}
|
|
4260
|
-
this.write({ ...message, id });
|
|
4314
|
+
this.write({ ...message, id, authToken: this.currentAuthToken() });
|
|
4261
4315
|
});
|
|
4262
4316
|
}
|
|
4263
4317
|
async ensureConnected(spawnIfMissing) {
|
|
@@ -4304,6 +4358,7 @@ var ProjectServerConnection = class {
|
|
|
4304
4358
|
connectOnce() {
|
|
4305
4359
|
this.socket?.destroy();
|
|
4306
4360
|
this.socket = null;
|
|
4361
|
+
this.authToken = void 0;
|
|
4307
4362
|
this.info = null;
|
|
4308
4363
|
this.activity = null;
|
|
4309
4364
|
this.health = null;
|
|
@@ -4500,7 +4555,26 @@ var ProjectServerConnection = class {
|
|
|
4500
4555
|
}
|
|
4501
4556
|
};
|
|
4502
4557
|
var connections = /* @__PURE__ */ new Map();
|
|
4558
|
+
var MAX_CACHED_CONNECTIONS = 8;
|
|
4503
4559
|
var heartbeatTimer;
|
|
4560
|
+
function forgetConnection(endpoint, connection) {
|
|
4561
|
+
if (connections.get(endpoint) === connection) connections.delete(endpoint);
|
|
4562
|
+
connection.close();
|
|
4563
|
+
connectionStates.delete(endpoint);
|
|
4564
|
+
if (latestConnectionState.endpoint !== endpoint) return;
|
|
4565
|
+
latestConnectionState = [...connectionStates.values()].at(-1) ?? {
|
|
4566
|
+
status: isProjectIndexServerAvailable() ? "offline" : "unavailable",
|
|
4567
|
+
connected: false
|
|
4568
|
+
};
|
|
4569
|
+
}
|
|
4570
|
+
function trimConnectionCache(protectedConnection) {
|
|
4571
|
+
if (connections.size <= MAX_CACHED_CONNECTIONS) return;
|
|
4572
|
+
for (const [endpoint, connection] of connections) {
|
|
4573
|
+
if (connections.size <= MAX_CACHED_CONNECTIONS) break;
|
|
4574
|
+
if (connection === protectedConnection || !connection.isEvictable()) continue;
|
|
4575
|
+
forgetConnection(endpoint, connection);
|
|
4576
|
+
}
|
|
4577
|
+
}
|
|
4504
4578
|
function ensureHeartbeatLoop() {
|
|
4505
4579
|
if (heartbeatTimer) return;
|
|
4506
4580
|
heartbeatTimer = setInterval(() => {
|
|
@@ -4523,7 +4597,11 @@ function connectionFor(projectRoot, indexDir) {
|
|
|
4523
4597
|
if (!connection) {
|
|
4524
4598
|
connection = new ProjectServerConnection(projectRoot, indexDir, endpoint);
|
|
4525
4599
|
connections.set(endpoint, connection);
|
|
4600
|
+
} else {
|
|
4601
|
+
connections.delete(endpoint);
|
|
4602
|
+
connections.set(endpoint, connection);
|
|
4526
4603
|
}
|
|
4604
|
+
trimConnectionCache(connection);
|
|
4527
4605
|
return connection;
|
|
4528
4606
|
}
|
|
4529
4607
|
function callProjectIndexServer(op, args, options) {
|
|
@@ -6243,8 +6321,9 @@ function runDeadCodeScan(projectRoot, opts = {}) {
|
|
|
6243
6321
|
const content = fs12.readFileSync(epFile, "utf8");
|
|
6244
6322
|
const strippedContent = content.replace(/\/\*[\s\S]*?\*\//g, (m) => " ".repeat(m.length)).replace(/\/\/[^\n]*/g, (m) => " ".repeat(m.length));
|
|
6245
6323
|
const reExportRe = /export\s+(?:(?:type\s+)?\{[\s\S]*?\}\s+from|\*\s+as\s+\w+\s+from|\*\s+from)\s+['"]([^'"]+)['"]/g;
|
|
6246
|
-
|
|
6247
|
-
|
|
6324
|
+
for (; ; ) {
|
|
6325
|
+
const match = reExportRe.exec(strippedContent);
|
|
6326
|
+
if (match === null) break;
|
|
6248
6327
|
const moduleSpec = match[1];
|
|
6249
6328
|
const resolvedFiles = resolveModulePath(epFile, moduleSpec, indexedFiles);
|
|
6250
6329
|
for (const rf of resolvedFiles) {
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { OpName, OpShapes } from './worker-protocol.js';
|
|
2
2
|
/** Hard ceiling for one newline-delimited IPC message (measured as JS characters). */
|
|
3
3
|
export declare const PROJECT_INDEX_SERVER_MAX_FRAME_CHARS: number;
|
|
4
|
+
/**
|
|
5
|
+
* What the daemon tells a connecting client about itself. Carries NO secret —
|
|
6
|
+
* see {@link ProjectIndexServerMetadata}.
|
|
7
|
+
*/
|
|
4
8
|
export interface ProjectIndexServerInfo {
|
|
5
9
|
protocolVersion: number;
|
|
6
10
|
buildId: string;
|
|
@@ -41,11 +45,30 @@ export interface ProjectIndexServerHealth {
|
|
|
41
45
|
oldestClientIdleMs?: number | undefined;
|
|
42
46
|
activity: ProjectIndexServerActivity;
|
|
43
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* The daemon's on-disk metadata file, written 0600. The auth token exists here
|
|
50
|
+
* and nowhere else — never in the `hello` frame, which the daemon sends to
|
|
51
|
+
* every socket that connects (the mistake WS-028 found in the SAGE daemon).
|
|
52
|
+
*/
|
|
53
|
+
export interface ProjectIndexServerMetadata extends ProjectIndexServerInfo {
|
|
54
|
+
authToken: string;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* WS-027: this daemon owns the project's code index — it reads every file in
|
|
58
|
+
* the repository and answers content queries over it — and it had no handshake
|
|
59
|
+
* credential at all. Any process that could open the socket could search the
|
|
60
|
+
* whole codebase through it or drive a reindex. `authToken` comes from the
|
|
61
|
+
* daemon's owner-only metadata file, so a caller must prove same-user access.
|
|
62
|
+
*
|
|
63
|
+
* `cancel` is ungated: it reaches only the sending connection's own in-flight
|
|
64
|
+
* request, so there is nothing to escalate.
|
|
65
|
+
*/
|
|
44
66
|
export type ProjectServerClientMessage = {
|
|
45
67
|
type: 'request';
|
|
46
68
|
id: number;
|
|
47
69
|
op: OpName;
|
|
48
70
|
args: OpShapes[OpName]['args'];
|
|
71
|
+
authToken?: string | undefined;
|
|
49
72
|
} | {
|
|
50
73
|
type: 'cancel';
|
|
51
74
|
id: number;
|
|
@@ -54,13 +77,16 @@ export type ProjectServerClientMessage = {
|
|
|
54
77
|
id: number;
|
|
55
78
|
watchExternal: boolean;
|
|
56
79
|
debounceMs: number;
|
|
80
|
+
authToken?: string | undefined;
|
|
57
81
|
} | {
|
|
58
82
|
type: 'ping';
|
|
59
83
|
id: number;
|
|
84
|
+
authToken?: string | undefined;
|
|
60
85
|
} | {
|
|
61
86
|
type: 'shutdown';
|
|
62
87
|
id: number;
|
|
63
88
|
reason?: string | undefined;
|
|
89
|
+
authToken?: string | undefined;
|
|
64
90
|
};
|
|
65
91
|
export type ProjectServerMessage = ({
|
|
66
92
|
type: 'hello';
|
|
@@ -1972,11 +1972,13 @@ var init_yaml_parser = __esm({
|
|
|
1972
1972
|
});
|
|
1973
1973
|
|
|
1974
1974
|
// src/codebase-index/project-server.ts
|
|
1975
|
+
import { randomBytes } from "node:crypto";
|
|
1975
1976
|
import * as fs10 from "node:fs";
|
|
1976
1977
|
import * as net from "node:net";
|
|
1977
1978
|
import * as path13 from "node:path";
|
|
1978
1979
|
import {
|
|
1979
1980
|
DEFAULT_WALK_IGNORE_SET,
|
|
1981
|
+
startSharedHeapWatchdog,
|
|
1980
1982
|
useDaemonPerfDefaults,
|
|
1981
1983
|
watchProjectTree
|
|
1982
1984
|
} from "@wrongstack/core/utils";
|
|
@@ -2938,6 +2940,7 @@ var REFS_INDEX_SQL = [
|
|
|
2938
2940
|
var SYMBOLS_FTS_SQL = "CREATE VIRTUAL TABLE IF NOT EXISTS symbols_fts USING fts5(text, tokenize = 'unicode61')";
|
|
2939
2941
|
|
|
2940
2942
|
// src/codebase-index/writer-search-helpers.ts
|
|
2943
|
+
var SEARCH_CANDIDATE_SCAN_CAP = 5e3;
|
|
2941
2944
|
function normalizeSearchLimit(limit) {
|
|
2942
2945
|
return typeof limit === "number" && Number.isFinite(limit) ? Math.max(0, Math.trunc(limit)) : void 0;
|
|
2943
2946
|
}
|
|
@@ -2967,8 +2970,8 @@ function buildWriterSearchWhere(query, filter) {
|
|
|
2967
2970
|
}
|
|
2968
2971
|
if (query.trim()) {
|
|
2969
2972
|
const tokens = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
2970
|
-
conditions.push(`(${tokens.map(() => "text LIKE ?").join(" OR ")})`);
|
|
2971
|
-
for (const token of tokens) values.push(`%${token}%`);
|
|
2973
|
+
conditions.push(`(${tokens.map(() => "text LIKE ? ESCAPE '\\'").join(" OR ")})`);
|
|
2974
|
+
for (const token of tokens) values.push(`%${escapeLike(token)}%`);
|
|
2972
2975
|
}
|
|
2973
2976
|
return { where: conditions.length ? `WHERE ${conditions.join(" AND ")}` : "", values };
|
|
2974
2977
|
}
|
|
@@ -3077,6 +3080,7 @@ var StorePool = class {
|
|
|
3077
3080
|
|
|
3078
3081
|
// src/codebase-index/writer.ts
|
|
3079
3082
|
var DB_FILE2 = "index.db";
|
|
3083
|
+
var MAX_STATEMENT_CACHE = 128;
|
|
3080
3084
|
var IndexStore = class _IndexStore {
|
|
3081
3085
|
db;
|
|
3082
3086
|
/** Absolute path to this project's index directory. */
|
|
@@ -3114,12 +3118,31 @@ var IndexStore = class _IndexStore {
|
|
|
3114
3118
|
// Dirty on open so the first getOrBuildBm25() rebuilds against current rows;
|
|
3115
3119
|
// an empty or pre-existing corpus makes a stale IDF table meaningless.
|
|
3116
3120
|
bm25Dirty = true;
|
|
3117
|
-
/**
|
|
3121
|
+
/**
|
|
3122
|
+
* Prepare-once helper: compile `sql` on first use, reuse thereafter.
|
|
3123
|
+
*
|
|
3124
|
+
* Bounded LRU rather than an open Map. The cache is keyed by SQL TEXT, and
|
|
3125
|
+
* the fallback search builder emits one `text LIKE ?` clause per query token
|
|
3126
|
+
* — so the SQL varies with the token count and a stream of differently-sized
|
|
3127
|
+
* queries grew the cache without limit. Sage's store already bounds its
|
|
3128
|
+
* equivalent at 128 (WS-096).
|
|
3129
|
+
*
|
|
3130
|
+
* Re-inserting on a hit keeps the hot fixed-SQL statements (upsertFile,
|
|
3131
|
+
* insertRefs, …) at the young end, so a burst of one-off search SQL evicts
|
|
3132
|
+
* itself rather than the reindex hot path.
|
|
3133
|
+
*/
|
|
3118
3134
|
stmt(sql) {
|
|
3119
|
-
|
|
3120
|
-
if (
|
|
3121
|
-
|
|
3122
|
-
this.stmtCache.set(sql,
|
|
3135
|
+
const cached = this.stmtCache.get(sql);
|
|
3136
|
+
if (cached !== void 0) {
|
|
3137
|
+
this.stmtCache.delete(sql);
|
|
3138
|
+
this.stmtCache.set(sql, cached);
|
|
3139
|
+
return cached;
|
|
3140
|
+
}
|
|
3141
|
+
const s = this.db.prepare(sql);
|
|
3142
|
+
this.stmtCache.set(sql, s);
|
|
3143
|
+
if (this.stmtCache.size > MAX_STATEMENT_CACHE) {
|
|
3144
|
+
const oldest = this.stmtCache.keys().next();
|
|
3145
|
+
if (!oldest.done) this.stmtCache.delete(oldest.value);
|
|
3123
3146
|
}
|
|
3124
3147
|
return s;
|
|
3125
3148
|
}
|
|
@@ -3392,7 +3415,7 @@ var IndexStore = class _IndexStore {
|
|
|
3392
3415
|
const { where, values } = built;
|
|
3393
3416
|
const limit = normalizeSearchLimit(opts?.limit);
|
|
3394
3417
|
const limitSql = limit !== void 0 ? " LIMIT ?" : "";
|
|
3395
|
-
const sql = `SELECT id, lang, kind, name, file, line, col, signature, doc_comment
|
|
3418
|
+
const sql = `SELECT id, lang, kind, name, file, line, col, signature, doc_comment FROM symbols ${where}${limitSql}`;
|
|
3396
3419
|
const binds = limit !== void 0 ? [...values, limit] : values;
|
|
3397
3420
|
const rows = this.stmt(sql).all(
|
|
3398
3421
|
...binds
|
|
@@ -3521,11 +3544,13 @@ var IndexStore = class _IndexStore {
|
|
|
3521
3544
|
/** Legacy ranked path: LIKE candidates + in-process BM25 + JS snippets. */
|
|
3522
3545
|
searchRankedFallback(query, filter, limit) {
|
|
3523
3546
|
if (!query.trim()) {
|
|
3524
|
-
const
|
|
3525
|
-
if (
|
|
3526
|
-
return { results: this.search(query, filter, { limit }), total };
|
|
3547
|
+
const total2 = this.countSearch(query, filter);
|
|
3548
|
+
if (total2 === 0) return { results: [], total: 0 };
|
|
3549
|
+
return { results: this.search(query, filter, { limit }), total: total2 };
|
|
3527
3550
|
}
|
|
3528
|
-
const
|
|
3551
|
+
const total = this.countSearch(query, filter);
|
|
3552
|
+
if (total === 0) return { results: [], total: 0 };
|
|
3553
|
+
const candidates = this.search(query, filter, { limit: SEARCH_CANDIDATE_SCAN_CAP });
|
|
3529
3554
|
if (candidates.length === 0) return { results: [], total: 0 };
|
|
3530
3555
|
const candidateById = new Map(candidates.map((c) => [c.id, c]));
|
|
3531
3556
|
const bm25 = this.getOrBuildBm25();
|
|
@@ -3551,7 +3576,7 @@ var IndexStore = class _IndexStore {
|
|
|
3551
3576
|
const c = expectDefined4(candidateById.get(id));
|
|
3552
3577
|
return { ...c, score, snippet: bm25.extractSnippet(id, qTokens) };
|
|
3553
3578
|
});
|
|
3554
|
-
return { results, total
|
|
3579
|
+
return { results, total };
|
|
3555
3580
|
}
|
|
3556
3581
|
getAllIndexable() {
|
|
3557
3582
|
return getAllIndexableWithStatement((sql) => this.stmt(sql));
|
|
@@ -4586,6 +4611,11 @@ var clientLeaseMsRaw = Number(process.env["WRONGSTACK_INDEX_SERVER_CLIENT_LEASE_
|
|
|
4586
4611
|
var clientLeaseMs = Number.isFinite(clientLeaseMsRaw) && clientLeaseMsRaw >= 100 ? clientLeaseMsRaw : DEFAULT_CLIENT_LEASE_MS;
|
|
4587
4612
|
var clientLeaseSweepMs = Math.min(1e4, Math.max(100, Math.floor(clientLeaseMs / 3)));
|
|
4588
4613
|
var startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4614
|
+
var authToken = randomBytes(16).toString("hex");
|
|
4615
|
+
var markMetadataWritten;
|
|
4616
|
+
var metadataWritten = new Promise((resolve4) => {
|
|
4617
|
+
markMetadataWritten = resolve4;
|
|
4618
|
+
});
|
|
4589
4619
|
var serverInfo = {
|
|
4590
4620
|
protocolVersion: PROJECT_INDEX_SERVER_PROTOCOL_VERSION,
|
|
4591
4621
|
buildId: projectIndexServerBuildId(import.meta.url),
|
|
@@ -4617,6 +4647,20 @@ var indexActivity = {
|
|
|
4617
4647
|
updatedAt: null,
|
|
4618
4648
|
lastError: null
|
|
4619
4649
|
};
|
|
4650
|
+
var stopMemoryWatchdog = startSharedHeapWatchdog({
|
|
4651
|
+
collectStats: () => ({
|
|
4652
|
+
surface: "codebase-index-project-server",
|
|
4653
|
+
clients: clients.size,
|
|
4654
|
+
activeRequests,
|
|
4655
|
+
activeWrites,
|
|
4656
|
+
queuedWrites,
|
|
4657
|
+
searchCache: searchCache.size,
|
|
4658
|
+
statsCache: statsCache.size,
|
|
4659
|
+
packageGraphCache: packageGraphCache.size,
|
|
4660
|
+
fileGraphCache: fileGraphCache.size,
|
|
4661
|
+
symbolGraphCache: symbolGraphCache.size
|
|
4662
|
+
})
|
|
4663
|
+
});
|
|
4620
4664
|
var lastProgressBroadcastAt = 0;
|
|
4621
4665
|
var externalWatcher;
|
|
4622
4666
|
var DEFAULT_EXTERNAL_DEBOUNCE_MS = 400;
|
|
@@ -4846,6 +4890,16 @@ async function handleMessage(state, message) {
|
|
|
4846
4890
|
state.cancel.get(message.id)?.();
|
|
4847
4891
|
return;
|
|
4848
4892
|
}
|
|
4893
|
+
if (message.authToken !== authToken) {
|
|
4894
|
+
send(state, {
|
|
4895
|
+
type: "response",
|
|
4896
|
+
id: message.id,
|
|
4897
|
+
ok: false,
|
|
4898
|
+
error: "Codebase-index IPC request rejected: missing or invalid authToken. Reconnect to refresh metadata (server.json#authToken).",
|
|
4899
|
+
errorName: "UnauthorizedIndexRequest"
|
|
4900
|
+
});
|
|
4901
|
+
return;
|
|
4902
|
+
}
|
|
4849
4903
|
if (message.type === "ping") {
|
|
4850
4904
|
send(state, { type: "response", id: message.id, ok: true, result: serverHealth() });
|
|
4851
4905
|
return;
|
|
@@ -5032,7 +5086,8 @@ function removeMetadataIfOwned() {
|
|
|
5032
5086
|
function writeMetadata() {
|
|
5033
5087
|
fs10.mkdirSync(path13.dirname(metadataPath), { recursive: true });
|
|
5034
5088
|
const temporary = `${metadataPath}.${process.pid}.tmp`;
|
|
5035
|
-
|
|
5089
|
+
const metadata = { ...serverInfo, authToken };
|
|
5090
|
+
fs10.writeFileSync(temporary, `${JSON.stringify(metadata, null, 2)}
|
|
5036
5091
|
`, { mode: 384 });
|
|
5037
5092
|
try {
|
|
5038
5093
|
fs10.renameSync(temporary, metadataPath);
|
|
@@ -5056,8 +5111,11 @@ var server = net.createServer((socket) => {
|
|
|
5056
5111
|
lastSeenAt: Date.now()
|
|
5057
5112
|
};
|
|
5058
5113
|
clients.add(state);
|
|
5059
|
-
|
|
5060
|
-
|
|
5114
|
+
void metadataWritten.then(() => {
|
|
5115
|
+
if (socket.destroyed) return;
|
|
5116
|
+
send(state, { type: "hello", ...serverInfo });
|
|
5117
|
+
send(state, { type: "index-state", state: indexActivity });
|
|
5118
|
+
});
|
|
5061
5119
|
socket.on("data", (chunk) => consume(state, chunk));
|
|
5062
5120
|
socket.on("close", () => {
|
|
5063
5121
|
for (const cancel of state.cancel.values()) cancel();
|
|
@@ -5109,6 +5167,7 @@ async function stop(_reason) {
|
|
|
5109
5167
|
} catch {
|
|
5110
5168
|
}
|
|
5111
5169
|
}
|
|
5170
|
+
await stopMemoryWatchdog();
|
|
5112
5171
|
}
|
|
5113
5172
|
ensureProjectIndexSocketDirectory(endpoint);
|
|
5114
5173
|
server.once("error", (error) => {
|
|
@@ -5128,6 +5187,7 @@ server.listen(endpoint, () => {
|
|
|
5128
5187
|
}
|
|
5129
5188
|
}
|
|
5130
5189
|
writeMetadata();
|
|
5190
|
+
markMetadataWritten?.();
|
|
5131
5191
|
scheduleIdleStop();
|
|
5132
5192
|
});
|
|
5133
5193
|
process.once("SIGINT", () => void stop("SIGINT"));
|
|
@@ -2927,6 +2927,7 @@ var REFS_INDEX_SQL = [
|
|
|
2927
2927
|
var SYMBOLS_FTS_SQL = "CREATE VIRTUAL TABLE IF NOT EXISTS symbols_fts USING fts5(text, tokenize = 'unicode61')";
|
|
2928
2928
|
|
|
2929
2929
|
// src/codebase-index/writer-search-helpers.ts
|
|
2930
|
+
var SEARCH_CANDIDATE_SCAN_CAP = 5e3;
|
|
2930
2931
|
function normalizeSearchLimit(limit) {
|
|
2931
2932
|
return typeof limit === "number" && Number.isFinite(limit) ? Math.max(0, Math.trunc(limit)) : void 0;
|
|
2932
2933
|
}
|
|
@@ -2956,8 +2957,8 @@ function buildWriterSearchWhere(query, filter) {
|
|
|
2956
2957
|
}
|
|
2957
2958
|
if (query.trim()) {
|
|
2958
2959
|
const tokens = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
2959
|
-
conditions.push(`(${tokens.map(() => "text LIKE ?").join(" OR ")})`);
|
|
2960
|
-
for (const token of tokens) values.push(`%${token}%`);
|
|
2960
|
+
conditions.push(`(${tokens.map(() => "text LIKE ? ESCAPE '\\'").join(" OR ")})`);
|
|
2961
|
+
for (const token of tokens) values.push(`%${escapeLike(token)}%`);
|
|
2961
2962
|
}
|
|
2962
2963
|
return { where: conditions.length ? `WHERE ${conditions.join(" AND ")}` : "", values };
|
|
2963
2964
|
}
|
|
@@ -3066,6 +3067,7 @@ var StorePool = class {
|
|
|
3066
3067
|
|
|
3067
3068
|
// src/codebase-index/writer.ts
|
|
3068
3069
|
var DB_FILE2 = "index.db";
|
|
3070
|
+
var MAX_STATEMENT_CACHE = 128;
|
|
3069
3071
|
var IndexStore = class _IndexStore {
|
|
3070
3072
|
db;
|
|
3071
3073
|
/** Absolute path to this project's index directory. */
|
|
@@ -3103,12 +3105,31 @@ var IndexStore = class _IndexStore {
|
|
|
3103
3105
|
// Dirty on open so the first getOrBuildBm25() rebuilds against current rows;
|
|
3104
3106
|
// an empty or pre-existing corpus makes a stale IDF table meaningless.
|
|
3105
3107
|
bm25Dirty = true;
|
|
3106
|
-
/**
|
|
3108
|
+
/**
|
|
3109
|
+
* Prepare-once helper: compile `sql` on first use, reuse thereafter.
|
|
3110
|
+
*
|
|
3111
|
+
* Bounded LRU rather than an open Map. The cache is keyed by SQL TEXT, and
|
|
3112
|
+
* the fallback search builder emits one `text LIKE ?` clause per query token
|
|
3113
|
+
* — so the SQL varies with the token count and a stream of differently-sized
|
|
3114
|
+
* queries grew the cache without limit. Sage's store already bounds its
|
|
3115
|
+
* equivalent at 128 (WS-096).
|
|
3116
|
+
*
|
|
3117
|
+
* Re-inserting on a hit keeps the hot fixed-SQL statements (upsertFile,
|
|
3118
|
+
* insertRefs, …) at the young end, so a burst of one-off search SQL evicts
|
|
3119
|
+
* itself rather than the reindex hot path.
|
|
3120
|
+
*/
|
|
3107
3121
|
stmt(sql) {
|
|
3108
|
-
|
|
3109
|
-
if (
|
|
3110
|
-
|
|
3111
|
-
this.stmtCache.set(sql,
|
|
3122
|
+
const cached = this.stmtCache.get(sql);
|
|
3123
|
+
if (cached !== void 0) {
|
|
3124
|
+
this.stmtCache.delete(sql);
|
|
3125
|
+
this.stmtCache.set(sql, cached);
|
|
3126
|
+
return cached;
|
|
3127
|
+
}
|
|
3128
|
+
const s = this.db.prepare(sql);
|
|
3129
|
+
this.stmtCache.set(sql, s);
|
|
3130
|
+
if (this.stmtCache.size > MAX_STATEMENT_CACHE) {
|
|
3131
|
+
const oldest = this.stmtCache.keys().next();
|
|
3132
|
+
if (!oldest.done) this.stmtCache.delete(oldest.value);
|
|
3112
3133
|
}
|
|
3113
3134
|
return s;
|
|
3114
3135
|
}
|
|
@@ -3381,7 +3402,7 @@ var IndexStore = class _IndexStore {
|
|
|
3381
3402
|
const { where, values } = built;
|
|
3382
3403
|
const limit = normalizeSearchLimit(opts?.limit);
|
|
3383
3404
|
const limitSql = limit !== void 0 ? " LIMIT ?" : "";
|
|
3384
|
-
const sql = `SELECT id, lang, kind, name, file, line, col, signature, doc_comment
|
|
3405
|
+
const sql = `SELECT id, lang, kind, name, file, line, col, signature, doc_comment FROM symbols ${where}${limitSql}`;
|
|
3385
3406
|
const binds = limit !== void 0 ? [...values, limit] : values;
|
|
3386
3407
|
const rows = this.stmt(sql).all(
|
|
3387
3408
|
...binds
|
|
@@ -3510,11 +3531,13 @@ var IndexStore = class _IndexStore {
|
|
|
3510
3531
|
/** Legacy ranked path: LIKE candidates + in-process BM25 + JS snippets. */
|
|
3511
3532
|
searchRankedFallback(query, filter, limit) {
|
|
3512
3533
|
if (!query.trim()) {
|
|
3513
|
-
const
|
|
3514
|
-
if (
|
|
3515
|
-
return { results: this.search(query, filter, { limit }), total };
|
|
3534
|
+
const total2 = this.countSearch(query, filter);
|
|
3535
|
+
if (total2 === 0) return { results: [], total: 0 };
|
|
3536
|
+
return { results: this.search(query, filter, { limit }), total: total2 };
|
|
3516
3537
|
}
|
|
3517
|
-
const
|
|
3538
|
+
const total = this.countSearch(query, filter);
|
|
3539
|
+
if (total === 0) return { results: [], total: 0 };
|
|
3540
|
+
const candidates = this.search(query, filter, { limit: SEARCH_CANDIDATE_SCAN_CAP });
|
|
3518
3541
|
if (candidates.length === 0) return { results: [], total: 0 };
|
|
3519
3542
|
const candidateById = new Map(candidates.map((c) => [c.id, c]));
|
|
3520
3543
|
const bm25 = this.getOrBuildBm25();
|
|
@@ -3540,7 +3563,7 @@ var IndexStore = class _IndexStore {
|
|
|
3540
3563
|
const c = expectDefined4(candidateById.get(id));
|
|
3541
3564
|
return { ...c, score, snippet: bm25.extractSnippet(id, qTokens) };
|
|
3542
3565
|
});
|
|
3543
|
-
return { results, total
|
|
3566
|
+
return { results, total };
|
|
3544
3567
|
}
|
|
3545
3568
|
getAllIndexable() {
|
|
3546
3569
|
return getAllIndexableWithStatement((sql) => this.stmt(sql));
|
|
@@ -19,6 +19,22 @@ export interface WriterSearchRow {
|
|
|
19
19
|
score?: number;
|
|
20
20
|
snippet?: string;
|
|
21
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Ceiling on how many LIKE candidates the FTS5-less ranked fallback pulls into
|
|
24
|
+
* memory before scoring (WS-031).
|
|
25
|
+
*
|
|
26
|
+
* The fallback ranks with an in-process BM25 pass, so it needs more than the
|
|
27
|
+
* caller's `limit` (≤100) to rank well — but it does not need the whole corpus.
|
|
28
|
+
* Without a bound, a broad query on a large index materializes every matching
|
|
29
|
+
* row as a JS object; this subsystem has a documented OOM history, and search
|
|
30
|
+
* runs on the request path.
|
|
31
|
+
*
|
|
32
|
+
* Set far above any query a human would type, so ranking quality is unchanged
|
|
33
|
+
* in practice: it bounds the pathological case, it does not shape normal ones.
|
|
34
|
+
* `total` is reported from a SQL `COUNT(*)` rather than the truncated array, so
|
|
35
|
+
* capping never makes the reported match count wrong.
|
|
36
|
+
*/
|
|
37
|
+
export declare const SEARCH_CANDIDATE_SCAN_CAP = 5000;
|
|
22
38
|
export declare function normalizeSearchLimit(limit: number | undefined): number | undefined;
|
|
23
39
|
export declare function buildWriterSearchWhere(query: string, filter?: WriterSearchFilter | undefined): {
|
|
24
40
|
where: string;
|
|
@@ -38,7 +38,19 @@ export declare class IndexStore {
|
|
|
38
38
|
*/
|
|
39
39
|
private bm25Cache;
|
|
40
40
|
private bm25Dirty;
|
|
41
|
-
/**
|
|
41
|
+
/**
|
|
42
|
+
* Prepare-once helper: compile `sql` on first use, reuse thereafter.
|
|
43
|
+
*
|
|
44
|
+
* Bounded LRU rather than an open Map. The cache is keyed by SQL TEXT, and
|
|
45
|
+
* the fallback search builder emits one `text LIKE ?` clause per query token
|
|
46
|
+
* — so the SQL varies with the token count and a stream of differently-sized
|
|
47
|
+
* queries grew the cache without limit. Sage's store already bounds its
|
|
48
|
+
* equivalent at 128 (WS-096).
|
|
49
|
+
*
|
|
50
|
+
* Re-inserting on a hit keeps the hot fixed-SQL statements (upsertFile,
|
|
51
|
+
* insertRefs, …) at the young end, so a burst of one-off search SQL evicts
|
|
52
|
+
* itself rather than the reindex hot path.
|
|
53
|
+
*/
|
|
42
54
|
private stmt;
|
|
43
55
|
constructor(projectRoot: string, opts?: {
|
|
44
56
|
indexDir?: string | undefined;
|
package/dist/design.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { Tool } from '@wrongstack/core/types';
|
|
2
1
|
import { type SemanticTune } from '@wrongstack/core/design';
|
|
2
|
+
import type { Tool } from '@wrongstack/core/types';
|
|
3
3
|
type Overrides = Record<string, string>;
|
|
4
4
|
interface DesignInput {
|
|
5
5
|
action?: 'list' | 'use' | 'foundations' | 'set' | 'tune' | 'materialize' | 'verify' | undefined;
|
package/dist/design.js
CHANGED
|
@@ -48,6 +48,9 @@ var designTool = {
|
|
|
48
48
|
description: 'Browse, load, customize, and enforce curated frontend/mobile UI design kits. Use BEFORE writing UI code to commit to one coherent, modern, responsive, dark/light, accessible design. Actions: "list" (menu), "use" (load+pin a kit for a stack), "foundations" (baseline), "set" (override kit colors/tokens), "materialize" (write the tokens to a real theme file \u2014 CSS @theme/OKLCH or native), "verify" (scan UI files for off-palette colors).',
|
|
49
49
|
usageHint: 'Flow: `design {action:"use", kit:"minimal-clarity", stack:"web"}` \u2192 optionally `design {action:"set", set:{primary:"oklch(62% 0.2 25)"}}` \u2192 `design {action:"materialize"}` to write tokens to disk \u2192 implement against them \u2192 `design {action:"verify"}`.',
|
|
50
50
|
permission: "confirm",
|
|
51
|
+
// WS-046: gives permission decisions something to key on.
|
|
52
|
+
// The action performed; `out` is optional so it cannot be the subject.
|
|
53
|
+
subjectKey: "action",
|
|
51
54
|
mutating: true,
|
|
52
55
|
capabilities: ["fs.write"],
|
|
53
56
|
timeoutMs: 15e3,
|
|
@@ -228,9 +231,7 @@ Run \`design {action:"materialize"}\` to write the tuned tokens to your theme fi
|
|
|
228
231
|
const abs = path.join(absParent, path.basename(absResolved));
|
|
229
232
|
const rel = path.relative(root, abs);
|
|
230
233
|
if (rel.startsWith("..") || path.isAbsolute(rel)) {
|
|
231
|
-
throw new Error(
|
|
232
|
-
`design: materialize path "${result.path}" would escape the project root`
|
|
233
|
-
);
|
|
234
|
+
throw new Error(`design: materialize path "${result.path}" would escape the project root`);
|
|
234
235
|
}
|
|
235
236
|
let exists = false;
|
|
236
237
|
try {
|