offhands 0.1.15 → 0.1.16
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/daemon.mjs +67 -18
- package/package.json +1 -1
package/dist/daemon.mjs
CHANGED
|
@@ -6831,6 +6831,12 @@ function extractTerms(prompt, maxTerms = 8) {
|
|
|
6831
6831
|
}
|
|
6832
6832
|
return deduped.slice().sort((a2, b2) => b2.length - a2.length).slice(0, maxTerms);
|
|
6833
6833
|
}
|
|
6834
|
+
var JUNK_EXTS = [".stackdump", ".log", ".tmp", ".bak", ".orig", ".rej", ".swp", ".png", ".jpg", ".jpeg", ".gif", ".webp", ".mp4", ".mov", ".zip", ".tgz"];
|
|
6835
|
+
var JUNK_BASENAMES = /* @__PURE__ */ new Set([".ds_store", "thumbs.db"]);
|
|
6836
|
+
function isJunk(path) {
|
|
6837
|
+
const base = (path.split(/[\\/]/).pop() ?? "").toLowerCase();
|
|
6838
|
+
return JUNK_BASENAMES.has(base) || base.endsWith("~") || JUNK_EXTS.some((e) => base.endsWith(e));
|
|
6839
|
+
}
|
|
6834
6840
|
var SECRET_BASENAMES = /* @__PURE__ */ new Set([".npmrc", ".netrc", "id_rsa", "id_dsa", "id_ecdsa", "id_ed25519", "credentials.json", "secrets.json"]);
|
|
6835
6841
|
var SECRET_EXTS = [".pem", ".key", ".p12", ".pfx", ".keystore", ".jks", ".env"];
|
|
6836
6842
|
var ENV_TEMPLATE_SUFFIXES = [".example", ".sample", ".template", ".dist"];
|
|
@@ -6842,16 +6848,16 @@ function isSecretLike(path) {
|
|
|
6842
6848
|
}
|
|
6843
6849
|
function parsePorcelainZ(out) {
|
|
6844
6850
|
const parts = out.split("\0");
|
|
6845
|
-
const
|
|
6851
|
+
const entries = [];
|
|
6846
6852
|
for (let i2 = 0; i2 < parts.length; i2++) {
|
|
6847
6853
|
const p2 = parts[i2];
|
|
6848
6854
|
if (p2.length < 4) continue;
|
|
6849
6855
|
const x2 = p2[0];
|
|
6850
6856
|
const y2 = p2[1];
|
|
6851
6857
|
if (x2 === "R" || x2 === "C" || y2 === "R" || y2 === "C") i2++;
|
|
6852
|
-
|
|
6858
|
+
entries.push({ path: p2.slice(3), untracked: x2 === "?" && y2 === "?" });
|
|
6853
6859
|
}
|
|
6854
|
-
return
|
|
6860
|
+
return entries;
|
|
6855
6861
|
}
|
|
6856
6862
|
function isDirectory(abs) {
|
|
6857
6863
|
try {
|
|
@@ -6876,15 +6882,20 @@ function stem(word) {
|
|
|
6876
6882
|
function pathWords(path) {
|
|
6877
6883
|
const split = (s2) => s2.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[^A-Za-z0-9]+/).filter(Boolean).map(stem);
|
|
6878
6884
|
const slash = path.lastIndexOf("/");
|
|
6879
|
-
const
|
|
6885
|
+
const file = path.slice(slash + 1);
|
|
6886
|
+
const base = new Set(split(file));
|
|
6880
6887
|
const all = /* @__PURE__ */ new Set([...split(path.slice(0, Math.max(slash, 0))), ...base]);
|
|
6881
|
-
|
|
6888
|
+
const nameWords = split(file.replace(/\.[^.]+$/, ""));
|
|
6889
|
+
return { all, base, last: nameWords[nameWords.length - 1] ?? "" };
|
|
6882
6890
|
}
|
|
6883
6891
|
var NOISE_FILES = /* @__PURE__ */ new Set(["package-lock.json", "pnpm-lock.yaml", "yarn.lock", "bun.lockb", "bun.lock", "composer.lock", "cargo.lock", "poetry.lock"]);
|
|
6884
6892
|
var isNoise = (p2) => NOISE_FILES.has((p2.split("/").pop() ?? "").toLowerCase());
|
|
6885
6893
|
var MAX_MATCHED_SHOWN = 14;
|
|
6886
6894
|
var MAX_CONTENT_PROBES = 10;
|
|
6887
6895
|
var MAX_CONTENT_MATCHES = 6;
|
|
6896
|
+
var COVERAGE_BONUS = 0.3;
|
|
6897
|
+
var ROLE_WORD_FACTOR = 0.4;
|
|
6898
|
+
var MIN_SOLID_TO_DROP_WEAK = 5;
|
|
6888
6899
|
var MAX_UNMATCHED_WITH_MATCHES = 3;
|
|
6889
6900
|
var MAX_NAME_MATCHES = 10;
|
|
6890
6901
|
var MAX_DIRTY_ONLY = 8;
|
|
@@ -6915,14 +6926,23 @@ async function buildContext(workspace, prompt, opts = {}) {
|
|
|
6915
6926
|
const { findings, retrievalOpsRun, ripgrepAvailable, gitState } = result;
|
|
6916
6927
|
if (findings.length === 0) return null;
|
|
6917
6928
|
const rank = (f2) => f2.matchScore > 0 ? f2.dirty ? 5 : 4 : f2.dirty ? 2 : 1;
|
|
6918
|
-
|
|
6929
|
+
const strength = (f2) => f2.matchScore * (1 + COVERAGE_BONUS * Math.max(f2.terms.size - 1, 0));
|
|
6930
|
+
findings.sort((a2, b2) => rank(b2) - rank(a2) || strength(b2) - strength(a2));
|
|
6919
6931
|
const strongMatches = findings.filter((f2) => f2.matchScore > 0).length;
|
|
6920
6932
|
const unmatchedCap = strongMatches >= 3 ? MAX_UNMATCHED_WITH_MATCHES : Infinity;
|
|
6933
|
+
const solidCount = findings.filter((f2) => f2.solid > 0).length;
|
|
6934
|
+
const droppingWeak = solidCount >= MIN_SOLID_TO_DROP_WEAK;
|
|
6935
|
+
const droppingUntracked = strongMatches >= 3;
|
|
6921
6936
|
let unmatched = 0;
|
|
6922
6937
|
let matchedShown = 0;
|
|
6923
|
-
const shown = findings.filter(
|
|
6924
|
-
(f2
|
|
6925
|
-
|
|
6938
|
+
const shown = findings.filter((f2) => {
|
|
6939
|
+
if (f2.matchScore > 0) {
|
|
6940
|
+
if (droppingWeak && f2.solid === 0) return false;
|
|
6941
|
+
return ++matchedShown <= MAX_MATCHED_SHOWN;
|
|
6942
|
+
}
|
|
6943
|
+
if (droppingUntracked && f2.untracked) return false;
|
|
6944
|
+
return ++unmatched <= unmatchedCap;
|
|
6945
|
+
});
|
|
6926
6946
|
const { text: block, selectedCount } = formatBlock(prompt, shown, gitState, maxChars);
|
|
6927
6947
|
if (selectedCount === 0) return null;
|
|
6928
6948
|
return {
|
|
@@ -6945,7 +6965,7 @@ async function gatherFindings(workspace, prompt) {
|
|
|
6945
6965
|
const touch = (path) => {
|
|
6946
6966
|
let f2 = byPath.get(path);
|
|
6947
6967
|
if (!f2) {
|
|
6948
|
-
f2 = { path, reasons: [], dirty: false, matchScore: 0 };
|
|
6968
|
+
f2 = { path, reasons: [], dirty: false, matchScore: 0, terms: /* @__PURE__ */ new Set(), solid: 0, untracked: false };
|
|
6949
6969
|
byPath.set(path, f2);
|
|
6950
6970
|
}
|
|
6951
6971
|
return f2;
|
|
@@ -6973,7 +6993,8 @@ async function gatherFindings(workspace, prompt) {
|
|
|
6973
6993
|
if (statusOut !== null) {
|
|
6974
6994
|
const entries = parsePorcelainZ(statusOut);
|
|
6975
6995
|
let missing = 0;
|
|
6976
|
-
|
|
6996
|
+
const untrackedSet = /* @__PURE__ */ new Set();
|
|
6997
|
+
for (const { path: p2, untracked } of entries) {
|
|
6977
6998
|
const rel = liveRel(p2);
|
|
6978
6999
|
if (rel === null) {
|
|
6979
7000
|
missing++;
|
|
@@ -6981,14 +7002,22 @@ async function gatherFindings(workspace, prompt) {
|
|
|
6981
7002
|
}
|
|
6982
7003
|
if (isSecretLike(rel)) continue;
|
|
6983
7004
|
if (rel.endsWith("/") || isDirectory(join6(gitRoot, p2))) dirtyDirs.push(rel.replace(/\/?$/, "/"));
|
|
6984
|
-
else
|
|
7005
|
+
else {
|
|
7006
|
+
dirtySet.add(rel);
|
|
7007
|
+
if (untracked) untrackedSet.add(rel);
|
|
7008
|
+
}
|
|
6985
7009
|
}
|
|
7010
|
+
const dirtyOrder = [
|
|
7011
|
+
...[...dirtySet].filter((r2) => !untrackedSet.has(r2)),
|
|
7012
|
+
...[...dirtySet].filter((r2) => untrackedSet.has(r2) && !isJunk(r2))
|
|
7013
|
+
];
|
|
6986
7014
|
let dirtyOnly = 0;
|
|
6987
|
-
for (const rel of
|
|
7015
|
+
for (const rel of dirtyOrder) {
|
|
6988
7016
|
if (dirtyOnly >= MAX_DIRTY_ONLY) break;
|
|
6989
7017
|
if (isNoise(rel)) continue;
|
|
6990
7018
|
const f2 = touch(rel);
|
|
6991
7019
|
f2.dirty = true;
|
|
7020
|
+
f2.untracked = untrackedSet.has(rel);
|
|
6992
7021
|
note(f2, "currently dirty");
|
|
6993
7022
|
dirtyOnly++;
|
|
6994
7023
|
}
|
|
@@ -7011,6 +7040,14 @@ async function gatherFindings(workspace, prompt) {
|
|
|
7011
7040
|
else if (!stems.has(stem(t2))) stems.set(stem(t2), t2);
|
|
7012
7041
|
}
|
|
7013
7042
|
const words = all.map(pathWords);
|
|
7043
|
+
const lastByDir = /* @__PURE__ */ new Map();
|
|
7044
|
+
words.forEach((w2, i2) => {
|
|
7045
|
+
if (!w2.last) return;
|
|
7046
|
+
const key = `${all[i2].slice(0, all[i2].lastIndexOf("/") + 1)}\0${w2.last}`;
|
|
7047
|
+
lastByDir.set(key, (lastByDir.get(key) ?? 0) + 1);
|
|
7048
|
+
});
|
|
7049
|
+
const roleWords = /* @__PURE__ */ new Set();
|
|
7050
|
+
for (const [key, n2] of lastByDir) if (n2 >= 2) roleWords.add(key.slice(key.indexOf("\0") + 1));
|
|
7014
7051
|
const lowered = literals.length > 0 ? all.map((p2) => p2.toLowerCase()) : [];
|
|
7015
7052
|
const idf = /* @__PURE__ */ new Map();
|
|
7016
7053
|
const wording = /* @__PURE__ */ new Map();
|
|
@@ -7029,15 +7066,21 @@ async function gatherFindings(workspace, prompt) {
|
|
|
7029
7066
|
const scored = [];
|
|
7030
7067
|
for (let i2 = 0; i2 < all.length; i2++) {
|
|
7031
7068
|
let score = 0;
|
|
7069
|
+
let solid = 0;
|
|
7032
7070
|
const matched = [];
|
|
7033
7071
|
for (const [key, weight] of idf) {
|
|
7034
7072
|
const isLiteral = literals.includes(key);
|
|
7035
|
-
if (isLiteral ? lowered[i2].includes(key) : words[i2].base.has(key))
|
|
7036
|
-
|
|
7037
|
-
|
|
7073
|
+
if (isLiteral ? lowered[i2].includes(key) : words[i2].base.has(key)) {
|
|
7074
|
+
const roleOnly = !isLiteral && roleWords.has(key) && words[i2].last === key;
|
|
7075
|
+
score += weight * 2 * (roleOnly ? ROLE_WORD_FACTOR : 1);
|
|
7076
|
+
if (!roleOnly) solid += weight * 2;
|
|
7077
|
+
} else if (!isLiteral && words[i2].all.has(key)) {
|
|
7078
|
+
score += weight;
|
|
7079
|
+
solid += weight;
|
|
7080
|
+
} else continue;
|
|
7038
7081
|
matched.push(wording.get(key));
|
|
7039
7082
|
}
|
|
7040
|
-
if (score > 0) scored.push({ path: all[i2], score, matched });
|
|
7083
|
+
if (score > 0) scored.push({ path: all[i2], score, solid, matched });
|
|
7041
7084
|
}
|
|
7042
7085
|
scored.sort((a2, b2) => b2.score - a2.score);
|
|
7043
7086
|
let taken = 0;
|
|
@@ -7046,6 +7089,8 @@ async function gatherFindings(workspace, prompt) {
|
|
|
7046
7089
|
if (isSecretLike(s2.path) || !existsSync7(join6(workspace, s2.path))) continue;
|
|
7047
7090
|
const f2 = touch(s2.path);
|
|
7048
7091
|
f2.matchScore += s2.score;
|
|
7092
|
+
f2.solid += s2.solid;
|
|
7093
|
+
for (const m3 of s2.matched) f2.terms.add(stem(m3));
|
|
7049
7094
|
if (isDirty2(s2.path)) f2.dirty = true;
|
|
7050
7095
|
note(f2, `path matches ${s2.matched.map((m3) => `"${m3}"`).join(", ")}`);
|
|
7051
7096
|
taken++;
|
|
@@ -7085,6 +7130,8 @@ async function gatherFindings(workspace, prompt) {
|
|
|
7085
7130
|
if (isSecretLike(file) || !existsSync7(join6(workspace, file))) continue;
|
|
7086
7131
|
const f2 = touch(file);
|
|
7087
7132
|
f2.matchScore += score;
|
|
7133
|
+
f2.solid += score;
|
|
7134
|
+
for (const w2 of words) f2.terms.add(stem(w2));
|
|
7088
7135
|
if (isDirty2(file)) f2.dirty = true;
|
|
7089
7136
|
note(f2, `content mentions ${words.slice(0, 3).map((w2) => `"${w2}"`).join(", ")}`);
|
|
7090
7137
|
taken++;
|
|
@@ -7099,6 +7146,8 @@ async function gatherFindings(workspace, prompt) {
|
|
|
7099
7146
|
const term = terms.find((t2) => hit.text.toLowerCase().includes(t2));
|
|
7100
7147
|
const f2 = touch(file);
|
|
7101
7148
|
f2.matchScore += 1;
|
|
7149
|
+
f2.solid += 1;
|
|
7150
|
+
if (term) f2.terms.add(stem(term));
|
|
7102
7151
|
if (isDirty2(file)) f2.dirty = true;
|
|
7103
7152
|
note(f2, term ? `matched "${term}"` : "matched search terms");
|
|
7104
7153
|
}
|
|
@@ -7110,7 +7159,7 @@ async function gatherFindings(workspace, prompt) {
|
|
|
7110
7159
|
let taken = 0;
|
|
7111
7160
|
for (const p2 of recent) {
|
|
7112
7161
|
if (taken >= MAX_RECENT) break;
|
|
7113
|
-
if (isSecretLike(p2) || isNoise(p2) || !existsSync7(join6(workspace, p2))) continue;
|
|
7162
|
+
if (isSecretLike(p2) || isNoise(p2) || isJunk(p2) || !existsSync7(join6(workspace, p2))) continue;
|
|
7114
7163
|
note(touch(p2), "recently changed");
|
|
7115
7164
|
taken++;
|
|
7116
7165
|
}
|