@reposkein/mcp 0.2.7 → 0.4.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/README.md +73 -1
- package/binary-digests.json +4 -4
- package/dist/SKILL.md +63 -1
- package/dist/cli/adr.d.ts +37 -0
- package/dist/cli/adr.js +231 -0
- package/dist/cli/adr.js.map +1 -0
- package/dist/cli/agentAdapters.d.ts +68 -0
- package/dist/cli/agentAdapters.js +223 -0
- package/dist/cli/agentAdapters.js.map +1 -0
- package/dist/cli/ansi.d.ts +24 -0
- package/dist/cli/ansi.js +40 -0
- package/dist/cli/ansi.js.map +1 -0
- package/dist/cli/doctor.d.ts +34 -2
- package/dist/cli/doctor.js +53 -5
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/doctorDecisions.d.ts +5 -0
- package/dist/cli/doctorDecisions.js +83 -0
- package/dist/cli/doctorDecisions.js.map +1 -0
- package/dist/cli/doctorFreshness.d.ts +43 -0
- package/dist/cli/doctorFreshness.js +111 -0
- package/dist/cli/doctorFreshness.js.map +1 -0
- package/dist/cli/doctorSummaries.d.ts +2 -0
- package/dist/cli/doctorSummaries.js +95 -0
- package/dist/cli/doctorSummaries.js.map +1 -0
- package/dist/cli/federatedDiscovery.d.ts +16 -0
- package/dist/cli/federatedDiscovery.js +75 -0
- package/dist/cli/federatedDiscovery.js.map +1 -0
- package/dist/cli/init.d.ts +69 -6
- package/dist/cli/init.js +194 -28
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/sourceSlices.d.ts +17 -0
- package/dist/cli/sourceSlices.js +59 -0
- package/dist/cli/sourceSlices.js.map +1 -0
- package/dist/cli/stats.d.ts +29 -0
- package/dist/cli/stats.js +180 -0
- package/dist/cli/stats.js.map +1 -0
- package/dist/cli/view.d.ts +71 -3
- package/dist/cli/view.js +147 -12
- package/dist/cli/view.js.map +1 -1
- package/dist/guard/decisionValidation.d.ts +29 -0
- package/dist/guard/decisionValidation.js +51 -0
- package/dist/guard/decisionValidation.js.map +1 -0
- package/dist/guard/summaryValidation.d.ts +3 -0
- package/dist/guard/summaryValidation.js +5 -3
- package/dist/guard/summaryValidation.js.map +1 -1
- package/dist/index.d.ts +48 -0
- package/dist/index.js +355 -78
- package/dist/index.js.map +1 -1
- package/dist/indexer/decisionsAffected.d.ts +42 -0
- package/dist/indexer/decisionsAffected.js +149 -0
- package/dist/indexer/decisionsAffected.js.map +1 -0
- package/dist/indexer/fetchBinary.d.ts +14 -0
- package/dist/indexer/fetchBinary.js +81 -40
- package/dist/indexer/fetchBinary.js.map +1 -1
- package/dist/indexer/runIndexer.d.ts +3 -2
- package/dist/indexer/runIndexer.js +8 -4
- package/dist/indexer/runIndexer.js.map +1 -1
- package/dist/profile/decisions.d.ts +37 -0
- package/dist/profile/decisions.js +112 -0
- package/dist/profile/decisions.js.map +1 -0
- package/dist/profile/types.d.ts +8 -0
- package/dist/store/GraphStore.d.ts +5 -0
- package/dist/store/JsonlGraphStore.d.ts +14 -3
- package/dist/store/JsonlGraphStore.js +66 -19
- package/dist/store/JsonlGraphStore.js.map +1 -1
- package/dist/store/decisions.d.ts +131 -0
- package/dist/store/decisions.js +321 -0
- package/dist/store/decisions.js.map +1 -0
- package/dist/store/indexedAt.d.ts +18 -0
- package/dist/store/indexedAt.js +56 -0
- package/dist/store/indexedAt.js.map +1 -0
- package/dist/store/instrumentTool.d.ts +40 -0
- package/dist/store/instrumentTool.js +82 -0
- package/dist/store/instrumentTool.js.map +1 -0
- package/dist/store/repoContextCache.d.ts +15 -0
- package/dist/store/repoContextCache.js +26 -0
- package/dist/store/repoContextCache.js.map +1 -0
- package/dist/store/repoSession.d.ts +63 -0
- package/dist/store/repoSession.js +123 -0
- package/dist/store/repoSession.js.map +1 -0
- package/dist/store/resolveRepoPath.d.ts +31 -0
- package/dist/store/resolveRepoPath.js +85 -0
- package/dist/store/resolveRepoPath.js.map +1 -0
- package/dist/store/sessionLog.d.ts +98 -0
- package/dist/store/sessionLog.js +216 -0
- package/dist/store/sessionLog.js.map +1 -0
- package/dist/store/sessionStats.d.ts +55 -0
- package/dist/store/sessionStats.js +140 -0
- package/dist/store/sessionStats.js.map +1 -0
- package/dist/store/sidecar.d.ts +39 -3
- package/dist/store/sidecar.js +89 -5
- package/dist/store/sidecar.js.map +1 -1
- package/dist/store/summaryShards.d.ts +117 -0
- package/dist/store/summaryShards.js +291 -0
- package/dist/store/summaryShards.js.map +1 -0
- package/dist/store/teamConfig.d.ts +13 -0
- package/dist/store/teamConfig.js +49 -0
- package/dist/store/teamConfig.js.map +1 -0
- package/dist/tools/getContextProfile.d.ts +1 -1
- package/dist/tools/getContextProfile.js +9 -1
- package/dist/tools/getContextProfile.js.map +1 -1
- package/dist/tools/getDecision.d.ts +6 -0
- package/dist/tools/getDecision.js +59 -0
- package/dist/tools/getDecision.js.map +1 -0
- package/dist/tools/impact.d.ts +1 -1
- package/dist/tools/impact.js +13 -1
- package/dist/tools/impact.js.map +1 -1
- package/dist/tools/indexerTools.d.ts +16 -2
- package/dist/tools/indexerTools.js +44 -5
- package/dist/tools/indexerTools.js.map +1 -1
- package/dist/tools/listDecisions.d.ts +19 -0
- package/dist/tools/listDecisions.js +93 -0
- package/dist/tools/listDecisions.js.map +1 -0
- package/dist/tools/reaffirmDecision.d.ts +11 -0
- package/dist/tools/reaffirmDecision.js +55 -0
- package/dist/tools/reaffirmDecision.js.map +1 -0
- package/dist/tools/recordDecision.d.ts +24 -0
- package/dist/tools/recordDecision.js +135 -0
- package/dist/tools/recordDecision.js.map +1 -0
- package/dist/tools/semanticFind.d.ts +9 -2
- package/dist/tools/semanticFind.js +44 -2
- package/dist/tools/semanticFind.js.map +1 -1
- package/dist/tools/setDecisionStatus.d.ts +8 -0
- package/dist/tools/setDecisionStatus.js +34 -0
- package/dist/tools/setDecisionStatus.js.map +1 -0
- package/dist/viz/assets/{graph.worker-CC7BeE-8.js → graph.worker-DkH6-lMU.js} +1 -1
- package/dist/viz/assets/index-CxqeGuRS.css +1 -0
- package/dist/viz/assets/index-DiNZyrXx.js +9 -0
- package/dist/viz/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
- package/dist/viz/assets/jetbrains-mono-latin-500-normal-BWZEU5yA.woff2 +0 -0
- package/dist/viz/assets/jetbrains-mono-latin-700-normal-BYuf6tUa.woff2 +0 -0
- package/dist/viz/assets/r3f-BZY3QR6_.js +336 -0
- package/dist/viz/assets/tanstack-CjXr-L-s.js +62 -0
- package/dist/viz/assets/three-Cpu5RUEM.js +4116 -0
- package/dist/viz/index.html +5 -4
- package/package.json +5 -4
- package/dist/viz/assets/index-C_8UkiJ8.js +0 -9
- package/dist/viz/assets/r3f-Cku1OhB-.js +0 -277
- package/dist/viz/assets/tanstack-DyXcNRWk.js +0 -77
- package/dist/viz/assets/three-Z8XaLGmf.js +0 -3839
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, writeFileSync, } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
const STATUSES = [
|
|
5
|
+
"proposed",
|
|
6
|
+
"accepted",
|
|
7
|
+
"rejected",
|
|
8
|
+
"deprecated",
|
|
9
|
+
"superseded",
|
|
10
|
+
];
|
|
11
|
+
/** Merge-damage tiebreak when the same id appears in two files: the more
|
|
12
|
+
* terminal status wins (a lost supersede flip is worse than a lost flag). */
|
|
13
|
+
const STATUS_PRECEDENCE = {
|
|
14
|
+
superseded: 4,
|
|
15
|
+
deprecated: 3,
|
|
16
|
+
rejected: 2,
|
|
17
|
+
accepted: 1,
|
|
18
|
+
proposed: 0,
|
|
19
|
+
};
|
|
20
|
+
export function decisionsDir(repoPath) {
|
|
21
|
+
return join(repoPath, ".reposkein", "decisions");
|
|
22
|
+
}
|
|
23
|
+
/** Kebab-cases a title into the id slug: lowercase [a-z0-9-], capped. */
|
|
24
|
+
function slugify(title) {
|
|
25
|
+
const slug = title
|
|
26
|
+
.toLowerCase()
|
|
27
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
28
|
+
.replace(/^-+|-+$/g, "")
|
|
29
|
+
.slice(0, 60)
|
|
30
|
+
.replace(/-+$/, "");
|
|
31
|
+
return slug || "decision";
|
|
32
|
+
}
|
|
33
|
+
/** The ordinal-free id a (date, title) pair maps to. */
|
|
34
|
+
export function decisionBaseId(date, title) {
|
|
35
|
+
return `adr:${date}-${slugify(title)}`;
|
|
36
|
+
}
|
|
37
|
+
/** A short, content-derived salt for a colliding slug.
|
|
38
|
+
*
|
|
39
|
+
* The old collision escape was an ordinal (`.1`, `.2`). That is exactly wrong
|
|
40
|
+
* for the case it exists for: two branches recording a same-day decision with
|
|
41
|
+
* the same title BOTH see `.1` free and both mint it, so both write the same
|
|
42
|
+
* filename — reintroducing the merge conflict that file-per-decision exists to
|
|
43
|
+
* remove, on a file whose bodies differ.
|
|
44
|
+
*
|
|
45
|
+
* Salting from the body instead makes the id a function of the content: two
|
|
46
|
+
* branches with genuinely different decisions get different files and merge
|
|
47
|
+
* without touching each other, while two identical recordings collapse to one
|
|
48
|
+
* id (which is the right answer — they are the same decision). */
|
|
49
|
+
export function decisionSlugSalt(source) {
|
|
50
|
+
return createHash("sha256").update(source).digest("hex").slice(0, 6);
|
|
51
|
+
}
|
|
52
|
+
/** Mints `adr:<date>-<slug>`, salting the slug with content when taken.
|
|
53
|
+
*
|
|
54
|
+
* `saltSource` should be something that differs between two decisions a person
|
|
55
|
+
* would consider distinct — the body hash, or the prose itself. Omitting it
|
|
56
|
+
* falls back to the old ordinal, which is fine for a single-writer context
|
|
57
|
+
* (an import loop) but not for concurrent branches. */
|
|
58
|
+
export function mintDecisionId(date, title, taken, saltSource) {
|
|
59
|
+
const base = decisionBaseId(date, title);
|
|
60
|
+
if (!taken.has(base))
|
|
61
|
+
return base;
|
|
62
|
+
if (saltSource !== undefined) {
|
|
63
|
+
const salted = `${base}-${decisionSlugSalt(saltSource)}`;
|
|
64
|
+
if (!taken.has(salted))
|
|
65
|
+
return salted;
|
|
66
|
+
// Same date, same title, same body, and the id is taken: the record is
|
|
67
|
+
// already there. Anything further is a genuine duplicate, so an ordinal is
|
|
68
|
+
// the honest fallback.
|
|
69
|
+
for (let i = 1;; i++) {
|
|
70
|
+
const candidate = `${salted}.${i}`;
|
|
71
|
+
if (!taken.has(candidate))
|
|
72
|
+
return candidate;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
for (let i = 1;; i++) {
|
|
76
|
+
const candidate = `${base}.${i}`;
|
|
77
|
+
if (!taken.has(candidate))
|
|
78
|
+
return candidate;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/** Every id that must not be minted again: parsed record ids PLUS ids implied
|
|
82
|
+
* by existing filenames. The filename side matters when a file is damaged
|
|
83
|
+
* (conflict markers): its record doesn't parse, but minting its id anyway
|
|
84
|
+
* would let writeDecision rename over the hand-recoverable file — violating
|
|
85
|
+
* "records are never machine-deleted". */
|
|
86
|
+
export function takenDecisionIds(repoPath) {
|
|
87
|
+
const taken = new Set();
|
|
88
|
+
for (const { file, record } of loadDecisionFiles(repoPath)) {
|
|
89
|
+
if (record)
|
|
90
|
+
taken.add(record.id);
|
|
91
|
+
taken.add(`adr:${file.replace(/\.json$/, "")}`);
|
|
92
|
+
}
|
|
93
|
+
return taken;
|
|
94
|
+
}
|
|
95
|
+
/** SHA-256 over the immutable body. Excludes status/superseded_by (lifecycle)
|
|
96
|
+
* and anchor hash values (re-stamped by reaffirm) — but covers WHICH nodes
|
|
97
|
+
* and paths the decision governs, and every prose field. */
|
|
98
|
+
export function computeBodyHash(rec) {
|
|
99
|
+
const body = {
|
|
100
|
+
alternatives: rec.alternatives ?? "",
|
|
101
|
+
anchor_node_ids: rec.anchors.map((a) => a.node_id),
|
|
102
|
+
consequences: rec.consequences ?? "",
|
|
103
|
+
context: rec.context,
|
|
104
|
+
decided_at: rec.decided_at,
|
|
105
|
+
decided_by: rec.decided_by,
|
|
106
|
+
decision: rec.decision,
|
|
107
|
+
paths: rec.paths,
|
|
108
|
+
supersedes: rec.supersedes,
|
|
109
|
+
title: rec.title,
|
|
110
|
+
trigger: rec.trigger.kind,
|
|
111
|
+
};
|
|
112
|
+
return createHash("sha256").update(JSON.stringify(body)).digest("hex");
|
|
113
|
+
}
|
|
114
|
+
/** File name for a decision id (`adr:` prefix stripped). */
|
|
115
|
+
export function decisionFileName(id) {
|
|
116
|
+
return `${id.replace(/^adr:/, "")}.json`;
|
|
117
|
+
}
|
|
118
|
+
function sortedKeys(value) {
|
|
119
|
+
if (Array.isArray(value))
|
|
120
|
+
return value.map(sortedKeys);
|
|
121
|
+
if (value !== null && typeof value === "object") {
|
|
122
|
+
const out = {};
|
|
123
|
+
for (const k of Object.keys(value).sort()) {
|
|
124
|
+
out[k] = sortedKeys(value[k]);
|
|
125
|
+
}
|
|
126
|
+
return out;
|
|
127
|
+
}
|
|
128
|
+
return value;
|
|
129
|
+
}
|
|
130
|
+
/** Canonical bytes: `id` first, then sorted keys, 2-space indent, trailing LF.
|
|
131
|
+
* Pretty-printed (unlike the derived JSONL) because these files are authored
|
|
132
|
+
* history that humans review in PR diffs. */
|
|
133
|
+
export function canonicalizeDecision(rec) {
|
|
134
|
+
const { id, ...rest } = rec;
|
|
135
|
+
const sorted = sortedKeys(rest);
|
|
136
|
+
const withOptionalsDropped = {};
|
|
137
|
+
for (const [k, v] of Object.entries(sorted)) {
|
|
138
|
+
if (v === undefined)
|
|
139
|
+
continue;
|
|
140
|
+
withOptionalsDropped[k] = v;
|
|
141
|
+
}
|
|
142
|
+
return JSON.stringify({ id, ...withOptionalsDropped }, null, 2) + "\n";
|
|
143
|
+
}
|
|
144
|
+
/** Atomic write: temp file in the same dir, then rename. */
|
|
145
|
+
export function writeDecision(repoPath, rec) {
|
|
146
|
+
const dir = decisionsDir(repoPath);
|
|
147
|
+
mkdirSync(dir, { recursive: true });
|
|
148
|
+
const file = join(dir, decisionFileName(rec.id));
|
|
149
|
+
const tmp = join(dir, `.${decisionFileName(rec.id)}.tmp`);
|
|
150
|
+
writeFileSync(tmp, canonicalizeDecision(rec));
|
|
151
|
+
renameSync(tmp, file);
|
|
152
|
+
}
|
|
153
|
+
function parseDecision(obj) {
|
|
154
|
+
if (typeof obj.id !== "string" || !obj.id.startsWith("adr:"))
|
|
155
|
+
return null;
|
|
156
|
+
if (typeof obj.title !== "string" || typeof obj.context !== "string")
|
|
157
|
+
return null;
|
|
158
|
+
if (typeof obj.decision !== "string" || typeof obj.decided_at !== "string")
|
|
159
|
+
return null;
|
|
160
|
+
const status = obj.status;
|
|
161
|
+
if (typeof status !== "string" || !STATUSES.includes(status))
|
|
162
|
+
return null;
|
|
163
|
+
const anchorsRaw = Array.isArray(obj.anchors) ? obj.anchors : [];
|
|
164
|
+
const anchors = [];
|
|
165
|
+
for (const a of anchorsRaw) {
|
|
166
|
+
if (a === null || typeof a !== "object")
|
|
167
|
+
continue;
|
|
168
|
+
const o = a;
|
|
169
|
+
if (typeof o.node_id !== "string")
|
|
170
|
+
continue;
|
|
171
|
+
anchors.push({
|
|
172
|
+
node_id: o.node_id,
|
|
173
|
+
path: typeof o.path === "string" ? o.path : "",
|
|
174
|
+
name: typeof o.name === "string" ? o.name : "",
|
|
175
|
+
kind: typeof o.kind === "string" ? o.kind : "",
|
|
176
|
+
hash: typeof o.hash === "string" ? o.hash : null,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
const strArray = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string") : [];
|
|
180
|
+
const triggerKind = obj.trigger !== null &&
|
|
181
|
+
typeof obj.trigger === "object" &&
|
|
182
|
+
typeof obj.trigger.kind === "string"
|
|
183
|
+
? obj.trigger.kind
|
|
184
|
+
: "manual";
|
|
185
|
+
const rec = {
|
|
186
|
+
id: obj.id,
|
|
187
|
+
title: obj.title,
|
|
188
|
+
status: status,
|
|
189
|
+
context: obj.context,
|
|
190
|
+
decision: obj.decision,
|
|
191
|
+
anchors,
|
|
192
|
+
paths: strArray(obj.paths),
|
|
193
|
+
supersedes: strArray(obj.supersedes),
|
|
194
|
+
decided_at: obj.decided_at,
|
|
195
|
+
decided_by: typeof obj.decided_by === "string" ? obj.decided_by : "unknown",
|
|
196
|
+
trigger: {
|
|
197
|
+
kind: ["manual", "graph_delta", "drift"].includes(triggerKind)
|
|
198
|
+
? triggerKind
|
|
199
|
+
: "manual",
|
|
200
|
+
},
|
|
201
|
+
body_hash: typeof obj.body_hash === "string" ? obj.body_hash : "",
|
|
202
|
+
};
|
|
203
|
+
if (typeof obj.consequences === "string")
|
|
204
|
+
rec.consequences = obj.consequences;
|
|
205
|
+
if (typeof obj.alternatives === "string")
|
|
206
|
+
rec.alternatives = obj.alternatives;
|
|
207
|
+
if (typeof obj.superseded_by === "string")
|
|
208
|
+
rec.superseded_by = obj.superseded_by;
|
|
209
|
+
return rec;
|
|
210
|
+
}
|
|
211
|
+
/** Raw per-file scan (no dedupe) — the substrate for loadDecisions and for
|
|
212
|
+
* doctor's duplicate/integrity checks. */
|
|
213
|
+
export function loadDecisionFiles(repoPath) {
|
|
214
|
+
const dir = decisionsDir(repoPath);
|
|
215
|
+
if (!existsSync(dir))
|
|
216
|
+
return [];
|
|
217
|
+
let files;
|
|
218
|
+
try {
|
|
219
|
+
files = readdirSync(dir).filter((f) => f.endsWith(".json") && !f.startsWith("."));
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
return [];
|
|
223
|
+
}
|
|
224
|
+
return files.sort().map((f) => {
|
|
225
|
+
let record = null;
|
|
226
|
+
try {
|
|
227
|
+
record = parseDecision(JSON.parse(readFileSync(join(dir, f), "utf8")));
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
record = null;
|
|
231
|
+
}
|
|
232
|
+
return { file: f, record };
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
/** Loads every decision file. Tolerant: malformed files (conflict markers,
|
|
236
|
+
* bad JSON, missing fields) are skipped with a warning, never thrown — a
|
|
237
|
+
* damaged file must not take recall down. */
|
|
238
|
+
export function loadDecisions(repoPath) {
|
|
239
|
+
const warnings = [];
|
|
240
|
+
const byId = new Map();
|
|
241
|
+
for (const { file, record } of loadDecisionFiles(repoPath)) {
|
|
242
|
+
if (!record) {
|
|
243
|
+
warnings.push(`skipped malformed decision file: ${file}`);
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
const existing = byId.get(record.id);
|
|
247
|
+
if (!existing || STATUS_PRECEDENCE[record.status] > STATUS_PRECEDENCE[existing.status]) {
|
|
248
|
+
byId.set(record.id, record);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
const decisions = [...byId.values()].sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
|
|
252
|
+
return { decisions, warnings };
|
|
253
|
+
}
|
|
254
|
+
/** Repo ids for anchor resolution: the root plus its federated children —
|
|
255
|
+
* decisions live at the root but may govern nodes in nested repos. Falls
|
|
256
|
+
* back to the root alone if federation lookup fails. */
|
|
257
|
+
export async function anchorRepoIds(store, repoId) {
|
|
258
|
+
try {
|
|
259
|
+
return [repoId, ...(await store.federatedRepoIds(repoId))];
|
|
260
|
+
}
|
|
261
|
+
catch {
|
|
262
|
+
return [repoId];
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
/** The portion of a node id that survives repo_id drift: `:<kind>:<rest>`.
|
|
266
|
+
* Shared by every decision surface — anchor resolution, profile governance,
|
|
267
|
+
* list filtering, and drift cross-referencing must agree on tolerance. */
|
|
268
|
+
export function nodeIdSuffix(nodeId) {
|
|
269
|
+
const m = /^rs1:[^:]+(:.+)$/.exec(nodeId);
|
|
270
|
+
return m ? m[1] : null;
|
|
271
|
+
}
|
|
272
|
+
/** The repo_id embedded in an rs1: node id, or null. */
|
|
273
|
+
export function nodeIdRepo(nodeId) {
|
|
274
|
+
const m = /^rs1:([^:]+):.+$/.exec(nodeId);
|
|
275
|
+
return m ? m[1] : null;
|
|
276
|
+
}
|
|
277
|
+
const idSuffix = nodeIdSuffix;
|
|
278
|
+
/** Resolves each anchor against the live graph:
|
|
279
|
+
* - current: node id live (exact or repo_id-suffix match), hash unchanged
|
|
280
|
+
* - stale: node id live, content differs since record time
|
|
281
|
+
* - moved: id dead, but the recorded content hash matches a live node —
|
|
282
|
+
* the only signal that survives renames
|
|
283
|
+
* - orphaned: nothing matches; kept and flagged, never deleted */
|
|
284
|
+
export async function resolveAnchorStates(store, repoIds, anchors) {
|
|
285
|
+
const out = [];
|
|
286
|
+
for (const anchor of anchors) {
|
|
287
|
+
let live = await store.getNode(repoIds, anchor.node_id);
|
|
288
|
+
if (!live) {
|
|
289
|
+
// Fork tolerance: the anchor may carry a different repo_id than the
|
|
290
|
+
// local checkout computes. Retry with each live repo id + the suffix.
|
|
291
|
+
const suffix = idSuffix(anchor.node_id);
|
|
292
|
+
if (suffix) {
|
|
293
|
+
for (const repoId of repoIds) {
|
|
294
|
+
const candidate = `rs1:${repoId}${suffix}`;
|
|
295
|
+
if (candidate === anchor.node_id)
|
|
296
|
+
continue;
|
|
297
|
+
live = await store.getNode(repoIds, candidate);
|
|
298
|
+
if (live)
|
|
299
|
+
break;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
if (live) {
|
|
304
|
+
const state = anchor.hash === null || live.content_hash === null || anchor.hash === live.content_hash
|
|
305
|
+
? "current"
|
|
306
|
+
: "stale";
|
|
307
|
+
out.push({ ...anchor, state, resolved_node_id: live.id });
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
if (anchor.hash && store.findByContentHash) {
|
|
311
|
+
const matches = await store.findByContentHash(repoIds, anchor.hash);
|
|
312
|
+
if (matches.length > 0) {
|
|
313
|
+
out.push({ ...anchor, state: "moved", resolved_node_id: matches[0].id });
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
out.push({ ...anchor, state: "orphaned" });
|
|
318
|
+
}
|
|
319
|
+
return out;
|
|
320
|
+
}
|
|
321
|
+
//# sourceMappingURL=decisions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decisions.js","sourceRoot":"","sources":["../../src/store/decisions.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Path to the file recording the git commit SHA that was HEAD the last time
|
|
2
|
+
* the mcp-side index invocation path (`reposkein-mcp index`, or `init`'s
|
|
3
|
+
* initial index) completed successfully. Lives under `.reposkein/local/` —
|
|
4
|
+
* per-machine scratch that's gitignored and never committed — so it never
|
|
5
|
+
* survives a `git clone`. That's deliberate: a fresh clone has no idea what
|
|
6
|
+
* commit (if any) its nonexistent graph was built from, which is exactly
|
|
7
|
+
* the state `doctor --ci`'s `graph_stale` check should fail on. */
|
|
8
|
+
export declare function indexedAtPath(repoPath: string): string;
|
|
9
|
+
/** Records the current git HEAD as "the commit the graph was just built
|
|
10
|
+
* from". Best-effort and silent: not a git repo / no HEAD yet / a read-only
|
|
11
|
+
* filesystem all just skip the write — `graph_stale` degrades to "never
|
|
12
|
+
* recorded" (fails, conservatively) rather than the index step itself
|
|
13
|
+
* failing over a scratch-file write. */
|
|
14
|
+
export declare function writeIndexedAtMarker(repoPath: string): void;
|
|
15
|
+
/** Reads the recorded indexed-at SHA, or null when never recorded — never
|
|
16
|
+
* indexed via the mcp-side path, or a fresh clone (local/ isn't committed,
|
|
17
|
+
* so it never carries over). */
|
|
18
|
+
export declare function readIndexedAtSha(repoPath: string): string | null;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { execFileSync } from "node:child_process";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
/** Path to the file recording the git commit SHA that was HEAD the last time
|
|
5
|
+
* the mcp-side index invocation path (`reposkein-mcp index`, or `init`'s
|
|
6
|
+
* initial index) completed successfully. Lives under `.reposkein/local/` —
|
|
7
|
+
* per-machine scratch that's gitignored and never committed — so it never
|
|
8
|
+
* survives a `git clone`. That's deliberate: a fresh clone has no idea what
|
|
9
|
+
* commit (if any) its nonexistent graph was built from, which is exactly
|
|
10
|
+
* the state `doctor --ci`'s `graph_stale` check should fail on. */
|
|
11
|
+
export function indexedAtPath(repoPath) {
|
|
12
|
+
return join(repoPath, ".reposkein", "local", "indexed-at");
|
|
13
|
+
}
|
|
14
|
+
/** Records the current git HEAD as "the commit the graph was just built
|
|
15
|
+
* from". Best-effort and silent: not a git repo / no HEAD yet / a read-only
|
|
16
|
+
* filesystem all just skip the write — `graph_stale` degrades to "never
|
|
17
|
+
* recorded" (fails, conservatively) rather than the index step itself
|
|
18
|
+
* failing over a scratch-file write. */
|
|
19
|
+
export function writeIndexedAtMarker(repoPath) {
|
|
20
|
+
let sha;
|
|
21
|
+
try {
|
|
22
|
+
sha = execFileSync("git", ["-C", repoPath, "rev-parse", "HEAD"], {
|
|
23
|
+
encoding: "utf8",
|
|
24
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
25
|
+
}).trim();
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (!sha)
|
|
31
|
+
return;
|
|
32
|
+
try {
|
|
33
|
+
const path = indexedAtPath(repoPath);
|
|
34
|
+
mkdirSync(join(path, ".."), { recursive: true });
|
|
35
|
+
writeFileSync(path, sha + "\n", "utf8");
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
/* best-effort */
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/** Reads the recorded indexed-at SHA, or null when never recorded — never
|
|
42
|
+
* indexed via the mcp-side path, or a fresh clone (local/ isn't committed,
|
|
43
|
+
* so it never carries over). */
|
|
44
|
+
export function readIndexedAtSha(repoPath) {
|
|
45
|
+
const path = indexedAtPath(repoPath);
|
|
46
|
+
if (!existsSync(path))
|
|
47
|
+
return null;
|
|
48
|
+
try {
|
|
49
|
+
const sha = readFileSync(path, "utf8").trim();
|
|
50
|
+
return sha || null;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=indexedAt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"indexedAt.js","sourceRoot":"","sources":["../../src/store/indexedAt.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC;;;;;;oEAMoE;AACpE,MAAM,UAAU,aAAa,CAAC,QAAgB;IAC5C,OAAO,IAAI,CAAC,QAAQ,EAAE,YAAY,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC;AAC7D,CAAC;AAED;;;;yCAIyC;AACzC,MAAM,UAAU,oBAAoB,CAAC,QAAgB;IACnD,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,YAAY,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,CAAC,EAAE;YAC/D,QAAQ,EAAE,MAAM;YAChB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC;SACpC,CAAC,CAAC,IAAI,EAAE,CAAC;IACZ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO;IACT,CAAC;IACD,IAAI,CAAC,GAAG;QAAE,OAAO;IACjB,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;QACrC,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACjD,aAAa,CAAC,IAAI,EAAE,GAAG,GAAG,IAAI,EAAE,MAAM,CAAC,CAAC;IAC1C,CAAC;IAAC,MAAM,CAAC;QACP,iBAAiB;IACnB,CAAC;AACH,CAAC;AAED;;iCAEiC;AACjC,MAAM,UAAU,gBAAgB,CAAC,QAAgB;IAC/C,MAAM,IAAI,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;IACrC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACnC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;QAC9C,OAAO,GAAG,IAAI,IAAI,CAAC;IACrB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { RepoResolution } from "./resolveRepoPath.js";
|
|
2
|
+
import type { RepoSession } from "./repoSession.js";
|
|
3
|
+
import { type SessionLogger, type ToolResultLike } from "./sessionLog.js";
|
|
4
|
+
export interface ToolLogger {
|
|
5
|
+
/** `session.resolve()`, memoized for the lifetime of the CURRENT tool
|
|
6
|
+
* call (see `withLog`) — a handler that calls this (directly, or via
|
|
7
|
+
* `resolveActiveRepo`) and the logging wrapper both see the same
|
|
8
|
+
* filesystem walk, not two. Isolated per call via AsyncLocalStorage, so
|
|
9
|
+
* concurrent/overlapping tool calls never share a cached value. Falls
|
|
10
|
+
* back to a plain, uncached `session.resolve()` when called outside any
|
|
11
|
+
* `withLog`-wrapped call (e.g. server startup). */
|
|
12
|
+
cachedResolve(): RepoResolution;
|
|
13
|
+
/** Wraps a tool handler so its call is logged to the active repo's
|
|
14
|
+
* session log — see sessionLog.ts. `Args`/result type are inferred from
|
|
15
|
+
* the handler passed in, so call sites (`server.registerTool(name,
|
|
16
|
+
* config, withLog(name, async (args) => {...}))`) need no extra typing. */
|
|
17
|
+
withLog<Args, R extends ToolResultLike>(name: string, cb: (args: Args) => Promise<R>): (args: Args) => Promise<R>;
|
|
18
|
+
}
|
|
19
|
+
/** Builds the tool-call instrumentation for one MCP server connection: one
|
|
20
|
+
* `ToolLogger` shared across every `server.registerTool` call (REP-20).
|
|
21
|
+
*
|
|
22
|
+
* Two hot-path fixes from review round 1, both scoped to this module so
|
|
23
|
+
* they're covered without spinning up the whole server:
|
|
24
|
+
*
|
|
25
|
+
* 1. Single resolution per call — `cachedResolve()` memoizes
|
|
26
|
+
* `session.resolve()` per call (AsyncLocalStorage-scoped), so a
|
|
27
|
+
* handler's own resolution (via `resolveActiveRepo`, or a direct
|
|
28
|
+
* `cachedResolve()` call for the couple of tools that don't gate on a
|
|
29
|
+
* full repo context) and the logging wrapper's resolution are the same
|
|
30
|
+
* filesystem walk, not two.
|
|
31
|
+
* 2. Deferred write — the `finally` block below only captures cheap,
|
|
32
|
+
* already-computed references synchronously (tool name, argsShape, the
|
|
33
|
+
* resolved repo path, the result object BY REFERENCE, ok, ts) and
|
|
34
|
+
* returns immediately; JSON-parsing the result for `extractTouchedIds`
|
|
35
|
+
* and the actual file write happen in a `schedule()`-deferred callback
|
|
36
|
+
* (default `setImmediate`), which runs only after the response has
|
|
37
|
+
* already been handed back to the MCP transport. The deferred callback
|
|
38
|
+
* swallows every error itself — it must never surface as an unhandled
|
|
39
|
+
* exception/rejection. */
|
|
40
|
+
export declare function createToolLogger(session: RepoSession, sessionLogger: SessionLogger, schedule?: (fn: () => void) => void): ToolLogger;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import { argsShapeOf, extractTouchedIds, resultByteSize } from "./sessionLog.js";
|
|
3
|
+
/** Builds the tool-call instrumentation for one MCP server connection: one
|
|
4
|
+
* `ToolLogger` shared across every `server.registerTool` call (REP-20).
|
|
5
|
+
*
|
|
6
|
+
* Two hot-path fixes from review round 1, both scoped to this module so
|
|
7
|
+
* they're covered without spinning up the whole server:
|
|
8
|
+
*
|
|
9
|
+
* 1. Single resolution per call — `cachedResolve()` memoizes
|
|
10
|
+
* `session.resolve()` per call (AsyncLocalStorage-scoped), so a
|
|
11
|
+
* handler's own resolution (via `resolveActiveRepo`, or a direct
|
|
12
|
+
* `cachedResolve()` call for the couple of tools that don't gate on a
|
|
13
|
+
* full repo context) and the logging wrapper's resolution are the same
|
|
14
|
+
* filesystem walk, not two.
|
|
15
|
+
* 2. Deferred write — the `finally` block below only captures cheap,
|
|
16
|
+
* already-computed references synchronously (tool name, argsShape, the
|
|
17
|
+
* resolved repo path, the result object BY REFERENCE, ok, ts) and
|
|
18
|
+
* returns immediately; JSON-parsing the result for `extractTouchedIds`
|
|
19
|
+
* and the actual file write happen in a `schedule()`-deferred callback
|
|
20
|
+
* (default `setImmediate`), which runs only after the response has
|
|
21
|
+
* already been handed back to the MCP transport. The deferred callback
|
|
22
|
+
* swallows every error itself — it must never surface as an unhandled
|
|
23
|
+
* exception/rejection. */
|
|
24
|
+
export function createToolLogger(session, sessionLogger, schedule = setImmediate) {
|
|
25
|
+
const als = new AsyncLocalStorage();
|
|
26
|
+
function cachedResolve() {
|
|
27
|
+
const store = als.getStore();
|
|
28
|
+
if (!store)
|
|
29
|
+
return session.resolve();
|
|
30
|
+
if (!store.value)
|
|
31
|
+
store.value = session.resolve();
|
|
32
|
+
return store.value;
|
|
33
|
+
}
|
|
34
|
+
function deferredLog(ts, repoPath, tool, argsShape, ok, result) {
|
|
35
|
+
schedule(() => {
|
|
36
|
+
try {
|
|
37
|
+
const resultBytes = resultByteSize(result);
|
|
38
|
+
const nodeIds = extractTouchedIds(result);
|
|
39
|
+
sessionLogger.logAt(ts, repoPath, {
|
|
40
|
+
tool,
|
|
41
|
+
argsShape,
|
|
42
|
+
resultBytes,
|
|
43
|
+
...(nodeIds.length ? { nodeIds } : {}),
|
|
44
|
+
ok,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// deferred logging must never surface as an unhandled exception
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
function withLog(name, cb) {
|
|
53
|
+
return (args) => als.run({}, async () => {
|
|
54
|
+
let result;
|
|
55
|
+
let threw = false;
|
|
56
|
+
try {
|
|
57
|
+
result = await cb(args);
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
threw = true;
|
|
62
|
+
throw err;
|
|
63
|
+
}
|
|
64
|
+
finally {
|
|
65
|
+
// Cheap, synchronous capture only — no JSON.parse, no fs calls on
|
|
66
|
+
// the hot path. `result` is captured BY REFERENCE; it isn't read
|
|
67
|
+
// until the deferred callback runs.
|
|
68
|
+
try {
|
|
69
|
+
const resolution = cachedResolve();
|
|
70
|
+
if (resolution.repoPath) {
|
|
71
|
+
deferredLog(new Date().toISOString(), resolution.repoPath, name, argsShapeOf(args), !threw && result?.isError !== true, result);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// instrumentation must never affect the tool call
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
return { cachedResolve, withLog };
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=instrumentTool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"instrumentTool.js","sourceRoot":"","sources":["../../src/store/instrumentTool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAGrD,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,cAAc,EAA2C,MAAM,iBAAiB,CAAC;AAsB1H;;;;;;;;;;;;;;;;;;;;8BAoB8B;AAC9B,MAAM,UAAU,gBAAgB,CAC9B,OAAoB,EACpB,aAA4B,EAC5B,WAAqC,YAAY;IAEjD,MAAM,GAAG,GAAG,IAAI,iBAAiB,EAAa,CAAC;IAE/C,SAAS,aAAa;QACpB,MAAM,KAAK,GAAG,GAAG,CAAC,QAAQ,EAAE,CAAC;QAC7B,IAAI,CAAC,KAAK;YAAE,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;QACrC,IAAI,CAAC,KAAK,CAAC,KAAK;YAAE,KAAK,CAAC,KAAK,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;QAClD,OAAO,KAAK,CAAC,KAAK,CAAC;IACrB,CAAC;IAED,SAAS,WAAW,CAClB,EAAU,EACV,QAAgB,EAChB,IAAY,EACZ,SAAmB,EACnB,EAAW,EACX,MAAkC;QAElC,QAAQ,CAAC,GAAG,EAAE;YACZ,IAAI,CAAC;gBACH,MAAM,WAAW,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;gBAC3C,MAAM,OAAO,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC;gBAC1C,aAAa,CAAC,KAAK,CAAC,EAAE,EAAE,QAAQ,EAAE;oBAChC,IAAI;oBACJ,SAAS;oBACT,WAAW;oBACX,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;oBACtC,EAAE;iBACH,CAAC,CAAC;YACL,CAAC;YAAC,MAAM,CAAC;gBACP,gEAAgE;YAClE,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAED,SAAS,OAAO,CACd,IAAY,EACZ,EAA8B;QAE9B,OAAO,CAAC,IAAU,EAAc,EAAE,CAChC,GAAG,CAAC,GAAG,CAAC,EAAE,EAAE,KAAK,IAAI,EAAE;YACrB,IAAI,MAAqB,CAAC;YAC1B,IAAI,KAAK,GAAG,KAAK,CAAC;YAClB,IAAI,CAAC;gBACH,MAAM,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,CAAC;gBACxB,OAAO,MAAM,CAAC;YAChB,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,KAAK,GAAG,IAAI,CAAC;gBACb,MAAM,GAAG,CAAC;YACZ,CAAC;oBAAS,CAAC;gBACT,kEAAkE;gBAClE,iEAAiE;gBACjE,oCAAoC;gBACpC,IAAI,CAAC;oBACH,MAAM,UAAU,GAAG,aAAa,EAAE,CAAC;oBACnC,IAAI,UAAU,CAAC,QAAQ,EAAE,CAAC;wBACxB,WAAW,CACT,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EACxB,UAAU,CAAC,QAAQ,EACnB,IAAI,EACJ,WAAW,CAAC,IAAI,CAAC,EACjB,CAAC,KAAK,IAAK,MAAqC,EAAE,OAAO,KAAK,IAAI,EAClE,MAAM,CACP,CAAC;oBACJ,CAAC;gBACH,CAAC;gBAAC,MAAM,CAAC;oBACP,kDAAkD;gBACpD,CAAC;YACH,CAAC;QACH,CAAC,CAAC,CAAC;IACP,CAAC;IAED,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,CAAC;AACpC,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** A minimal memoizing cache with a caching decision left to the caller —
|
|
2
|
+
* extracted so "never cache a failed/partial result" is one small, directly
|
|
3
|
+
* testable primitive rather than inline logic in mcp/src/index.ts.
|
|
4
|
+
*
|
|
5
|
+
* Context: index.ts's per-repoPath {repoId, store} cache used to cache
|
|
6
|
+
* every resolution unconditionally, including a repo whose `.reposkein/`
|
|
7
|
+
* exists but isn't indexed yet (no meta.json — `repoId` resolves to
|
|
8
|
+
* `undefined`). That froze a session on the failure forever: an agent that
|
|
9
|
+
* fixed the repo out-of-band (ran `reposkein-mcp index` in another
|
|
10
|
+
* terminal) stayed stuck until the server restarted, which defeats
|
|
11
|
+
* `select_repo`'s whole "no restart needed" point. `shouldCache` lets the
|
|
12
|
+
* caller keep re-attempting `compute` for exactly the states worth
|
|
13
|
+
* retrying, while still caching (and not repeatedly re-doing) real work
|
|
14
|
+
* for a successful resolution. */
|
|
15
|
+
export declare function makeCache<T>(compute: (key: string) => Promise<T>, shouldCache: (value: T) => boolean): (key: string) => Promise<T>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** A minimal memoizing cache with a caching decision left to the caller —
|
|
2
|
+
* extracted so "never cache a failed/partial result" is one small, directly
|
|
3
|
+
* testable primitive rather than inline logic in mcp/src/index.ts.
|
|
4
|
+
*
|
|
5
|
+
* Context: index.ts's per-repoPath {repoId, store} cache used to cache
|
|
6
|
+
* every resolution unconditionally, including a repo whose `.reposkein/`
|
|
7
|
+
* exists but isn't indexed yet (no meta.json — `repoId` resolves to
|
|
8
|
+
* `undefined`). That froze a session on the failure forever: an agent that
|
|
9
|
+
* fixed the repo out-of-band (ran `reposkein-mcp index` in another
|
|
10
|
+
* terminal) stayed stuck until the server restarted, which defeats
|
|
11
|
+
* `select_repo`'s whole "no restart needed" point. `shouldCache` lets the
|
|
12
|
+
* caller keep re-attempting `compute` for exactly the states worth
|
|
13
|
+
* retrying, while still caching (and not repeatedly re-doing) real work
|
|
14
|
+
* for a successful resolution. */
|
|
15
|
+
export function makeCache(compute, shouldCache) {
|
|
16
|
+
const cache = new Map();
|
|
17
|
+
return async (key) => {
|
|
18
|
+
if (cache.has(key))
|
|
19
|
+
return cache.get(key);
|
|
20
|
+
const value = await compute(key);
|
|
21
|
+
if (shouldCache(value))
|
|
22
|
+
cache.set(key, value);
|
|
23
|
+
return value;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=repoContextCache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repoContextCache.js","sourceRoot":"","sources":["../../src/store/repoContextCache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;mCAamC;AACnC,MAAM,UAAU,SAAS,CACvB,OAAoC,EACpC,WAAkC;IAElC,MAAM,KAAK,GAAG,IAAI,GAAG,EAAa,CAAC;IACnC,OAAO,KAAK,EAAE,GAAW,EAAc,EAAE;QACvC,IAAI,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,OAAO,KAAK,CAAC,GAAG,CAAC,GAAG,CAAM,CAAC;QAC/C,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC;QACjC,IAAI,WAAW,CAAC,KAAK,CAAC;YAAE,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QAC9C,OAAO,KAAK,CAAC;IACf,CAAC,CAAC;AACJ,CAAC"}
|