@wrongstack/tools 0.297.0 → 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 +113 -65
- package/dist/bash.js +5 -4
- package/dist/batch-tool-use.js +70 -7
- package/dist/browser/index.js +31 -1
- package/dist/builtin.js +841 -351
- package/dist/codebase-index/index.js +68 -16
- package/dist/codebase-index/project-server-protocol.d.ts +26 -0
- package/dist/codebase-index/project-server.js +60 -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 +560 -529
- package/dist/format.js +116 -65
- package/dist/git.js +22 -2
- package/dist/glob.js +11 -5
- package/dist/grep.js +160 -104
- package/dist/index.js +792 -290
- package/dist/install.js +120 -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 +128 -71
- package/dist/lint.js +113 -65
- package/dist/outdated.js +86 -26
- package/dist/pack.js +841 -351
- package/dist/patch.js +11 -1
- package/dist/plan.js +32 -10
- package/dist/read.js +77 -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 +113 -65
- package/dist/todo.js +3 -1
- package/dist/tool-tier.js +841 -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 +113 -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);
|
|
@@ -4218,6 +4243,31 @@ var ProjectServerConnection = class {
|
|
|
4218
4243
|
this.transition("offline");
|
|
4219
4244
|
maybeStopHeartbeatLoop();
|
|
4220
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
|
+
}
|
|
4221
4271
|
request(message, options) {
|
|
4222
4272
|
const socket = this.socket;
|
|
4223
4273
|
if (!socket || socket.destroyed) {
|
|
@@ -4261,7 +4311,7 @@ var ProjectServerConnection = class {
|
|
|
4261
4311
|
return;
|
|
4262
4312
|
}
|
|
4263
4313
|
}
|
|
4264
|
-
this.write({ ...message, id });
|
|
4314
|
+
this.write({ ...message, id, authToken: this.currentAuthToken() });
|
|
4265
4315
|
});
|
|
4266
4316
|
}
|
|
4267
4317
|
async ensureConnected(spawnIfMissing) {
|
|
@@ -4308,6 +4358,7 @@ var ProjectServerConnection = class {
|
|
|
4308
4358
|
connectOnce() {
|
|
4309
4359
|
this.socket?.destroy();
|
|
4310
4360
|
this.socket = null;
|
|
4361
|
+
this.authToken = void 0;
|
|
4311
4362
|
this.info = null;
|
|
4312
4363
|
this.activity = null;
|
|
4313
4364
|
this.health = null;
|
|
@@ -6270,8 +6321,9 @@ function runDeadCodeScan(projectRoot, opts = {}) {
|
|
|
6270
6321
|
const content = fs12.readFileSync(epFile, "utf8");
|
|
6271
6322
|
const strippedContent = content.replace(/\/\*[\s\S]*?\*\//g, (m) => " ".repeat(m.length)).replace(/\/\/[^\n]*/g, (m) => " ".repeat(m.length));
|
|
6272
6323
|
const reExportRe = /export\s+(?:(?:type\s+)?\{[\s\S]*?\}\s+from|\*\s+as\s+\w+\s+from|\*\s+from)\s+['"]([^'"]+)['"]/g;
|
|
6273
|
-
|
|
6274
|
-
|
|
6324
|
+
for (; ; ) {
|
|
6325
|
+
const match = reExportRe.exec(strippedContent);
|
|
6326
|
+
if (match === null) break;
|
|
6275
6327
|
const moduleSpec = match[1];
|
|
6276
6328
|
const resolvedFiles = resolveModulePath(epFile, moduleSpec, indexedFiles);
|
|
6277
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,6 +1972,7 @@ 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";
|
|
@@ -2939,6 +2940,7 @@ var REFS_INDEX_SQL = [
|
|
|
2939
2940
|
var SYMBOLS_FTS_SQL = "CREATE VIRTUAL TABLE IF NOT EXISTS symbols_fts USING fts5(text, tokenize = 'unicode61')";
|
|
2940
2941
|
|
|
2941
2942
|
// src/codebase-index/writer-search-helpers.ts
|
|
2943
|
+
var SEARCH_CANDIDATE_SCAN_CAP = 5e3;
|
|
2942
2944
|
function normalizeSearchLimit(limit) {
|
|
2943
2945
|
return typeof limit === "number" && Number.isFinite(limit) ? Math.max(0, Math.trunc(limit)) : void 0;
|
|
2944
2946
|
}
|
|
@@ -2968,8 +2970,8 @@ function buildWriterSearchWhere(query, filter) {
|
|
|
2968
2970
|
}
|
|
2969
2971
|
if (query.trim()) {
|
|
2970
2972
|
const tokens = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
2971
|
-
conditions.push(`(${tokens.map(() => "text LIKE ?").join(" OR ")})`);
|
|
2972
|
-
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)}%`);
|
|
2973
2975
|
}
|
|
2974
2976
|
return { where: conditions.length ? `WHERE ${conditions.join(" AND ")}` : "", values };
|
|
2975
2977
|
}
|
|
@@ -3078,6 +3080,7 @@ var StorePool = class {
|
|
|
3078
3080
|
|
|
3079
3081
|
// src/codebase-index/writer.ts
|
|
3080
3082
|
var DB_FILE2 = "index.db";
|
|
3083
|
+
var MAX_STATEMENT_CACHE = 128;
|
|
3081
3084
|
var IndexStore = class _IndexStore {
|
|
3082
3085
|
db;
|
|
3083
3086
|
/** Absolute path to this project's index directory. */
|
|
@@ -3115,12 +3118,31 @@ var IndexStore = class _IndexStore {
|
|
|
3115
3118
|
// Dirty on open so the first getOrBuildBm25() rebuilds against current rows;
|
|
3116
3119
|
// an empty or pre-existing corpus makes a stale IDF table meaningless.
|
|
3117
3120
|
bm25Dirty = true;
|
|
3118
|
-
/**
|
|
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
|
+
*/
|
|
3119
3134
|
stmt(sql) {
|
|
3120
|
-
|
|
3121
|
-
if (
|
|
3122
|
-
|
|
3123
|
-
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);
|
|
3124
3146
|
}
|
|
3125
3147
|
return s;
|
|
3126
3148
|
}
|
|
@@ -3393,7 +3415,7 @@ var IndexStore = class _IndexStore {
|
|
|
3393
3415
|
const { where, values } = built;
|
|
3394
3416
|
const limit = normalizeSearchLimit(opts?.limit);
|
|
3395
3417
|
const limitSql = limit !== void 0 ? " LIMIT ?" : "";
|
|
3396
|
-
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}`;
|
|
3397
3419
|
const binds = limit !== void 0 ? [...values, limit] : values;
|
|
3398
3420
|
const rows = this.stmt(sql).all(
|
|
3399
3421
|
...binds
|
|
@@ -3522,11 +3544,13 @@ var IndexStore = class _IndexStore {
|
|
|
3522
3544
|
/** Legacy ranked path: LIKE candidates + in-process BM25 + JS snippets. */
|
|
3523
3545
|
searchRankedFallback(query, filter, limit) {
|
|
3524
3546
|
if (!query.trim()) {
|
|
3525
|
-
const
|
|
3526
|
-
if (
|
|
3527
|
-
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 };
|
|
3528
3550
|
}
|
|
3529
|
-
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 });
|
|
3530
3554
|
if (candidates.length === 0) return { results: [], total: 0 };
|
|
3531
3555
|
const candidateById = new Map(candidates.map((c) => [c.id, c]));
|
|
3532
3556
|
const bm25 = this.getOrBuildBm25();
|
|
@@ -3552,7 +3576,7 @@ var IndexStore = class _IndexStore {
|
|
|
3552
3576
|
const c = expectDefined4(candidateById.get(id));
|
|
3553
3577
|
return { ...c, score, snippet: bm25.extractSnippet(id, qTokens) };
|
|
3554
3578
|
});
|
|
3555
|
-
return { results, total
|
|
3579
|
+
return { results, total };
|
|
3556
3580
|
}
|
|
3557
3581
|
getAllIndexable() {
|
|
3558
3582
|
return getAllIndexableWithStatement((sql) => this.stmt(sql));
|
|
@@ -4587,6 +4611,11 @@ var clientLeaseMsRaw = Number(process.env["WRONGSTACK_INDEX_SERVER_CLIENT_LEASE_
|
|
|
4587
4611
|
var clientLeaseMs = Number.isFinite(clientLeaseMsRaw) && clientLeaseMsRaw >= 100 ? clientLeaseMsRaw : DEFAULT_CLIENT_LEASE_MS;
|
|
4588
4612
|
var clientLeaseSweepMs = Math.min(1e4, Math.max(100, Math.floor(clientLeaseMs / 3)));
|
|
4589
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
|
+
});
|
|
4590
4619
|
var serverInfo = {
|
|
4591
4620
|
protocolVersion: PROJECT_INDEX_SERVER_PROTOCOL_VERSION,
|
|
4592
4621
|
buildId: projectIndexServerBuildId(import.meta.url),
|
|
@@ -4861,6 +4890,16 @@ async function handleMessage(state, message) {
|
|
|
4861
4890
|
state.cancel.get(message.id)?.();
|
|
4862
4891
|
return;
|
|
4863
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
|
+
}
|
|
4864
4903
|
if (message.type === "ping") {
|
|
4865
4904
|
send(state, { type: "response", id: message.id, ok: true, result: serverHealth() });
|
|
4866
4905
|
return;
|
|
@@ -5047,7 +5086,8 @@ function removeMetadataIfOwned() {
|
|
|
5047
5086
|
function writeMetadata() {
|
|
5048
5087
|
fs10.mkdirSync(path13.dirname(metadataPath), { recursive: true });
|
|
5049
5088
|
const temporary = `${metadataPath}.${process.pid}.tmp`;
|
|
5050
|
-
|
|
5089
|
+
const metadata = { ...serverInfo, authToken };
|
|
5090
|
+
fs10.writeFileSync(temporary, `${JSON.stringify(metadata, null, 2)}
|
|
5051
5091
|
`, { mode: 384 });
|
|
5052
5092
|
try {
|
|
5053
5093
|
fs10.renameSync(temporary, metadataPath);
|
|
@@ -5071,8 +5111,11 @@ var server = net.createServer((socket) => {
|
|
|
5071
5111
|
lastSeenAt: Date.now()
|
|
5072
5112
|
};
|
|
5073
5113
|
clients.add(state);
|
|
5074
|
-
|
|
5075
|
-
|
|
5114
|
+
void metadataWritten.then(() => {
|
|
5115
|
+
if (socket.destroyed) return;
|
|
5116
|
+
send(state, { type: "hello", ...serverInfo });
|
|
5117
|
+
send(state, { type: "index-state", state: indexActivity });
|
|
5118
|
+
});
|
|
5076
5119
|
socket.on("data", (chunk) => consume(state, chunk));
|
|
5077
5120
|
socket.on("close", () => {
|
|
5078
5121
|
for (const cancel of state.cancel.values()) cancel();
|
|
@@ -5144,6 +5187,7 @@ server.listen(endpoint, () => {
|
|
|
5144
5187
|
}
|
|
5145
5188
|
}
|
|
5146
5189
|
writeMetadata();
|
|
5190
|
+
markMetadataWritten?.();
|
|
5147
5191
|
scheduleIdleStop();
|
|
5148
5192
|
});
|
|
5149
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 {
|
package/dist/diff.js
CHANGED
|
@@ -31,6 +31,7 @@ function safeResolve(input, ctx) {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
// src/diff.ts
|
|
34
|
+
var MAX_FILE_DUMP_BYTES = 5 * 1024 * 1024;
|
|
34
35
|
var diffTool = {
|
|
35
36
|
name: "diff",
|
|
36
37
|
category: "Filesystem",
|
|
@@ -38,6 +39,7 @@ var diffTool = {
|
|
|
38
39
|
usageHint: 'USE FOR CODE REVIEW AND CHANGE INSPECTION:\n\n- `files` + no `a`/`b` \u2192 show file content with line numbers (NOT a unified diff; no +/- prefixes).\n- `a` and/or `b` \u2192 git-style commit/branch diff (unified format, real +/- prefixes).\n- `staged: true` \u2192 only show staged changes.\n- `mode` can be "unified", "stat", or "side-by-side" (only affects the git-diff path).\n\nNOTE: For a true file-vs-file unified diff, supply `a` and `b` so the tool delegates to `git diff`. The `files`-only path is a line-numbered dump, not a diff.\n\nThis tool has important safety guards against flag injection (see previous security findings).',
|
|
39
40
|
permission: "auto",
|
|
40
41
|
mutating: false,
|
|
42
|
+
maxOutputBytes: 262144,
|
|
41
43
|
capabilities: ["fs.read"],
|
|
42
44
|
icon: "diff",
|
|
43
45
|
timeoutMs: 1e4,
|
|
@@ -156,10 +158,18 @@ async function fileDiff(input, ctx, _signal) {
|
|
|
156
158
|
};
|
|
157
159
|
}
|
|
158
160
|
const results = [];
|
|
161
|
+
let truncated = false;
|
|
159
162
|
for (const file of files) {
|
|
160
163
|
const absPath = safeResolve(file, ctx);
|
|
161
164
|
const stat2 = await fs.stat(absPath).catch(() => null);
|
|
162
165
|
if (!stat2?.isFile()) continue;
|
|
166
|
+
if (stat2.size > MAX_FILE_DUMP_BYTES) {
|
|
167
|
+
truncated = true;
|
|
168
|
+
results.push(
|
|
169
|
+
`--- ${file} (skipped: ${stat2.size} bytes exceeds the ${MAX_FILE_DUMP_BYTES} limit; use the read tool with offset/limit) ---`
|
|
170
|
+
);
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
163
173
|
const content = await fs.readFile(absPath, "utf8");
|
|
164
174
|
const lines = content.split(/\r?\n/);
|
|
165
175
|
results.push(formatWithLineNumbers(file, lines));
|
|
@@ -167,7 +177,7 @@ async function fileDiff(input, ctx, _signal) {
|
|
|
167
177
|
return {
|
|
168
178
|
diff: results.join("\n\n"),
|
|
169
179
|
files,
|
|
170
|
-
truncated
|
|
180
|
+
truncated,
|
|
171
181
|
mode: input.mode ?? "unified"
|
|
172
182
|
};
|
|
173
183
|
}
|
package/dist/e2e.js
CHANGED
|
@@ -27,12 +27,13 @@ function ensureInsideRoot(absPath, ctx) {
|
|
|
27
27
|
function safeResolve(input, ctx) {
|
|
28
28
|
return ensureInsideRoot(resolvePath(input, ctx), ctx);
|
|
29
29
|
}
|
|
30
|
-
async function
|
|
31
|
-
if (ctx.allowOutsideProjectRoot) return;
|
|
30
|
+
async function resolveRealInsideRoot(absPath, ctx) {
|
|
31
|
+
if (ctx.allowOutsideProjectRoot) return absPath;
|
|
32
32
|
const realRoots = await Promise.all(
|
|
33
33
|
allowedRoots(ctx).map((r) => fsp.realpath(r).catch(() => path.resolve(r)))
|
|
34
34
|
);
|
|
35
35
|
let probe = absPath;
|
|
36
|
+
const pendingTail = [];
|
|
36
37
|
for (; ; ) {
|
|
37
38
|
let real;
|
|
38
39
|
try {
|
|
@@ -40,13 +41,16 @@ async function assertRealInsideRoot(absPath, ctx) {
|
|
|
40
41
|
} catch (err) {
|
|
41
42
|
if (err.code === "ENOENT") {
|
|
42
43
|
const parent = path.dirname(probe);
|
|
43
|
-
if (parent === probe) return;
|
|
44
|
+
if (parent === probe) return absPath;
|
|
45
|
+
pendingTail.unshift(path.basename(probe));
|
|
44
46
|
probe = parent;
|
|
45
47
|
continue;
|
|
46
48
|
}
|
|
47
49
|
throw err;
|
|
48
50
|
}
|
|
49
|
-
if (isInsideAny(real, realRoots))
|
|
51
|
+
if (isInsideAny(real, realRoots)) {
|
|
52
|
+
return pendingTail.length > 0 ? path.join(real, ...pendingTail) : real;
|
|
53
|
+
}
|
|
50
54
|
throw new Error(
|
|
51
55
|
`Path "${absPath}" resolves through a symlink outside project root "${realRoots[0]}"`
|
|
52
56
|
);
|
|
@@ -54,8 +58,7 @@ async function assertRealInsideRoot(absPath, ctx) {
|
|
|
54
58
|
}
|
|
55
59
|
async function safeResolveReal(input, ctx) {
|
|
56
60
|
const abs = safeResolve(input, ctx);
|
|
57
|
-
await
|
|
58
|
-
return abs;
|
|
61
|
+
return await resolveRealInsideRoot(abs, ctx);
|
|
59
62
|
}
|
|
60
63
|
|
|
61
64
|
// src/e2e.ts
|