@wrongstack/tools 0.306.0 → 0.306.3
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/builtin.js +256 -137
- package/dist/codebase-index/index.js +241 -137
- package/dist/codebase-index/project-server.js +233 -124
- package/dist/codebase-index/worker.js +210 -107
- package/dist/codebase-index/writer.d.ts +18 -0
- package/dist/index.js +258 -138
- package/dist/pack.js +256 -137
- package/dist/plan.js +15 -0
- package/dist/read.js +210 -110
- package/dist/session-kanban.js +3 -1
- package/dist/task.js +15 -0
- package/dist/todo.js +15 -0
- package/dist/tool-tier.js +256 -137
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -11503,6 +11503,85 @@ function runSqliteWithRetry(fn) {
|
|
|
11503
11503
|
throw lastError;
|
|
11504
11504
|
}
|
|
11505
11505
|
|
|
11506
|
+
// src/codebase-index/vector-search.ts
|
|
11507
|
+
var RRF_K = 60;
|
|
11508
|
+
var VECTOR_DIMENSIONS = 384;
|
|
11509
|
+
var NGRAM_SIZE = 3;
|
|
11510
|
+
function embedText(text) {
|
|
11511
|
+
const vec = new Float32Array(VECTOR_DIMENSIONS);
|
|
11512
|
+
const normalized = text.toLowerCase().trim();
|
|
11513
|
+
if (normalized.length < NGRAM_SIZE) {
|
|
11514
|
+
const padded = ` ${normalized} `.slice(0, Math.max(NGRAM_SIZE, normalized.length + 2));
|
|
11515
|
+
for (let i = 0; i <= padded.length - NGRAM_SIZE; i++) {
|
|
11516
|
+
const ngram = padded.slice(i, i + NGRAM_SIZE);
|
|
11517
|
+
const bucket = hashNgram(ngram) % VECTOR_DIMENSIONS;
|
|
11518
|
+
vec[bucket] += 1;
|
|
11519
|
+
}
|
|
11520
|
+
} else {
|
|
11521
|
+
for (let i = 0; i <= normalized.length - NGRAM_SIZE; i++) {
|
|
11522
|
+
const ngram = normalized.slice(i, i + NGRAM_SIZE);
|
|
11523
|
+
const bucket = hashNgram(ngram) % VECTOR_DIMENSIONS;
|
|
11524
|
+
vec[bucket] += 1;
|
|
11525
|
+
}
|
|
11526
|
+
}
|
|
11527
|
+
let norm = 0;
|
|
11528
|
+
for (let i = 0; i < VECTOR_DIMENSIONS; i++) {
|
|
11529
|
+
norm += vec[i] * vec[i];
|
|
11530
|
+
}
|
|
11531
|
+
norm = Math.sqrt(norm);
|
|
11532
|
+
if (norm > 0) {
|
|
11533
|
+
for (let i = 0; i < VECTOR_DIMENSIONS; i++) {
|
|
11534
|
+
vec[i] /= norm;
|
|
11535
|
+
}
|
|
11536
|
+
}
|
|
11537
|
+
return vec;
|
|
11538
|
+
}
|
|
11539
|
+
function hashNgram(str) {
|
|
11540
|
+
let hash = 2166136261;
|
|
11541
|
+
for (let i = 0; i < str.length; i++) {
|
|
11542
|
+
hash ^= str.charCodeAt(i);
|
|
11543
|
+
hash = Math.imul(hash, 16777619);
|
|
11544
|
+
}
|
|
11545
|
+
return hash >>> 0;
|
|
11546
|
+
}
|
|
11547
|
+
function cosineSimilarity(a, b) {
|
|
11548
|
+
let dot = 0;
|
|
11549
|
+
const len = Math.min(a.length, b.length);
|
|
11550
|
+
for (let i = 0; i < len; i++) {
|
|
11551
|
+
dot += a[i] * b[i];
|
|
11552
|
+
}
|
|
11553
|
+
return dot;
|
|
11554
|
+
}
|
|
11555
|
+
function encodeVector(vec) {
|
|
11556
|
+
return Buffer.from(vec.buffer, vec.byteOffset, vec.byteLength);
|
|
11557
|
+
}
|
|
11558
|
+
function decodeVector(buf) {
|
|
11559
|
+
const view = new DataView(
|
|
11560
|
+
buf.buffer,
|
|
11561
|
+
buf.byteOffset,
|
|
11562
|
+
buf.byteLength
|
|
11563
|
+
);
|
|
11564
|
+
const copy = new Float32Array(buf.byteLength / 4);
|
|
11565
|
+
for (let i = 0; i < copy.length; i++) {
|
|
11566
|
+
copy[i] = view.getFloat32(i * 4, true);
|
|
11567
|
+
}
|
|
11568
|
+
return copy;
|
|
11569
|
+
}
|
|
11570
|
+
function reciprocalRankFusion(bm25Ranks, vectorRanks, k = RRF_K) {
|
|
11571
|
+
const allIds = /* @__PURE__ */ new Set([...bm25Ranks.keys(), ...vectorRanks.keys()]);
|
|
11572
|
+
const scored = [];
|
|
11573
|
+
for (const id of allIds) {
|
|
11574
|
+
const bm25Rank = bm25Ranks.get(id);
|
|
11575
|
+
const vecRank = vectorRanks.get(id);
|
|
11576
|
+
let score = 0;
|
|
11577
|
+
if (bm25Rank !== void 0) score += 1 / (k + bm25Rank);
|
|
11578
|
+
if (vecRank !== void 0) score += 1 / (k + vecRank);
|
|
11579
|
+
scored.push([id, score]);
|
|
11580
|
+
}
|
|
11581
|
+
scored.sort((a, b) => b[1] - a[1]);
|
|
11582
|
+
return scored;
|
|
11583
|
+
}
|
|
11584
|
+
|
|
11506
11585
|
// src/codebase-index/writer-admin.ts
|
|
11507
11586
|
import * as fs9 from "node:fs";
|
|
11508
11587
|
import * as path14 from "node:path";
|
|
@@ -12864,90 +12943,19 @@ var StorePool = class {
|
|
|
12864
12943
|
}
|
|
12865
12944
|
};
|
|
12866
12945
|
|
|
12867
|
-
// src/codebase-index/vector-search.ts
|
|
12868
|
-
var RRF_K = 60;
|
|
12869
|
-
var VECTOR_DIMENSIONS = 384;
|
|
12870
|
-
var NGRAM_SIZE = 3;
|
|
12871
|
-
function embedText(text) {
|
|
12872
|
-
const vec = new Float32Array(VECTOR_DIMENSIONS);
|
|
12873
|
-
const normalized = text.toLowerCase().trim();
|
|
12874
|
-
if (normalized.length < NGRAM_SIZE) {
|
|
12875
|
-
const padded = ` ${normalized} `.slice(0, Math.max(NGRAM_SIZE, normalized.length + 2));
|
|
12876
|
-
for (let i = 0; i <= padded.length - NGRAM_SIZE; i++) {
|
|
12877
|
-
const ngram = padded.slice(i, i + NGRAM_SIZE);
|
|
12878
|
-
const bucket = hashNgram(ngram) % VECTOR_DIMENSIONS;
|
|
12879
|
-
vec[bucket] += 1;
|
|
12880
|
-
}
|
|
12881
|
-
} else {
|
|
12882
|
-
for (let i = 0; i <= normalized.length - NGRAM_SIZE; i++) {
|
|
12883
|
-
const ngram = normalized.slice(i, i + NGRAM_SIZE);
|
|
12884
|
-
const bucket = hashNgram(ngram) % VECTOR_DIMENSIONS;
|
|
12885
|
-
vec[bucket] += 1;
|
|
12886
|
-
}
|
|
12887
|
-
}
|
|
12888
|
-
let norm = 0;
|
|
12889
|
-
for (let i = 0; i < VECTOR_DIMENSIONS; i++) {
|
|
12890
|
-
norm += vec[i] * vec[i];
|
|
12891
|
-
}
|
|
12892
|
-
norm = Math.sqrt(norm);
|
|
12893
|
-
if (norm > 0) {
|
|
12894
|
-
for (let i = 0; i < VECTOR_DIMENSIONS; i++) {
|
|
12895
|
-
vec[i] /= norm;
|
|
12896
|
-
}
|
|
12897
|
-
}
|
|
12898
|
-
return vec;
|
|
12899
|
-
}
|
|
12900
|
-
function hashNgram(str) {
|
|
12901
|
-
let hash = 2166136261;
|
|
12902
|
-
for (let i = 0; i < str.length; i++) {
|
|
12903
|
-
hash ^= str.charCodeAt(i);
|
|
12904
|
-
hash = Math.imul(hash, 16777619);
|
|
12905
|
-
}
|
|
12906
|
-
return hash >>> 0;
|
|
12907
|
-
}
|
|
12908
|
-
function cosineSimilarity(a, b) {
|
|
12909
|
-
let dot = 0;
|
|
12910
|
-
const len = Math.min(a.length, b.length);
|
|
12911
|
-
for (let i = 0; i < len; i++) {
|
|
12912
|
-
dot += a[i] * b[i];
|
|
12913
|
-
}
|
|
12914
|
-
return dot;
|
|
12915
|
-
}
|
|
12916
|
-
function encodeVector(vec) {
|
|
12917
|
-
return Buffer.from(vec.buffer, vec.byteOffset, vec.byteLength);
|
|
12918
|
-
}
|
|
12919
|
-
function decodeVector(buf) {
|
|
12920
|
-
const view = new DataView(
|
|
12921
|
-
buf.buffer,
|
|
12922
|
-
buf.byteOffset,
|
|
12923
|
-
buf.byteLength
|
|
12924
|
-
);
|
|
12925
|
-
const copy = new Float32Array(buf.byteLength / 4);
|
|
12926
|
-
for (let i = 0; i < copy.length; i++) {
|
|
12927
|
-
copy[i] = view.getFloat32(i * 4, true);
|
|
12928
|
-
}
|
|
12929
|
-
return copy;
|
|
12930
|
-
}
|
|
12931
|
-
function reciprocalRankFusion(bm25Ranks, vectorRanks, k = RRF_K) {
|
|
12932
|
-
const allIds = /* @__PURE__ */ new Set([...bm25Ranks.keys(), ...vectorRanks.keys()]);
|
|
12933
|
-
const scored = [];
|
|
12934
|
-
for (const id of allIds) {
|
|
12935
|
-
const bm25Rank = bm25Ranks.get(id);
|
|
12936
|
-
const vecRank = vectorRanks.get(id);
|
|
12937
|
-
let score = 0;
|
|
12938
|
-
if (bm25Rank !== void 0) score += 1 / (k + bm25Rank);
|
|
12939
|
-
if (vecRank !== void 0) score += 1 / (k + vecRank);
|
|
12940
|
-
scored.push([id, score]);
|
|
12941
|
-
}
|
|
12942
|
-
scored.sort((a, b) => b[1] - a[1]);
|
|
12943
|
-
return scored;
|
|
12944
|
-
}
|
|
12945
|
-
|
|
12946
12946
|
// src/codebase-index/writer.ts
|
|
12947
12947
|
var DB_FILE2 = "index.db";
|
|
12948
12948
|
var MAX_STATEMENT_CACHE = 128;
|
|
12949
12949
|
var IndexStore = class _IndexStore {
|
|
12950
12950
|
db;
|
|
12951
|
+
/**
|
|
12952
|
+
* True while an index run owns one outer SQLite transaction. Individual
|
|
12953
|
+
* writer methods normally protect themselves with BEGIN/COMMIT, but during
|
|
12954
|
+
* a refresh they join this transaction so readers observe either the last
|
|
12955
|
+
* completed index or the next completed index, never an in-between batch.
|
|
12956
|
+
*/
|
|
12957
|
+
atomicIndexUpdateActive = false;
|
|
12958
|
+
writeSavepointSequence = 0;
|
|
12951
12959
|
/** Absolute path to this project's index directory. */
|
|
12952
12960
|
indexDir;
|
|
12953
12961
|
/**
|
|
@@ -13028,6 +13036,51 @@ var IndexStore = class _IndexStore {
|
|
|
13028
13036
|
runWithRetry(fn) {
|
|
13029
13037
|
return runSqliteWithRetry(fn);
|
|
13030
13038
|
}
|
|
13039
|
+
/** Run a complete index mutation as one WAL-visible publication. */
|
|
13040
|
+
async runAtomicIndexUpdate(job) {
|
|
13041
|
+
if (this.atomicIndexUpdateActive) return job();
|
|
13042
|
+
this.runWithRetry(() => this.db.exec("BEGIN IMMEDIATE"));
|
|
13043
|
+
this.atomicIndexUpdateActive = true;
|
|
13044
|
+
try {
|
|
13045
|
+
const result = await job();
|
|
13046
|
+
this.db.exec("COMMIT");
|
|
13047
|
+
return result;
|
|
13048
|
+
} catch (error) {
|
|
13049
|
+
try {
|
|
13050
|
+
this.db.exec("ROLLBACK");
|
|
13051
|
+
} catch {
|
|
13052
|
+
}
|
|
13053
|
+
throw error;
|
|
13054
|
+
} finally {
|
|
13055
|
+
this.atomicIndexUpdateActive = false;
|
|
13056
|
+
}
|
|
13057
|
+
}
|
|
13058
|
+
/**
|
|
13059
|
+
* Begin a method-local transaction. Inside an atomic index publication a
|
|
13060
|
+
* SAVEPOINT preserves the old per-batch rollback boundary, which is needed
|
|
13061
|
+
* when commitBatch falls back to per-file writes after one batch fails.
|
|
13062
|
+
*/
|
|
13063
|
+
beginWriteTransaction() {
|
|
13064
|
+
if (this.atomicIndexUpdateActive) {
|
|
13065
|
+
const savepoint = `index_write_${++this.writeSavepointSequence}`;
|
|
13066
|
+
this.db.exec(`SAVEPOINT ${savepoint}`);
|
|
13067
|
+
return savepoint;
|
|
13068
|
+
}
|
|
13069
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
13070
|
+
return null;
|
|
13071
|
+
}
|
|
13072
|
+
commitWriteTransaction(savepoint) {
|
|
13073
|
+
if (savepoint) this.db.exec(`RELEASE SAVEPOINT ${savepoint}`);
|
|
13074
|
+
else this.db.exec("COMMIT");
|
|
13075
|
+
}
|
|
13076
|
+
rollbackWriteTransaction(savepoint) {
|
|
13077
|
+
if (savepoint) {
|
|
13078
|
+
this.db.exec(`ROLLBACK TO SAVEPOINT ${savepoint}`);
|
|
13079
|
+
this.db.exec(`RELEASE SAVEPOINT ${savepoint}`);
|
|
13080
|
+
} else {
|
|
13081
|
+
this.db.exec("ROLLBACK");
|
|
13082
|
+
}
|
|
13083
|
+
}
|
|
13031
13084
|
/**
|
|
13032
13085
|
* Mirror the in-process language→family map into SQLite.
|
|
13033
13086
|
*
|
|
@@ -13262,7 +13315,7 @@ var IndexStore = class _IndexStore {
|
|
|
13262
13315
|
insertSymbols(symbols) {
|
|
13263
13316
|
this.invalidateBm25();
|
|
13264
13317
|
return this.runWithRetry(() => {
|
|
13265
|
-
this.
|
|
13318
|
+
const ownsTransaction = this.beginWriteTransaction();
|
|
13266
13319
|
try {
|
|
13267
13320
|
let nextId = this.allocateSymbolIds(symbols.length);
|
|
13268
13321
|
const result = [];
|
|
@@ -13289,7 +13342,9 @@ var IndexStore = class _IndexStore {
|
|
|
13289
13342
|
}
|
|
13290
13343
|
vectorRows.push({
|
|
13291
13344
|
id,
|
|
13292
|
-
vector: encodeVector(
|
|
13345
|
+
vector: encodeVector(
|
|
13346
|
+
embedText(s.text || buildIndexableText(s.name, s.signature, s.docComment))
|
|
13347
|
+
)
|
|
13293
13348
|
});
|
|
13294
13349
|
result.push({ ...s, id });
|
|
13295
13350
|
}
|
|
@@ -13307,10 +13362,10 @@ var IndexStore = class _IndexStore {
|
|
|
13307
13362
|
vectorRows
|
|
13308
13363
|
);
|
|
13309
13364
|
}
|
|
13310
|
-
this.
|
|
13365
|
+
this.commitWriteTransaction(ownsTransaction);
|
|
13311
13366
|
return result;
|
|
13312
13367
|
} catch (err) {
|
|
13313
|
-
this.
|
|
13368
|
+
this.rollbackWriteTransaction(ownsTransaction);
|
|
13314
13369
|
throw err;
|
|
13315
13370
|
}
|
|
13316
13371
|
});
|
|
@@ -13318,7 +13373,7 @@ var IndexStore = class _IndexStore {
|
|
|
13318
13373
|
deleteSymbolsForFile(file) {
|
|
13319
13374
|
this.invalidateBm25();
|
|
13320
13375
|
this.runWithRetry(() => {
|
|
13321
|
-
this.
|
|
13376
|
+
const ownsTransaction = this.beginWriteTransaction();
|
|
13322
13377
|
try {
|
|
13323
13378
|
const affectedNames = this.invalidateIncomingRefsForFiles([file]);
|
|
13324
13379
|
if (this.ftsAvailable) {
|
|
@@ -13333,9 +13388,9 @@ var IndexStore = class _IndexStore {
|
|
|
13333
13388
|
}
|
|
13334
13389
|
this.stmt("DELETE FROM symbols WHERE file_fk = ?").run(file);
|
|
13335
13390
|
this.resolveRefsForNamesUnsafe(affectedNames);
|
|
13336
|
-
this.
|
|
13391
|
+
this.commitWriteTransaction(ownsTransaction);
|
|
13337
13392
|
} catch (error) {
|
|
13338
|
-
this.
|
|
13393
|
+
this.rollbackWriteTransaction(ownsTransaction);
|
|
13339
13394
|
throw error;
|
|
13340
13395
|
}
|
|
13341
13396
|
});
|
|
@@ -13348,7 +13403,7 @@ var IndexStore = class _IndexStore {
|
|
|
13348
13403
|
deleteFile(file) {
|
|
13349
13404
|
this.invalidateBm25();
|
|
13350
13405
|
this.runWithRetry(() => {
|
|
13351
|
-
this.
|
|
13406
|
+
const ownsTransaction = this.beginWriteTransaction();
|
|
13352
13407
|
try {
|
|
13353
13408
|
const affectedNames = this.invalidateIncomingRefsForFiles([file]);
|
|
13354
13409
|
if (this.ftsAvailable) {
|
|
@@ -13367,9 +13422,9 @@ var IndexStore = class _IndexStore {
|
|
|
13367
13422
|
this.stmt("DELETE FROM symbols WHERE file_fk = ?").run(file);
|
|
13368
13423
|
this.stmt("DELETE FROM files WHERE file = ?").run(file);
|
|
13369
13424
|
this.resolveRefsForNamesUnsafe(affectedNames);
|
|
13370
|
-
this.
|
|
13425
|
+
this.commitWriteTransaction(ownsTransaction);
|
|
13371
13426
|
} catch (err) {
|
|
13372
|
-
this.
|
|
13427
|
+
this.rollbackWriteTransaction(ownsTransaction);
|
|
13373
13428
|
throw err;
|
|
13374
13429
|
}
|
|
13375
13430
|
});
|
|
@@ -13756,7 +13811,7 @@ var IndexStore = class _IndexStore {
|
|
|
13756
13811
|
clearAll() {
|
|
13757
13812
|
this.invalidateBm25();
|
|
13758
13813
|
this.runWithRetry(() => {
|
|
13759
|
-
this.
|
|
13814
|
+
const ownsTransaction = this.beginWriteTransaction();
|
|
13760
13815
|
try {
|
|
13761
13816
|
this.db.exec("DROP TABLE IF EXISTS refs");
|
|
13762
13817
|
this.db.exec("DROP TABLE IF EXISTS symbols");
|
|
@@ -13764,15 +13819,15 @@ var IndexStore = class _IndexStore {
|
|
|
13764
13819
|
this.db.exec("DROP TABLE IF EXISTS metadata");
|
|
13765
13820
|
if (this.ftsAvailable) this.db.exec("DROP TABLE IF EXISTS symbols_fts");
|
|
13766
13821
|
this.db.exec("DROP TABLE IF EXISTS symbol_vectors");
|
|
13767
|
-
this.db.exec("COMMIT");
|
|
13768
13822
|
this.stmtCache.clear();
|
|
13769
13823
|
this.initSchema();
|
|
13770
13824
|
this.stmt("INSERT OR REPLACE INTO metadata(key, value) VALUES (?, ?)").run(
|
|
13771
13825
|
_IndexStore.NEXT_SYMBOL_ID_KEY,
|
|
13772
13826
|
"1"
|
|
13773
13827
|
);
|
|
13828
|
+
this.commitWriteTransaction(ownsTransaction);
|
|
13774
13829
|
} catch (err) {
|
|
13775
|
-
this.
|
|
13830
|
+
this.rollbackWriteTransaction(ownsTransaction);
|
|
13776
13831
|
throw err;
|
|
13777
13832
|
}
|
|
13778
13833
|
});
|
|
@@ -13834,7 +13889,7 @@ var IndexStore = class _IndexStore {
|
|
|
13834
13889
|
}
|
|
13835
13890
|
this.invalidateBm25();
|
|
13836
13891
|
return this.runWithRetry(() => {
|
|
13837
|
-
this.
|
|
13892
|
+
const ownsTransaction = this.beginWriteTransaction();
|
|
13838
13893
|
try {
|
|
13839
13894
|
const affectedNames = /* @__PURE__ */ new Set();
|
|
13840
13895
|
for (const entry of entries) {
|
|
@@ -13895,7 +13950,9 @@ var IndexStore = class _IndexStore {
|
|
|
13895
13950
|
}
|
|
13896
13951
|
vectorRows.push({
|
|
13897
13952
|
id,
|
|
13898
|
-
vector: encodeVector(
|
|
13953
|
+
vector: encodeVector(
|
|
13954
|
+
embedText(s.text || buildIndexableText(s.name, s.signature, s.docComment))
|
|
13955
|
+
)
|
|
13899
13956
|
});
|
|
13900
13957
|
const inserted = { ...s, id };
|
|
13901
13958
|
allInserted.push(inserted);
|
|
@@ -13940,10 +13997,10 @@ var IndexStore = class _IndexStore {
|
|
|
13940
13997
|
);
|
|
13941
13998
|
}
|
|
13942
13999
|
this.resolveRefsForNamesUnsafe(affectedNames);
|
|
13943
|
-
this.
|
|
14000
|
+
this.commitWriteTransaction(ownsTransaction);
|
|
13944
14001
|
return allInserted;
|
|
13945
14002
|
} catch (err) {
|
|
13946
|
-
this.
|
|
14003
|
+
this.rollbackWriteTransaction(ownsTransaction);
|
|
13947
14004
|
throw err;
|
|
13948
14005
|
}
|
|
13949
14006
|
});
|
|
@@ -14020,7 +14077,7 @@ var IndexStore = class _IndexStore {
|
|
|
14020
14077
|
replaceEmptyFile(meta) {
|
|
14021
14078
|
this.invalidateBm25();
|
|
14022
14079
|
this.runWithRetry(() => {
|
|
14023
|
-
this.
|
|
14080
|
+
const ownsTransaction = this.beginWriteTransaction();
|
|
14024
14081
|
try {
|
|
14025
14082
|
const affectedNames = this.invalidateIncomingRefsForFiles([meta.file]);
|
|
14026
14083
|
if (this.ftsAvailable) {
|
|
@@ -14055,9 +14112,9 @@ var IndexStore = class _IndexStore {
|
|
|
14055
14112
|
meta.lastIndexed
|
|
14056
14113
|
);
|
|
14057
14114
|
this.resolveRefsForNamesUnsafe(affectedNames);
|
|
14058
|
-
this.
|
|
14115
|
+
this.commitWriteTransaction(ownsTransaction);
|
|
14059
14116
|
} catch (err) {
|
|
14060
|
-
this.
|
|
14117
|
+
this.rollbackWriteTransaction(ownsTransaction);
|
|
14061
14118
|
throw err;
|
|
14062
14119
|
}
|
|
14063
14120
|
});
|
|
@@ -15015,6 +15072,7 @@ import { Worker as Worker2 } from "node:worker_threads";
|
|
|
15015
15072
|
// src/codebase-index/indexer.ts
|
|
15016
15073
|
import { expectDefined as expectDefined6 } from "@wrongstack/core/utils";
|
|
15017
15074
|
import { execFile } from "node:child_process";
|
|
15075
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
15018
15076
|
import * as fs18 from "node:fs/promises";
|
|
15019
15077
|
import { availableParallelism } from "node:os";
|
|
15020
15078
|
import * as path24 from "node:path";
|
|
@@ -15855,6 +15913,10 @@ var DEFAULT_IGNORE_FILES = /* @__PURE__ */ new Set(["package-lock.json", "pnpm-l
|
|
|
15855
15913
|
var INDEXABLE_EXTENSION_SET = new Set(INDEXABLE_EXTENSIONS);
|
|
15856
15914
|
var MAX_INDEX_FILE_BYTES = 5 * 1024 * 1024;
|
|
15857
15915
|
var MAX_GIT_FILE_LIST_BYTES = 64 * 1024 * 1024;
|
|
15916
|
+
var GIT_SNAPSHOT_METADATA_KEY = "git_discovery_snapshot";
|
|
15917
|
+
var IndexSourceChangedError = class extends Error {
|
|
15918
|
+
name = "IndexSourceChangedError";
|
|
15919
|
+
};
|
|
15858
15920
|
function isWithinProject(projectRoot, file) {
|
|
15859
15921
|
const rel = path24.relative(projectRoot, file);
|
|
15860
15922
|
return rel !== "" && !rel.startsWith(`..${path24.sep}`) && rel !== ".." && !path24.isAbsolute(rel);
|
|
@@ -15891,7 +15953,7 @@ async function findGitSourceFiles(projectRoot, ignore, signal) {
|
|
|
15891
15953
|
if (normalizeComparablePath(topLevel) !== normalizeComparablePath(projectRoot)) return null;
|
|
15892
15954
|
throwIfAborted(signal);
|
|
15893
15955
|
const ignoreSet = /* @__PURE__ */ new Set([...DEFAULT_IGNORE, ...ignore]);
|
|
15894
|
-
const [output, statusOutput] = await Promise.all([
|
|
15956
|
+
const [output, statusOutput, stagedOutput] = await Promise.all([
|
|
15895
15957
|
gitOutput(projectRoot, ["ls-files", "--cached", "--others", "--exclude-standard", "-z"]),
|
|
15896
15958
|
gitOutput(projectRoot, [
|
|
15897
15959
|
"status",
|
|
@@ -15899,7 +15961,8 @@ async function findGitSourceFiles(projectRoot, ignore, signal) {
|
|
|
15899
15961
|
"-z",
|
|
15900
15962
|
"--untracked-files=all",
|
|
15901
15963
|
"--ignored=no"
|
|
15902
|
-
])
|
|
15964
|
+
]),
|
|
15965
|
+
gitOutput(projectRoot, ["ls-files", "--stage", "-z"])
|
|
15903
15966
|
]);
|
|
15904
15967
|
throwIfAborted(signal);
|
|
15905
15968
|
const dirty = /* @__PURE__ */ new Set();
|
|
@@ -15929,9 +15992,17 @@ async function findGitSourceFiles(projectRoot, ignore, signal) {
|
|
|
15929
15992
|
const ext = path24.extname(relative13).toLowerCase();
|
|
15930
15993
|
if (INDEXABLE_EXTENSION_SET.has(ext) || detectLang(full) !== null) files.push(full);
|
|
15931
15994
|
}
|
|
15995
|
+
const snapshot = createHash5("sha256").update(stagedOutput).update("\0").update(statusOutput);
|
|
15996
|
+
const indexedFiles = new Set(files);
|
|
15997
|
+
for (const dirtyFile of [...dirty].sort()) {
|
|
15998
|
+
if (!indexedFiles.has(dirtyFile) || deleted.has(dirtyFile)) continue;
|
|
15999
|
+
snapshot.update("\0").update(dirtyFile).update("\0");
|
|
16000
|
+
snapshot.update(xxhash64String(await fs18.readFile(dirtyFile, "utf8")));
|
|
16001
|
+
}
|
|
15932
16002
|
return {
|
|
15933
16003
|
files,
|
|
15934
|
-
trustedUnchanged: new Set(files.filter((file) => !dirty.has(file)))
|
|
16004
|
+
trustedUnchanged: new Set(files.filter((file) => !dirty.has(file))),
|
|
16005
|
+
snapshotKey: snapshot.digest("hex")
|
|
15935
16006
|
};
|
|
15936
16007
|
} catch {
|
|
15937
16008
|
return null;
|
|
@@ -15944,7 +16015,8 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
15944
16015
|
files: gitFiles.files,
|
|
15945
16016
|
complete: true,
|
|
15946
16017
|
errors: [],
|
|
15947
|
-
trustedUnchanged: gitFiles.trustedUnchanged
|
|
16018
|
+
trustedUnchanged: gitFiles.trustedUnchanged,
|
|
16019
|
+
snapshotKey: gitFiles.snapshotKey
|
|
15948
16020
|
};
|
|
15949
16021
|
}
|
|
15950
16022
|
const results = [];
|
|
@@ -16031,6 +16103,17 @@ async function resolveProjectRelations(store, projectRoot, opts) {
|
|
|
16031
16103
|
}
|
|
16032
16104
|
}
|
|
16033
16105
|
async function runIndexerWithStore(store, opts) {
|
|
16106
|
+
let result;
|
|
16107
|
+
try {
|
|
16108
|
+
result = await store.runAtomicIndexUpdate(() => runIndexerAtomic(store, opts));
|
|
16109
|
+
} catch (error) {
|
|
16110
|
+
if (!(error instanceof IndexSourceChangedError)) throw error;
|
|
16111
|
+
result = await store.runAtomicIndexUpdate(() => runIndexerAtomic(store, opts));
|
|
16112
|
+
}
|
|
16113
|
+
if (!opts.files) store.compactIfNeeded();
|
|
16114
|
+
return result;
|
|
16115
|
+
}
|
|
16116
|
+
async function runIndexerAtomic(store, opts) {
|
|
16034
16117
|
const { projectRoot, langs, ignore = [], signal } = opts;
|
|
16035
16118
|
const relationGraphVersion = "2";
|
|
16036
16119
|
const refResolutionVersion = "2";
|
|
@@ -16050,6 +16133,7 @@ async function runIndexerWithStore(store, opts) {
|
|
|
16050
16133
|
let discoveredFiles = null;
|
|
16051
16134
|
let discoveryComplete = true;
|
|
16052
16135
|
let trustedUnchanged;
|
|
16136
|
+
let discoverySnapshotKey;
|
|
16053
16137
|
if (opts.files && opts.files.length > 0) {
|
|
16054
16138
|
files = opts.files.map((f) => path24.resolve(projectRoot, f)).filter((f) => {
|
|
16055
16139
|
if (!isWithinProject(projectRoot, f)) return false;
|
|
@@ -16063,6 +16147,7 @@ async function runIndexerWithStore(store, opts) {
|
|
|
16063
16147
|
discoveryComplete = discovery.complete;
|
|
16064
16148
|
discoveredFiles = new Set(files);
|
|
16065
16149
|
trustedUnchanged = discovery.trustedUnchanged;
|
|
16150
|
+
discoverySnapshotKey = discovery.snapshotKey;
|
|
16066
16151
|
}
|
|
16067
16152
|
if (langs && langs.length > 0) {
|
|
16068
16153
|
const langSet = new Set(langs);
|
|
@@ -16076,6 +16161,8 @@ async function runIndexerWithStore(store, opts) {
|
|
|
16076
16161
|
if (!force) {
|
|
16077
16162
|
for (const meta of store.getAllFileMetas()) existingMeta.set(meta.file, meta);
|
|
16078
16163
|
}
|
|
16164
|
+
const snapshotTrusted = !force && discoverySnapshotKey !== void 0 && store.getMetadata(GIT_SNAPSHOT_METADATA_KEY) === discoverySnapshotKey;
|
|
16165
|
+
if (!snapshotTrusted) trustedUnchanged = void 0;
|
|
16079
16166
|
const totalFilesForProgress = files.length;
|
|
16080
16167
|
let filesPreSkipped = 0;
|
|
16081
16168
|
if (!force && trustedUnchanged) {
|
|
@@ -16138,9 +16225,6 @@ async function runIndexerWithStore(store, opts) {
|
|
|
16138
16225
|
};
|
|
16139
16226
|
}
|
|
16140
16227
|
const meta = existingMeta.get(file);
|
|
16141
|
-
if (!force && meta && meta.mtimeMs === Math.floor(stat20.mtimeMs)) {
|
|
16142
|
-
return { file, stat: stat20, lang, parsed: null, skippedMeta: meta };
|
|
16143
|
-
}
|
|
16144
16228
|
let content;
|
|
16145
16229
|
try {
|
|
16146
16230
|
content = await fs18.readFile(file, { encoding: "utf8", signal });
|
|
@@ -16369,9 +16453,18 @@ async function runIndexerWithStore(store, opts) {
|
|
|
16369
16453
|
});
|
|
16370
16454
|
store.setMetadata("ref_resolution_version", refResolutionVersion);
|
|
16371
16455
|
store.setMetadata("relation_graph_version", relationGraphVersion);
|
|
16456
|
+
const completeProjectScope = !opts.files && (!langs || langs.length === 0) && (!opts.ignore || opts.ignore.length === 0);
|
|
16457
|
+
if (completeProjectScope && discoverySnapshotKey !== void 0) {
|
|
16458
|
+
const finalSnapshot = await findGitSourceFiles(projectRoot, ignore, signal);
|
|
16459
|
+
if (!finalSnapshot || finalSnapshot.snapshotKey !== discoverySnapshotKey) {
|
|
16460
|
+
throw new IndexSourceChangedError(
|
|
16461
|
+
"Project files changed during indexing; retrying before publishing the generation."
|
|
16462
|
+
);
|
|
16463
|
+
}
|
|
16464
|
+
store.setMetadata(GIT_SNAPSHOT_METADATA_KEY, errors.length === 0 ? discoverySnapshotKey : "");
|
|
16465
|
+
}
|
|
16372
16466
|
if (!opts.files || filesIndexed >= 50) store.optimize();
|
|
16373
16467
|
store.setLastIndexed(Date.now());
|
|
16374
|
-
if (!opts.files) store.compactIfNeeded();
|
|
16375
16468
|
const durationMs = Date.now() - startMs;
|
|
16376
16469
|
return {
|
|
16377
16470
|
filesIndexed,
|
|
@@ -16682,6 +16775,13 @@ function callIndexOp(op, args, opts) {
|
|
|
16682
16775
|
});
|
|
16683
16776
|
}
|
|
16684
16777
|
async function callInline(op, args, opts) {
|
|
16778
|
+
if (op !== "index" && _indexing) {
|
|
16779
|
+
const error = new Error(
|
|
16780
|
+
"Codebase index refresh in progress; retry after the completed generation is published."
|
|
16781
|
+
);
|
|
16782
|
+
error.name = "IndexRefreshInProgressError";
|
|
16783
|
+
throw error;
|
|
16784
|
+
}
|
|
16685
16785
|
const ac = new AbortController();
|
|
16686
16786
|
const onOuterAbort = () => ac.abort(opts.signal?.reason ?? new Error("Indexing cancelled"));
|
|
16687
16787
|
if (opts.signal?.aborted) onOuterAbort();
|
|
@@ -17026,12 +17126,12 @@ var codebaseSearchTool = {
|
|
|
17026
17126
|
},
|
|
17027
17127
|
async execute(input, ctx, execOpts) {
|
|
17028
17128
|
const state = getIndexState();
|
|
17029
|
-
if (state.indexing
|
|
17129
|
+
if (state.indexing) {
|
|
17030
17130
|
return {
|
|
17031
17131
|
results: [],
|
|
17032
17132
|
total: 0,
|
|
17033
17133
|
query: input.query,
|
|
17034
|
-
indexStatus: `
|
|
17134
|
+
indexStatus: `Index refresh in progress (${state.currentFile}/${state.totalFiles} files) \u2014 retry after the completed generation is published.`
|
|
17035
17135
|
};
|
|
17036
17136
|
}
|
|
17037
17137
|
if (state.lastError) {
|
|
@@ -17212,12 +17312,12 @@ var codebaseIncomingCallsTool = {
|
|
|
17212
17312
|
},
|
|
17213
17313
|
async execute(input, ctx) {
|
|
17214
17314
|
const state = getIndexState();
|
|
17215
|
-
if (state.indexing
|
|
17315
|
+
if (state.indexing) {
|
|
17216
17316
|
return {
|
|
17217
17317
|
symbol: input.symbol,
|
|
17218
17318
|
calls: [],
|
|
17219
17319
|
total: 0,
|
|
17220
|
-
indexStatus: `
|
|
17320
|
+
indexStatus: `Index refresh in progress (${state.currentFile}/${state.totalFiles} files) \u2014 retry after the completed generation is published.`
|
|
17221
17321
|
};
|
|
17222
17322
|
}
|
|
17223
17323
|
if (state.lastError) {
|
|
@@ -17234,16 +17334,14 @@ var codebaseIncomingCallsTool = {
|
|
|
17234
17334
|
const transitive = input.transitive === true;
|
|
17235
17335
|
let serviced;
|
|
17236
17336
|
try {
|
|
17237
|
-
serviced = await incomingCallsService2(
|
|
17238
|
-
|
|
17239
|
-
|
|
17240
|
-
|
|
17241
|
-
|
|
17242
|
-
|
|
17243
|
-
|
|
17244
|
-
|
|
17245
|
-
}
|
|
17246
|
-
);
|
|
17337
|
+
serviced = await incomingCallsService2({
|
|
17338
|
+
projectRoot: ctx.projectRoot,
|
|
17339
|
+
indexDir: codebaseIndexDirOverride(ctx),
|
|
17340
|
+
symbol: input.symbol,
|
|
17341
|
+
file: input.file,
|
|
17342
|
+
limit,
|
|
17343
|
+
transitive
|
|
17344
|
+
});
|
|
17247
17345
|
} catch (err) {
|
|
17248
17346
|
return {
|
|
17249
17347
|
symbol: input.symbol,
|
|
@@ -17282,10 +17380,14 @@ var codebaseIncomingCallsTool = {
|
|
|
17282
17380
|
}
|
|
17283
17381
|
const notes = [];
|
|
17284
17382
|
if (totalMatches > limit) {
|
|
17285
|
-
notes.push(
|
|
17383
|
+
notes.push(
|
|
17384
|
+
`Results capped at ${limit} of ${totalMatches} call sites. Increase \`limit\` or use \`file\` to narrow.`
|
|
17385
|
+
);
|
|
17286
17386
|
}
|
|
17287
17387
|
if (ambiguous) {
|
|
17288
|
-
notes.push(
|
|
17388
|
+
notes.push(
|
|
17389
|
+
`Symbol "${input.symbol}" exists in multiple files. Results include callers of all same-named symbols. Use codebase-search to find the exact file and pass it as \`file\`.`
|
|
17390
|
+
);
|
|
17289
17391
|
}
|
|
17290
17392
|
return {
|
|
17291
17393
|
symbol: input.symbol,
|
|
@@ -17335,12 +17437,12 @@ var codebaseOutgoingCallsTool = {
|
|
|
17335
17437
|
},
|
|
17336
17438
|
async execute(input, ctx) {
|
|
17337
17439
|
const state = getIndexState();
|
|
17338
|
-
if (state.indexing
|
|
17440
|
+
if (state.indexing) {
|
|
17339
17441
|
return {
|
|
17340
17442
|
symbol: input.symbol,
|
|
17341
17443
|
calls: [],
|
|
17342
17444
|
total: 0,
|
|
17343
|
-
indexStatus: `
|
|
17445
|
+
indexStatus: `Index refresh in progress (${state.currentFile}/${state.totalFiles} files) \u2014 retry after the completed generation is published.`
|
|
17344
17446
|
};
|
|
17345
17447
|
}
|
|
17346
17448
|
if (state.lastError) {
|
|
@@ -17357,16 +17459,14 @@ var codebaseOutgoingCallsTool = {
|
|
|
17357
17459
|
const transitive = input.transitive === true;
|
|
17358
17460
|
let serviced;
|
|
17359
17461
|
try {
|
|
17360
|
-
serviced = await outgoingCallsService2(
|
|
17361
|
-
|
|
17362
|
-
|
|
17363
|
-
|
|
17364
|
-
|
|
17365
|
-
|
|
17366
|
-
|
|
17367
|
-
|
|
17368
|
-
}
|
|
17369
|
-
);
|
|
17462
|
+
serviced = await outgoingCallsService2({
|
|
17463
|
+
projectRoot: ctx.projectRoot,
|
|
17464
|
+
indexDir: codebaseIndexDirOverride(ctx),
|
|
17465
|
+
symbol: input.symbol,
|
|
17466
|
+
file: input.file,
|
|
17467
|
+
limit,
|
|
17468
|
+
transitive
|
|
17469
|
+
});
|
|
17370
17470
|
} catch (err) {
|
|
17371
17471
|
return {
|
|
17372
17472
|
symbol: input.symbol,
|
|
@@ -17405,10 +17505,14 @@ var codebaseOutgoingCallsTool = {
|
|
|
17405
17505
|
}
|
|
17406
17506
|
const notes = [];
|
|
17407
17507
|
if (totalMatches > limit) {
|
|
17408
|
-
notes.push(
|
|
17508
|
+
notes.push(
|
|
17509
|
+
`Results capped at ${limit} of ${totalMatches} call sites. Increase \`limit\` or use \`file\` to narrow.`
|
|
17510
|
+
);
|
|
17409
17511
|
}
|
|
17410
17512
|
if (unresolvedCount > 0) {
|
|
17411
|
-
notes.push(
|
|
17513
|
+
notes.push(
|
|
17514
|
+
`${unresolvedCount} unresolved reference(s) not shown \u2014 their targets could not be resolved during indexing.`
|
|
17515
|
+
);
|
|
17412
17516
|
}
|
|
17413
17517
|
return {
|
|
17414
17518
|
symbol: input.symbol,
|
|
@@ -25035,6 +25139,7 @@ function sourceStatus(task) {
|
|
|
25035
25139
|
function todoStatus(task) {
|
|
25036
25140
|
const status = sourceStatus(task);
|
|
25037
25141
|
if (status === "completed") return "completed";
|
|
25142
|
+
if (status === "review" && task.assignment?.status === "completed") return "completed";
|
|
25038
25143
|
if (status === "in_progress" || status === "review") return "in_progress";
|
|
25039
25144
|
return "pending";
|
|
25040
25145
|
}
|
|
@@ -25156,11 +25261,12 @@ async function applySessionKanbanTaskToSource(context, task, options = {}) {
|
|
|
25156
25261
|
const graphId = task.origin?.graphId ?? "";
|
|
25157
25262
|
if (!originId) return { source: null };
|
|
25158
25263
|
if (task.origin?.system === "session-todo" || graphId.startsWith("todo:")) {
|
|
25264
|
+
const mappedStatus = todoStatus(task);
|
|
25159
25265
|
const next = options.remove ? context.todos.filter((todo) => todo.id !== originId) : context.todos.map(
|
|
25160
25266
|
(todo) => todo.id === originId ? {
|
|
25161
25267
|
...todo,
|
|
25162
25268
|
content: task.title,
|
|
25163
|
-
status:
|
|
25269
|
+
status: mappedStatus
|
|
25164
25270
|
} : todo
|
|
25165
25271
|
);
|
|
25166
25272
|
suppressedTodoMirrors.add(context);
|
|
@@ -27077,6 +27183,20 @@ async function synchronizeManagedKanban(items, board, ctx, signal) {
|
|
|
27077
27183
|
}
|
|
27078
27184
|
const task = board.tasks.find((candidate) => candidate.id === item.kanbanTaskId);
|
|
27079
27185
|
if (!task || task.status === "completed") continue;
|
|
27186
|
+
const stage = task.lifecycle?.currentStage;
|
|
27187
|
+
if (stage === "backlog" || stage === "todo") {
|
|
27188
|
+
const started = await execute({
|
|
27189
|
+
action: "start_task",
|
|
27190
|
+
boardId: board.id,
|
|
27191
|
+
taskId: task.id,
|
|
27192
|
+
author: actor,
|
|
27193
|
+
agentId: actor,
|
|
27194
|
+
transitionComment: `Auto-started for completion: ${item.content}`
|
|
27195
|
+
});
|
|
27196
|
+
if (!started.ok) {
|
|
27197
|
+
continue;
|
|
27198
|
+
}
|
|
27199
|
+
}
|
|
27080
27200
|
await execute({
|
|
27081
27201
|
action: "mark_assignment",
|
|
27082
27202
|
boardId: board.id,
|