@wrongstack/tools 0.306.2 → 0.306.4
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/audit.js +36 -1
- package/dist/builtin.js +258 -153
- package/dist/codebase-index/index.js +241 -137
- package/dist/codebase-index/project-server.js +224 -114
- package/dist/codebase-index/worker.js +210 -107
- package/dist/codebase-index/writer.d.ts +18 -0
- package/dist/diff.js +3 -2
- package/dist/document.js +37 -2
- package/dist/e2e.js +3 -2
- package/dist/edit.js +3 -2
- package/dist/exec.js +3 -2
- package/dist/format.js +36 -1
- package/dist/glob.js +3 -2
- package/dist/grep.js +3 -2
- package/dist/index.js +258 -153
- package/dist/install.js +36 -1
- package/dist/json.js +3 -2
- package/dist/languages/index.js +3 -2
- package/dist/lint.js +36 -1
- package/dist/logs.js +4 -3
- package/dist/outdated.js +4 -3
- package/dist/pack.js +258 -153
- package/dist/patch.js +3 -2
- package/dist/read.js +213 -112
- package/dist/replace.js +36 -1
- package/dist/scaffold.js +36 -1
- package/dist/test.js +36 -1
- package/dist/tool-tier.js +258 -153
- package/dist/tree.js +36 -1
- package/dist/typecheck.js +37 -2
- package/dist/write.js +3 -2
- package/package.json +4 -4
package/dist/patch.js
CHANGED
|
@@ -55,8 +55,9 @@ async function resolveRealInsideRoot(absPath, ctx) {
|
|
|
55
55
|
}
|
|
56
56
|
throw err;
|
|
57
57
|
}
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
const candidate = pendingTail.length > 0 ? path.join(real, ...pendingTail) : real;
|
|
59
|
+
if (isInsideAny(candidate, realRoots)) {
|
|
60
|
+
return candidate;
|
|
60
61
|
}
|
|
61
62
|
throw new Error(
|
|
62
63
|
`Path "${absPath}" resolves through a symlink outside project root "${realRoots[0]}"`
|
package/dist/read.js
CHANGED
|
@@ -2753,8 +2753,9 @@ async function resolveRealInsideRoot(absPath, ctx) {
|
|
|
2753
2753
|
}
|
|
2754
2754
|
throw err;
|
|
2755
2755
|
}
|
|
2756
|
-
|
|
2757
|
-
|
|
2756
|
+
const candidate = pendingTail.length > 0 ? path.join(real, ...pendingTail) : real;
|
|
2757
|
+
if (isInsideAny(candidate, realRoots)) {
|
|
2758
|
+
return candidate;
|
|
2758
2759
|
}
|
|
2759
2760
|
throw new Error(
|
|
2760
2761
|
`Path "${absPath}" resolves through a symlink outside project root "${realRoots[0]}"`
|
|
@@ -2858,6 +2859,7 @@ var indexCircuitBreaker = new IndexCircuitBreaker();
|
|
|
2858
2859
|
// src/codebase-index/indexer.ts
|
|
2859
2860
|
import { expectDefined as expectDefined5 } from "@wrongstack/core/utils";
|
|
2860
2861
|
import { execFile } from "node:child_process";
|
|
2862
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
2861
2863
|
import * as fs9 from "node:fs/promises";
|
|
2862
2864
|
import { availableParallelism } from "node:os";
|
|
2863
2865
|
import * as path13 from "node:path";
|
|
@@ -4178,6 +4180,85 @@ function runSqliteWithRetry(fn) {
|
|
|
4178
4180
|
throw lastError;
|
|
4179
4181
|
}
|
|
4180
4182
|
|
|
4183
|
+
// src/codebase-index/vector-search.ts
|
|
4184
|
+
var RRF_K = 60;
|
|
4185
|
+
var VECTOR_DIMENSIONS = 384;
|
|
4186
|
+
var NGRAM_SIZE = 3;
|
|
4187
|
+
function embedText(text) {
|
|
4188
|
+
const vec = new Float32Array(VECTOR_DIMENSIONS);
|
|
4189
|
+
const normalized = text.toLowerCase().trim();
|
|
4190
|
+
if (normalized.length < NGRAM_SIZE) {
|
|
4191
|
+
const padded = ` ${normalized} `.slice(0, Math.max(NGRAM_SIZE, normalized.length + 2));
|
|
4192
|
+
for (let i = 0; i <= padded.length - NGRAM_SIZE; i++) {
|
|
4193
|
+
const ngram = padded.slice(i, i + NGRAM_SIZE);
|
|
4194
|
+
const bucket = hashNgram(ngram) % VECTOR_DIMENSIONS;
|
|
4195
|
+
vec[bucket] += 1;
|
|
4196
|
+
}
|
|
4197
|
+
} else {
|
|
4198
|
+
for (let i = 0; i <= normalized.length - NGRAM_SIZE; i++) {
|
|
4199
|
+
const ngram = normalized.slice(i, i + NGRAM_SIZE);
|
|
4200
|
+
const bucket = hashNgram(ngram) % VECTOR_DIMENSIONS;
|
|
4201
|
+
vec[bucket] += 1;
|
|
4202
|
+
}
|
|
4203
|
+
}
|
|
4204
|
+
let norm = 0;
|
|
4205
|
+
for (let i = 0; i < VECTOR_DIMENSIONS; i++) {
|
|
4206
|
+
norm += vec[i] * vec[i];
|
|
4207
|
+
}
|
|
4208
|
+
norm = Math.sqrt(norm);
|
|
4209
|
+
if (norm > 0) {
|
|
4210
|
+
for (let i = 0; i < VECTOR_DIMENSIONS; i++) {
|
|
4211
|
+
vec[i] /= norm;
|
|
4212
|
+
}
|
|
4213
|
+
}
|
|
4214
|
+
return vec;
|
|
4215
|
+
}
|
|
4216
|
+
function hashNgram(str) {
|
|
4217
|
+
let hash = 2166136261;
|
|
4218
|
+
for (let i = 0; i < str.length; i++) {
|
|
4219
|
+
hash ^= str.charCodeAt(i);
|
|
4220
|
+
hash = Math.imul(hash, 16777619);
|
|
4221
|
+
}
|
|
4222
|
+
return hash >>> 0;
|
|
4223
|
+
}
|
|
4224
|
+
function cosineSimilarity(a, b) {
|
|
4225
|
+
let dot = 0;
|
|
4226
|
+
const len = Math.min(a.length, b.length);
|
|
4227
|
+
for (let i = 0; i < len; i++) {
|
|
4228
|
+
dot += a[i] * b[i];
|
|
4229
|
+
}
|
|
4230
|
+
return dot;
|
|
4231
|
+
}
|
|
4232
|
+
function encodeVector(vec) {
|
|
4233
|
+
return Buffer.from(vec.buffer, vec.byteOffset, vec.byteLength);
|
|
4234
|
+
}
|
|
4235
|
+
function decodeVector(buf) {
|
|
4236
|
+
const view = new DataView(
|
|
4237
|
+
buf.buffer,
|
|
4238
|
+
buf.byteOffset,
|
|
4239
|
+
buf.byteLength
|
|
4240
|
+
);
|
|
4241
|
+
const copy = new Float32Array(buf.byteLength / 4);
|
|
4242
|
+
for (let i = 0; i < copy.length; i++) {
|
|
4243
|
+
copy[i] = view.getFloat32(i * 4, true);
|
|
4244
|
+
}
|
|
4245
|
+
return copy;
|
|
4246
|
+
}
|
|
4247
|
+
function reciprocalRankFusion(bm25Ranks, vectorRanks, k = RRF_K) {
|
|
4248
|
+
const allIds = /* @__PURE__ */ new Set([...bm25Ranks.keys(), ...vectorRanks.keys()]);
|
|
4249
|
+
const scored = [];
|
|
4250
|
+
for (const id of allIds) {
|
|
4251
|
+
const bm25Rank = bm25Ranks.get(id);
|
|
4252
|
+
const vecRank = vectorRanks.get(id);
|
|
4253
|
+
let score = 0;
|
|
4254
|
+
if (bm25Rank !== void 0) score += 1 / (k + bm25Rank);
|
|
4255
|
+
if (vecRank !== void 0) score += 1 / (k + vecRank);
|
|
4256
|
+
scored.push([id, score]);
|
|
4257
|
+
}
|
|
4258
|
+
scored.sort((a, b) => b[1] - a[1]);
|
|
4259
|
+
return scored;
|
|
4260
|
+
}
|
|
4261
|
+
|
|
4181
4262
|
// src/codebase-index/writer-admin.ts
|
|
4182
4263
|
import * as fs7 from "node:fs";
|
|
4183
4264
|
import * as path11 from "node:path";
|
|
@@ -5237,90 +5318,19 @@ var StorePool = class {
|
|
|
5237
5318
|
}
|
|
5238
5319
|
};
|
|
5239
5320
|
|
|
5240
|
-
// src/codebase-index/vector-search.ts
|
|
5241
|
-
var RRF_K = 60;
|
|
5242
|
-
var VECTOR_DIMENSIONS = 384;
|
|
5243
|
-
var NGRAM_SIZE = 3;
|
|
5244
|
-
function embedText(text) {
|
|
5245
|
-
const vec = new Float32Array(VECTOR_DIMENSIONS);
|
|
5246
|
-
const normalized = text.toLowerCase().trim();
|
|
5247
|
-
if (normalized.length < NGRAM_SIZE) {
|
|
5248
|
-
const padded = ` ${normalized} `.slice(0, Math.max(NGRAM_SIZE, normalized.length + 2));
|
|
5249
|
-
for (let i = 0; i <= padded.length - NGRAM_SIZE; i++) {
|
|
5250
|
-
const ngram = padded.slice(i, i + NGRAM_SIZE);
|
|
5251
|
-
const bucket = hashNgram(ngram) % VECTOR_DIMENSIONS;
|
|
5252
|
-
vec[bucket] += 1;
|
|
5253
|
-
}
|
|
5254
|
-
} else {
|
|
5255
|
-
for (let i = 0; i <= normalized.length - NGRAM_SIZE; i++) {
|
|
5256
|
-
const ngram = normalized.slice(i, i + NGRAM_SIZE);
|
|
5257
|
-
const bucket = hashNgram(ngram) % VECTOR_DIMENSIONS;
|
|
5258
|
-
vec[bucket] += 1;
|
|
5259
|
-
}
|
|
5260
|
-
}
|
|
5261
|
-
let norm = 0;
|
|
5262
|
-
for (let i = 0; i < VECTOR_DIMENSIONS; i++) {
|
|
5263
|
-
norm += vec[i] * vec[i];
|
|
5264
|
-
}
|
|
5265
|
-
norm = Math.sqrt(norm);
|
|
5266
|
-
if (norm > 0) {
|
|
5267
|
-
for (let i = 0; i < VECTOR_DIMENSIONS; i++) {
|
|
5268
|
-
vec[i] /= norm;
|
|
5269
|
-
}
|
|
5270
|
-
}
|
|
5271
|
-
return vec;
|
|
5272
|
-
}
|
|
5273
|
-
function hashNgram(str) {
|
|
5274
|
-
let hash = 2166136261;
|
|
5275
|
-
for (let i = 0; i < str.length; i++) {
|
|
5276
|
-
hash ^= str.charCodeAt(i);
|
|
5277
|
-
hash = Math.imul(hash, 16777619);
|
|
5278
|
-
}
|
|
5279
|
-
return hash >>> 0;
|
|
5280
|
-
}
|
|
5281
|
-
function cosineSimilarity(a, b) {
|
|
5282
|
-
let dot = 0;
|
|
5283
|
-
const len = Math.min(a.length, b.length);
|
|
5284
|
-
for (let i = 0; i < len; i++) {
|
|
5285
|
-
dot += a[i] * b[i];
|
|
5286
|
-
}
|
|
5287
|
-
return dot;
|
|
5288
|
-
}
|
|
5289
|
-
function encodeVector(vec) {
|
|
5290
|
-
return Buffer.from(vec.buffer, vec.byteOffset, vec.byteLength);
|
|
5291
|
-
}
|
|
5292
|
-
function decodeVector(buf) {
|
|
5293
|
-
const view = new DataView(
|
|
5294
|
-
buf.buffer,
|
|
5295
|
-
buf.byteOffset,
|
|
5296
|
-
buf.byteLength
|
|
5297
|
-
);
|
|
5298
|
-
const copy = new Float32Array(buf.byteLength / 4);
|
|
5299
|
-
for (let i = 0; i < copy.length; i++) {
|
|
5300
|
-
copy[i] = view.getFloat32(i * 4, true);
|
|
5301
|
-
}
|
|
5302
|
-
return copy;
|
|
5303
|
-
}
|
|
5304
|
-
function reciprocalRankFusion(bm25Ranks, vectorRanks, k = RRF_K) {
|
|
5305
|
-
const allIds = /* @__PURE__ */ new Set([...bm25Ranks.keys(), ...vectorRanks.keys()]);
|
|
5306
|
-
const scored = [];
|
|
5307
|
-
for (const id of allIds) {
|
|
5308
|
-
const bm25Rank = bm25Ranks.get(id);
|
|
5309
|
-
const vecRank = vectorRanks.get(id);
|
|
5310
|
-
let score = 0;
|
|
5311
|
-
if (bm25Rank !== void 0) score += 1 / (k + bm25Rank);
|
|
5312
|
-
if (vecRank !== void 0) score += 1 / (k + vecRank);
|
|
5313
|
-
scored.push([id, score]);
|
|
5314
|
-
}
|
|
5315
|
-
scored.sort((a, b) => b[1] - a[1]);
|
|
5316
|
-
return scored;
|
|
5317
|
-
}
|
|
5318
|
-
|
|
5319
5321
|
// src/codebase-index/writer.ts
|
|
5320
5322
|
var DB_FILE2 = "index.db";
|
|
5321
5323
|
var MAX_STATEMENT_CACHE = 128;
|
|
5322
5324
|
var IndexStore = class _IndexStore {
|
|
5323
5325
|
db;
|
|
5326
|
+
/**
|
|
5327
|
+
* True while an index run owns one outer SQLite transaction. Individual
|
|
5328
|
+
* writer methods normally protect themselves with BEGIN/COMMIT, but during
|
|
5329
|
+
* a refresh they join this transaction so readers observe either the last
|
|
5330
|
+
* completed index or the next completed index, never an in-between batch.
|
|
5331
|
+
*/
|
|
5332
|
+
atomicIndexUpdateActive = false;
|
|
5333
|
+
writeSavepointSequence = 0;
|
|
5324
5334
|
/** Absolute path to this project's index directory. */
|
|
5325
5335
|
indexDir;
|
|
5326
5336
|
/**
|
|
@@ -5401,6 +5411,51 @@ var IndexStore = class _IndexStore {
|
|
|
5401
5411
|
runWithRetry(fn) {
|
|
5402
5412
|
return runSqliteWithRetry(fn);
|
|
5403
5413
|
}
|
|
5414
|
+
/** Run a complete index mutation as one WAL-visible publication. */
|
|
5415
|
+
async runAtomicIndexUpdate(job) {
|
|
5416
|
+
if (this.atomicIndexUpdateActive) return job();
|
|
5417
|
+
this.runWithRetry(() => this.db.exec("BEGIN IMMEDIATE"));
|
|
5418
|
+
this.atomicIndexUpdateActive = true;
|
|
5419
|
+
try {
|
|
5420
|
+
const result = await job();
|
|
5421
|
+
this.db.exec("COMMIT");
|
|
5422
|
+
return result;
|
|
5423
|
+
} catch (error) {
|
|
5424
|
+
try {
|
|
5425
|
+
this.db.exec("ROLLBACK");
|
|
5426
|
+
} catch {
|
|
5427
|
+
}
|
|
5428
|
+
throw error;
|
|
5429
|
+
} finally {
|
|
5430
|
+
this.atomicIndexUpdateActive = false;
|
|
5431
|
+
}
|
|
5432
|
+
}
|
|
5433
|
+
/**
|
|
5434
|
+
* Begin a method-local transaction. Inside an atomic index publication a
|
|
5435
|
+
* SAVEPOINT preserves the old per-batch rollback boundary, which is needed
|
|
5436
|
+
* when commitBatch falls back to per-file writes after one batch fails.
|
|
5437
|
+
*/
|
|
5438
|
+
beginWriteTransaction() {
|
|
5439
|
+
if (this.atomicIndexUpdateActive) {
|
|
5440
|
+
const savepoint = `index_write_${++this.writeSavepointSequence}`;
|
|
5441
|
+
this.db.exec(`SAVEPOINT ${savepoint}`);
|
|
5442
|
+
return savepoint;
|
|
5443
|
+
}
|
|
5444
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
5445
|
+
return null;
|
|
5446
|
+
}
|
|
5447
|
+
commitWriteTransaction(savepoint) {
|
|
5448
|
+
if (savepoint) this.db.exec(`RELEASE SAVEPOINT ${savepoint}`);
|
|
5449
|
+
else this.db.exec("COMMIT");
|
|
5450
|
+
}
|
|
5451
|
+
rollbackWriteTransaction(savepoint) {
|
|
5452
|
+
if (savepoint) {
|
|
5453
|
+
this.db.exec(`ROLLBACK TO SAVEPOINT ${savepoint}`);
|
|
5454
|
+
this.db.exec(`RELEASE SAVEPOINT ${savepoint}`);
|
|
5455
|
+
} else {
|
|
5456
|
+
this.db.exec("ROLLBACK");
|
|
5457
|
+
}
|
|
5458
|
+
}
|
|
5404
5459
|
/**
|
|
5405
5460
|
* Mirror the in-process language→family map into SQLite.
|
|
5406
5461
|
*
|
|
@@ -5635,7 +5690,7 @@ var IndexStore = class _IndexStore {
|
|
|
5635
5690
|
insertSymbols(symbols) {
|
|
5636
5691
|
this.invalidateBm25();
|
|
5637
5692
|
return this.runWithRetry(() => {
|
|
5638
|
-
this.
|
|
5693
|
+
const ownsTransaction = this.beginWriteTransaction();
|
|
5639
5694
|
try {
|
|
5640
5695
|
let nextId = this.allocateSymbolIds(symbols.length);
|
|
5641
5696
|
const result = [];
|
|
@@ -5662,7 +5717,9 @@ var IndexStore = class _IndexStore {
|
|
|
5662
5717
|
}
|
|
5663
5718
|
vectorRows.push({
|
|
5664
5719
|
id,
|
|
5665
|
-
vector: encodeVector(
|
|
5720
|
+
vector: encodeVector(
|
|
5721
|
+
embedText(s.text || buildIndexableText(s.name, s.signature, s.docComment))
|
|
5722
|
+
)
|
|
5666
5723
|
});
|
|
5667
5724
|
result.push({ ...s, id });
|
|
5668
5725
|
}
|
|
@@ -5680,10 +5737,10 @@ var IndexStore = class _IndexStore {
|
|
|
5680
5737
|
vectorRows
|
|
5681
5738
|
);
|
|
5682
5739
|
}
|
|
5683
|
-
this.
|
|
5740
|
+
this.commitWriteTransaction(ownsTransaction);
|
|
5684
5741
|
return result;
|
|
5685
5742
|
} catch (err) {
|
|
5686
|
-
this.
|
|
5743
|
+
this.rollbackWriteTransaction(ownsTransaction);
|
|
5687
5744
|
throw err;
|
|
5688
5745
|
}
|
|
5689
5746
|
});
|
|
@@ -5691,7 +5748,7 @@ var IndexStore = class _IndexStore {
|
|
|
5691
5748
|
deleteSymbolsForFile(file) {
|
|
5692
5749
|
this.invalidateBm25();
|
|
5693
5750
|
this.runWithRetry(() => {
|
|
5694
|
-
this.
|
|
5751
|
+
const ownsTransaction = this.beginWriteTransaction();
|
|
5695
5752
|
try {
|
|
5696
5753
|
const affectedNames = this.invalidateIncomingRefsForFiles([file]);
|
|
5697
5754
|
if (this.ftsAvailable) {
|
|
@@ -5706,9 +5763,9 @@ var IndexStore = class _IndexStore {
|
|
|
5706
5763
|
}
|
|
5707
5764
|
this.stmt("DELETE FROM symbols WHERE file_fk = ?").run(file);
|
|
5708
5765
|
this.resolveRefsForNamesUnsafe(affectedNames);
|
|
5709
|
-
this.
|
|
5766
|
+
this.commitWriteTransaction(ownsTransaction);
|
|
5710
5767
|
} catch (error) {
|
|
5711
|
-
this.
|
|
5768
|
+
this.rollbackWriteTransaction(ownsTransaction);
|
|
5712
5769
|
throw error;
|
|
5713
5770
|
}
|
|
5714
5771
|
});
|
|
@@ -5721,7 +5778,7 @@ var IndexStore = class _IndexStore {
|
|
|
5721
5778
|
deleteFile(file) {
|
|
5722
5779
|
this.invalidateBm25();
|
|
5723
5780
|
this.runWithRetry(() => {
|
|
5724
|
-
this.
|
|
5781
|
+
const ownsTransaction = this.beginWriteTransaction();
|
|
5725
5782
|
try {
|
|
5726
5783
|
const affectedNames = this.invalidateIncomingRefsForFiles([file]);
|
|
5727
5784
|
if (this.ftsAvailable) {
|
|
@@ -5740,9 +5797,9 @@ var IndexStore = class _IndexStore {
|
|
|
5740
5797
|
this.stmt("DELETE FROM symbols WHERE file_fk = ?").run(file);
|
|
5741
5798
|
this.stmt("DELETE FROM files WHERE file = ?").run(file);
|
|
5742
5799
|
this.resolveRefsForNamesUnsafe(affectedNames);
|
|
5743
|
-
this.
|
|
5800
|
+
this.commitWriteTransaction(ownsTransaction);
|
|
5744
5801
|
} catch (err) {
|
|
5745
|
-
this.
|
|
5802
|
+
this.rollbackWriteTransaction(ownsTransaction);
|
|
5746
5803
|
throw err;
|
|
5747
5804
|
}
|
|
5748
5805
|
});
|
|
@@ -6129,7 +6186,7 @@ var IndexStore = class _IndexStore {
|
|
|
6129
6186
|
clearAll() {
|
|
6130
6187
|
this.invalidateBm25();
|
|
6131
6188
|
this.runWithRetry(() => {
|
|
6132
|
-
this.
|
|
6189
|
+
const ownsTransaction = this.beginWriteTransaction();
|
|
6133
6190
|
try {
|
|
6134
6191
|
this.db.exec("DROP TABLE IF EXISTS refs");
|
|
6135
6192
|
this.db.exec("DROP TABLE IF EXISTS symbols");
|
|
@@ -6137,15 +6194,15 @@ var IndexStore = class _IndexStore {
|
|
|
6137
6194
|
this.db.exec("DROP TABLE IF EXISTS metadata");
|
|
6138
6195
|
if (this.ftsAvailable) this.db.exec("DROP TABLE IF EXISTS symbols_fts");
|
|
6139
6196
|
this.db.exec("DROP TABLE IF EXISTS symbol_vectors");
|
|
6140
|
-
this.db.exec("COMMIT");
|
|
6141
6197
|
this.stmtCache.clear();
|
|
6142
6198
|
this.initSchema();
|
|
6143
6199
|
this.stmt("INSERT OR REPLACE INTO metadata(key, value) VALUES (?, ?)").run(
|
|
6144
6200
|
_IndexStore.NEXT_SYMBOL_ID_KEY,
|
|
6145
6201
|
"1"
|
|
6146
6202
|
);
|
|
6203
|
+
this.commitWriteTransaction(ownsTransaction);
|
|
6147
6204
|
} catch (err) {
|
|
6148
|
-
this.
|
|
6205
|
+
this.rollbackWriteTransaction(ownsTransaction);
|
|
6149
6206
|
throw err;
|
|
6150
6207
|
}
|
|
6151
6208
|
});
|
|
@@ -6207,7 +6264,7 @@ var IndexStore = class _IndexStore {
|
|
|
6207
6264
|
}
|
|
6208
6265
|
this.invalidateBm25();
|
|
6209
6266
|
return this.runWithRetry(() => {
|
|
6210
|
-
this.
|
|
6267
|
+
const ownsTransaction = this.beginWriteTransaction();
|
|
6211
6268
|
try {
|
|
6212
6269
|
const affectedNames = /* @__PURE__ */ new Set();
|
|
6213
6270
|
for (const entry of entries) {
|
|
@@ -6268,7 +6325,9 @@ var IndexStore = class _IndexStore {
|
|
|
6268
6325
|
}
|
|
6269
6326
|
vectorRows.push({
|
|
6270
6327
|
id,
|
|
6271
|
-
vector: encodeVector(
|
|
6328
|
+
vector: encodeVector(
|
|
6329
|
+
embedText(s.text || buildIndexableText(s.name, s.signature, s.docComment))
|
|
6330
|
+
)
|
|
6272
6331
|
});
|
|
6273
6332
|
const inserted = { ...s, id };
|
|
6274
6333
|
allInserted.push(inserted);
|
|
@@ -6313,10 +6372,10 @@ var IndexStore = class _IndexStore {
|
|
|
6313
6372
|
);
|
|
6314
6373
|
}
|
|
6315
6374
|
this.resolveRefsForNamesUnsafe(affectedNames);
|
|
6316
|
-
this.
|
|
6375
|
+
this.commitWriteTransaction(ownsTransaction);
|
|
6317
6376
|
return allInserted;
|
|
6318
6377
|
} catch (err) {
|
|
6319
|
-
this.
|
|
6378
|
+
this.rollbackWriteTransaction(ownsTransaction);
|
|
6320
6379
|
throw err;
|
|
6321
6380
|
}
|
|
6322
6381
|
});
|
|
@@ -6393,7 +6452,7 @@ var IndexStore = class _IndexStore {
|
|
|
6393
6452
|
replaceEmptyFile(meta) {
|
|
6394
6453
|
this.invalidateBm25();
|
|
6395
6454
|
this.runWithRetry(() => {
|
|
6396
|
-
this.
|
|
6455
|
+
const ownsTransaction = this.beginWriteTransaction();
|
|
6397
6456
|
try {
|
|
6398
6457
|
const affectedNames = this.invalidateIncomingRefsForFiles([meta.file]);
|
|
6399
6458
|
if (this.ftsAvailable) {
|
|
@@ -6428,9 +6487,9 @@ var IndexStore = class _IndexStore {
|
|
|
6428
6487
|
meta.lastIndexed
|
|
6429
6488
|
);
|
|
6430
6489
|
this.resolveRefsForNamesUnsafe(affectedNames);
|
|
6431
|
-
this.
|
|
6490
|
+
this.commitWriteTransaction(ownsTransaction);
|
|
6432
6491
|
} catch (err) {
|
|
6433
|
-
this.
|
|
6492
|
+
this.rollbackWriteTransaction(ownsTransaction);
|
|
6434
6493
|
throw err;
|
|
6435
6494
|
}
|
|
6436
6495
|
});
|
|
@@ -6623,6 +6682,10 @@ var DEFAULT_IGNORE_FILES = /* @__PURE__ */ new Set(["package-lock.json", "pnpm-l
|
|
|
6623
6682
|
var INDEXABLE_EXTENSION_SET = new Set(INDEXABLE_EXTENSIONS);
|
|
6624
6683
|
var MAX_INDEX_FILE_BYTES = 5 * 1024 * 1024;
|
|
6625
6684
|
var MAX_GIT_FILE_LIST_BYTES = 64 * 1024 * 1024;
|
|
6685
|
+
var GIT_SNAPSHOT_METADATA_KEY = "git_discovery_snapshot";
|
|
6686
|
+
var IndexSourceChangedError = class extends Error {
|
|
6687
|
+
name = "IndexSourceChangedError";
|
|
6688
|
+
};
|
|
6626
6689
|
function isWithinProject(projectRoot, file) {
|
|
6627
6690
|
const rel = path13.relative(projectRoot, file);
|
|
6628
6691
|
return rel !== "" && !rel.startsWith(`..${path13.sep}`) && rel !== ".." && !path13.isAbsolute(rel);
|
|
@@ -6659,7 +6722,7 @@ async function findGitSourceFiles(projectRoot, ignore, signal) {
|
|
|
6659
6722
|
if (normalizeComparablePath(topLevel) !== normalizeComparablePath(projectRoot)) return null;
|
|
6660
6723
|
throwIfAborted(signal);
|
|
6661
6724
|
const ignoreSet = /* @__PURE__ */ new Set([...DEFAULT_IGNORE, ...ignore]);
|
|
6662
|
-
const [output, statusOutput] = await Promise.all([
|
|
6725
|
+
const [output, statusOutput, stagedOutput] = await Promise.all([
|
|
6663
6726
|
gitOutput(projectRoot, ["ls-files", "--cached", "--others", "--exclude-standard", "-z"]),
|
|
6664
6727
|
gitOutput(projectRoot, [
|
|
6665
6728
|
"status",
|
|
@@ -6667,7 +6730,8 @@ async function findGitSourceFiles(projectRoot, ignore, signal) {
|
|
|
6667
6730
|
"-z",
|
|
6668
6731
|
"--untracked-files=all",
|
|
6669
6732
|
"--ignored=no"
|
|
6670
|
-
])
|
|
6733
|
+
]),
|
|
6734
|
+
gitOutput(projectRoot, ["ls-files", "--stage", "-z"])
|
|
6671
6735
|
]);
|
|
6672
6736
|
throwIfAborted(signal);
|
|
6673
6737
|
const dirty = /* @__PURE__ */ new Set();
|
|
@@ -6697,9 +6761,17 @@ async function findGitSourceFiles(projectRoot, ignore, signal) {
|
|
|
6697
6761
|
const ext = path13.extname(relative3).toLowerCase();
|
|
6698
6762
|
if (INDEXABLE_EXTENSION_SET.has(ext) || detectLang(full) !== null) files.push(full);
|
|
6699
6763
|
}
|
|
6764
|
+
const snapshot = createHash2("sha256").update(stagedOutput).update("\0").update(statusOutput);
|
|
6765
|
+
const indexedFiles = new Set(files);
|
|
6766
|
+
for (const dirtyFile of [...dirty].sort()) {
|
|
6767
|
+
if (!indexedFiles.has(dirtyFile) || deleted.has(dirtyFile)) continue;
|
|
6768
|
+
snapshot.update("\0").update(dirtyFile).update("\0");
|
|
6769
|
+
snapshot.update(xxhash64String(await fs9.readFile(dirtyFile, "utf8")));
|
|
6770
|
+
}
|
|
6700
6771
|
return {
|
|
6701
6772
|
files,
|
|
6702
|
-
trustedUnchanged: new Set(files.filter((file) => !dirty.has(file)))
|
|
6773
|
+
trustedUnchanged: new Set(files.filter((file) => !dirty.has(file))),
|
|
6774
|
+
snapshotKey: snapshot.digest("hex")
|
|
6703
6775
|
};
|
|
6704
6776
|
} catch {
|
|
6705
6777
|
return null;
|
|
@@ -6712,7 +6784,8 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
6712
6784
|
files: gitFiles.files,
|
|
6713
6785
|
complete: true,
|
|
6714
6786
|
errors: [],
|
|
6715
|
-
trustedUnchanged: gitFiles.trustedUnchanged
|
|
6787
|
+
trustedUnchanged: gitFiles.trustedUnchanged,
|
|
6788
|
+
snapshotKey: gitFiles.snapshotKey
|
|
6716
6789
|
};
|
|
6717
6790
|
}
|
|
6718
6791
|
const results = [];
|
|
@@ -6799,6 +6872,17 @@ async function resolveProjectRelations(store, projectRoot, opts) {
|
|
|
6799
6872
|
}
|
|
6800
6873
|
}
|
|
6801
6874
|
async function runIndexerWithStore(store, opts) {
|
|
6875
|
+
let result;
|
|
6876
|
+
try {
|
|
6877
|
+
result = await store.runAtomicIndexUpdate(() => runIndexerAtomic(store, opts));
|
|
6878
|
+
} catch (error) {
|
|
6879
|
+
if (!(error instanceof IndexSourceChangedError)) throw error;
|
|
6880
|
+
result = await store.runAtomicIndexUpdate(() => runIndexerAtomic(store, opts));
|
|
6881
|
+
}
|
|
6882
|
+
if (!opts.files) store.compactIfNeeded();
|
|
6883
|
+
return result;
|
|
6884
|
+
}
|
|
6885
|
+
async function runIndexerAtomic(store, opts) {
|
|
6802
6886
|
const { projectRoot, langs, ignore = [], signal } = opts;
|
|
6803
6887
|
const relationGraphVersion = "2";
|
|
6804
6888
|
const refResolutionVersion = "2";
|
|
@@ -6818,6 +6902,7 @@ async function runIndexerWithStore(store, opts) {
|
|
|
6818
6902
|
let discoveredFiles = null;
|
|
6819
6903
|
let discoveryComplete = true;
|
|
6820
6904
|
let trustedUnchanged;
|
|
6905
|
+
let discoverySnapshotKey;
|
|
6821
6906
|
if (opts.files && opts.files.length > 0) {
|
|
6822
6907
|
files = opts.files.map((f) => path13.resolve(projectRoot, f)).filter((f) => {
|
|
6823
6908
|
if (!isWithinProject(projectRoot, f)) return false;
|
|
@@ -6831,6 +6916,7 @@ async function runIndexerWithStore(store, opts) {
|
|
|
6831
6916
|
discoveryComplete = discovery.complete;
|
|
6832
6917
|
discoveredFiles = new Set(files);
|
|
6833
6918
|
trustedUnchanged = discovery.trustedUnchanged;
|
|
6919
|
+
discoverySnapshotKey = discovery.snapshotKey;
|
|
6834
6920
|
}
|
|
6835
6921
|
if (langs && langs.length > 0) {
|
|
6836
6922
|
const langSet = new Set(langs);
|
|
@@ -6844,6 +6930,8 @@ async function runIndexerWithStore(store, opts) {
|
|
|
6844
6930
|
if (!force) {
|
|
6845
6931
|
for (const meta of store.getAllFileMetas()) existingMeta.set(meta.file, meta);
|
|
6846
6932
|
}
|
|
6933
|
+
const snapshotTrusted = !force && discoverySnapshotKey !== void 0 && store.getMetadata(GIT_SNAPSHOT_METADATA_KEY) === discoverySnapshotKey;
|
|
6934
|
+
if (!snapshotTrusted) trustedUnchanged = void 0;
|
|
6847
6935
|
const totalFilesForProgress = files.length;
|
|
6848
6936
|
let filesPreSkipped = 0;
|
|
6849
6937
|
if (!force && trustedUnchanged) {
|
|
@@ -6906,9 +6994,6 @@ async function runIndexerWithStore(store, opts) {
|
|
|
6906
6994
|
};
|
|
6907
6995
|
}
|
|
6908
6996
|
const meta = existingMeta.get(file);
|
|
6909
|
-
if (!force && meta && meta.mtimeMs === Math.floor(stat3.mtimeMs)) {
|
|
6910
|
-
return { file, stat: stat3, lang, parsed: null, skippedMeta: meta };
|
|
6911
|
-
}
|
|
6912
6997
|
let content;
|
|
6913
6998
|
try {
|
|
6914
6999
|
content = await fs9.readFile(file, { encoding: "utf8", signal });
|
|
@@ -7137,9 +7222,18 @@ async function runIndexerWithStore(store, opts) {
|
|
|
7137
7222
|
});
|
|
7138
7223
|
store.setMetadata("ref_resolution_version", refResolutionVersion);
|
|
7139
7224
|
store.setMetadata("relation_graph_version", relationGraphVersion);
|
|
7225
|
+
const completeProjectScope = !opts.files && (!langs || langs.length === 0) && (!opts.ignore || opts.ignore.length === 0);
|
|
7226
|
+
if (completeProjectScope && discoverySnapshotKey !== void 0) {
|
|
7227
|
+
const finalSnapshot = await findGitSourceFiles(projectRoot, ignore, signal);
|
|
7228
|
+
if (!finalSnapshot || finalSnapshot.snapshotKey !== discoverySnapshotKey) {
|
|
7229
|
+
throw new IndexSourceChangedError(
|
|
7230
|
+
"Project files changed during indexing; retrying before publishing the generation."
|
|
7231
|
+
);
|
|
7232
|
+
}
|
|
7233
|
+
store.setMetadata(GIT_SNAPSHOT_METADATA_KEY, errors.length === 0 ? discoverySnapshotKey : "");
|
|
7234
|
+
}
|
|
7140
7235
|
if (!opts.files || filesIndexed >= 50) store.optimize();
|
|
7141
7236
|
store.setLastIndexed(Date.now());
|
|
7142
|
-
if (!opts.files) store.compactIfNeeded();
|
|
7143
7237
|
const durationMs = Date.now() - startMs;
|
|
7144
7238
|
return {
|
|
7145
7239
|
filesIndexed,
|
|
@@ -7272,7 +7366,7 @@ function decodeBinaryFrame(payload) {
|
|
|
7272
7366
|
}
|
|
7273
7367
|
|
|
7274
7368
|
// src/codebase-index/project-server-endpoint.ts
|
|
7275
|
-
import { createHash as
|
|
7369
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
7276
7370
|
import * as fs10 from "node:fs";
|
|
7277
7371
|
import * as os3 from "node:os";
|
|
7278
7372
|
import * as path14 from "node:path";
|
|
@@ -7290,7 +7384,7 @@ function projectIndexServerBuildId(entrypoint) {
|
|
|
7290
7384
|
if (buildIdCache?.file === file && buildIdCache.mtimeMs === stat3.mtimeMs && buildIdCache.size === stat3.size) {
|
|
7291
7385
|
return buildIdCache.buildId;
|
|
7292
7386
|
}
|
|
7293
|
-
const buildId =
|
|
7387
|
+
const buildId = createHash3("sha256").update(fs10.readFileSync(file)).digest("hex").slice(0, 24);
|
|
7294
7388
|
buildIdCache = { file, mtimeMs: stat3.mtimeMs, size: stat3.size, buildId };
|
|
7295
7389
|
return buildId;
|
|
7296
7390
|
} catch {
|
|
@@ -7303,7 +7397,7 @@ function normalizeLocalPath(value) {
|
|
|
7303
7397
|
}
|
|
7304
7398
|
function projectIndexServerKey(projectRoot, indexDir) {
|
|
7305
7399
|
const resolvedIndexDir = normalizeLocalPath(resolveIndexDir(projectRoot, indexDir));
|
|
7306
|
-
return
|
|
7400
|
+
return createHash3("sha256").update(resolvedIndexDir).digest("hex").slice(0, 24);
|
|
7307
7401
|
}
|
|
7308
7402
|
function projectIndexServerEndpoint(projectRoot, indexDir) {
|
|
7309
7403
|
const key = projectIndexServerKey(projectRoot, indexDir);
|
|
@@ -8192,6 +8286,13 @@ function callIndexOp(op, args, opts) {
|
|
|
8192
8286
|
});
|
|
8193
8287
|
}
|
|
8194
8288
|
async function callInline(op, args, opts) {
|
|
8289
|
+
if (op !== "index" && _indexing) {
|
|
8290
|
+
const error = new Error(
|
|
8291
|
+
"Codebase index refresh in progress; retry after the completed generation is published."
|
|
8292
|
+
);
|
|
8293
|
+
error.name = "IndexRefreshInProgressError";
|
|
8294
|
+
throw error;
|
|
8295
|
+
}
|
|
8195
8296
|
const ac = new AbortController();
|
|
8196
8297
|
const onOuterAbort = () => ac.abort(opts.signal?.reason ?? new Error("Indexing cancelled"));
|
|
8197
8298
|
if (opts.signal?.aborted) onOuterAbort();
|
package/dist/replace.js
CHANGED
|
@@ -139,6 +139,7 @@ var MAX_SUBJECT_LEN = 64 * 1024;
|
|
|
139
139
|
|
|
140
140
|
// src/_util.ts
|
|
141
141
|
import { createHash } from "node:crypto";
|
|
142
|
+
import * as fsp from "node:fs/promises";
|
|
142
143
|
import * as path from "node:path";
|
|
143
144
|
import * as Core from "@wrongstack/core/utils";
|
|
144
145
|
function sha256hex(content) {
|
|
@@ -165,6 +166,40 @@ function ensureInsideRoot(absPath, ctx) {
|
|
|
165
166
|
function safeResolve(input, ctx) {
|
|
166
167
|
return ensureInsideRoot(resolvePath(input, ctx), ctx);
|
|
167
168
|
}
|
|
169
|
+
async function resolveRealInsideRoot(absPath, ctx) {
|
|
170
|
+
if (ctx.allowOutsideProjectRoot) return absPath;
|
|
171
|
+
const realRoots = await Promise.all(
|
|
172
|
+
allowedRoots(ctx).map((r) => fsp.realpath(r).catch(() => path.resolve(r)))
|
|
173
|
+
);
|
|
174
|
+
let probe = absPath;
|
|
175
|
+
const pendingTail = [];
|
|
176
|
+
for (; ; ) {
|
|
177
|
+
let real;
|
|
178
|
+
try {
|
|
179
|
+
real = await fsp.realpath(probe);
|
|
180
|
+
} catch (err) {
|
|
181
|
+
if (err.code === "ENOENT") {
|
|
182
|
+
const parent = path.dirname(probe);
|
|
183
|
+
if (parent === probe) return absPath;
|
|
184
|
+
pendingTail.unshift(path.basename(probe));
|
|
185
|
+
probe = parent;
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
throw err;
|
|
189
|
+
}
|
|
190
|
+
const candidate = pendingTail.length > 0 ? path.join(real, ...pendingTail) : real;
|
|
191
|
+
if (isInsideAny(candidate, realRoots)) {
|
|
192
|
+
return candidate;
|
|
193
|
+
}
|
|
194
|
+
throw new Error(
|
|
195
|
+
`Path "${absPath}" resolves through a symlink outside project root "${realRoots[0]}"`
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
async function safeResolveReal(input, ctx) {
|
|
200
|
+
const abs = safeResolve(input, ctx);
|
|
201
|
+
return await resolveRealInsideRoot(abs, ctx);
|
|
202
|
+
}
|
|
168
203
|
function truncateDiffPayload(diff, maxBytes) {
|
|
169
204
|
const total = Buffer.byteLength(diff, "utf8");
|
|
170
205
|
if (total <= maxBytes) return { text: diff, truncated: false };
|
|
@@ -408,7 +443,7 @@ async function resolveFiles(filesInput, ctx, extraGlob) {
|
|
|
408
443
|
const parts = normalized.split(",").map((s) => s.trim()).filter(Boolean);
|
|
409
444
|
const resolved = [];
|
|
410
445
|
for (const p of parts) {
|
|
411
|
-
const absPath =
|
|
446
|
+
const absPath = await safeResolveReal(p, ctx);
|
|
412
447
|
if (extraGlob && !passesExtraGlob(extraGlob, path2.basename(absPath), absPath)) continue;
|
|
413
448
|
const stat2 = await fs.stat(absPath).catch(() => null);
|
|
414
449
|
if (stat2?.isFile()) {
|