eminiar 0.8.0 → 0.9.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/.claude-plugin/plugin.json +1 -1
- package/README.md +2 -1
- package/dist/cli.js +2839 -2147
- package/dist/hook.js +459 -111
- package/dist/server.js +135 -20
- package/eminiar.config.schema.json +6 -0
- package/package.json +1 -1
package/dist/hook.js
CHANGED
|
@@ -3941,11 +3941,11 @@ var require_out = __commonJS({
|
|
|
3941
3941
|
async.read(path2, getSettings(optionsOrSettingsOrCallback), callback);
|
|
3942
3942
|
}
|
|
3943
3943
|
exports.stat = stat;
|
|
3944
|
-
function
|
|
3944
|
+
function statSync6(path2, optionsOrSettings) {
|
|
3945
3945
|
const settings = getSettings(optionsOrSettings);
|
|
3946
3946
|
return sync.read(path2, settings);
|
|
3947
3947
|
}
|
|
3948
|
-
exports.statSync =
|
|
3948
|
+
exports.statSync = statSync6;
|
|
3949
3949
|
function getSettings(settingsOrOptions = {}) {
|
|
3950
3950
|
if (settingsOrOptions instanceof settings_1.default) {
|
|
3951
3951
|
return settingsOrOptions;
|
|
@@ -4403,7 +4403,7 @@ var require_queue = __commonJS({
|
|
|
4403
4403
|
if (!(_concurrency >= 1)) {
|
|
4404
4404
|
throw new Error("fastqueue concurrency must be equal to or greater than 1");
|
|
4405
4405
|
}
|
|
4406
|
-
var
|
|
4406
|
+
var cache3 = reusify(Task);
|
|
4407
4407
|
var queueHead = null;
|
|
4408
4408
|
var queueTail = null;
|
|
4409
4409
|
var _running = 0;
|
|
@@ -4482,7 +4482,7 @@ var require_queue = __commonJS({
|
|
|
4482
4482
|
return _running === 0 && self.length() === 0;
|
|
4483
4483
|
}
|
|
4484
4484
|
function push(value, done) {
|
|
4485
|
-
var current =
|
|
4485
|
+
var current = cache3.get();
|
|
4486
4486
|
current.context = context;
|
|
4487
4487
|
current.release = release;
|
|
4488
4488
|
current.value = value;
|
|
@@ -4503,7 +4503,7 @@ var require_queue = __commonJS({
|
|
|
4503
4503
|
}
|
|
4504
4504
|
}
|
|
4505
4505
|
function unshift(value, done) {
|
|
4506
|
-
var current =
|
|
4506
|
+
var current = cache3.get();
|
|
4507
4507
|
current.context = context;
|
|
4508
4508
|
current.release = release;
|
|
4509
4509
|
current.value = value;
|
|
@@ -4525,7 +4525,7 @@ var require_queue = __commonJS({
|
|
|
4525
4525
|
}
|
|
4526
4526
|
function release(holder) {
|
|
4527
4527
|
if (holder) {
|
|
4528
|
-
|
|
4528
|
+
cache3.release(holder);
|
|
4529
4529
|
}
|
|
4530
4530
|
var next = queueHead;
|
|
4531
4531
|
if (next && _running <= _concurrency) {
|
|
@@ -4575,7 +4575,7 @@ var require_queue = __commonJS({
|
|
|
4575
4575
|
errorHandler2(new Error("abort"), val);
|
|
4576
4576
|
}
|
|
4577
4577
|
callback.call(context2, new Error("abort"));
|
|
4578
|
-
|
|
4578
|
+
cache3.release(current);
|
|
4579
4579
|
current = next;
|
|
4580
4580
|
}
|
|
4581
4581
|
self.drain = noop;
|
|
@@ -5917,7 +5917,9 @@ var CATEGORIES = {
|
|
|
5917
5917
|
intent: 4,
|
|
5918
5918
|
data: 4,
|
|
5919
5919
|
tool_call: 4,
|
|
5920
|
-
session_start: 4
|
|
5920
|
+
session_start: 4,
|
|
5921
|
+
/** A proactive recall decision (fired or quiet) and what it showed; never in the guide. */
|
|
5922
|
+
recall: 4
|
|
5921
5923
|
};
|
|
5922
5924
|
var GUIDE_ORDER = [
|
|
5923
5925
|
{ title: "Last Request", categories: ["user_prompt"] },
|
|
@@ -25733,21 +25735,21 @@ function visit(schema, fnOrHandlers) {
|
|
|
25733
25735
|
const h = fnOrHandlers[node2._zod.def.type];
|
|
25734
25736
|
return h ? h(node2, rewritten) : node2;
|
|
25735
25737
|
};
|
|
25736
|
-
const
|
|
25738
|
+
const cache3 = /* @__PURE__ */ new Map();
|
|
25737
25739
|
function run(s) {
|
|
25738
|
-
const cached3 =
|
|
25740
|
+
const cached3 = cache3.get(s);
|
|
25739
25741
|
if (cached3 === RESOLVING) {
|
|
25740
25742
|
return new $ZodLazy({
|
|
25741
25743
|
type: "lazy",
|
|
25742
|
-
getter: () =>
|
|
25744
|
+
getter: () => cache3.get(s)
|
|
25743
25745
|
});
|
|
25744
25746
|
}
|
|
25745
25747
|
if (cached3 !== void 0)
|
|
25746
25748
|
return cached3;
|
|
25747
|
-
|
|
25749
|
+
cache3.set(s, RESOLVING);
|
|
25748
25750
|
const inner = mapInner(s);
|
|
25749
25751
|
const mapped = fn(inner, inner !== s);
|
|
25750
|
-
|
|
25752
|
+
cache3.set(s, mapped);
|
|
25751
25753
|
return mapped;
|
|
25752
25754
|
}
|
|
25753
25755
|
function mapInner(s) {
|
|
@@ -26082,7 +26084,12 @@ var ConfigSchema = external_exports.object({
|
|
|
26082
26084
|
/** Before WebSearch / WebFetch: the indexed doc pages that already answer it. */
|
|
26083
26085
|
onWeb: external_exports.boolean().default(true),
|
|
26084
26086
|
maxMemories: external_exports.number().int().min(0).max(10).default(3),
|
|
26085
|
-
maxDocs: external_exports.number().int().min(0).max(10).default(2)
|
|
26087
|
+
maxDocs: external_exports.number().int().min(0).max(10).default(2),
|
|
26088
|
+
/**
|
|
26089
|
+
* Prompts after which an item already shown in this context window may be injected
|
|
26090
|
+
* again when it matches (0 = once per context window only).
|
|
26091
|
+
*/
|
|
26092
|
+
repeatAfter: external_exports.number().int().min(0).max(1e3).default(10)
|
|
26086
26093
|
}).prefault({}),
|
|
26087
26094
|
docs: external_exports.object({
|
|
26088
26095
|
/** Project-relative manifest of documentation sources (`em_docs`); commit it. */
|
|
@@ -26975,6 +26982,8 @@ var PROMPT_FILLER = /* @__PURE__ */ new Set([
|
|
|
26975
26982
|
]);
|
|
26976
26983
|
var MAX_TERMS = 16;
|
|
26977
26984
|
var RECALL_CATEGORY = "recall";
|
|
26985
|
+
var DEFAULT_REPEAT_AFTER = 10;
|
|
26986
|
+
var NAME_MATCH_RATIO = 0.3;
|
|
26978
26987
|
function promptTerms(prompt) {
|
|
26979
26988
|
const body = prompt.replace(/^\s*\/\S+/, " ");
|
|
26980
26989
|
return parseQuery(body).terms.filter((t) => t.length >= 3 && !PROMPT_FILLER.has(t) && !/^\d+$/.test(t)).slice(0, MAX_TERMS);
|
|
@@ -26982,26 +26991,50 @@ function promptTerms(prompt) {
|
|
|
26982
26991
|
function requiredMatches(termCount) {
|
|
26983
26992
|
return Math.min(3, Math.max(2, Math.ceil(termCount * 0.2)));
|
|
26984
26993
|
}
|
|
26985
|
-
function shownKeys(db, sessionId) {
|
|
26994
|
+
function shownKeys(db, sessionId, repeatAfter = DEFAULT_REPEAT_AFTER) {
|
|
26986
26995
|
const events = db.eventsBySession(sessionId, {
|
|
26987
|
-
categories: [RECALL_CATEGORY, "session_start"]
|
|
26996
|
+
categories: [RECALL_CATEGORY, "session_start", "user_prompt"]
|
|
26988
26997
|
});
|
|
26989
|
-
const
|
|
26998
|
+
const shownAt = /* @__PURE__ */ new Map();
|
|
26999
|
+
let prompts = 0;
|
|
26990
27000
|
for (const e of events) {
|
|
26991
27001
|
if (e.category === "session_start") {
|
|
26992
|
-
|
|
27002
|
+
shownAt.clear();
|
|
27003
|
+
prompts = 0;
|
|
27004
|
+
continue;
|
|
27005
|
+
}
|
|
27006
|
+
if (e.category === "user_prompt") {
|
|
27007
|
+
prompts++;
|
|
26993
27008
|
continue;
|
|
26994
27009
|
}
|
|
26995
27010
|
try {
|
|
26996
|
-
for (const k of JSON.parse(e.payload).keys ?? [])
|
|
27011
|
+
for (const k of JSON.parse(e.payload).keys ?? [])
|
|
27012
|
+
shownAt.set(k, prompts);
|
|
26997
27013
|
} catch {
|
|
26998
27014
|
}
|
|
26999
27015
|
}
|
|
27016
|
+
const out = /* @__PURE__ */ new Set();
|
|
27017
|
+
for (const [k, at] of shownAt) if (repeatAfter <= 0 || prompts - at < repeatAfter) out.add(k);
|
|
27000
27018
|
return out;
|
|
27001
27019
|
}
|
|
27002
|
-
function recordShown(db, sessionId, keys) {
|
|
27003
|
-
if (!keys.length) return;
|
|
27004
|
-
|
|
27020
|
+
function recordShown(db, sessionId, keys, info = { path: "prompt" }) {
|
|
27021
|
+
if (!keys.length && !info.trace) return;
|
|
27022
|
+
const t = info.trace;
|
|
27023
|
+
db.eventInsert({
|
|
27024
|
+
sessionId,
|
|
27025
|
+
category: RECALL_CATEGORY,
|
|
27026
|
+
priority: 4,
|
|
27027
|
+
payload: {
|
|
27028
|
+
keys,
|
|
27029
|
+
path: info.path,
|
|
27030
|
+
...t ? {
|
|
27031
|
+
terms: t.terms,
|
|
27032
|
+
need: t.need,
|
|
27033
|
+
candidates: t.candidates.length,
|
|
27034
|
+
gated: t.candidates.filter((c) => c.outcome !== "shown").slice(0, 6).map((c) => `${c.key} ${c.outcome}${c.why ? ` (${c.why})` : ""}`)
|
|
27035
|
+
} : {}
|
|
27036
|
+
}
|
|
27037
|
+
});
|
|
27005
27038
|
}
|
|
27006
27039
|
function clip2(s, max) {
|
|
27007
27040
|
const t = s.replace(/\s+/g, " ").trim();
|
|
@@ -27024,16 +27057,53 @@ function kindRank(kind) {
|
|
|
27024
27057
|
const i = KIND_ORDER.indexOf(kind);
|
|
27025
27058
|
return i === -1 ? KIND_ORDER.length : i;
|
|
27026
27059
|
}
|
|
27060
|
+
function sharedTerms(terms, ...texts) {
|
|
27061
|
+
const hay = texts.join("\n").toLowerCase();
|
|
27062
|
+
return terms.filter((t) => {
|
|
27063
|
+
const base = t.replace(/\*$/, "").toLowerCase();
|
|
27064
|
+
return !!base && new RegExp(`(?<![\\p{L}\\p{N}_])${escapeRe(base)}`, "u").test(hay);
|
|
27065
|
+
});
|
|
27066
|
+
}
|
|
27067
|
+
function nameTerms(m) {
|
|
27068
|
+
const slug = (m.name ?? "").split("/").pop() ?? "";
|
|
27069
|
+
let tags = [];
|
|
27070
|
+
try {
|
|
27071
|
+
tags = m.tags ? JSON.parse(m.tags) : [];
|
|
27072
|
+
} catch {
|
|
27073
|
+
}
|
|
27074
|
+
return [...slug.toLowerCase().split(/[-._]+/), ...tags.map((t) => t.toLowerCase())].filter(
|
|
27075
|
+
(t) => t.length >= 4
|
|
27076
|
+
);
|
|
27077
|
+
}
|
|
27078
|
+
function namesMemory(term, names) {
|
|
27079
|
+
const t = term.replace(/\*$/, "");
|
|
27080
|
+
if (t.length < 4) return false;
|
|
27081
|
+
return names.some((n) => t === n || t.includes(n) || n.includes(t));
|
|
27082
|
+
}
|
|
27083
|
+
function qualifies(matched, need, termCount, m, text) {
|
|
27084
|
+
if (matched.length >= need) return { ok: true };
|
|
27085
|
+
if (m?.name && text?.toLowerCase().includes(m.name)) return { ok: true, why: "named" };
|
|
27086
|
+
if (m && matched.length >= 1 && matched.length / Math.max(1, termCount) >= NAME_MATCH_RATIO) {
|
|
27087
|
+
const names = nameTerms(m);
|
|
27088
|
+
const hit = matched.find((t) => namesMemory(t, names));
|
|
27089
|
+
if (hit) return { ok: true, why: `slug/tag "${hit}"` };
|
|
27090
|
+
}
|
|
27091
|
+
return { ok: false, why: `${matched.length}/${need} terms` };
|
|
27092
|
+
}
|
|
27027
27093
|
async function recallForText(db, text, opts = {}) {
|
|
27028
27094
|
const maxMemories = opts.maxMemories ?? 3;
|
|
27029
27095
|
const maxDocs = opts.maxDocs ?? 2;
|
|
27030
27096
|
const terms = promptTerms(text);
|
|
27031
|
-
|
|
27097
|
+
const trace = opts.trace ? { path: opts.path ?? "prompt", terms, need: 0, candidates: [] } : void 0;
|
|
27098
|
+
const quiet = () => ({ text: "", keys: [], trace });
|
|
27099
|
+
if (terms.length < 2 || maxMemories <= 0 && maxDocs <= 0) return quiet();
|
|
27032
27100
|
const need = opts.minMatches ?? requiredMatches(terms.length);
|
|
27101
|
+
if (trace) trace.need = need;
|
|
27033
27102
|
const query = terms.join(" ");
|
|
27034
|
-
const seen = opts.sessionId ? shownKeys(db, opts.sessionId) : /* @__PURE__ */ new Set();
|
|
27103
|
+
const seen = opts.sessionId ? shownKeys(db, opts.sessionId, opts.repeatAfter) : /* @__PURE__ */ new Set();
|
|
27035
27104
|
const lines = [];
|
|
27036
27105
|
const keys = [];
|
|
27106
|
+
const note = (c) => trace?.candidates.push(c);
|
|
27037
27107
|
if (maxMemories > 0) {
|
|
27038
27108
|
const r = await search(db, query, {
|
|
27039
27109
|
mode: "lexical",
|
|
@@ -27042,14 +27112,38 @@ async function recallForText(db, text, opts = {}) {
|
|
|
27042
27112
|
filters: { source: "memory" }
|
|
27043
27113
|
});
|
|
27044
27114
|
let n = 0;
|
|
27115
|
+
const considered = /* @__PURE__ */ new Set();
|
|
27045
27116
|
for (const h of r.hits) {
|
|
27046
|
-
if (n >= maxMemories) break;
|
|
27047
|
-
if (h.matched.length < need) continue;
|
|
27048
27117
|
const uri = h.chunk.document.uri;
|
|
27049
27118
|
const name = uri?.startsWith("memory:") ? uri.slice(7) : void 0;
|
|
27050
27119
|
const m = name ? db.memoryGetByName(name) : void 0;
|
|
27051
27120
|
const key = `memory:${name}`;
|
|
27052
|
-
if (!m?.name ||
|
|
27121
|
+
if (!m?.name || considered.has(key)) continue;
|
|
27122
|
+
considered.add(key);
|
|
27123
|
+
const label = `${m.name} [${m.kind}]`;
|
|
27124
|
+
const matched = sharedTerms(terms, m.name, m.content, m.tags ?? "");
|
|
27125
|
+
if (!live(m)) {
|
|
27126
|
+
note({ key, label, matched, outcome: "skipped", why: m.status ?? "retired" });
|
|
27127
|
+
continue;
|
|
27128
|
+
}
|
|
27129
|
+
if (!specific(m)) {
|
|
27130
|
+
note({ key, label, matched, outcome: "skipped", why: "onboarding" });
|
|
27131
|
+
continue;
|
|
27132
|
+
}
|
|
27133
|
+
const q = qualifies(matched, need, terms.length, m, text);
|
|
27134
|
+
if (!q.ok) {
|
|
27135
|
+
note({ key, label, matched, outcome: "gated", why: q.why });
|
|
27136
|
+
continue;
|
|
27137
|
+
}
|
|
27138
|
+
if (seen.has(key)) {
|
|
27139
|
+
note({ key, label, matched, outcome: "seen", why: q.why });
|
|
27140
|
+
continue;
|
|
27141
|
+
}
|
|
27142
|
+
if (n >= maxMemories) {
|
|
27143
|
+
note({ key, label, matched, outcome: "over-limit", why: q.why });
|
|
27144
|
+
continue;
|
|
27145
|
+
}
|
|
27146
|
+
note({ key, label, matched, outcome: "shown", why: q.why });
|
|
27053
27147
|
lines.push(`- memory ${m.name} [${m.kind}]: ${gist(m)}`);
|
|
27054
27148
|
keys.push(key);
|
|
27055
27149
|
n++;
|
|
@@ -27064,28 +27158,62 @@ async function recallForText(db, text, opts = {}) {
|
|
|
27064
27158
|
let n = 0;
|
|
27065
27159
|
const docsSeen = /* @__PURE__ */ new Set();
|
|
27066
27160
|
for (const h of r.hits) {
|
|
27067
|
-
if (n >= maxDocs) break;
|
|
27068
|
-
if (h.matched.length < need) continue;
|
|
27069
27161
|
const d = h.chunk.document;
|
|
27070
27162
|
const key = `doc:${d.id}`;
|
|
27071
|
-
if (docsSeen.has(d.id)
|
|
27163
|
+
if (docsSeen.has(d.id)) continue;
|
|
27072
27164
|
docsSeen.add(d.id);
|
|
27165
|
+
const label = `${d.source} \u203A ${clip2(d.title, 60)}`;
|
|
27166
|
+
const matched = sharedTerms(terms, h.chunk.title, h.chunk.body);
|
|
27167
|
+
if (matched.length < need) {
|
|
27168
|
+
note({ key, label, matched, outcome: "gated", why: `${matched.length}/${need} terms` });
|
|
27169
|
+
continue;
|
|
27170
|
+
}
|
|
27171
|
+
if (seen.has(key)) {
|
|
27172
|
+
note({ key, label, matched, outcome: "seen" });
|
|
27173
|
+
continue;
|
|
27174
|
+
}
|
|
27175
|
+
if (n >= maxDocs) {
|
|
27176
|
+
note({ key, label, matched, outcome: "over-limit" });
|
|
27177
|
+
continue;
|
|
27178
|
+
}
|
|
27179
|
+
note({ key, label, matched, outcome: "shown" });
|
|
27073
27180
|
lines.push(`- ${d.source} \u203A ${clip2(d.title, 80)}: ${clip2(h.snippet, 160)}`);
|
|
27074
27181
|
keys.push(key);
|
|
27075
27182
|
n++;
|
|
27076
27183
|
}
|
|
27077
27184
|
}
|
|
27078
|
-
if (!lines.length) return
|
|
27185
|
+
if (!lines.length) return quiet();
|
|
27079
27186
|
return {
|
|
27080
27187
|
text: [
|
|
27081
27188
|
"eminiar recall \u2014 project memories and indexed docs that match this request (check them before deciding; superseded ones are omitted):",
|
|
27082
27189
|
...lines,
|
|
27083
27190
|
'Full text: em_memory {action:"read", name} for a memory, em_docs {action:"lookup", query} for docs; em_recall {query} searches further.'
|
|
27084
27191
|
].join("\n"),
|
|
27085
|
-
keys
|
|
27192
|
+
keys,
|
|
27193
|
+
trace
|
|
27086
27194
|
};
|
|
27087
27195
|
}
|
|
27088
|
-
|
|
27196
|
+
var SYMBOL_KINDS = [
|
|
27197
|
+
"function",
|
|
27198
|
+
"class",
|
|
27199
|
+
"method",
|
|
27200
|
+
"interface",
|
|
27201
|
+
"trait",
|
|
27202
|
+
"type",
|
|
27203
|
+
"enum",
|
|
27204
|
+
"variable"
|
|
27205
|
+
];
|
|
27206
|
+
function fileSymbols(db, rel) {
|
|
27207
|
+
try {
|
|
27208
|
+
return db.graphFindNodes({ path: rel, kinds: SYMBOL_KINDS, limit: 400 }).filter((n) => n.path === rel).map((n) => n.name);
|
|
27209
|
+
} catch {
|
|
27210
|
+
return [];
|
|
27211
|
+
}
|
|
27212
|
+
}
|
|
27213
|
+
function escapeRe(s) {
|
|
27214
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
27215
|
+
}
|
|
27216
|
+
function linksFile(m, rel) {
|
|
27089
27217
|
if (!m.node_ids) return false;
|
|
27090
27218
|
try {
|
|
27091
27219
|
return JSON.parse(m.node_ids).some((id) => fileOf(id) === rel);
|
|
@@ -27093,23 +27221,50 @@ function mentionsFile(m, rel) {
|
|
|
27093
27221
|
return false;
|
|
27094
27222
|
}
|
|
27095
27223
|
}
|
|
27224
|
+
function bodyNamesPath(body, rel) {
|
|
27225
|
+
return new RegExp(`(?:^|[^\\w/.-]|\\./)${escapeRe(rel)}(?![\\w-])`, "m").test(body);
|
|
27226
|
+
}
|
|
27227
|
+
function symbolMentionPattern(symbols) {
|
|
27228
|
+
const names = [...new Set(symbols)].filter((s) => /^[A-Za-z_$][\w$]{4,}$/.test(s));
|
|
27229
|
+
if (!names.length) return void 0;
|
|
27230
|
+
const alt = names.map(escapeRe).join("|");
|
|
27231
|
+
return new RegExp(`(?:[\`#.](?:${alt})(?!\\w)|(?<!\\w)(?:${alt})\\()`);
|
|
27232
|
+
}
|
|
27096
27233
|
function memoriesForFile(db, rel, opts = {}) {
|
|
27097
|
-
const
|
|
27098
|
-
|
|
27099
|
-
|
|
27234
|
+
const symRe = opts.symbols?.length ? symbolMentionPattern(opts.symbols) : void 0;
|
|
27235
|
+
const how = (m) => {
|
|
27236
|
+
if (linksFile(m, rel)) return "related";
|
|
27237
|
+
if (bodyNamesPath(m.content, rel)) return "names the path";
|
|
27238
|
+
if (symRe?.test(m.content)) return "names a symbol";
|
|
27239
|
+
return void 0;
|
|
27240
|
+
};
|
|
27241
|
+
const trace = opts.trace ? { path: "file", terms: [rel], need: 0, candidates: [] } : void 0;
|
|
27242
|
+
const linked = db.memoryList({ limit: 1e3 }).map((m) => ({ m, why: how(m) })).filter((x) => !!x.why && live(x.m) && specific(x.m)).sort((a, b) => kindRank(a.m.kind) - kindRank(b.m.kind));
|
|
27243
|
+
if (!linked.length) return { text: "", keys: [], trace };
|
|
27244
|
+
const seen = opts.sessionId ? shownKeys(db, opts.sessionId, opts.repeatAfter) : /* @__PURE__ */ new Set();
|
|
27100
27245
|
const lines = [];
|
|
27101
27246
|
const keys = [];
|
|
27102
|
-
for (const m of linked) {
|
|
27103
|
-
if (lines.length >= (opts.max ?? 3)) break;
|
|
27247
|
+
for (const { m, why } of linked) {
|
|
27104
27248
|
const key = `memory:${m.name}`;
|
|
27105
|
-
|
|
27249
|
+
const label = `${m.name} [${m.kind}]`;
|
|
27250
|
+
if (!m.name) continue;
|
|
27251
|
+
if (seen.has(key)) {
|
|
27252
|
+
trace?.candidates.push({ key, label, matched: [rel], outcome: "seen", why });
|
|
27253
|
+
continue;
|
|
27254
|
+
}
|
|
27255
|
+
if (lines.length >= (opts.max ?? 3)) {
|
|
27256
|
+
trace?.candidates.push({ key, label, matched: [rel], outcome: "over-limit", why });
|
|
27257
|
+
continue;
|
|
27258
|
+
}
|
|
27259
|
+
trace?.candidates.push({ key, label, matched: [rel], outcome: "shown", why });
|
|
27106
27260
|
lines.push(`${m.name} [${m.kind}]: ${gist(m, 140)}`);
|
|
27107
27261
|
keys.push(key);
|
|
27108
27262
|
}
|
|
27109
|
-
if (!lines.length) return { text: "", keys: [] };
|
|
27263
|
+
if (!lines.length) return { text: "", keys: [], trace };
|
|
27110
27264
|
return {
|
|
27111
27265
|
text: `eminiar memory: ${rel} is covered by ${lines.join(" \xB7 ")} \u2014 respect it (em_memory {action:"read", name} for the full text) or update the memory if this change overrides it.`,
|
|
27112
|
-
keys
|
|
27266
|
+
keys,
|
|
27267
|
+
trace
|
|
27113
27268
|
};
|
|
27114
27269
|
}
|
|
27115
27270
|
var RISKY = 0.3;
|
|
@@ -27125,22 +27280,32 @@ function riskLine(path2, f) {
|
|
|
27125
27280
|
return `${path2} is bug-prone (${parts.join("; ")})`;
|
|
27126
27281
|
}
|
|
27127
27282
|
function fileContext(db, rel, opts = {}) {
|
|
27128
|
-
const
|
|
27129
|
-
if (opts.sessionId && shownKeys(db, opts.sessionId).has(key)) return { text: "", keys: [] };
|
|
27283
|
+
const seen = opts.sessionId ? shownKeys(db, opts.sessionId, opts.repeatAfter) : /* @__PURE__ */ new Set();
|
|
27130
27284
|
const lines = [];
|
|
27131
|
-
const
|
|
27132
|
-
const
|
|
27133
|
-
if (
|
|
27134
|
-
|
|
27135
|
-
|
|
27136
|
-
)
|
|
27137
|
-
|
|
27138
|
-
|
|
27139
|
-
|
|
27140
|
-
|
|
27141
|
-
|
|
27285
|
+
const keys = [];
|
|
27286
|
+
const histKey = `hist:${rel}`;
|
|
27287
|
+
if (!seen.has(histKey)) {
|
|
27288
|
+
const f = readFileRisk(db)[rel];
|
|
27289
|
+
const risk = f ? riskLine(rel, f) : void 0;
|
|
27290
|
+
if (risk) {
|
|
27291
|
+
lines.push(
|
|
27292
|
+
`eminiar history: ${risk}. em_history {action:"file", path:"${rel}"} shows those fixes; keep the behaviour they restored covered by a test.`
|
|
27293
|
+
);
|
|
27294
|
+
keys.push(histKey);
|
|
27295
|
+
}
|
|
27296
|
+
}
|
|
27297
|
+
const fanKey = `fanin:${rel}`;
|
|
27298
|
+
if (opts.edit && !seen.has(fanKey)) {
|
|
27299
|
+
const fanIn = db.metricsGet([fileId(rel)])[0]?.fan_in ?? 0;
|
|
27300
|
+
if (fanIn >= FAN_IN) {
|
|
27301
|
+
lines.push(
|
|
27302
|
+
`eminiar graph: ${rel} is imported by ${fanIn} files; em_graph_impact {node:"${fileId(rel)}"} lists them before you change an exported signature.`
|
|
27303
|
+
);
|
|
27304
|
+
keys.push(fanKey);
|
|
27305
|
+
}
|
|
27306
|
+
}
|
|
27142
27307
|
if (!lines.length) return { text: "", keys: [] };
|
|
27143
|
-
return { text: lines.join("\n"), keys
|
|
27308
|
+
return { text: lines.join("\n"), keys };
|
|
27144
27309
|
}
|
|
27145
27310
|
function commitContext(db, files) {
|
|
27146
27311
|
if (!files.length) return "";
|
|
@@ -27197,7 +27362,7 @@ async function recallForError(db, error62, opts = {}) {
|
|
|
27197
27362
|
const terms = promptTerms(error62.slice(0, 600)).filter((t) => !ERROR_FILLER.has(t));
|
|
27198
27363
|
if (terms.length < 3) return { text: "", keys: [] };
|
|
27199
27364
|
const need = Math.max(3, Math.ceil(terms.length * 0.4));
|
|
27200
|
-
const seen = opts.sessionId ? shownKeys(db, opts.sessionId) : /* @__PURE__ */ new Set();
|
|
27365
|
+
const seen = opts.sessionId ? shownKeys(db, opts.sessionId, opts.repeatAfter) : /* @__PURE__ */ new Set();
|
|
27201
27366
|
const past = [];
|
|
27202
27367
|
const keys = [];
|
|
27203
27368
|
const r = await search(db, terms.join(" "), {
|
|
@@ -27223,8 +27388,10 @@ async function recallForError(db, error62, opts = {}) {
|
|
|
27223
27388
|
maxMemories: opts.maxMemories ?? 2,
|
|
27224
27389
|
maxDocs: 0,
|
|
27225
27390
|
sessionId: opts.sessionId,
|
|
27391
|
+
repeatAfter: opts.repeatAfter,
|
|
27226
27392
|
// error text is wordy (paths, module names): the same proportional bar as past errors
|
|
27227
|
-
minMatches: need
|
|
27393
|
+
minMatches: need,
|
|
27394
|
+
path: "error"
|
|
27228
27395
|
});
|
|
27229
27396
|
const memLines = mem.text.split("\n").filter((l) => l.startsWith("- memory "));
|
|
27230
27397
|
if (!past.length && !memLines.length) return { text: "", keys: [] };
|
|
@@ -28720,10 +28887,13 @@ var userPromptSubmit = async (input2) => {
|
|
|
28720
28887
|
const r = await recallForText(db, input2.prompt, {
|
|
28721
28888
|
maxMemories: recall.maxMemories,
|
|
28722
28889
|
maxDocs: recall.maxDocs,
|
|
28723
|
-
sessionId
|
|
28890
|
+
sessionId,
|
|
28891
|
+
repeatAfter: recall.repeatAfter,
|
|
28892
|
+
trace: true,
|
|
28893
|
+
path: "prompt"
|
|
28724
28894
|
});
|
|
28895
|
+
recordShown(db, sessionId, r.keys, { path: "prompt", trace: r.trace });
|
|
28725
28896
|
if (!r.text) return void 0;
|
|
28726
|
-
recordShown(db, sessionId, r.keys);
|
|
28727
28897
|
return {
|
|
28728
28898
|
hookSpecificOutput: { hookEventName: "UserPromptSubmit", additionalContext: r.text }
|
|
28729
28899
|
};
|
|
@@ -28773,11 +28943,12 @@ var postToolUseFailure = async (input2) => {
|
|
|
28773
28943
|
try {
|
|
28774
28944
|
const r = await recallForError(db, input2.error, {
|
|
28775
28945
|
sessionId,
|
|
28776
|
-
maxMemories: Math.min(2, recall.maxMemories)
|
|
28946
|
+
maxMemories: Math.min(2, recall.maxMemories),
|
|
28947
|
+
repeatAfter: recall.repeatAfter
|
|
28777
28948
|
});
|
|
28778
28949
|
if (r.text) {
|
|
28779
28950
|
recalled = r.text;
|
|
28780
|
-
recordShown(db, sessionId, r.keys);
|
|
28951
|
+
recordShown(db, sessionId, r.keys, { path: "error" });
|
|
28781
28952
|
}
|
|
28782
28953
|
} catch {
|
|
28783
28954
|
}
|
|
@@ -28947,8 +29118,8 @@ var preCompact = (input2) => {
|
|
|
28947
29118
|
|
|
28948
29119
|
// src/hooks/routing.ts
|
|
28949
29120
|
init_esm_shims();
|
|
28950
|
-
import { realpathSync as
|
|
28951
|
-
import { extname as extname2, join as
|
|
29121
|
+
import { realpathSync as realpathSync4, statSync as statSync4 } from "fs";
|
|
29122
|
+
import { extname as extname2, join as join11, relative as relative4, resolve as resolve3 } from "path";
|
|
28952
29123
|
|
|
28953
29124
|
// src/docs/manifest.ts
|
|
28954
29125
|
init_esm_shims();
|
|
@@ -28998,10 +29169,107 @@ function readManifest(ctx) {
|
|
|
28998
29169
|
return readManifestAt(manifestPath(ctx));
|
|
28999
29170
|
}
|
|
29000
29171
|
|
|
29172
|
+
// src/permissions/settings.ts
|
|
29173
|
+
init_esm_shims();
|
|
29174
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4, realpathSync as realpathSync3, statSync as statSync2 } from "fs";
|
|
29175
|
+
import { homedir as homedir2 } from "os";
|
|
29176
|
+
import { join as join5 } from "path";
|
|
29177
|
+
|
|
29178
|
+
// src/permissions/rules.ts
|
|
29179
|
+
init_esm_shims();
|
|
29180
|
+
function parseRule(raw) {
|
|
29181
|
+
const m = /^([A-Za-z][\w-]*)(?:\((.*)\))?$/s.exec(raw.trim());
|
|
29182
|
+
if (!m) return void 0;
|
|
29183
|
+
return { raw, tool: m[1], pattern: m[2] };
|
|
29184
|
+
}
|
|
29185
|
+
function escapeRe2(s) {
|
|
29186
|
+
return s.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
|
|
29187
|
+
}
|
|
29188
|
+
var readRegexCache = /* @__PURE__ */ new Map();
|
|
29189
|
+
function readRegex(pattern) {
|
|
29190
|
+
let re = readRegexCache.get(pattern);
|
|
29191
|
+
if (!re) {
|
|
29192
|
+
if (readRegexCache.size >= 1024) readRegexCache.clear();
|
|
29193
|
+
re = new RegExp(
|
|
29194
|
+
`^${pattern.split("**").map((seg) => seg.split("*").map(escapeRe2).join("[^/]*")).join(".*")}$`
|
|
29195
|
+
);
|
|
29196
|
+
readRegexCache.set(pattern, re);
|
|
29197
|
+
}
|
|
29198
|
+
return re;
|
|
29199
|
+
}
|
|
29200
|
+
function matchRead(rule, absPath) {
|
|
29201
|
+
if (rule.tool !== "Read") return false;
|
|
29202
|
+
if (rule.pattern === void 0) return true;
|
|
29203
|
+
let p = rule.pattern;
|
|
29204
|
+
if (p.startsWith("~")) p = (process.env.HOME ?? "") + p.slice(1);
|
|
29205
|
+
return readRegex(p).test(absPath);
|
|
29206
|
+
}
|
|
29207
|
+
|
|
29208
|
+
// src/permissions/split.ts
|
|
29209
|
+
init_esm_shims();
|
|
29210
|
+
|
|
29211
|
+
// src/permissions/settings.ts
|
|
29212
|
+
function readRules(file3) {
|
|
29213
|
+
if (!existsSync4(file3)) return {};
|
|
29214
|
+
try {
|
|
29215
|
+
const s = JSON.parse(readFileSync4(file3, "utf8"));
|
|
29216
|
+
return s.permissions ?? {};
|
|
29217
|
+
} catch {
|
|
29218
|
+
return {};
|
|
29219
|
+
}
|
|
29220
|
+
}
|
|
29221
|
+
function stamp(file3) {
|
|
29222
|
+
try {
|
|
29223
|
+
return statSync2(file3).mtimeMs;
|
|
29224
|
+
} catch {
|
|
29225
|
+
return -1;
|
|
29226
|
+
}
|
|
29227
|
+
}
|
|
29228
|
+
var cache2 = /* @__PURE__ */ new Map();
|
|
29229
|
+
function loadPermissionRules(root, extraDeny = []) {
|
|
29230
|
+
const files = [
|
|
29231
|
+
join5(homedir2(), ".claude", "settings.json"),
|
|
29232
|
+
join5(root, ".claude", "settings.json"),
|
|
29233
|
+
join5(root, ".claude", "settings.local.json")
|
|
29234
|
+
];
|
|
29235
|
+
const key = `${root}\0${extraDeny.join("\0")}`;
|
|
29236
|
+
const stamps = files.map(stamp);
|
|
29237
|
+
const hit = cache2.get(key);
|
|
29238
|
+
if (hit?.stamps.every((s, i) => s === stamps[i])) return hit.rules;
|
|
29239
|
+
const out = { allow: [], deny: [], ask: [] };
|
|
29240
|
+
const push = (list, raws) => {
|
|
29241
|
+
for (const raw of raws ?? []) {
|
|
29242
|
+
const r = parseRule(raw);
|
|
29243
|
+
if (r) list.push(r);
|
|
29244
|
+
}
|
|
29245
|
+
};
|
|
29246
|
+
for (const f of files) {
|
|
29247
|
+
const p = readRules(f);
|
|
29248
|
+
push(out.allow, p.allow);
|
|
29249
|
+
push(out.deny, p.deny);
|
|
29250
|
+
push(out.ask, p.ask);
|
|
29251
|
+
}
|
|
29252
|
+
push(out.deny, extraDeny);
|
|
29253
|
+
if (cache2.size >= 16) cache2.clear();
|
|
29254
|
+
cache2.set(key, { stamps, rules: out });
|
|
29255
|
+
return out;
|
|
29256
|
+
}
|
|
29257
|
+
function checkRead(rules, absPaths) {
|
|
29258
|
+
for (const kind of ["deny", "ask"]) {
|
|
29259
|
+
for (const r of rules[kind]) {
|
|
29260
|
+
if (absPaths.some((p) => matchRead(r, p))) return { allowed: false, rule: r.raw, kind };
|
|
29261
|
+
}
|
|
29262
|
+
}
|
|
29263
|
+
return { allowed: true };
|
|
29264
|
+
}
|
|
29265
|
+
function checkReadPath(rules, abs) {
|
|
29266
|
+
return checkRead(rules, [.../* @__PURE__ */ new Set([abs, realpathSync3(abs), realpathSync3.native(abs)])]);
|
|
29267
|
+
}
|
|
29268
|
+
|
|
29001
29269
|
// src/session/brief.ts
|
|
29002
29270
|
init_esm_shims();
|
|
29003
|
-
import { existsSync as
|
|
29004
|
-
import { join as
|
|
29271
|
+
import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
|
|
29272
|
+
import { join as join7 } from "path";
|
|
29005
29273
|
|
|
29006
29274
|
// src/memory/frontmatter.ts
|
|
29007
29275
|
init_esm_shims();
|
|
@@ -29035,15 +29303,15 @@ function parseMemoryFile(text) {
|
|
|
29035
29303
|
init_esm_shims();
|
|
29036
29304
|
var import_fast_glob2 = __toESM(require_out4(), 1);
|
|
29037
29305
|
import {
|
|
29038
|
-
existsSync as
|
|
29306
|
+
existsSync as existsSync5,
|
|
29039
29307
|
mkdirSync as mkdirSync3,
|
|
29040
|
-
readFileSync as
|
|
29308
|
+
readFileSync as readFileSync5,
|
|
29041
29309
|
renameSync,
|
|
29042
|
-
statSync as
|
|
29310
|
+
statSync as statSync3,
|
|
29043
29311
|
unlinkSync,
|
|
29044
29312
|
writeFileSync as writeFileSync2
|
|
29045
29313
|
} from "fs";
|
|
29046
|
-
import { dirname as dirname3, join as
|
|
29314
|
+
import { dirname as dirname3, join as join6, relative as relative3, sep } from "path";
|
|
29047
29315
|
|
|
29048
29316
|
// src/storage/types.ts
|
|
29049
29317
|
init_esm_shims();
|
|
@@ -29055,7 +29323,7 @@ function memoriesRel(ctx) {
|
|
|
29055
29323
|
return ctx.config.memories.dir.replace(/^\.\//, "").replace(/\/+$/, "");
|
|
29056
29324
|
}
|
|
29057
29325
|
function memoriesDir(ctx) {
|
|
29058
|
-
return
|
|
29326
|
+
return join6(ctx.root, memoriesRel(ctx));
|
|
29059
29327
|
}
|
|
29060
29328
|
|
|
29061
29329
|
// src/session/brief.ts
|
|
@@ -29098,10 +29366,10 @@ function historyLine(db) {
|
|
|
29098
29366
|
return `${parts.join("; ")}. em_history {action:"file", path} before editing, {action:"risk"} before committing.`;
|
|
29099
29367
|
}
|
|
29100
29368
|
function overviewParagraph(ctx) {
|
|
29101
|
-
const file3 =
|
|
29102
|
-
if (!
|
|
29369
|
+
const file3 = join7(memoriesDir(ctx), "onboarding", "overview.md");
|
|
29370
|
+
if (!existsSync6(file3)) return void 0;
|
|
29103
29371
|
try {
|
|
29104
|
-
const body = parseMemoryFile(
|
|
29372
|
+
const body = parseMemoryFile(readFileSync6(file3, "utf8")).body.trim();
|
|
29105
29373
|
const para = body.split(/\n\s*\n/).map(
|
|
29106
29374
|
(p) => p.replace(/^#+\s.*$/m, "").replace(/\s+/g, " ").trim()
|
|
29107
29375
|
).find((p) => p.length > 20);
|
|
@@ -29150,10 +29418,10 @@ function fitBudget(lines, budget) {
|
|
|
29150
29418
|
|
|
29151
29419
|
// src/session/server-alive.ts
|
|
29152
29420
|
init_esm_shims();
|
|
29153
|
-
import { existsSync as
|
|
29154
|
-
import { join as
|
|
29421
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync4, readdirSync, rmSync, writeFileSync as writeFileSync3 } from "fs";
|
|
29422
|
+
import { join as join8 } from "path";
|
|
29155
29423
|
function markersDir(ctx) {
|
|
29156
|
-
return
|
|
29424
|
+
return join8(projectDir(ctx.id), "servers");
|
|
29157
29425
|
}
|
|
29158
29426
|
function processAlive(pid) {
|
|
29159
29427
|
try {
|
|
@@ -29165,13 +29433,13 @@ function processAlive(pid) {
|
|
|
29165
29433
|
}
|
|
29166
29434
|
function liveServers(ctx) {
|
|
29167
29435
|
const dir = markersDir(ctx);
|
|
29168
|
-
if (!
|
|
29436
|
+
if (!existsSync7(dir)) return [];
|
|
29169
29437
|
const live2 = [];
|
|
29170
29438
|
for (const name of readdirSync(dir)) {
|
|
29171
29439
|
const pid = Number(name);
|
|
29172
29440
|
if (!Number.isInteger(pid) || pid <= 0) continue;
|
|
29173
29441
|
if (processAlive(pid)) live2.push(pid);
|
|
29174
|
-
else rmSync(
|
|
29442
|
+
else rmSync(join8(dir, name), { force: true });
|
|
29175
29443
|
}
|
|
29176
29444
|
return live2;
|
|
29177
29445
|
}
|
|
@@ -29181,30 +29449,30 @@ function serverAlive(ctx) {
|
|
|
29181
29449
|
|
|
29182
29450
|
// src/hooks/coexist.ts
|
|
29183
29451
|
init_esm_shims();
|
|
29184
|
-
import { readFileSync as
|
|
29185
|
-
import { join as
|
|
29452
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
29453
|
+
import { join as join10 } from "path";
|
|
29186
29454
|
|
|
29187
29455
|
// src/install/claude-config.ts
|
|
29188
29456
|
init_esm_shims();
|
|
29189
29457
|
import {
|
|
29190
29458
|
copyFileSync,
|
|
29191
|
-
existsSync as
|
|
29459
|
+
existsSync as existsSync8,
|
|
29192
29460
|
mkdirSync as mkdirSync5,
|
|
29193
29461
|
readdirSync as readdirSync2,
|
|
29194
|
-
readFileSync as
|
|
29462
|
+
readFileSync as readFileSync7,
|
|
29195
29463
|
writeFileSync as writeFileSync4
|
|
29196
29464
|
} from "fs";
|
|
29197
|
-
import { homedir as
|
|
29198
|
-
import { dirname as dirname4, join as
|
|
29199
|
-
var claudeDir = () => process.env.CLAUDE_CONFIG_DIR ||
|
|
29200
|
-
var pluginsDir = () =>
|
|
29465
|
+
import { homedir as homedir3 } from "os";
|
|
29466
|
+
import { dirname as dirname4, join as join9 } from "path";
|
|
29467
|
+
var claudeDir = () => process.env.CLAUDE_CONFIG_DIR || join9(homedir3(), ".claude");
|
|
29468
|
+
var pluginsDir = () => join9(claudeDir(), "plugins");
|
|
29201
29469
|
|
|
29202
29470
|
// src/hooks/coexist.ts
|
|
29203
29471
|
var cached2;
|
|
29204
29472
|
function contextModeInstalled() {
|
|
29205
29473
|
if (cached2 !== void 0) return cached2;
|
|
29206
29474
|
try {
|
|
29207
|
-
const reg = JSON.parse(
|
|
29475
|
+
const reg = JSON.parse(readFileSync8(join10(pluginsDir(), "installed_plugins.json"), "utf8"));
|
|
29208
29476
|
cached2 = Object.keys(reg.plugins ?? {}).some((id) => id.startsWith("context-mode@"));
|
|
29209
29477
|
} catch {
|
|
29210
29478
|
cached2 = false;
|
|
@@ -29229,7 +29497,7 @@ function hookLog(level, msg) {
|
|
|
29229
29497
|
}
|
|
29230
29498
|
|
|
29231
29499
|
// src/hooks/routing.ts
|
|
29232
|
-
var
|
|
29500
|
+
var SYMBOL_KINDS2 = [
|
|
29233
29501
|
"function",
|
|
29234
29502
|
"class",
|
|
29235
29503
|
"method",
|
|
@@ -29247,7 +29515,7 @@ function literalOf(pattern) {
|
|
|
29247
29515
|
function symbolHints(db, pattern) {
|
|
29248
29516
|
const lit = literalOf(pattern);
|
|
29249
29517
|
if (!lit) return void 0;
|
|
29250
|
-
const nodes = db.graphFindNodes({ query: lit, kinds:
|
|
29518
|
+
const nodes = db.graphFindNodes({ query: lit, kinds: SYMBOL_KINDS2, limit: 5 });
|
|
29251
29519
|
if (!nodes.length) return void 0;
|
|
29252
29520
|
const list = nodes.map((n) => `${n.kind} ${n.name} (${n.path}:${n.start_line ?? "?"})`).join("; ");
|
|
29253
29521
|
return `eminiar graph: symbols matching "${lit}": ${list}. em_symbols {name_path, include_body: true} reads one; {mode:"references"} lists its callers.`;
|
|
@@ -29257,9 +29525,50 @@ function coverageHint(db, rel) {
|
|
|
29257
29525
|
if (!row || row.status === "indexed") return void 0;
|
|
29258
29526
|
return `eminiar graph: ${rel} is ${row.status}${row.reason ? ` (${row.reason})` : ""}; graph answers about it may be incomplete.`;
|
|
29259
29527
|
}
|
|
29528
|
+
var GIT_HISTORY = /(?:^|[;&|]\s*)git\s+(?:(?:-C|--git-dir|--work-tree)[= ]\S+\s+|--no-pager\s+)*(log|blame|shortlog|show|whatchanged|reflog)\b([^;&|]*)/;
|
|
29529
|
+
var ONE_COMMIT = /(^|\s)(-1|-n\s*1|-n1|--max-count[= ]1)(?=\s|$)/;
|
|
29530
|
+
function historyCall(cmd) {
|
|
29531
|
+
const m = GIT_HISTORY.exec(cmd);
|
|
29532
|
+
if (!m) return void 0;
|
|
29533
|
+
const sub = m[1];
|
|
29534
|
+
const rest = (m[2] ?? "").trim();
|
|
29535
|
+
const args = rest.split(/\s+/).filter(Boolean);
|
|
29536
|
+
const q = (s) => JSON.stringify(s.replace(/^['"]|['"]$/g, ""));
|
|
29537
|
+
const positional = (a) => !a.startsWith("-");
|
|
29538
|
+
if (sub === "show") {
|
|
29539
|
+
if (/\S+:\S+/.test(rest)) return void 0;
|
|
29540
|
+
const sha = args.find(positional);
|
|
29541
|
+
return `{action:"commit", sha:${q(sha ?? "HEAD")}}`;
|
|
29542
|
+
}
|
|
29543
|
+
if (sub === "blame") {
|
|
29544
|
+
const path3 = args.filter(positional).pop();
|
|
29545
|
+
return path3 ? `{action:"file", path:${q(path3)}}` : void 0;
|
|
29546
|
+
}
|
|
29547
|
+
if (sub === "log" && ONE_COMMIT.test(rest) && !/(^|\s)(-p|--patch|--stat)\b/.test(rest))
|
|
29548
|
+
return void 0;
|
|
29549
|
+
const lineRange = /-L\s*:?([^:\s]+):(\S+)/.exec(rest);
|
|
29550
|
+
if (lineRange) {
|
|
29551
|
+
const [, fn, file3] = lineRange;
|
|
29552
|
+
return /^[A-Za-z_$]/.test(fn) ? `{action:"symbol", name_path:${q(`${file3}#${fn}`)}}` : `{action:"file", path:${q(file3)}}`;
|
|
29553
|
+
}
|
|
29554
|
+
const grep = /--grep[= ]("[^"]*"|'[^']*'|\S+)/.exec(rest);
|
|
29555
|
+
if (grep) return `{action:"search", query:${q(grep[1])}}`;
|
|
29556
|
+
const author = /--author[= ]("[^"]*"|'[^']*'|\S+)/.exec(rest);
|
|
29557
|
+
const range = args.find((a) => positional(a) && /\.\.\.?/.test(a));
|
|
29558
|
+
if (range) {
|
|
29559
|
+
const [a, b] = range.split(/\.\.\.?/);
|
|
29560
|
+
return `{action:"branch", name:${q(b || a || "HEAD")}}`;
|
|
29561
|
+
}
|
|
29562
|
+
const dash = args.indexOf("--");
|
|
29563
|
+
const path2 = dash !== -1 ? args[dash + 1] : args.find((a) => positional(a) && /[/.]/.test(a) && !/^[0-9a-f]{7,40}$/i.test(a));
|
|
29564
|
+
if (path2) return `{action:"file", path:${q(path2)}}`;
|
|
29565
|
+
if (author) return `{action:"timeline", author:${q(author[1])}}`;
|
|
29566
|
+
if (sub === "shortlog") return `{action:"timeline"}`;
|
|
29567
|
+
return `{action:"overview"}`;
|
|
29568
|
+
}
|
|
29260
29569
|
function docsSources(root) {
|
|
29261
29570
|
try {
|
|
29262
|
-
return readManifestAt(
|
|
29571
|
+
return readManifestAt(join11(root, ".eminiar", "docs.json")).sources;
|
|
29263
29572
|
} catch {
|
|
29264
29573
|
return [];
|
|
29265
29574
|
}
|
|
@@ -29300,12 +29609,21 @@ var BIG_TEXT_EXT = /* @__PURE__ */ new Set([
|
|
|
29300
29609
|
function withoutHeredocs(cmd) {
|
|
29301
29610
|
return cmd.replace(/<<-?\s*(['"]?)(\w+)\1[^\n]*\n[\s\S]*?\n\s*\2\s*(?=\n|$)/g, "<<$2");
|
|
29302
29611
|
}
|
|
29303
|
-
function suggest(toolName, input2, root) {
|
|
29612
|
+
function suggest(toolName, input2, root, extra = {}) {
|
|
29304
29613
|
switch (toolName) {
|
|
29305
29614
|
case "Bash": {
|
|
29306
29615
|
const cmd = withoutHeredocs(String(input2.command ?? ""));
|
|
29307
29616
|
if (!cmd || TINY_BASH.test(cmd)) return void 0;
|
|
29308
29617
|
if (/^\s*cat\b[^|\n]*>/.test(cmd) && !cmd.includes("|")) return void 0;
|
|
29618
|
+
const history = historyCall(cmd);
|
|
29619
|
+
if (history) {
|
|
29620
|
+
return {
|
|
29621
|
+
tool: "em_history",
|
|
29622
|
+
reason: `the git history is already classified (commit kinds, fixes traced to the change that caused them, risk per file); em_history ${history} answers this without the raw log`
|
|
29623
|
+
};
|
|
29624
|
+
}
|
|
29625
|
+
if (/^\s*git\s+log\b/.test(cmd) && ONE_COMMIT.test(cmd) && !cmd.includes("|"))
|
|
29626
|
+
return void 0;
|
|
29309
29627
|
if (HEAVY_BASH.test(cmd) || /\|\s*(head|tail|grep|wc|sort|uniq|jq|less)\b/.test(cmd)) {
|
|
29310
29628
|
return {
|
|
29311
29629
|
tool: "em_execute",
|
|
@@ -29318,9 +29636,11 @@ function suggest(toolName, input2, root) {
|
|
|
29318
29636
|
const fp = String(input2.file_path ?? "");
|
|
29319
29637
|
if (!fp) return void 0;
|
|
29320
29638
|
try {
|
|
29321
|
-
const
|
|
29639
|
+
const abs = resolve3(root, fp);
|
|
29640
|
+
const st = statSync4(abs);
|
|
29322
29641
|
const ext = extname2(fp).toLowerCase();
|
|
29323
29642
|
if (st.size > 200 * 1024 || BIG_TEXT_EXT.has(ext) && st.size > 20 * 1024) {
|
|
29643
|
+
if (!checkReadPath(loadPermissionRules(root), abs).allowed) return void 0;
|
|
29324
29644
|
return {
|
|
29325
29645
|
tool: "em_execute_file",
|
|
29326
29646
|
reason: `${fp} is ${Math.round(st.size / 1024)} KB; em_execute_file {path, code} processes it in the sandbox so only what you print enters context`
|
|
@@ -29331,10 +29651,18 @@ function suggest(toolName, input2, root) {
|
|
|
29331
29651
|
return void 0;
|
|
29332
29652
|
}
|
|
29333
29653
|
case "Grep": {
|
|
29334
|
-
|
|
29654
|
+
const content = input2.output_mode === "content" && !input2.head_limit;
|
|
29655
|
+
if (extra.symbol) {
|
|
29656
|
+
return {
|
|
29657
|
+
tool: "em_symbols",
|
|
29658
|
+
annotateOnly: !content,
|
|
29659
|
+
reason: `the graph indexes "${extra.symbol}": em_symbols {name_path:"${extra.symbol}", mode:"references"} lists its uses, {include_body:true} its definition; em_search {queries:["${extra.symbol}"], source:"code"} finds the code by symbol`
|
|
29660
|
+
};
|
|
29661
|
+
}
|
|
29662
|
+
if (content) {
|
|
29335
29663
|
return {
|
|
29336
29664
|
tool: "em_execute",
|
|
29337
|
-
reason: `content-mode grep can be large; em_execute with rg and an intent returns only matching sections`
|
|
29665
|
+
reason: `content-mode grep can be large; em_execute with rg and an intent returns only matching sections (em_search {queries, mode:"regex"} scans the indexed files with context)`
|
|
29338
29666
|
};
|
|
29339
29667
|
}
|
|
29340
29668
|
return void 0;
|
|
@@ -29362,7 +29690,7 @@ function suggest(toolName, input2, root) {
|
|
|
29362
29690
|
return {
|
|
29363
29691
|
tool: "em_*",
|
|
29364
29692
|
reason: "subagents should prefer em_execute / em_batch_execute / em_search over Bash, Read and Grep",
|
|
29365
|
-
prefixPrompt: 'Use the eminiar tools (em_execute, em_batch_execute, em_execute_file, em_fetch_and_index, em_search) instead of Bash/Read/Grep/WebFetch whenever output may be large; return only derived answers. Check em_recall {query} for project decisions and constraints before proposing changes, and em_docs {action:"lookup", query} before WebFetch/WebSearch for anything the project depends on.\n\n'
|
|
29693
|
+
prefixPrompt: 'Use the eminiar tools (em_execute, em_batch_execute, em_execute_file, em_fetch_and_index, em_search, em_symbols, em_history) instead of Bash/Read/Grep/WebFetch whenever output may be large; return only derived answers. Check em_recall {query} for project decisions and constraints before proposing changes, em_history {action:"file", path} before editing a file, em_symbols / em_search {source:"code"} before grepping for an identifier, and em_docs {action:"lookup", query} before WebFetch/WebSearch for anything the project depends on.\n\n'
|
|
29366
29694
|
};
|
|
29367
29695
|
default:
|
|
29368
29696
|
return void 0;
|
|
@@ -29372,7 +29700,7 @@ function projectRel(ctx, fp) {
|
|
|
29372
29700
|
if (!fp) return void 0;
|
|
29373
29701
|
let abs = resolve3(ctx.root, fp);
|
|
29374
29702
|
try {
|
|
29375
|
-
abs =
|
|
29703
|
+
abs = realpathSync4(abs);
|
|
29376
29704
|
} catch {
|
|
29377
29705
|
}
|
|
29378
29706
|
const rel = relative4(ctx.root, abs).split("\\").join("/");
|
|
@@ -29403,15 +29731,20 @@ function fileKnowledge(ctx, input2) {
|
|
|
29403
29731
|
if (!rel) return void 0;
|
|
29404
29732
|
const db = getStorage(ctx, { vectors: false });
|
|
29405
29733
|
const sessionId = input2.session_id || void 0;
|
|
29406
|
-
const
|
|
29407
|
-
|
|
29734
|
+
const repeatAfter = ctx.config.recall.repeatAfter;
|
|
29735
|
+
const edit = EDIT_TOOLS2.has(input2.tool_name ?? "");
|
|
29736
|
+
const results = [
|
|
29737
|
+
memoriesForFile(db, rel, { sessionId, symbols: fileSymbols(db, rel), repeatAfter }),
|
|
29738
|
+
fileContext(db, rel, { sessionId, edit, repeatAfter })
|
|
29739
|
+
];
|
|
29408
29740
|
const shown = results.filter((r) => r.text);
|
|
29409
29741
|
if (!shown.length) return void 0;
|
|
29410
29742
|
if (sessionId)
|
|
29411
29743
|
recordShown(
|
|
29412
29744
|
db,
|
|
29413
29745
|
sessionId,
|
|
29414
|
-
shown.flatMap((r) => r.keys)
|
|
29746
|
+
shown.flatMap((r) => r.keys),
|
|
29747
|
+
{ path: "file" }
|
|
29415
29748
|
);
|
|
29416
29749
|
return shown.map((r) => r.text).join("\n");
|
|
29417
29750
|
} catch {
|
|
@@ -29451,7 +29784,7 @@ async function webKnowledge(ctx, input2) {
|
|
|
29451
29784
|
if (!text.trim()) return void 0;
|
|
29452
29785
|
try {
|
|
29453
29786
|
const db = getStorage(ctx, { vectors: false });
|
|
29454
|
-
const r = await recallForText(db, text, { maxMemories: 0, maxDocs: 3 });
|
|
29787
|
+
const r = await recallForText(db, text, { maxMemories: 0, maxDocs: 3, path: "web" });
|
|
29455
29788
|
if (!r.text) return void 0;
|
|
29456
29789
|
const pages = r.text.split("\n").filter((l) => l.startsWith("- docs:"));
|
|
29457
29790
|
return [
|
|
@@ -29494,8 +29827,10 @@ var preToolUse = async (input2) => {
|
|
|
29494
29827
|
const coexisting = contextModeInstalled() && ctx.config.routing.deferToContextMode;
|
|
29495
29828
|
if (coexisting && ["Bash", "Read", "Grep", "WebFetch"].includes(input2.tool_name))
|
|
29496
29829
|
return withContext(void 0, memo2);
|
|
29497
|
-
const
|
|
29498
|
-
const
|
|
29830
|
+
const graph = graphContext(ctx, input2);
|
|
29831
|
+
const hint = joinHints(graph, memo2);
|
|
29832
|
+
const symbol2 = input2.tool_name === "Grep" && graph ? literalOf(String(input2.tool_input.pattern ?? "")) : void 0;
|
|
29833
|
+
const s = suggest(input2.tool_name, input2.tool_input, ctx.root, { symbol: symbol2 });
|
|
29499
29834
|
if (!s) return withContext(void 0, hint);
|
|
29500
29835
|
if (s.annotateOnly) return withContext(annotate(s), hint);
|
|
29501
29836
|
if (mode === "enforce") {
|
|
@@ -29552,7 +29887,8 @@ Verify structure with em_symbols / em_graph_impact before reading files; check e
|
|
|
29552
29887
|
if (recall.onAgent && task) {
|
|
29553
29888
|
const r = await recallForText(db, task, {
|
|
29554
29889
|
maxMemories: recall.maxMemories,
|
|
29555
|
-
maxDocs: recall.maxDocs
|
|
29890
|
+
maxDocs: recall.maxDocs,
|
|
29891
|
+
path: "agent"
|
|
29556
29892
|
});
|
|
29557
29893
|
if (r.text) parts.push(r.text);
|
|
29558
29894
|
}
|
|
@@ -29573,15 +29909,15 @@ function annotate(s, note = "") {
|
|
|
29573
29909
|
|
|
29574
29910
|
// src/hooks/session-start.ts
|
|
29575
29911
|
init_esm_shims();
|
|
29576
|
-
import { appendFileSync as appendFileSync2, existsSync as
|
|
29577
|
-
import { join as
|
|
29912
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync9, readdirSync as readdirSync3 } from "fs";
|
|
29913
|
+
import { join as join13 } from "path";
|
|
29578
29914
|
|
|
29579
29915
|
// src/session/current.ts
|
|
29580
29916
|
init_esm_shims();
|
|
29581
|
-
import { readFileSync as
|
|
29582
|
-
import { join as
|
|
29917
|
+
import { readFileSync as readFileSync9, statSync as statSync5, writeFileSync as writeFileSync5 } from "fs";
|
|
29918
|
+
import { join as join12 } from "path";
|
|
29583
29919
|
function file2(ctx) {
|
|
29584
|
-
return
|
|
29920
|
+
return join12(projectDir(ctx.id), "current-session");
|
|
29585
29921
|
}
|
|
29586
29922
|
function setCurrentSession(ctx, sessionId) {
|
|
29587
29923
|
writeFileSync5(file2(ctx), sessionId);
|
|
@@ -29609,7 +29945,8 @@ function routingGuidance(mode) {
|
|
|
29609
29945
|
'- em_search {queries, sort:"timeline"}: everything indexed plus session memory.',
|
|
29610
29946
|
"- em_remember {content, kind, name?, status?}: memories are markdown under .eminiar/memories (survive compaction, shareable via git); em_memory {action: list|read|edit|rename|delete|status} manages them.",
|
|
29611
29947
|
'- em_architecture {aspects?}: the project map (folders, entry points, routes, hotspots, decisions) in one call; em_symbols {name_path | mode:"overview", file} for a symbol or a file outline; em_graph_impact {node} before a refactor; em_graph_changes for what the current diff reaches; em_graph_query {pattern | preset} to walk the graph; em_index_status for coverage. em_stats shows savings.',
|
|
29612
|
-
"- em_history {action: overview | file {path} | risk | commit {sha} | branch {name} | symbol {name_path} | search {query}}: the git history classified (feat/fix/refactor\u2026), branches vs base, and each fix traced back to the commit that caused it \u2014 ask it before editing a file or committing instead of running git log/blame; commit messages are searchable via em_search (source git).",
|
|
29948
|
+
"- em_history {action: overview | file {path} | risk | commit {sha} | branch {name} | symbol {name_path} | search {query}}: the git history classified (feat/fix/refactor\u2026), branches vs base, and each fix traced back to the commit that caused it \u2014 ask it before editing a file or committing instead of running git log/blame/show (those are routed to it); commit messages are searchable via em_search (source git).",
|
|
29949
|
+
'- A Grep for an identifier the graph knows is routed to em_symbols {name_path, mode:"references"} / em_search {queries, source:"code"}: symbol-level answers instead of raw matches.',
|
|
29613
29950
|
"Plain Bash/Read/Grep remain right for short fixed output and files you are about to edit."
|
|
29614
29951
|
].join("\n");
|
|
29615
29952
|
}
|
|
@@ -29664,8 +30001,8 @@ ${guide}`);
|
|
|
29664
30001
|
} catch {
|
|
29665
30002
|
}
|
|
29666
30003
|
try {
|
|
29667
|
-
const onboarding =
|
|
29668
|
-
const onboarded =
|
|
30004
|
+
const onboarding = join13(memoriesDir(ctx), "onboarding");
|
|
30005
|
+
const onboarded = existsSync9(onboarding) && readdirSync3(onboarding).some((f) => f.endsWith(".md"));
|
|
29669
30006
|
if (!onboarded)
|
|
29670
30007
|
parts.push(
|
|
29671
30008
|
"No onboarding memories yet: run /eminiar:em-onboard once so future sessions start with the project's structure, build and test commands."
|
|
@@ -29676,11 +30013,22 @@ ${guide}`);
|
|
|
29676
30013
|
);
|
|
29677
30014
|
} catch {
|
|
29678
30015
|
}
|
|
30016
|
+
try {
|
|
30017
|
+
if (existsSync9(join13(ctx.root, ".git")) && !db.metaGet("history_files"))
|
|
30018
|
+
parts.push(
|
|
30019
|
+
'Git history is not classified yet: em_history {action:"overview"} builds it (the server also does this in the background shortly after start); bug-prone files are flagged before you read or edit them once it exists.'
|
|
30020
|
+
);
|
|
30021
|
+
} catch {
|
|
30022
|
+
}
|
|
29679
30023
|
const docs = db.countDocuments();
|
|
29680
30024
|
const latest = db.sessionLatest();
|
|
29681
30025
|
parts.push(
|
|
29682
30026
|
`eminiar index: ${docs} documents${latest && latest.id !== sessionId && latest.started_at ? `; previous session ${new Date(latest.started_at).toISOString().slice(0, 16)}` : ""}. Use em_search {sort:"timeline"} or em_recall to pick up where you left off.`
|
|
29683
30027
|
);
|
|
30028
|
+
hookLog(
|
|
30029
|
+
"info",
|
|
30030
|
+
`session_start source=${input2.source ?? "?"} session=${sessionId.slice(0, 8)} root=${ctx.root} db=${ctx.dbPath} documents=${docs} memories=${db.memoryCount()} history=${db.metaGet("history_files") ? "yes" : "no"}`
|
|
30031
|
+
);
|
|
29684
30032
|
return {
|
|
29685
30033
|
hookSpecificOutput: {
|
|
29686
30034
|
hookEventName: "SessionStart",
|