crumbtrail-node 0.1.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-EASSXKUJ.js → chunk-GHLXGRLJ.js} +454 -127
- package/dist/cli.cjs +453 -127
- package/dist/cli.js +2 -1
- package/dist/index.cjs +456 -125
- package/dist/index.d.cts +79 -4
- package/dist/index.d.ts +79 -4
- package/dist/index.js +5 -1
- package/package.json +7 -3
|
@@ -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,
|
|
@@ -2702,6 +2820,7 @@ var SentryError = class extends Error {
|
|
|
2702
2820
|
this.status = status;
|
|
2703
2821
|
this.name = "SentryError";
|
|
2704
2822
|
}
|
|
2823
|
+
status;
|
|
2705
2824
|
};
|
|
2706
2825
|
function sanitizeUrl2(u) {
|
|
2707
2826
|
try {
|
|
@@ -3115,6 +3234,8 @@ var CloudWatchError = class extends Error {
|
|
|
3115
3234
|
this.target = target;
|
|
3116
3235
|
this.name = "CloudWatchError";
|
|
3117
3236
|
}
|
|
3237
|
+
status;
|
|
3238
|
+
target;
|
|
3118
3239
|
};
|
|
3119
3240
|
function isTransient2(error) {
|
|
3120
3241
|
if (error instanceof CloudWatchError) {
|
|
@@ -3527,6 +3648,7 @@ var SplunkError = class extends Error {
|
|
|
3527
3648
|
this.status = status;
|
|
3528
3649
|
this.name = "SplunkError";
|
|
3529
3650
|
}
|
|
3651
|
+
status;
|
|
3530
3652
|
};
|
|
3531
3653
|
function sanitizeUrl3(u) {
|
|
3532
3654
|
try {
|
|
@@ -3877,6 +3999,7 @@ var DatadogError = class extends Error {
|
|
|
3877
3999
|
this.status = status;
|
|
3878
4000
|
this.name = "DatadogError";
|
|
3879
4001
|
}
|
|
4002
|
+
status;
|
|
3880
4003
|
};
|
|
3881
4004
|
function sanitizeUrl4(u) {
|
|
3882
4005
|
try {
|
|
@@ -4240,6 +4363,7 @@ var PostHogError = class extends Error {
|
|
|
4240
4363
|
this.status = status;
|
|
4241
4364
|
this.name = "PostHogError";
|
|
4242
4365
|
}
|
|
4366
|
+
status;
|
|
4243
4367
|
};
|
|
4244
4368
|
function sanitizeUrl5(u) {
|
|
4245
4369
|
try {
|
|
@@ -4630,6 +4754,8 @@ var CloudflareError = class extends Error {
|
|
|
4630
4754
|
this.op = op;
|
|
4631
4755
|
this.name = "CloudflareError";
|
|
4632
4756
|
}
|
|
4757
|
+
status;
|
|
4758
|
+
op;
|
|
4633
4759
|
};
|
|
4634
4760
|
function isTransient6(error) {
|
|
4635
4761
|
if (error instanceof CloudflareError) {
|
|
@@ -10482,6 +10608,8 @@ var AudioProcessingError = class extends Error {
|
|
|
10482
10608
|
this.cause = cause;
|
|
10483
10609
|
this.name = "AudioProcessingError";
|
|
10484
10610
|
}
|
|
10611
|
+
phase;
|
|
10612
|
+
cause;
|
|
10485
10613
|
};
|
|
10486
10614
|
var AudioTranscriptionUnavailableError = class extends Error {
|
|
10487
10615
|
constructor() {
|
|
@@ -12269,7 +12397,7 @@ function timeProximity(now, candidateTime) {
|
|
|
12269
12397
|
const decay = Math.pow(0.5, ageMs / TIME_PROXIMITY_HALF_LIFE_MS);
|
|
12270
12398
|
return { boost: TIME_PROXIMITY_WEIGHT * decay, recent: decay >= 0.5 };
|
|
12271
12399
|
}
|
|
12272
|
-
function
|
|
12400
|
+
function clamp012(value) {
|
|
12273
12401
|
return Math.min(1, Math.max(0, value));
|
|
12274
12402
|
}
|
|
12275
12403
|
function locateIncident(symptom, store, opts = {}) {
|
|
@@ -12298,7 +12426,7 @@ function locateIncident(symptom, store, opts = {}) {
|
|
|
12298
12426
|
candidates.push({
|
|
12299
12427
|
sessionId: id,
|
|
12300
12428
|
bugId: bug.bugId,
|
|
12301
|
-
confidence:
|
|
12429
|
+
confidence: clamp012(base.score + time.boost + releaseBoost),
|
|
12302
12430
|
reasons,
|
|
12303
12431
|
bug
|
|
12304
12432
|
});
|
|
@@ -12444,7 +12572,20 @@ async function locateAndAssemble(symptom, store, opts = {}) {
|
|
|
12444
12572
|
const adapter = await gatherAdapterEvidence(symptom, located, opts);
|
|
12445
12573
|
const evidence = [...located.evidence, ...adapter.items];
|
|
12446
12574
|
const gaps = located.evidence.length === 0 ? [NO_LOCATED_SESSION_GAP, ...adapter.gaps] : [...adapter.gaps];
|
|
12447
|
-
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
|
+
});
|
|
12448
12589
|
return { bundle, match: located.match, sources: adapter.sources };
|
|
12449
12590
|
}
|
|
12450
12591
|
|
|
@@ -12964,12 +13105,12 @@ import fs10 from "fs";
|
|
|
12964
13105
|
// package.json
|
|
12965
13106
|
var package_default = {
|
|
12966
13107
|
name: "crumbtrail-node",
|
|
12967
|
-
version: "0.1
|
|
13108
|
+
version: "0.2.1",
|
|
12968
13109
|
description: "Local Crumbtrail server, MCP evidence tools, Express middleware, and CLI for self-hosted debugging sessions.",
|
|
12969
13110
|
license: "MIT",
|
|
12970
13111
|
repository: {
|
|
12971
13112
|
type: "git",
|
|
12972
|
-
url: "https://github.com/
|
|
13113
|
+
url: "git+https://github.com/crumbtrail-dev/crumbtrail-js.git",
|
|
12973
13114
|
directory: "packages/node"
|
|
12974
13115
|
},
|
|
12975
13116
|
keywords: [
|
|
@@ -13022,6 +13163,10 @@ var package_default = {
|
|
|
13022
13163
|
"@types/node": "^25.5.0",
|
|
13023
13164
|
express: "^5.2.1",
|
|
13024
13165
|
typescript: "^5.7.0"
|
|
13166
|
+
},
|
|
13167
|
+
homepage: "https://github.com/crumbtrail-dev/crumbtrail-js/tree/main/packages/node#readme",
|
|
13168
|
+
bugs: {
|
|
13169
|
+
url: "https://github.com/crumbtrail-dev/crumbtrail-js/issues"
|
|
13025
13170
|
}
|
|
13026
13171
|
};
|
|
13027
13172
|
|
|
@@ -13299,6 +13444,10 @@ var RequestValidationError = class extends Error {
|
|
|
13299
13444
|
this.retryable = retryable;
|
|
13300
13445
|
this.name = "RequestValidationError";
|
|
13301
13446
|
}
|
|
13447
|
+
status;
|
|
13448
|
+
publicMessage;
|
|
13449
|
+
code;
|
|
13450
|
+
retryable;
|
|
13302
13451
|
};
|
|
13303
13452
|
function serveStatic(res, staticDir, urlPath) {
|
|
13304
13453
|
const resolved = resolveStaticPath(staticDir, urlPath);
|
|
@@ -14028,6 +14177,7 @@ var BoundedTraceSessionCache = class {
|
|
|
14028
14177
|
constructor(maxEntries) {
|
|
14029
14178
|
this.maxEntries = maxEntries;
|
|
14030
14179
|
}
|
|
14180
|
+
maxEntries;
|
|
14031
14181
|
entries = /* @__PURE__ */ new Map();
|
|
14032
14182
|
get(traceId) {
|
|
14033
14183
|
const existing = this.entries.get(traceId);
|
|
@@ -14726,6 +14876,20 @@ function createServer(config) {
|
|
|
14726
14876
|
});
|
|
14727
14877
|
server.on("close", () => sweeper.stop());
|
|
14728
14878
|
}
|
|
14879
|
+
if (config.ai?.enabled && config.ai.backfillOnStart) {
|
|
14880
|
+
const sessionDirs = sessions.listSummaries().map((summary) => sessions.getExistingSessionDir(summary.id)).filter((dir) => dir !== void 0);
|
|
14881
|
+
const timer = setTimeout(() => {
|
|
14882
|
+
void backfillAiDiagnoses(
|
|
14883
|
+
sessionDirs,
|
|
14884
|
+
config.ai
|
|
14885
|
+
).then(
|
|
14886
|
+
(result) => config.ai?.log?.(
|
|
14887
|
+
`Crumbtrail AI diagnosis backfill complete: ${JSON.stringify(result)}`
|
|
14888
|
+
)
|
|
14889
|
+
);
|
|
14890
|
+
}, 0);
|
|
14891
|
+
server.on("close", () => clearTimeout(timer));
|
|
14892
|
+
}
|
|
14729
14893
|
if (fastFinalizer) {
|
|
14730
14894
|
server.on("close", () => fastFinalizer.stop());
|
|
14731
14895
|
}
|
|
@@ -14769,6 +14933,7 @@ var FixContextError = class extends Error {
|
|
|
14769
14933
|
this.code = code;
|
|
14770
14934
|
this.name = "FixContextError";
|
|
14771
14935
|
}
|
|
14936
|
+
code;
|
|
14772
14937
|
};
|
|
14773
14938
|
function buildFixContext(sessionDirOrId, opts = {}) {
|
|
14774
14939
|
const sessionDir = resolveSessionDir(sessionDirOrId, opts);
|
|
@@ -15701,16 +15866,85 @@ function diffEnvironment(a, b, rules, disabled) {
|
|
|
15701
15866
|
addSuppressedRules(rules, beforeRules, afterRules, disabled);
|
|
15702
15867
|
return [];
|
|
15703
15868
|
}
|
|
15869
|
+
const envDelta = buildEnvDiff(a, b, disabled);
|
|
15704
15870
|
return [
|
|
15705
15871
|
{
|
|
15706
15872
|
plane: "env",
|
|
15707
15873
|
kind: "env.snapshot",
|
|
15708
15874
|
before: a,
|
|
15709
15875
|
after: b,
|
|
15710
|
-
brief:
|
|
15876
|
+
brief: envBrief(envDelta),
|
|
15877
|
+
envDelta
|
|
15711
15878
|
}
|
|
15712
15879
|
];
|
|
15713
15880
|
}
|
|
15881
|
+
function buildEnvDiff(a, b, disabled) {
|
|
15882
|
+
const delta = {
|
|
15883
|
+
flags: channelDelta(recordField(a.flags), recordField(b.flags), disabled),
|
|
15884
|
+
config: channelDelta(
|
|
15885
|
+
recordField(a.config),
|
|
15886
|
+
recordField(b.config),
|
|
15887
|
+
disabled
|
|
15888
|
+
)
|
|
15889
|
+
};
|
|
15890
|
+
const releaseBefore = stringOrUndefined(a.release);
|
|
15891
|
+
const releaseAfter = stringOrUndefined(b.release);
|
|
15892
|
+
if (releaseBefore !== releaseAfter) {
|
|
15893
|
+
delta.release = {
|
|
15894
|
+
...releaseBefore !== void 0 ? { before: releaseBefore } : {},
|
|
15895
|
+
...releaseAfter !== void 0 ? { after: releaseAfter } : {}
|
|
15896
|
+
};
|
|
15897
|
+
}
|
|
15898
|
+
const buildBefore = stringOrUndefined(a.build);
|
|
15899
|
+
const buildAfter = stringOrUndefined(b.build);
|
|
15900
|
+
if (buildBefore !== buildAfter) {
|
|
15901
|
+
delta.build = {
|
|
15902
|
+
...buildBefore !== void 0 ? { before: buildBefore } : {},
|
|
15903
|
+
...buildAfter !== void 0 ? { after: buildAfter } : {}
|
|
15904
|
+
};
|
|
15905
|
+
}
|
|
15906
|
+
return delta;
|
|
15907
|
+
}
|
|
15908
|
+
function recordField(value) {
|
|
15909
|
+
return isRecord14(value) ? value : {};
|
|
15910
|
+
}
|
|
15911
|
+
function channelDelta(a, b, disabled) {
|
|
15912
|
+
const added = [];
|
|
15913
|
+
const removed = [];
|
|
15914
|
+
const changed = [];
|
|
15915
|
+
for (const key of Object.keys(a).sort()) {
|
|
15916
|
+
if (!(key in b)) {
|
|
15917
|
+
removed.push({ key, before: a[key] });
|
|
15918
|
+
} else if (envValuesDiffer(a[key], b[key], disabled)) {
|
|
15919
|
+
changed.push({ key, before: a[key], after: b[key] });
|
|
15920
|
+
}
|
|
15921
|
+
}
|
|
15922
|
+
for (const key of Object.keys(b).sort()) {
|
|
15923
|
+
if (!(key in a)) added.push({ key, after: b[key] });
|
|
15924
|
+
}
|
|
15925
|
+
return { added, removed, changed };
|
|
15926
|
+
}
|
|
15927
|
+
function envValuesDiffer(before, after, disabled) {
|
|
15928
|
+
const beforeRules = /* @__PURE__ */ new Set();
|
|
15929
|
+
const afterRules = /* @__PURE__ */ new Set();
|
|
15930
|
+
if (canonicalize(before, beforeRules) !== canonicalize(after, afterRules))
|
|
15931
|
+
return true;
|
|
15932
|
+
const rawDiffers = stableStringify(before) !== stableStringify(after);
|
|
15933
|
+
return rawDiffers && hasDisabledRule(beforeRules, afterRules, disabled);
|
|
15934
|
+
}
|
|
15935
|
+
function envBrief(delta) {
|
|
15936
|
+
const counts = delta.flags.added.length + delta.flags.removed.length + delta.flags.changed.length + delta.config.added.length + delta.config.removed.length + delta.config.changed.length + (delta.release ? 1 : 0) + (delta.build ? 1 : 0);
|
|
15937
|
+
const parts = [];
|
|
15938
|
+
const flagCount = delta.flags.added.length + delta.flags.removed.length + delta.flags.changed.length;
|
|
15939
|
+
const configCount = delta.config.added.length + delta.config.removed.length + delta.config.changed.length;
|
|
15940
|
+
if (flagCount > 0) parts.push(`${flagCount} flag(s)`);
|
|
15941
|
+
if (configCount > 0) parts.push(`${configCount} config value(s)`);
|
|
15942
|
+
if (delta.release) parts.push("release");
|
|
15943
|
+
if (delta.build) parts.push("build");
|
|
15944
|
+
if (counts === 0 || parts.length === 0)
|
|
15945
|
+
return "feature flag, config, release, or build snapshot changed between sessions";
|
|
15946
|
+
return `environment delta between sessions: ${parts.join(", ")} changed`;
|
|
15947
|
+
}
|
|
15714
15948
|
function confidenceFor(divergences) {
|
|
15715
15949
|
if (divergences.length === 0) return "high";
|
|
15716
15950
|
if (divergences.some(
|
|
@@ -15737,6 +15971,9 @@ async function compareSessions(aDir, bDir, options = {}) {
|
|
|
15737
15971
|
...diffDb(a.dbWrites, b.dbWrites, rules, disabled),
|
|
15738
15972
|
...diffEnvironment(a.environment, b.environment, rules, disabled)
|
|
15739
15973
|
];
|
|
15974
|
+
const envDelta = divergences.find(
|
|
15975
|
+
(divergence) => divergence.kind === "env.snapshot"
|
|
15976
|
+
)?.envDelta;
|
|
15740
15977
|
return {
|
|
15741
15978
|
schemaVersion: SESSION_COMPARE_SCHEMA_VERSION,
|
|
15742
15979
|
verdict: divergences.length > 0 ? "regression" : "clean",
|
|
@@ -15759,20 +15996,185 @@ async function compareSessions(aDir, bDir, options = {}) {
|
|
|
15759
15996
|
divergences,
|
|
15760
15997
|
noise: { suppressedCount: rules.size, rules: [...rules].sort() },
|
|
15761
15998
|
evidence: divergencesToEvidence(divergences),
|
|
15762
|
-
intent: []
|
|
15999
|
+
intent: [],
|
|
16000
|
+
...envDelta ? { envDelta } : {}
|
|
15763
16001
|
};
|
|
15764
16002
|
}
|
|
15765
16003
|
|
|
16004
|
+
// src/compare/report.ts
|
|
16005
|
+
var PLANE_ORDER = ["flow", "network", "db", "env"];
|
|
16006
|
+
var PLANE_LABELS = {
|
|
16007
|
+
flow: "Flow steps",
|
|
16008
|
+
network: "Network calls",
|
|
16009
|
+
db: "Database rows",
|
|
16010
|
+
env: "Environment and flags"
|
|
16011
|
+
};
|
|
16012
|
+
function renderCompareReport(comparison) {
|
|
16013
|
+
const lines = [];
|
|
16014
|
+
const regressed = comparison.verdict === "regression";
|
|
16015
|
+
lines.push(
|
|
16016
|
+
`# Session comparison - ${formatSessionRef(comparison.a)} vs ${formatSessionRef(comparison.b)}`
|
|
16017
|
+
);
|
|
16018
|
+
lines.push("");
|
|
16019
|
+
lines.push(
|
|
16020
|
+
regressed ? `> **Verdict: REGRESSION** (confidence: ${comparison.confidence}) - recorded behavior changed between these sessions.` : `> **Verdict: CLEAN** (confidence: ${comparison.confidence}) - the recorded sessions show no behavioral divergence.`
|
|
16021
|
+
);
|
|
16022
|
+
lines.push("");
|
|
16023
|
+
lines.push(
|
|
16024
|
+
"A is the baseline session; B is the candidate release/build. Every row below is grounded in the recorded evidence of both sessions."
|
|
16025
|
+
);
|
|
16026
|
+
lines.push("");
|
|
16027
|
+
lines.push("## Aligned flow");
|
|
16028
|
+
lines.push("");
|
|
16029
|
+
const { matchedSteps, unmatchedA, unmatchedB } = comparison.alignment;
|
|
16030
|
+
lines.push(
|
|
16031
|
+
`${matchedSteps} step(s) matched by component identity \xB7 ${unmatchedA} only in A \xB7 ${unmatchedB} only in B`
|
|
16032
|
+
);
|
|
16033
|
+
lines.push("");
|
|
16034
|
+
if (comparison.divergences.length > 0) {
|
|
16035
|
+
lines.push("| Plane | Kind | Component | Divergence |");
|
|
16036
|
+
lines.push("|---|---|---|---|");
|
|
16037
|
+
for (const d of comparison.divergences) {
|
|
16038
|
+
const component = d.sig ? `\`${escapeCell(d.sig)}\`` : "\u2014";
|
|
16039
|
+
lines.push(
|
|
16040
|
+
`| **${d.plane}** | \`${d.kind}\` | ${component} | ${escapeCell(d.brief)} |`
|
|
16041
|
+
);
|
|
16042
|
+
}
|
|
16043
|
+
lines.push("");
|
|
16044
|
+
}
|
|
16045
|
+
for (const plane of PLANE_ORDER) {
|
|
16046
|
+
const planeDivergences = comparison.divergences.filter(
|
|
16047
|
+
(d) => d.plane === plane
|
|
16048
|
+
);
|
|
16049
|
+
if (planeDivergences.length === 0) continue;
|
|
16050
|
+
lines.push(`## ${PLANE_LABELS[plane]}`);
|
|
16051
|
+
lines.push("");
|
|
16052
|
+
for (const d of planeDivergences) {
|
|
16053
|
+
lines.push(`### \`${d.kind}\` - ${escapeCell(d.brief)}`);
|
|
16054
|
+
lines.push("");
|
|
16055
|
+
if (d.sig) lines.push(`- Component: \`${d.sig}\``);
|
|
16056
|
+
if (d.requestId) lines.push(`- Request: \`${d.requestId}\``);
|
|
16057
|
+
if (d.table)
|
|
16058
|
+
lines.push(
|
|
16059
|
+
`- Table: \`${d.table}\`${d.pk ? ` \xB7 pk \`${JSON.stringify(d.pk)}\`` : ""}`
|
|
16060
|
+
);
|
|
16061
|
+
lines.push("");
|
|
16062
|
+
if (d.envDelta) {
|
|
16063
|
+
lines.push(...renderEnvDelta(d.envDelta));
|
|
16064
|
+
} else {
|
|
16065
|
+
lines.push("```diff");
|
|
16066
|
+
lines.push(`- before: ${JSON.stringify(d.before)}`);
|
|
16067
|
+
lines.push(`+ after: ${JSON.stringify(d.after)}`);
|
|
16068
|
+
lines.push("```");
|
|
16069
|
+
}
|
|
16070
|
+
lines.push("");
|
|
16071
|
+
}
|
|
16072
|
+
}
|
|
16073
|
+
lines.push("---");
|
|
16074
|
+
lines.push("");
|
|
16075
|
+
lines.push(
|
|
16076
|
+
comparison.noise.suppressedCount > 0 ? `*${comparison.noise.suppressedCount} expected-variance difference(s) suppressed by the noise model (${comparison.noise.rules.join(", ")}).*` : "*No differences were suppressed by the noise model.*"
|
|
16077
|
+
);
|
|
16078
|
+
lines.push("");
|
|
16079
|
+
return lines.join("\n");
|
|
16080
|
+
}
|
|
16081
|
+
function formatComparisonSummary(comparison) {
|
|
16082
|
+
const lines = [];
|
|
16083
|
+
lines.push(
|
|
16084
|
+
`crumbtrail-server compare - ${formatSessionRef(comparison.a)} vs ${formatSessionRef(comparison.b)}`
|
|
16085
|
+
);
|
|
16086
|
+
lines.push(` Schema: ${comparison.schemaVersion}`);
|
|
16087
|
+
lines.push(
|
|
16088
|
+
` Verdict: ${comparison.verdict.toUpperCase()} (confidence ${comparison.confidence})`
|
|
16089
|
+
);
|
|
16090
|
+
lines.push(
|
|
16091
|
+
` Alignment: ${comparison.alignment.matchedSteps} matched \xB7 ${comparison.alignment.unmatchedA} only in A \xB7 ${comparison.alignment.unmatchedB} only in B`
|
|
16092
|
+
);
|
|
16093
|
+
lines.push(` Divergences: ${comparison.divergences.length}`);
|
|
16094
|
+
for (const d of comparison.divergences.slice(0, 5)) {
|
|
16095
|
+
lines.push(` [${d.plane}] ${d.kind} - ${d.brief}`);
|
|
16096
|
+
}
|
|
16097
|
+
if (comparison.divergences.length > 5) {
|
|
16098
|
+
lines.push(
|
|
16099
|
+
` ... and ${comparison.divergences.length - 5} more (use --json or --report for the complete list)`
|
|
16100
|
+
);
|
|
16101
|
+
}
|
|
16102
|
+
const rules = comparison.noise.rules.length > 0 ? ` (${comparison.noise.rules.join(", ")})` : "";
|
|
16103
|
+
lines.push(
|
|
16104
|
+
` Noise: ${comparison.noise.suppressedCount} suppressed${rules}`
|
|
16105
|
+
);
|
|
16106
|
+
return lines.join("\n");
|
|
16107
|
+
}
|
|
16108
|
+
function escapeCell(text2) {
|
|
16109
|
+
return text2.replace(/\|/g, "\\|").replace(/\r?\n/g, " ");
|
|
16110
|
+
}
|
|
16111
|
+
function sessionRefLabel(ref) {
|
|
16112
|
+
return ref.release ?? ref.sessionId;
|
|
16113
|
+
}
|
|
16114
|
+
function comparisonTitle(comparison) {
|
|
16115
|
+
return `${sessionRefLabel(comparison.a)} vs ${sessionRefLabel(comparison.b)}`;
|
|
16116
|
+
}
|
|
16117
|
+
function renderEnvDelta(delta) {
|
|
16118
|
+
const lines = ["```diff"];
|
|
16119
|
+
renderEnvChannel(lines, "flags", delta.flags);
|
|
16120
|
+
renderEnvChannel(lines, "config", delta.config);
|
|
16121
|
+
if (delta.release) {
|
|
16122
|
+
lines.push(
|
|
16123
|
+
`~ release: ${JSON.stringify(delta.release.before ?? null)} -> ${JSON.stringify(delta.release.after ?? null)}`
|
|
16124
|
+
);
|
|
16125
|
+
}
|
|
16126
|
+
if (delta.build) {
|
|
16127
|
+
lines.push(
|
|
16128
|
+
`~ build: ${JSON.stringify(delta.build.before ?? null)} -> ${JSON.stringify(delta.build.after ?? null)}`
|
|
16129
|
+
);
|
|
16130
|
+
}
|
|
16131
|
+
if (lines.length === 1) lines.push(" (no field-level changes)");
|
|
16132
|
+
lines.push("```");
|
|
16133
|
+
return lines;
|
|
16134
|
+
}
|
|
16135
|
+
function renderEnvChannel(lines, channel, delta) {
|
|
16136
|
+
for (const change of delta.added) {
|
|
16137
|
+
lines.push(
|
|
16138
|
+
`+ ${channel}.${change.key}: ${JSON.stringify(change.after ?? null)}`
|
|
16139
|
+
);
|
|
16140
|
+
}
|
|
16141
|
+
for (const change of delta.removed) {
|
|
16142
|
+
lines.push(
|
|
16143
|
+
`- ${channel}.${change.key}: ${JSON.stringify(change.before ?? null)}`
|
|
16144
|
+
);
|
|
16145
|
+
}
|
|
16146
|
+
for (const change of delta.changed) {
|
|
16147
|
+
lines.push(
|
|
16148
|
+
`~ ${channel}.${change.key}: ${JSON.stringify(change.before ?? null)} -> ${JSON.stringify(change.after ?? null)}`
|
|
16149
|
+
);
|
|
16150
|
+
}
|
|
16151
|
+
}
|
|
16152
|
+
function formatSessionRef(ref) {
|
|
16153
|
+
if (ref.release) {
|
|
16154
|
+
const detail = [
|
|
16155
|
+
`session ${ref.sessionId}`,
|
|
16156
|
+
ref.build ? `build ${ref.build}` : void 0
|
|
16157
|
+
].filter(Boolean);
|
|
16158
|
+
return `${ref.release} (${detail.join(", ")})`;
|
|
16159
|
+
}
|
|
16160
|
+
return ref.build ? `${ref.sessionId} (build ${ref.build})` : ref.sessionId;
|
|
16161
|
+
}
|
|
16162
|
+
|
|
15766
16163
|
// src/compare/regression-context.ts
|
|
15767
16164
|
import fs13 from "fs";
|
|
15768
16165
|
import path13 from "path";
|
|
15769
16166
|
var REGRESSION_CONTEXT_SCHEMA_VERSION = "regression-context.v1";
|
|
15770
16167
|
function buildRegressionContext(comparison, bDir) {
|
|
15771
|
-
const requestIds = unique(
|
|
15772
|
-
|
|
16168
|
+
const requestIds = unique(
|
|
16169
|
+
comparison.divergences.map((d) => d.requestId).filter(isString)
|
|
16170
|
+
);
|
|
16171
|
+
const times = requestIds.flatMap(
|
|
16172
|
+
(requestId) => requestTimesFromIndex(bDir, requestId)
|
|
16173
|
+
);
|
|
15773
16174
|
const firstSig = comparison.divergences.map((d) => d.sig).find(isString);
|
|
15774
16175
|
return {
|
|
15775
16176
|
schemaVersion: REGRESSION_CONTEXT_SCHEMA_VERSION,
|
|
16177
|
+
title: comparisonTitle(comparison),
|
|
15776
16178
|
comparison,
|
|
15777
16179
|
divergent_interaction: firstSig ? interactionForSig(bDir, firstSig) : null,
|
|
15778
16180
|
causal_window: requestIds.length > 0 ? {
|
|
@@ -15790,6 +16192,7 @@ function buildRegressionContext(comparison, bDir) {
|
|
|
15790
16192
|
before: d.before,
|
|
15791
16193
|
after: d.after
|
|
15792
16194
|
})),
|
|
16195
|
+
env_delta: comparison.envDelta ?? null,
|
|
15793
16196
|
repro_hint: reproHint(bDir)
|
|
15794
16197
|
};
|
|
15795
16198
|
}
|
|
@@ -15834,7 +16237,8 @@ function collectRefTimes(value, times) {
|
|
|
15834
16237
|
const start = isRecord15(value.start) ? value.start : void 0;
|
|
15835
16238
|
const end = isRecord15(value.end) ? value.end : void 0;
|
|
15836
16239
|
for (const candidate of [ref, start, end]) {
|
|
15837
|
-
if (typeof candidate?.t === "number" && Number.isFinite(candidate.t))
|
|
16240
|
+
if (typeof candidate?.t === "number" && Number.isFinite(candidate.t))
|
|
16241
|
+
times.push(candidate.t);
|
|
15838
16242
|
}
|
|
15839
16243
|
}
|
|
15840
16244
|
function reproHint(sessionDir) {
|
|
@@ -16892,12 +17296,29 @@ var McpServer = class {
|
|
|
16892
17296
|
"sha"
|
|
16893
17297
|
]))
|
|
16894
17298
|
continue;
|
|
16895
|
-
sessions.push(meta);
|
|
17299
|
+
sessions.push(this.withReleaseBuild(meta));
|
|
16896
17300
|
} catch {
|
|
16897
17301
|
}
|
|
16898
17302
|
}
|
|
16899
17303
|
return textResult(sessions);
|
|
16900
17304
|
}
|
|
17305
|
+
/**
|
|
17306
|
+
* Surfaces release/build as first-class list-row fields regardless of which
|
|
17307
|
+
* alias the app used (release/releaseId/version, build/buildId/commit/sha), so
|
|
17308
|
+
* an agent can label and group sessions by release without re-reading each
|
|
17309
|
+
* meta. Additive: the raw meta keys are preserved.
|
|
17310
|
+
*/
|
|
17311
|
+
withReleaseBuild(meta) {
|
|
17312
|
+
const release = stringField5(meta.release ?? meta.releaseId ?? meta.version);
|
|
17313
|
+
const build = stringField5(
|
|
17314
|
+
meta.build ?? meta.buildId ?? meta.commit ?? meta.sha
|
|
17315
|
+
);
|
|
17316
|
+
return {
|
|
17317
|
+
...meta,
|
|
17318
|
+
...release !== void 0 ? { release } : {},
|
|
17319
|
+
...build !== void 0 ? { build } : {}
|
|
17320
|
+
};
|
|
17321
|
+
}
|
|
16901
17322
|
sessionMetadataMatches(meta, expected, keys) {
|
|
16902
17323
|
return keys.some((key) => meta[key] === expected);
|
|
16903
17324
|
}
|
|
@@ -17301,6 +17722,7 @@ var McpServer = class {
|
|
|
17301
17722
|
const currentSession = stringField5(args.currentSession);
|
|
17302
17723
|
let evidence = [];
|
|
17303
17724
|
let intent = [];
|
|
17725
|
+
let locatedDecision;
|
|
17304
17726
|
const adapterGaps = [];
|
|
17305
17727
|
let sessionlessAdapterBundle = false;
|
|
17306
17728
|
if (baselineSession && currentSession) {
|
|
@@ -17316,6 +17738,13 @@ var McpServer = class {
|
|
|
17316
17738
|
try {
|
|
17317
17739
|
const located = locateEvidence(symptom, this.recallStore());
|
|
17318
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
|
+
};
|
|
17319
17748
|
const adapter = await gatherAdapterEvidence(symptom, located, {
|
|
17320
17749
|
sources: this.evidenceSources()
|
|
17321
17750
|
});
|
|
@@ -17401,7 +17830,13 @@ var McpServer = class {
|
|
|
17401
17830
|
}
|
|
17402
17831
|
] : []
|
|
17403
17832
|
];
|
|
17404
|
-
const bundle = assembleBundle({
|
|
17833
|
+
const bundle = assembleBundle({
|
|
17834
|
+
symptom,
|
|
17835
|
+
evidence,
|
|
17836
|
+
intent,
|
|
17837
|
+
gaps,
|
|
17838
|
+
located: locatedDecision
|
|
17839
|
+
});
|
|
17405
17840
|
if (budget.maxTokens === void 0) return textResult(bundle);
|
|
17406
17841
|
return this.budgetedTextResult(
|
|
17407
17842
|
bundle,
|
|
@@ -18273,6 +18708,7 @@ var InspectError = class extends Error {
|
|
|
18273
18708
|
this.code = code;
|
|
18274
18709
|
this.name = "InspectError";
|
|
18275
18710
|
}
|
|
18711
|
+
code;
|
|
18276
18712
|
};
|
|
18277
18713
|
function inspectSession(sessionDirOrId, opts = {}) {
|
|
18278
18714
|
const sessionDir = resolveSessionDir2(sessionDirOrId, opts);
|
|
@@ -18400,117 +18836,6 @@ function isRecord17(value) {
|
|
|
18400
18836
|
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
18401
18837
|
}
|
|
18402
18838
|
|
|
18403
|
-
// src/compare/report.ts
|
|
18404
|
-
var PLANE_ORDER = ["flow", "network", "db", "env"];
|
|
18405
|
-
var PLANE_LABELS = {
|
|
18406
|
-
flow: "Flow steps",
|
|
18407
|
-
network: "Network calls",
|
|
18408
|
-
db: "Database rows",
|
|
18409
|
-
env: "Environment and flags"
|
|
18410
|
-
};
|
|
18411
|
-
function renderCompareReport(comparison) {
|
|
18412
|
-
const lines = [];
|
|
18413
|
-
const regressed = comparison.verdict === "regression";
|
|
18414
|
-
lines.push(
|
|
18415
|
-
`# Session comparison - ${formatSessionRef(comparison.a)} vs ${formatSessionRef(comparison.b)}`
|
|
18416
|
-
);
|
|
18417
|
-
lines.push("");
|
|
18418
|
-
lines.push(
|
|
18419
|
-
regressed ? `> **Verdict: REGRESSION** (confidence: ${comparison.confidence}) - recorded behavior changed between these sessions.` : `> **Verdict: CLEAN** (confidence: ${comparison.confidence}) - the recorded sessions show no behavioral divergence.`
|
|
18420
|
-
);
|
|
18421
|
-
lines.push("");
|
|
18422
|
-
lines.push(
|
|
18423
|
-
"A is the baseline session; B is the candidate release/build. Every row below is grounded in the recorded evidence of both sessions."
|
|
18424
|
-
);
|
|
18425
|
-
lines.push("");
|
|
18426
|
-
lines.push("## Aligned flow");
|
|
18427
|
-
lines.push("");
|
|
18428
|
-
const { matchedSteps, unmatchedA, unmatchedB } = comparison.alignment;
|
|
18429
|
-
lines.push(
|
|
18430
|
-
`${matchedSteps} step(s) matched by component identity \xB7 ${unmatchedA} only in A \xB7 ${unmatchedB} only in B`
|
|
18431
|
-
);
|
|
18432
|
-
lines.push("");
|
|
18433
|
-
if (comparison.divergences.length > 0) {
|
|
18434
|
-
lines.push("| Plane | Kind | Component | Divergence |");
|
|
18435
|
-
lines.push("|---|---|---|---|");
|
|
18436
|
-
for (const d of comparison.divergences) {
|
|
18437
|
-
const component = d.sig ? `\`${escapeCell(d.sig)}\`` : "\u2014";
|
|
18438
|
-
lines.push(
|
|
18439
|
-
`| **${d.plane}** | \`${d.kind}\` | ${component} | ${escapeCell(d.brief)} |`
|
|
18440
|
-
);
|
|
18441
|
-
}
|
|
18442
|
-
lines.push("");
|
|
18443
|
-
}
|
|
18444
|
-
for (const plane of PLANE_ORDER) {
|
|
18445
|
-
const planeDivergences = comparison.divergences.filter(
|
|
18446
|
-
(d) => d.plane === plane
|
|
18447
|
-
);
|
|
18448
|
-
if (planeDivergences.length === 0) continue;
|
|
18449
|
-
lines.push(`## ${PLANE_LABELS[plane]}`);
|
|
18450
|
-
lines.push("");
|
|
18451
|
-
for (const d of planeDivergences) {
|
|
18452
|
-
lines.push(`### \`${d.kind}\` - ${escapeCell(d.brief)}`);
|
|
18453
|
-
lines.push("");
|
|
18454
|
-
if (d.sig) lines.push(`- Component: \`${d.sig}\``);
|
|
18455
|
-
if (d.requestId) lines.push(`- Request: \`${d.requestId}\``);
|
|
18456
|
-
if (d.table)
|
|
18457
|
-
lines.push(
|
|
18458
|
-
`- Table: \`${d.table}\`${d.pk ? ` \xB7 pk \`${JSON.stringify(d.pk)}\`` : ""}`
|
|
18459
|
-
);
|
|
18460
|
-
lines.push("");
|
|
18461
|
-
lines.push("```diff");
|
|
18462
|
-
lines.push(`- before: ${JSON.stringify(d.before)}`);
|
|
18463
|
-
lines.push(`+ after: ${JSON.stringify(d.after)}`);
|
|
18464
|
-
lines.push("```");
|
|
18465
|
-
lines.push("");
|
|
18466
|
-
}
|
|
18467
|
-
}
|
|
18468
|
-
lines.push("---");
|
|
18469
|
-
lines.push("");
|
|
18470
|
-
lines.push(
|
|
18471
|
-
comparison.noise.suppressedCount > 0 ? `*${comparison.noise.suppressedCount} expected-variance difference(s) suppressed by the noise model (${comparison.noise.rules.join(", ")}).*` : "*No differences were suppressed by the noise model.*"
|
|
18472
|
-
);
|
|
18473
|
-
lines.push("");
|
|
18474
|
-
return lines.join("\n");
|
|
18475
|
-
}
|
|
18476
|
-
function formatComparisonSummary(comparison) {
|
|
18477
|
-
const lines = [];
|
|
18478
|
-
lines.push(
|
|
18479
|
-
`crumbtrail-server compare - ${formatSessionRef(comparison.a)} vs ${formatSessionRef(comparison.b)}`
|
|
18480
|
-
);
|
|
18481
|
-
lines.push(` Schema: ${comparison.schemaVersion}`);
|
|
18482
|
-
lines.push(
|
|
18483
|
-
` Verdict: ${comparison.verdict.toUpperCase()} (confidence ${comparison.confidence})`
|
|
18484
|
-
);
|
|
18485
|
-
lines.push(
|
|
18486
|
-
` Alignment: ${comparison.alignment.matchedSteps} matched \xB7 ${comparison.alignment.unmatchedA} only in A \xB7 ${comparison.alignment.unmatchedB} only in B`
|
|
18487
|
-
);
|
|
18488
|
-
lines.push(` Divergences: ${comparison.divergences.length}`);
|
|
18489
|
-
for (const d of comparison.divergences.slice(0, 5)) {
|
|
18490
|
-
lines.push(` [${d.plane}] ${d.kind} - ${d.brief}`);
|
|
18491
|
-
}
|
|
18492
|
-
if (comparison.divergences.length > 5) {
|
|
18493
|
-
lines.push(
|
|
18494
|
-
` ... and ${comparison.divergences.length - 5} more (use --json or --report for the complete list)`
|
|
18495
|
-
);
|
|
18496
|
-
}
|
|
18497
|
-
const rules = comparison.noise.rules.length > 0 ? ` (${comparison.noise.rules.join(", ")})` : "";
|
|
18498
|
-
lines.push(
|
|
18499
|
-
` Noise: ${comparison.noise.suppressedCount} suppressed${rules}`
|
|
18500
|
-
);
|
|
18501
|
-
return lines.join("\n");
|
|
18502
|
-
}
|
|
18503
|
-
function escapeCell(text2) {
|
|
18504
|
-
return text2.replace(/\|/g, "\\|").replace(/\r?\n/g, " ");
|
|
18505
|
-
}
|
|
18506
|
-
function formatSessionRef(ref) {
|
|
18507
|
-
const tags = [
|
|
18508
|
-
ref.release ? `release ${ref.release}` : void 0,
|
|
18509
|
-
ref.build ? `build ${ref.build}` : void 0
|
|
18510
|
-
].filter(Boolean);
|
|
18511
|
-
return tags.length > 0 ? `${ref.sessionId} (${tags.join(", ")})` : ref.sessionId;
|
|
18512
|
-
}
|
|
18513
|
-
|
|
18514
18839
|
// src/version.ts
|
|
18515
18840
|
import fs15 from "fs";
|
|
18516
18841
|
import path16 from "path";
|
|
@@ -18895,6 +19220,10 @@ export {
|
|
|
18895
19220
|
SESSION_COMPARE_SCHEMA_VERSION,
|
|
18896
19221
|
CompareError,
|
|
18897
19222
|
compareSessions,
|
|
19223
|
+
renderCompareReport,
|
|
19224
|
+
formatComparisonSummary,
|
|
19225
|
+
sessionRefLabel,
|
|
19226
|
+
comparisonTitle,
|
|
18898
19227
|
REGRESSION_CONTEXT_SCHEMA_VERSION,
|
|
18899
19228
|
buildRegressionContext,
|
|
18900
19229
|
McpServer,
|
|
@@ -18914,7 +19243,5 @@ export {
|
|
|
18914
19243
|
InspectError,
|
|
18915
19244
|
inspectSession,
|
|
18916
19245
|
formatInspection,
|
|
18917
|
-
renderCompareReport,
|
|
18918
|
-
formatComparisonSummary,
|
|
18919
19246
|
readPackageVersion
|
|
18920
19247
|
};
|