crumbtrail-node 0.2.0 → 0.2.1
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/{chunk-5HPWIOG3.js → chunk-GHLXGRLJ.js} +152 -7
- package/dist/cli.cjs +152 -7
- package/dist/cli.js +1 -1
- package/dist/index.cjs +152 -7
- package/dist/index.js +1 -1
- package/package.json +2 -2
|
@@ -1105,21 +1105,139 @@ function inferIntent(evidence, commits) {
|
|
|
1105
1105
|
var FUSION_SCHEMA_VERSION = "fusion.v1";
|
|
1106
1106
|
function assembleBundle(input) {
|
|
1107
1107
|
const evidence = rankEvidence(input.symptom, input.evidence);
|
|
1108
|
-
const
|
|
1108
|
+
const classified = classifyHypotheses(
|
|
1109
1109
|
input.symptom,
|
|
1110
1110
|
input.evidence,
|
|
1111
1111
|
input.intent
|
|
1112
1112
|
);
|
|
1113
1113
|
const gaps = input.gaps ?? [];
|
|
1114
|
+
const located = input.located;
|
|
1115
|
+
const hypotheses = classified.map((hypothesis) => {
|
|
1116
|
+
const verification = deriveVerification(hypothesis, evidence);
|
|
1117
|
+
return verification.length > 0 ? { ...hypothesis, verification } : hypothesis;
|
|
1118
|
+
});
|
|
1119
|
+
const contextCompleteness = deriveContextCompleteness(
|
|
1120
|
+
evidence,
|
|
1121
|
+
gaps,
|
|
1122
|
+
hypotheses,
|
|
1123
|
+
located
|
|
1124
|
+
);
|
|
1125
|
+
const escalation = deriveEscalation(contextCompleteness, hypotheses);
|
|
1114
1126
|
return {
|
|
1115
1127
|
schemaVersion: FUSION_SCHEMA_VERSION,
|
|
1116
1128
|
symptom: input.symptom,
|
|
1117
1129
|
evidence,
|
|
1118
1130
|
opinion: { stance: "advisory", hypotheses },
|
|
1119
1131
|
gaps,
|
|
1120
|
-
directives: suggestCaptureDirectives(input.symptom, input.evidence, gaps)
|
|
1132
|
+
directives: suggestCaptureDirectives(input.symptom, input.evidence, gaps),
|
|
1133
|
+
contextCompleteness,
|
|
1134
|
+
escalation,
|
|
1135
|
+
...located ? { located } : {}
|
|
1121
1136
|
};
|
|
1122
1137
|
}
|
|
1138
|
+
function clamp01(value) {
|
|
1139
|
+
if (Number.isNaN(value)) return 0;
|
|
1140
|
+
return Math.max(0, Math.min(1, value));
|
|
1141
|
+
}
|
|
1142
|
+
var COMPLETENESS_LANES = [
|
|
1143
|
+
"network",
|
|
1144
|
+
"db",
|
|
1145
|
+
"flow",
|
|
1146
|
+
"browser",
|
|
1147
|
+
"env"
|
|
1148
|
+
];
|
|
1149
|
+
function deriveContextCompleteness(evidence, gaps, hypotheses, located) {
|
|
1150
|
+
const lanesPresent = new Set(evidence.map((item) => item.lane));
|
|
1151
|
+
const informativePresent = COMPLETENESS_LANES.filter(
|
|
1152
|
+
(lane) => lanesPresent.has(lane)
|
|
1153
|
+
);
|
|
1154
|
+
const breadth = Math.min(1, informativePresent.length / 3);
|
|
1155
|
+
const volume = Math.min(1, evidence.length / 5);
|
|
1156
|
+
const top = hypotheses[0];
|
|
1157
|
+
const hypothesisStrength = !top || top.kind === "inconclusive" ? 0 : top.confidence;
|
|
1158
|
+
let score = 0.4 * breadth + 0.25 * volume + 0.35 * hypothesisStrength;
|
|
1159
|
+
if (located) {
|
|
1160
|
+
score = located.outcome === "matched" ? 0.85 * score + 0.15 * clamp01(located.confidence) : score * 0.6;
|
|
1161
|
+
}
|
|
1162
|
+
const hardGaps = gaps.filter((gap) => gap.kind === "source-unavailable").length;
|
|
1163
|
+
const softGaps = gaps.length - hardGaps;
|
|
1164
|
+
const gapPenalty = Math.min(0.5, softGaps * 0.1 + hardGaps * 0.3);
|
|
1165
|
+
score = clamp01(score - gapPenalty);
|
|
1166
|
+
const level = score < 0.34 ? "low" : score < 0.67 ? "medium" : "high";
|
|
1167
|
+
const reasons = [];
|
|
1168
|
+
const missingLanes = COMPLETENESS_LANES.filter(
|
|
1169
|
+
(lane) => !lanesPresent.has(lane)
|
|
1170
|
+
);
|
|
1171
|
+
if (informativePresent.length === 0) {
|
|
1172
|
+
reasons.push("no network/db/flow/browser/env evidence captured");
|
|
1173
|
+
} else if (missingLanes.length > 0) {
|
|
1174
|
+
reasons.push(`missing evidence lanes: ${missingLanes.join(", ")}`);
|
|
1175
|
+
}
|
|
1176
|
+
if (evidence.length > 0 && evidence.length < 3) {
|
|
1177
|
+
reasons.push(`thin evidence (${evidence.length} item(s))`);
|
|
1178
|
+
}
|
|
1179
|
+
if (hardGaps > 0) reasons.push(`source unavailable for ${hardGaps} lane(s)`);
|
|
1180
|
+
if (softGaps > 0) reasons.push(`${softGaps} evidence gap(s)`);
|
|
1181
|
+
if (located?.outcome === "inconclusive") {
|
|
1182
|
+
reasons.push("incident location inconclusive");
|
|
1183
|
+
}
|
|
1184
|
+
if (!top || top.kind === "inconclusive") {
|
|
1185
|
+
reasons.push("no distinguishing hypothesis");
|
|
1186
|
+
}
|
|
1187
|
+
return { score, level, reasons };
|
|
1188
|
+
}
|
|
1189
|
+
function pkString(pk) {
|
|
1190
|
+
return Object.keys(pk).sort().map((key) => `${key}=${String(pk[key])}`).join(", ");
|
|
1191
|
+
}
|
|
1192
|
+
function deriveVerification(hypothesis, evidence) {
|
|
1193
|
+
if (hypothesis.kind === "inconclusive") return [];
|
|
1194
|
+
const cited = new Set(hypothesis.evidenceIds);
|
|
1195
|
+
const out = [];
|
|
1196
|
+
for (const item of evidence) {
|
|
1197
|
+
if (!cited.has(item.id)) continue;
|
|
1198
|
+
const ref = item.ref;
|
|
1199
|
+
if (item.lane === "db" && ref.table && ref.pk && Object.keys(ref.pk).length > 0) {
|
|
1200
|
+
out.push({
|
|
1201
|
+
observation: `row in ${ref.table} (${pkString(ref.pk)}) matches the intended post-fix state`,
|
|
1202
|
+
evidenceIds: [item.id],
|
|
1203
|
+
how: "db"
|
|
1204
|
+
});
|
|
1205
|
+
} else if (ref.requestId) {
|
|
1206
|
+
const sigPart = ref.sig ? ` for signature ${ref.sig}` : "";
|
|
1207
|
+
out.push({
|
|
1208
|
+
observation: `request ${ref.requestId} succeeds${sigPart} on a fresh run (no error response)`,
|
|
1209
|
+
evidenceIds: [item.id],
|
|
1210
|
+
how: "request"
|
|
1211
|
+
});
|
|
1212
|
+
} else if (ref.sig) {
|
|
1213
|
+
out.push({
|
|
1214
|
+
observation: `error signature ${ref.sig} no longer appears in a fresh session over the same route`,
|
|
1215
|
+
evidenceIds: [item.id],
|
|
1216
|
+
how: "session"
|
|
1217
|
+
});
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
return out;
|
|
1221
|
+
}
|
|
1222
|
+
function deriveEscalation(completeness, hypotheses) {
|
|
1223
|
+
const allInconclusive = hypotheses.length > 0 && hypotheses.every((hypothesis) => hypothesis.kind === "inconclusive");
|
|
1224
|
+
const recommended = completeness.level === "low" || allInconclusive;
|
|
1225
|
+
if (!recommended) return { recommended: false, when: [] };
|
|
1226
|
+
const when = [
|
|
1227
|
+
"if you cannot reproduce the symptom via the anchored request or session, stop and request human triage \u2014 do not widen the search"
|
|
1228
|
+
];
|
|
1229
|
+
if (completeness.level === "low") {
|
|
1230
|
+
when.push(
|
|
1231
|
+
"context is thin: confirm the missing evidence lanes before acting on the top hypothesis"
|
|
1232
|
+
);
|
|
1233
|
+
}
|
|
1234
|
+
if (allInconclusive) {
|
|
1235
|
+
when.push(
|
|
1236
|
+
"no hypothesis is distinguished; treat the listed causes as equally unproven"
|
|
1237
|
+
);
|
|
1238
|
+
}
|
|
1239
|
+
return { recommended, when };
|
|
1240
|
+
}
|
|
1123
1241
|
var LANE_PRIOR = {
|
|
1124
1242
|
db: 0.2,
|
|
1125
1243
|
network: 0.2,
|
|
@@ -12279,7 +12397,7 @@ function timeProximity(now, candidateTime) {
|
|
|
12279
12397
|
const decay = Math.pow(0.5, ageMs / TIME_PROXIMITY_HALF_LIFE_MS);
|
|
12280
12398
|
return { boost: TIME_PROXIMITY_WEIGHT * decay, recent: decay >= 0.5 };
|
|
12281
12399
|
}
|
|
12282
|
-
function
|
|
12400
|
+
function clamp012(value) {
|
|
12283
12401
|
return Math.min(1, Math.max(0, value));
|
|
12284
12402
|
}
|
|
12285
12403
|
function locateIncident(symptom, store, opts = {}) {
|
|
@@ -12308,7 +12426,7 @@ function locateIncident(symptom, store, opts = {}) {
|
|
|
12308
12426
|
candidates.push({
|
|
12309
12427
|
sessionId: id,
|
|
12310
12428
|
bugId: bug.bugId,
|
|
12311
|
-
confidence:
|
|
12429
|
+
confidence: clamp012(base.score + time.boost + releaseBoost),
|
|
12312
12430
|
reasons,
|
|
12313
12431
|
bug
|
|
12314
12432
|
});
|
|
@@ -12454,7 +12572,20 @@ async function locateAndAssemble(symptom, store, opts = {}) {
|
|
|
12454
12572
|
const adapter = await gatherAdapterEvidence(symptom, located, opts);
|
|
12455
12573
|
const evidence = [...located.evidence, ...adapter.items];
|
|
12456
12574
|
const gaps = located.evidence.length === 0 ? [NO_LOCATED_SESSION_GAP, ...adapter.gaps] : [...adapter.gaps];
|
|
12457
|
-
const
|
|
12575
|
+
const located_ = {
|
|
12576
|
+
outcome: located.match.outcome,
|
|
12577
|
+
confidence: located.match.confidence,
|
|
12578
|
+
method: "fuzzy",
|
|
12579
|
+
...located.match.sessionId ? { sessionId: located.match.sessionId } : {},
|
|
12580
|
+
reasons: located.match.reasons
|
|
12581
|
+
};
|
|
12582
|
+
const bundle = assembleBundle({
|
|
12583
|
+
symptom,
|
|
12584
|
+
evidence,
|
|
12585
|
+
intent: [],
|
|
12586
|
+
gaps,
|
|
12587
|
+
located: located_
|
|
12588
|
+
});
|
|
12458
12589
|
return { bundle, match: located.match, sources: adapter.sources };
|
|
12459
12590
|
}
|
|
12460
12591
|
|
|
@@ -12974,7 +13105,7 @@ import fs10 from "fs";
|
|
|
12974
13105
|
// package.json
|
|
12975
13106
|
var package_default = {
|
|
12976
13107
|
name: "crumbtrail-node",
|
|
12977
|
-
version: "0.2.
|
|
13108
|
+
version: "0.2.1",
|
|
12978
13109
|
description: "Local Crumbtrail server, MCP evidence tools, Express middleware, and CLI for self-hosted debugging sessions.",
|
|
12979
13110
|
license: "MIT",
|
|
12980
13111
|
repository: {
|
|
@@ -17591,6 +17722,7 @@ var McpServer = class {
|
|
|
17591
17722
|
const currentSession = stringField5(args.currentSession);
|
|
17592
17723
|
let evidence = [];
|
|
17593
17724
|
let intent = [];
|
|
17725
|
+
let locatedDecision;
|
|
17594
17726
|
const adapterGaps = [];
|
|
17595
17727
|
let sessionlessAdapterBundle = false;
|
|
17596
17728
|
if (baselineSession && currentSession) {
|
|
@@ -17606,6 +17738,13 @@ var McpServer = class {
|
|
|
17606
17738
|
try {
|
|
17607
17739
|
const located = locateEvidence(symptom, this.recallStore());
|
|
17608
17740
|
evidence = located.evidence;
|
|
17741
|
+
locatedDecision = {
|
|
17742
|
+
outcome: located.match.outcome,
|
|
17743
|
+
confidence: located.match.confidence,
|
|
17744
|
+
method: "fuzzy",
|
|
17745
|
+
...located.match.sessionId ? { sessionId: located.match.sessionId } : {},
|
|
17746
|
+
reasons: located.match.reasons
|
|
17747
|
+
};
|
|
17609
17748
|
const adapter = await gatherAdapterEvidence(symptom, located, {
|
|
17610
17749
|
sources: this.evidenceSources()
|
|
17611
17750
|
});
|
|
@@ -17691,7 +17830,13 @@ var McpServer = class {
|
|
|
17691
17830
|
}
|
|
17692
17831
|
] : []
|
|
17693
17832
|
];
|
|
17694
|
-
const bundle = assembleBundle({
|
|
17833
|
+
const bundle = assembleBundle({
|
|
17834
|
+
symptom,
|
|
17835
|
+
evidence,
|
|
17836
|
+
intent,
|
|
17837
|
+
gaps,
|
|
17838
|
+
located: locatedDecision
|
|
17839
|
+
});
|
|
17695
17840
|
if (budget.maxTokens === void 0) return textResult(bundle);
|
|
17696
17841
|
return this.budgetedTextResult(
|
|
17697
17842
|
bundle,
|
package/dist/cli.cjs
CHANGED
|
@@ -214173,21 +214173,139 @@ function inferIntent(evidence, commits) {
|
|
|
214173
214173
|
var FUSION_SCHEMA_VERSION = "fusion.v1";
|
|
214174
214174
|
function assembleBundle(input) {
|
|
214175
214175
|
const evidence = rankEvidence(input.symptom, input.evidence);
|
|
214176
|
-
const
|
|
214176
|
+
const classified = classifyHypotheses(
|
|
214177
214177
|
input.symptom,
|
|
214178
214178
|
input.evidence,
|
|
214179
214179
|
input.intent
|
|
214180
214180
|
);
|
|
214181
214181
|
const gaps = input.gaps ?? [];
|
|
214182
|
+
const located = input.located;
|
|
214183
|
+
const hypotheses = classified.map((hypothesis) => {
|
|
214184
|
+
const verification = deriveVerification(hypothesis, evidence);
|
|
214185
|
+
return verification.length > 0 ? { ...hypothesis, verification } : hypothesis;
|
|
214186
|
+
});
|
|
214187
|
+
const contextCompleteness = deriveContextCompleteness(
|
|
214188
|
+
evidence,
|
|
214189
|
+
gaps,
|
|
214190
|
+
hypotheses,
|
|
214191
|
+
located
|
|
214192
|
+
);
|
|
214193
|
+
const escalation = deriveEscalation(contextCompleteness, hypotheses);
|
|
214182
214194
|
return {
|
|
214183
214195
|
schemaVersion: FUSION_SCHEMA_VERSION,
|
|
214184
214196
|
symptom: input.symptom,
|
|
214185
214197
|
evidence,
|
|
214186
214198
|
opinion: { stance: "advisory", hypotheses },
|
|
214187
214199
|
gaps,
|
|
214188
|
-
directives: suggestCaptureDirectives(input.symptom, input.evidence, gaps)
|
|
214200
|
+
directives: suggestCaptureDirectives(input.symptom, input.evidence, gaps),
|
|
214201
|
+
contextCompleteness,
|
|
214202
|
+
escalation,
|
|
214203
|
+
...located ? { located } : {}
|
|
214189
214204
|
};
|
|
214190
214205
|
}
|
|
214206
|
+
function clamp01(value) {
|
|
214207
|
+
if (Number.isNaN(value)) return 0;
|
|
214208
|
+
return Math.max(0, Math.min(1, value));
|
|
214209
|
+
}
|
|
214210
|
+
var COMPLETENESS_LANES = [
|
|
214211
|
+
"network",
|
|
214212
|
+
"db",
|
|
214213
|
+
"flow",
|
|
214214
|
+
"browser",
|
|
214215
|
+
"env"
|
|
214216
|
+
];
|
|
214217
|
+
function deriveContextCompleteness(evidence, gaps, hypotheses, located) {
|
|
214218
|
+
const lanesPresent = new Set(evidence.map((item) => item.lane));
|
|
214219
|
+
const informativePresent = COMPLETENESS_LANES.filter(
|
|
214220
|
+
(lane) => lanesPresent.has(lane)
|
|
214221
|
+
);
|
|
214222
|
+
const breadth = Math.min(1, informativePresent.length / 3);
|
|
214223
|
+
const volume = Math.min(1, evidence.length / 5);
|
|
214224
|
+
const top = hypotheses[0];
|
|
214225
|
+
const hypothesisStrength = !top || top.kind === "inconclusive" ? 0 : top.confidence;
|
|
214226
|
+
let score = 0.4 * breadth + 0.25 * volume + 0.35 * hypothesisStrength;
|
|
214227
|
+
if (located) {
|
|
214228
|
+
score = located.outcome === "matched" ? 0.85 * score + 0.15 * clamp01(located.confidence) : score * 0.6;
|
|
214229
|
+
}
|
|
214230
|
+
const hardGaps = gaps.filter((gap) => gap.kind === "source-unavailable").length;
|
|
214231
|
+
const softGaps = gaps.length - hardGaps;
|
|
214232
|
+
const gapPenalty = Math.min(0.5, softGaps * 0.1 + hardGaps * 0.3);
|
|
214233
|
+
score = clamp01(score - gapPenalty);
|
|
214234
|
+
const level = score < 0.34 ? "low" : score < 0.67 ? "medium" : "high";
|
|
214235
|
+
const reasons = [];
|
|
214236
|
+
const missingLanes = COMPLETENESS_LANES.filter(
|
|
214237
|
+
(lane) => !lanesPresent.has(lane)
|
|
214238
|
+
);
|
|
214239
|
+
if (informativePresent.length === 0) {
|
|
214240
|
+
reasons.push("no network/db/flow/browser/env evidence captured");
|
|
214241
|
+
} else if (missingLanes.length > 0) {
|
|
214242
|
+
reasons.push(`missing evidence lanes: ${missingLanes.join(", ")}`);
|
|
214243
|
+
}
|
|
214244
|
+
if (evidence.length > 0 && evidence.length < 3) {
|
|
214245
|
+
reasons.push(`thin evidence (${evidence.length} item(s))`);
|
|
214246
|
+
}
|
|
214247
|
+
if (hardGaps > 0) reasons.push(`source unavailable for ${hardGaps} lane(s)`);
|
|
214248
|
+
if (softGaps > 0) reasons.push(`${softGaps} evidence gap(s)`);
|
|
214249
|
+
if (located?.outcome === "inconclusive") {
|
|
214250
|
+
reasons.push("incident location inconclusive");
|
|
214251
|
+
}
|
|
214252
|
+
if (!top || top.kind === "inconclusive") {
|
|
214253
|
+
reasons.push("no distinguishing hypothesis");
|
|
214254
|
+
}
|
|
214255
|
+
return { score, level, reasons };
|
|
214256
|
+
}
|
|
214257
|
+
function pkString(pk) {
|
|
214258
|
+
return Object.keys(pk).sort().map((key) => `${key}=${String(pk[key])}`).join(", ");
|
|
214259
|
+
}
|
|
214260
|
+
function deriveVerification(hypothesis, evidence) {
|
|
214261
|
+
if (hypothesis.kind === "inconclusive") return [];
|
|
214262
|
+
const cited = new Set(hypothesis.evidenceIds);
|
|
214263
|
+
const out = [];
|
|
214264
|
+
for (const item of evidence) {
|
|
214265
|
+
if (!cited.has(item.id)) continue;
|
|
214266
|
+
const ref = item.ref;
|
|
214267
|
+
if (item.lane === "db" && ref.table && ref.pk && Object.keys(ref.pk).length > 0) {
|
|
214268
|
+
out.push({
|
|
214269
|
+
observation: `row in ${ref.table} (${pkString(ref.pk)}) matches the intended post-fix state`,
|
|
214270
|
+
evidenceIds: [item.id],
|
|
214271
|
+
how: "db"
|
|
214272
|
+
});
|
|
214273
|
+
} else if (ref.requestId) {
|
|
214274
|
+
const sigPart = ref.sig ? ` for signature ${ref.sig}` : "";
|
|
214275
|
+
out.push({
|
|
214276
|
+
observation: `request ${ref.requestId} succeeds${sigPart} on a fresh run (no error response)`,
|
|
214277
|
+
evidenceIds: [item.id],
|
|
214278
|
+
how: "request"
|
|
214279
|
+
});
|
|
214280
|
+
} else if (ref.sig) {
|
|
214281
|
+
out.push({
|
|
214282
|
+
observation: `error signature ${ref.sig} no longer appears in a fresh session over the same route`,
|
|
214283
|
+
evidenceIds: [item.id],
|
|
214284
|
+
how: "session"
|
|
214285
|
+
});
|
|
214286
|
+
}
|
|
214287
|
+
}
|
|
214288
|
+
return out;
|
|
214289
|
+
}
|
|
214290
|
+
function deriveEscalation(completeness, hypotheses) {
|
|
214291
|
+
const allInconclusive = hypotheses.length > 0 && hypotheses.every((hypothesis) => hypothesis.kind === "inconclusive");
|
|
214292
|
+
const recommended = completeness.level === "low" || allInconclusive;
|
|
214293
|
+
if (!recommended) return { recommended: false, when: [] };
|
|
214294
|
+
const when = [
|
|
214295
|
+
"if you cannot reproduce the symptom via the anchored request or session, stop and request human triage \u2014 do not widen the search"
|
|
214296
|
+
];
|
|
214297
|
+
if (completeness.level === "low") {
|
|
214298
|
+
when.push(
|
|
214299
|
+
"context is thin: confirm the missing evidence lanes before acting on the top hypothesis"
|
|
214300
|
+
);
|
|
214301
|
+
}
|
|
214302
|
+
if (allInconclusive) {
|
|
214303
|
+
when.push(
|
|
214304
|
+
"no hypothesis is distinguished; treat the listed causes as equally unproven"
|
|
214305
|
+
);
|
|
214306
|
+
}
|
|
214307
|
+
return { recommended, when };
|
|
214308
|
+
}
|
|
214191
214309
|
var LANE_PRIOR = {
|
|
214192
214310
|
db: 0.2,
|
|
214193
214311
|
network: 0.2,
|
|
@@ -218694,7 +218812,7 @@ function timeProximity(now, candidateTime) {
|
|
|
218694
218812
|
const decay = Math.pow(0.5, ageMs / TIME_PROXIMITY_HALF_LIFE_MS);
|
|
218695
218813
|
return { boost: TIME_PROXIMITY_WEIGHT * decay, recent: decay >= 0.5 };
|
|
218696
218814
|
}
|
|
218697
|
-
function
|
|
218815
|
+
function clamp012(value) {
|
|
218698
218816
|
return Math.min(1, Math.max(0, value));
|
|
218699
218817
|
}
|
|
218700
218818
|
function locateIncident(symptom, store, opts = {}) {
|
|
@@ -218723,7 +218841,7 @@ function locateIncident(symptom, store, opts = {}) {
|
|
|
218723
218841
|
candidates.push({
|
|
218724
218842
|
sessionId: id,
|
|
218725
218843
|
bugId: bug.bugId,
|
|
218726
|
-
confidence:
|
|
218844
|
+
confidence: clamp012(base.score + time.boost + releaseBoost),
|
|
218727
218845
|
reasons,
|
|
218728
218846
|
bug
|
|
218729
218847
|
});
|
|
@@ -218869,7 +218987,20 @@ async function locateAndAssemble(symptom, store, opts = {}) {
|
|
|
218869
218987
|
const adapter = await gatherAdapterEvidence(symptom, located, opts);
|
|
218870
218988
|
const evidence = [...located.evidence, ...adapter.items];
|
|
218871
218989
|
const gaps = located.evidence.length === 0 ? [NO_LOCATED_SESSION_GAP, ...adapter.gaps] : [...adapter.gaps];
|
|
218872
|
-
const
|
|
218990
|
+
const located_ = {
|
|
218991
|
+
outcome: located.match.outcome,
|
|
218992
|
+
confidence: located.match.confidence,
|
|
218993
|
+
method: "fuzzy",
|
|
218994
|
+
...located.match.sessionId ? { sessionId: located.match.sessionId } : {},
|
|
218995
|
+
reasons: located.match.reasons
|
|
218996
|
+
};
|
|
218997
|
+
const bundle = assembleBundle({
|
|
218998
|
+
symptom,
|
|
218999
|
+
evidence,
|
|
219000
|
+
intent: [],
|
|
219001
|
+
gaps,
|
|
219002
|
+
located: located_
|
|
219003
|
+
});
|
|
218873
219004
|
return { bundle, match: located.match, sources: adapter.sources };
|
|
218874
219005
|
}
|
|
218875
219006
|
|
|
@@ -225662,7 +225793,7 @@ var import_node_fs10 = __toESM(require("fs"), 1);
|
|
|
225662
225793
|
// package.json
|
|
225663
225794
|
var package_default = {
|
|
225664
225795
|
name: "crumbtrail-node",
|
|
225665
|
-
version: "0.2.
|
|
225796
|
+
version: "0.2.1",
|
|
225666
225797
|
description: "Local Crumbtrail server, MCP evidence tools, Express middleware, and CLI for self-hosted debugging sessions.",
|
|
225667
225798
|
license: "MIT",
|
|
225668
225799
|
repository: {
|
|
@@ -230654,6 +230785,7 @@ var McpServer = class {
|
|
|
230654
230785
|
const currentSession = stringField5(args.currentSession);
|
|
230655
230786
|
let evidence = [];
|
|
230656
230787
|
let intent = [];
|
|
230788
|
+
let locatedDecision;
|
|
230657
230789
|
const adapterGaps = [];
|
|
230658
230790
|
let sessionlessAdapterBundle = false;
|
|
230659
230791
|
if (baselineSession && currentSession) {
|
|
@@ -230669,6 +230801,13 @@ var McpServer = class {
|
|
|
230669
230801
|
try {
|
|
230670
230802
|
const located = locateEvidence(symptom, this.recallStore());
|
|
230671
230803
|
evidence = located.evidence;
|
|
230804
|
+
locatedDecision = {
|
|
230805
|
+
outcome: located.match.outcome,
|
|
230806
|
+
confidence: located.match.confidence,
|
|
230807
|
+
method: "fuzzy",
|
|
230808
|
+
...located.match.sessionId ? { sessionId: located.match.sessionId } : {},
|
|
230809
|
+
reasons: located.match.reasons
|
|
230810
|
+
};
|
|
230672
230811
|
const adapter = await gatherAdapterEvidence(symptom, located, {
|
|
230673
230812
|
sources: this.evidenceSources()
|
|
230674
230813
|
});
|
|
@@ -230754,7 +230893,13 @@ var McpServer = class {
|
|
|
230754
230893
|
}
|
|
230755
230894
|
] : []
|
|
230756
230895
|
];
|
|
230757
|
-
const bundle = assembleBundle({
|
|
230896
|
+
const bundle = assembleBundle({
|
|
230897
|
+
symptom,
|
|
230898
|
+
evidence,
|
|
230899
|
+
intent,
|
|
230900
|
+
gaps,
|
|
230901
|
+
located: locatedDecision
|
|
230902
|
+
});
|
|
230758
230903
|
if (budget.maxTokens === void 0) return textResult(bundle);
|
|
230759
230904
|
return this.budgetedTextResult(
|
|
230760
230905
|
bundle,
|
package/dist/cli.js
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -1300,21 +1300,139 @@ function inferIntent(evidence, commits) {
|
|
|
1300
1300
|
var FUSION_SCHEMA_VERSION = "fusion.v1";
|
|
1301
1301
|
function assembleBundle(input) {
|
|
1302
1302
|
const evidence = rankEvidence(input.symptom, input.evidence);
|
|
1303
|
-
const
|
|
1303
|
+
const classified = classifyHypotheses(
|
|
1304
1304
|
input.symptom,
|
|
1305
1305
|
input.evidence,
|
|
1306
1306
|
input.intent
|
|
1307
1307
|
);
|
|
1308
1308
|
const gaps = input.gaps ?? [];
|
|
1309
|
+
const located = input.located;
|
|
1310
|
+
const hypotheses = classified.map((hypothesis) => {
|
|
1311
|
+
const verification = deriveVerification(hypothesis, evidence);
|
|
1312
|
+
return verification.length > 0 ? { ...hypothesis, verification } : hypothesis;
|
|
1313
|
+
});
|
|
1314
|
+
const contextCompleteness = deriveContextCompleteness(
|
|
1315
|
+
evidence,
|
|
1316
|
+
gaps,
|
|
1317
|
+
hypotheses,
|
|
1318
|
+
located
|
|
1319
|
+
);
|
|
1320
|
+
const escalation = deriveEscalation(contextCompleteness, hypotheses);
|
|
1309
1321
|
return {
|
|
1310
1322
|
schemaVersion: FUSION_SCHEMA_VERSION,
|
|
1311
1323
|
symptom: input.symptom,
|
|
1312
1324
|
evidence,
|
|
1313
1325
|
opinion: { stance: "advisory", hypotheses },
|
|
1314
1326
|
gaps,
|
|
1315
|
-
directives: suggestCaptureDirectives(input.symptom, input.evidence, gaps)
|
|
1327
|
+
directives: suggestCaptureDirectives(input.symptom, input.evidence, gaps),
|
|
1328
|
+
contextCompleteness,
|
|
1329
|
+
escalation,
|
|
1330
|
+
...located ? { located } : {}
|
|
1316
1331
|
};
|
|
1317
1332
|
}
|
|
1333
|
+
function clamp01(value) {
|
|
1334
|
+
if (Number.isNaN(value)) return 0;
|
|
1335
|
+
return Math.max(0, Math.min(1, value));
|
|
1336
|
+
}
|
|
1337
|
+
var COMPLETENESS_LANES = [
|
|
1338
|
+
"network",
|
|
1339
|
+
"db",
|
|
1340
|
+
"flow",
|
|
1341
|
+
"browser",
|
|
1342
|
+
"env"
|
|
1343
|
+
];
|
|
1344
|
+
function deriveContextCompleteness(evidence, gaps, hypotheses, located) {
|
|
1345
|
+
const lanesPresent = new Set(evidence.map((item) => item.lane));
|
|
1346
|
+
const informativePresent = COMPLETENESS_LANES.filter(
|
|
1347
|
+
(lane) => lanesPresent.has(lane)
|
|
1348
|
+
);
|
|
1349
|
+
const breadth = Math.min(1, informativePresent.length / 3);
|
|
1350
|
+
const volume = Math.min(1, evidence.length / 5);
|
|
1351
|
+
const top = hypotheses[0];
|
|
1352
|
+
const hypothesisStrength = !top || top.kind === "inconclusive" ? 0 : top.confidence;
|
|
1353
|
+
let score = 0.4 * breadth + 0.25 * volume + 0.35 * hypothesisStrength;
|
|
1354
|
+
if (located) {
|
|
1355
|
+
score = located.outcome === "matched" ? 0.85 * score + 0.15 * clamp01(located.confidence) : score * 0.6;
|
|
1356
|
+
}
|
|
1357
|
+
const hardGaps = gaps.filter((gap) => gap.kind === "source-unavailable").length;
|
|
1358
|
+
const softGaps = gaps.length - hardGaps;
|
|
1359
|
+
const gapPenalty = Math.min(0.5, softGaps * 0.1 + hardGaps * 0.3);
|
|
1360
|
+
score = clamp01(score - gapPenalty);
|
|
1361
|
+
const level = score < 0.34 ? "low" : score < 0.67 ? "medium" : "high";
|
|
1362
|
+
const reasons = [];
|
|
1363
|
+
const missingLanes = COMPLETENESS_LANES.filter(
|
|
1364
|
+
(lane) => !lanesPresent.has(lane)
|
|
1365
|
+
);
|
|
1366
|
+
if (informativePresent.length === 0) {
|
|
1367
|
+
reasons.push("no network/db/flow/browser/env evidence captured");
|
|
1368
|
+
} else if (missingLanes.length > 0) {
|
|
1369
|
+
reasons.push(`missing evidence lanes: ${missingLanes.join(", ")}`);
|
|
1370
|
+
}
|
|
1371
|
+
if (evidence.length > 0 && evidence.length < 3) {
|
|
1372
|
+
reasons.push(`thin evidence (${evidence.length} item(s))`);
|
|
1373
|
+
}
|
|
1374
|
+
if (hardGaps > 0) reasons.push(`source unavailable for ${hardGaps} lane(s)`);
|
|
1375
|
+
if (softGaps > 0) reasons.push(`${softGaps} evidence gap(s)`);
|
|
1376
|
+
if (located?.outcome === "inconclusive") {
|
|
1377
|
+
reasons.push("incident location inconclusive");
|
|
1378
|
+
}
|
|
1379
|
+
if (!top || top.kind === "inconclusive") {
|
|
1380
|
+
reasons.push("no distinguishing hypothesis");
|
|
1381
|
+
}
|
|
1382
|
+
return { score, level, reasons };
|
|
1383
|
+
}
|
|
1384
|
+
function pkString(pk) {
|
|
1385
|
+
return Object.keys(pk).sort().map((key) => `${key}=${String(pk[key])}`).join(", ");
|
|
1386
|
+
}
|
|
1387
|
+
function deriveVerification(hypothesis, evidence) {
|
|
1388
|
+
if (hypothesis.kind === "inconclusive") return [];
|
|
1389
|
+
const cited = new Set(hypothesis.evidenceIds);
|
|
1390
|
+
const out = [];
|
|
1391
|
+
for (const item of evidence) {
|
|
1392
|
+
if (!cited.has(item.id)) continue;
|
|
1393
|
+
const ref = item.ref;
|
|
1394
|
+
if (item.lane === "db" && ref.table && ref.pk && Object.keys(ref.pk).length > 0) {
|
|
1395
|
+
out.push({
|
|
1396
|
+
observation: `row in ${ref.table} (${pkString(ref.pk)}) matches the intended post-fix state`,
|
|
1397
|
+
evidenceIds: [item.id],
|
|
1398
|
+
how: "db"
|
|
1399
|
+
});
|
|
1400
|
+
} else if (ref.requestId) {
|
|
1401
|
+
const sigPart = ref.sig ? ` for signature ${ref.sig}` : "";
|
|
1402
|
+
out.push({
|
|
1403
|
+
observation: `request ${ref.requestId} succeeds${sigPart} on a fresh run (no error response)`,
|
|
1404
|
+
evidenceIds: [item.id],
|
|
1405
|
+
how: "request"
|
|
1406
|
+
});
|
|
1407
|
+
} else if (ref.sig) {
|
|
1408
|
+
out.push({
|
|
1409
|
+
observation: `error signature ${ref.sig} no longer appears in a fresh session over the same route`,
|
|
1410
|
+
evidenceIds: [item.id],
|
|
1411
|
+
how: "session"
|
|
1412
|
+
});
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
return out;
|
|
1416
|
+
}
|
|
1417
|
+
function deriveEscalation(completeness, hypotheses) {
|
|
1418
|
+
const allInconclusive = hypotheses.length > 0 && hypotheses.every((hypothesis) => hypothesis.kind === "inconclusive");
|
|
1419
|
+
const recommended = completeness.level === "low" || allInconclusive;
|
|
1420
|
+
if (!recommended) return { recommended: false, when: [] };
|
|
1421
|
+
const when = [
|
|
1422
|
+
"if you cannot reproduce the symptom via the anchored request or session, stop and request human triage \u2014 do not widen the search"
|
|
1423
|
+
];
|
|
1424
|
+
if (completeness.level === "low") {
|
|
1425
|
+
when.push(
|
|
1426
|
+
"context is thin: confirm the missing evidence lanes before acting on the top hypothesis"
|
|
1427
|
+
);
|
|
1428
|
+
}
|
|
1429
|
+
if (allInconclusive) {
|
|
1430
|
+
when.push(
|
|
1431
|
+
"no hypothesis is distinguished; treat the listed causes as equally unproven"
|
|
1432
|
+
);
|
|
1433
|
+
}
|
|
1434
|
+
return { recommended, when };
|
|
1435
|
+
}
|
|
1318
1436
|
var LANE_PRIOR = {
|
|
1319
1437
|
db: 0.2,
|
|
1320
1438
|
network: 0.2,
|
|
@@ -5821,7 +5939,7 @@ function timeProximity(now, candidateTime) {
|
|
|
5821
5939
|
const decay = Math.pow(0.5, ageMs / TIME_PROXIMITY_HALF_LIFE_MS);
|
|
5822
5940
|
return { boost: TIME_PROXIMITY_WEIGHT * decay, recent: decay >= 0.5 };
|
|
5823
5941
|
}
|
|
5824
|
-
function
|
|
5942
|
+
function clamp012(value) {
|
|
5825
5943
|
return Math.min(1, Math.max(0, value));
|
|
5826
5944
|
}
|
|
5827
5945
|
function locateIncident(symptom, store, opts = {}) {
|
|
@@ -5850,7 +5968,7 @@ function locateIncident(symptom, store, opts = {}) {
|
|
|
5850
5968
|
candidates.push({
|
|
5851
5969
|
sessionId: id,
|
|
5852
5970
|
bugId: bug.bugId,
|
|
5853
|
-
confidence:
|
|
5971
|
+
confidence: clamp012(base.score + time.boost + releaseBoost),
|
|
5854
5972
|
reasons,
|
|
5855
5973
|
bug
|
|
5856
5974
|
});
|
|
@@ -5996,7 +6114,20 @@ async function locateAndAssemble(symptom, store, opts = {}) {
|
|
|
5996
6114
|
const adapter = await gatherAdapterEvidence(symptom, located, opts);
|
|
5997
6115
|
const evidence = [...located.evidence, ...adapter.items];
|
|
5998
6116
|
const gaps = located.evidence.length === 0 ? [NO_LOCATED_SESSION_GAP, ...adapter.gaps] : [...adapter.gaps];
|
|
5999
|
-
const
|
|
6117
|
+
const located_ = {
|
|
6118
|
+
outcome: located.match.outcome,
|
|
6119
|
+
confidence: located.match.confidence,
|
|
6120
|
+
method: "fuzzy",
|
|
6121
|
+
...located.match.sessionId ? { sessionId: located.match.sessionId } : {},
|
|
6122
|
+
reasons: located.match.reasons
|
|
6123
|
+
};
|
|
6124
|
+
const bundle = assembleBundle({
|
|
6125
|
+
symptom,
|
|
6126
|
+
evidence,
|
|
6127
|
+
intent: [],
|
|
6128
|
+
gaps,
|
|
6129
|
+
located: located_
|
|
6130
|
+
});
|
|
6000
6131
|
return { bundle, match: located.match, sources: adapter.sources };
|
|
6001
6132
|
}
|
|
6002
6133
|
|
|
@@ -12789,7 +12920,7 @@ var import_node_fs10 = __toESM(require("fs"), 1);
|
|
|
12789
12920
|
// package.json
|
|
12790
12921
|
var package_default = {
|
|
12791
12922
|
name: "crumbtrail-node",
|
|
12792
|
-
version: "0.2.
|
|
12923
|
+
version: "0.2.1",
|
|
12793
12924
|
description: "Local Crumbtrail server, MCP evidence tools, Express middleware, and CLI for self-hosted debugging sessions.",
|
|
12794
12925
|
license: "MIT",
|
|
12795
12926
|
repository: {
|
|
@@ -17781,6 +17912,7 @@ var McpServer = class {
|
|
|
17781
17912
|
const currentSession = stringField5(args.currentSession);
|
|
17782
17913
|
let evidence = [];
|
|
17783
17914
|
let intent = [];
|
|
17915
|
+
let locatedDecision;
|
|
17784
17916
|
const adapterGaps = [];
|
|
17785
17917
|
let sessionlessAdapterBundle = false;
|
|
17786
17918
|
if (baselineSession && currentSession) {
|
|
@@ -17796,6 +17928,13 @@ var McpServer = class {
|
|
|
17796
17928
|
try {
|
|
17797
17929
|
const located = locateEvidence(symptom, this.recallStore());
|
|
17798
17930
|
evidence = located.evidence;
|
|
17931
|
+
locatedDecision = {
|
|
17932
|
+
outcome: located.match.outcome,
|
|
17933
|
+
confidence: located.match.confidence,
|
|
17934
|
+
method: "fuzzy",
|
|
17935
|
+
...located.match.sessionId ? { sessionId: located.match.sessionId } : {},
|
|
17936
|
+
reasons: located.match.reasons
|
|
17937
|
+
};
|
|
17799
17938
|
const adapter = await gatherAdapterEvidence(symptom, located, {
|
|
17800
17939
|
sources: this.evidenceSources()
|
|
17801
17940
|
});
|
|
@@ -17881,7 +18020,13 @@ var McpServer = class {
|
|
|
17881
18020
|
}
|
|
17882
18021
|
] : []
|
|
17883
18022
|
];
|
|
17884
|
-
const bundle = assembleBundle({
|
|
18023
|
+
const bundle = assembleBundle({
|
|
18024
|
+
symptom,
|
|
18025
|
+
evidence,
|
|
18026
|
+
intent,
|
|
18027
|
+
gaps,
|
|
18028
|
+
located: locatedDecision
|
|
18029
|
+
});
|
|
17885
18030
|
if (budget.maxTokens === void 0) return textResult(bundle);
|
|
17886
18031
|
return this.budgetedTextResult(
|
|
17887
18032
|
bundle,
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "crumbtrail-node",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Local Crumbtrail server, MCP evidence tools, Express middleware, and CLI for self-hosted debugging sessions.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"protobufjs": "^8.6.5",
|
|
45
|
-
"crumbtrail-core": "^0.2.
|
|
45
|
+
"crumbtrail-core": "^0.2.1"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@types/express": "^5.0.6",
|