crumbtrail-node 0.1.0 → 0.2.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/dist/{chunk-EASSXKUJ.js → chunk-5HPWIOG3.js} +303 -121
- package/dist/cli.cjs +302 -121
- package/dist/cli.js +2 -1
- package/dist/index.cjs +305 -119
- 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
|
@@ -2702,6 +2702,7 @@ var SentryError = class extends Error {
|
|
|
2702
2702
|
this.status = status;
|
|
2703
2703
|
this.name = "SentryError";
|
|
2704
2704
|
}
|
|
2705
|
+
status;
|
|
2705
2706
|
};
|
|
2706
2707
|
function sanitizeUrl2(u) {
|
|
2707
2708
|
try {
|
|
@@ -3115,6 +3116,8 @@ var CloudWatchError = class extends Error {
|
|
|
3115
3116
|
this.target = target;
|
|
3116
3117
|
this.name = "CloudWatchError";
|
|
3117
3118
|
}
|
|
3119
|
+
status;
|
|
3120
|
+
target;
|
|
3118
3121
|
};
|
|
3119
3122
|
function isTransient2(error) {
|
|
3120
3123
|
if (error instanceof CloudWatchError) {
|
|
@@ -3527,6 +3530,7 @@ var SplunkError = class extends Error {
|
|
|
3527
3530
|
this.status = status;
|
|
3528
3531
|
this.name = "SplunkError";
|
|
3529
3532
|
}
|
|
3533
|
+
status;
|
|
3530
3534
|
};
|
|
3531
3535
|
function sanitizeUrl3(u) {
|
|
3532
3536
|
try {
|
|
@@ -3877,6 +3881,7 @@ var DatadogError = class extends Error {
|
|
|
3877
3881
|
this.status = status;
|
|
3878
3882
|
this.name = "DatadogError";
|
|
3879
3883
|
}
|
|
3884
|
+
status;
|
|
3880
3885
|
};
|
|
3881
3886
|
function sanitizeUrl4(u) {
|
|
3882
3887
|
try {
|
|
@@ -4240,6 +4245,7 @@ var PostHogError = class extends Error {
|
|
|
4240
4245
|
this.status = status;
|
|
4241
4246
|
this.name = "PostHogError";
|
|
4242
4247
|
}
|
|
4248
|
+
status;
|
|
4243
4249
|
};
|
|
4244
4250
|
function sanitizeUrl5(u) {
|
|
4245
4251
|
try {
|
|
@@ -4630,6 +4636,8 @@ var CloudflareError = class extends Error {
|
|
|
4630
4636
|
this.op = op;
|
|
4631
4637
|
this.name = "CloudflareError";
|
|
4632
4638
|
}
|
|
4639
|
+
status;
|
|
4640
|
+
op;
|
|
4633
4641
|
};
|
|
4634
4642
|
function isTransient6(error) {
|
|
4635
4643
|
if (error instanceof CloudflareError) {
|
|
@@ -10482,6 +10490,8 @@ var AudioProcessingError = class extends Error {
|
|
|
10482
10490
|
this.cause = cause;
|
|
10483
10491
|
this.name = "AudioProcessingError";
|
|
10484
10492
|
}
|
|
10493
|
+
phase;
|
|
10494
|
+
cause;
|
|
10485
10495
|
};
|
|
10486
10496
|
var AudioTranscriptionUnavailableError = class extends Error {
|
|
10487
10497
|
constructor() {
|
|
@@ -12964,12 +12974,12 @@ import fs10 from "fs";
|
|
|
12964
12974
|
// package.json
|
|
12965
12975
|
var package_default = {
|
|
12966
12976
|
name: "crumbtrail-node",
|
|
12967
|
-
version: "0.
|
|
12977
|
+
version: "0.2.0",
|
|
12968
12978
|
description: "Local Crumbtrail server, MCP evidence tools, Express middleware, and CLI for self-hosted debugging sessions.",
|
|
12969
12979
|
license: "MIT",
|
|
12970
12980
|
repository: {
|
|
12971
12981
|
type: "git",
|
|
12972
|
-
url: "https://github.com/
|
|
12982
|
+
url: "git+https://github.com/crumbtrail-dev/crumbtrail-js.git",
|
|
12973
12983
|
directory: "packages/node"
|
|
12974
12984
|
},
|
|
12975
12985
|
keywords: [
|
|
@@ -13022,6 +13032,10 @@ var package_default = {
|
|
|
13022
13032
|
"@types/node": "^25.5.0",
|
|
13023
13033
|
express: "^5.2.1",
|
|
13024
13034
|
typescript: "^5.7.0"
|
|
13035
|
+
},
|
|
13036
|
+
homepage: "https://github.com/crumbtrail-dev/crumbtrail-js/tree/main/packages/node#readme",
|
|
13037
|
+
bugs: {
|
|
13038
|
+
url: "https://github.com/crumbtrail-dev/crumbtrail-js/issues"
|
|
13025
13039
|
}
|
|
13026
13040
|
};
|
|
13027
13041
|
|
|
@@ -13299,6 +13313,10 @@ var RequestValidationError = class extends Error {
|
|
|
13299
13313
|
this.retryable = retryable;
|
|
13300
13314
|
this.name = "RequestValidationError";
|
|
13301
13315
|
}
|
|
13316
|
+
status;
|
|
13317
|
+
publicMessage;
|
|
13318
|
+
code;
|
|
13319
|
+
retryable;
|
|
13302
13320
|
};
|
|
13303
13321
|
function serveStatic(res, staticDir, urlPath) {
|
|
13304
13322
|
const resolved = resolveStaticPath(staticDir, urlPath);
|
|
@@ -14028,6 +14046,7 @@ var BoundedTraceSessionCache = class {
|
|
|
14028
14046
|
constructor(maxEntries) {
|
|
14029
14047
|
this.maxEntries = maxEntries;
|
|
14030
14048
|
}
|
|
14049
|
+
maxEntries;
|
|
14031
14050
|
entries = /* @__PURE__ */ new Map();
|
|
14032
14051
|
get(traceId) {
|
|
14033
14052
|
const existing = this.entries.get(traceId);
|
|
@@ -14726,6 +14745,20 @@ function createServer(config) {
|
|
|
14726
14745
|
});
|
|
14727
14746
|
server.on("close", () => sweeper.stop());
|
|
14728
14747
|
}
|
|
14748
|
+
if (config.ai?.enabled && config.ai.backfillOnStart) {
|
|
14749
|
+
const sessionDirs = sessions.listSummaries().map((summary) => sessions.getExistingSessionDir(summary.id)).filter((dir) => dir !== void 0);
|
|
14750
|
+
const timer = setTimeout(() => {
|
|
14751
|
+
void backfillAiDiagnoses(
|
|
14752
|
+
sessionDirs,
|
|
14753
|
+
config.ai
|
|
14754
|
+
).then(
|
|
14755
|
+
(result) => config.ai?.log?.(
|
|
14756
|
+
`Crumbtrail AI diagnosis backfill complete: ${JSON.stringify(result)}`
|
|
14757
|
+
)
|
|
14758
|
+
);
|
|
14759
|
+
}, 0);
|
|
14760
|
+
server.on("close", () => clearTimeout(timer));
|
|
14761
|
+
}
|
|
14729
14762
|
if (fastFinalizer) {
|
|
14730
14763
|
server.on("close", () => fastFinalizer.stop());
|
|
14731
14764
|
}
|
|
@@ -14769,6 +14802,7 @@ var FixContextError = class extends Error {
|
|
|
14769
14802
|
this.code = code;
|
|
14770
14803
|
this.name = "FixContextError";
|
|
14771
14804
|
}
|
|
14805
|
+
code;
|
|
14772
14806
|
};
|
|
14773
14807
|
function buildFixContext(sessionDirOrId, opts = {}) {
|
|
14774
14808
|
const sessionDir = resolveSessionDir(sessionDirOrId, opts);
|
|
@@ -15701,16 +15735,85 @@ function diffEnvironment(a, b, rules, disabled) {
|
|
|
15701
15735
|
addSuppressedRules(rules, beforeRules, afterRules, disabled);
|
|
15702
15736
|
return [];
|
|
15703
15737
|
}
|
|
15738
|
+
const envDelta = buildEnvDiff(a, b, disabled);
|
|
15704
15739
|
return [
|
|
15705
15740
|
{
|
|
15706
15741
|
plane: "env",
|
|
15707
15742
|
kind: "env.snapshot",
|
|
15708
15743
|
before: a,
|
|
15709
15744
|
after: b,
|
|
15710
|
-
brief:
|
|
15745
|
+
brief: envBrief(envDelta),
|
|
15746
|
+
envDelta
|
|
15711
15747
|
}
|
|
15712
15748
|
];
|
|
15713
15749
|
}
|
|
15750
|
+
function buildEnvDiff(a, b, disabled) {
|
|
15751
|
+
const delta = {
|
|
15752
|
+
flags: channelDelta(recordField(a.flags), recordField(b.flags), disabled),
|
|
15753
|
+
config: channelDelta(
|
|
15754
|
+
recordField(a.config),
|
|
15755
|
+
recordField(b.config),
|
|
15756
|
+
disabled
|
|
15757
|
+
)
|
|
15758
|
+
};
|
|
15759
|
+
const releaseBefore = stringOrUndefined(a.release);
|
|
15760
|
+
const releaseAfter = stringOrUndefined(b.release);
|
|
15761
|
+
if (releaseBefore !== releaseAfter) {
|
|
15762
|
+
delta.release = {
|
|
15763
|
+
...releaseBefore !== void 0 ? { before: releaseBefore } : {},
|
|
15764
|
+
...releaseAfter !== void 0 ? { after: releaseAfter } : {}
|
|
15765
|
+
};
|
|
15766
|
+
}
|
|
15767
|
+
const buildBefore = stringOrUndefined(a.build);
|
|
15768
|
+
const buildAfter = stringOrUndefined(b.build);
|
|
15769
|
+
if (buildBefore !== buildAfter) {
|
|
15770
|
+
delta.build = {
|
|
15771
|
+
...buildBefore !== void 0 ? { before: buildBefore } : {},
|
|
15772
|
+
...buildAfter !== void 0 ? { after: buildAfter } : {}
|
|
15773
|
+
};
|
|
15774
|
+
}
|
|
15775
|
+
return delta;
|
|
15776
|
+
}
|
|
15777
|
+
function recordField(value) {
|
|
15778
|
+
return isRecord14(value) ? value : {};
|
|
15779
|
+
}
|
|
15780
|
+
function channelDelta(a, b, disabled) {
|
|
15781
|
+
const added = [];
|
|
15782
|
+
const removed = [];
|
|
15783
|
+
const changed = [];
|
|
15784
|
+
for (const key of Object.keys(a).sort()) {
|
|
15785
|
+
if (!(key in b)) {
|
|
15786
|
+
removed.push({ key, before: a[key] });
|
|
15787
|
+
} else if (envValuesDiffer(a[key], b[key], disabled)) {
|
|
15788
|
+
changed.push({ key, before: a[key], after: b[key] });
|
|
15789
|
+
}
|
|
15790
|
+
}
|
|
15791
|
+
for (const key of Object.keys(b).sort()) {
|
|
15792
|
+
if (!(key in a)) added.push({ key, after: b[key] });
|
|
15793
|
+
}
|
|
15794
|
+
return { added, removed, changed };
|
|
15795
|
+
}
|
|
15796
|
+
function envValuesDiffer(before, after, disabled) {
|
|
15797
|
+
const beforeRules = /* @__PURE__ */ new Set();
|
|
15798
|
+
const afterRules = /* @__PURE__ */ new Set();
|
|
15799
|
+
if (canonicalize(before, beforeRules) !== canonicalize(after, afterRules))
|
|
15800
|
+
return true;
|
|
15801
|
+
const rawDiffers = stableStringify(before) !== stableStringify(after);
|
|
15802
|
+
return rawDiffers && hasDisabledRule(beforeRules, afterRules, disabled);
|
|
15803
|
+
}
|
|
15804
|
+
function envBrief(delta) {
|
|
15805
|
+
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);
|
|
15806
|
+
const parts = [];
|
|
15807
|
+
const flagCount = delta.flags.added.length + delta.flags.removed.length + delta.flags.changed.length;
|
|
15808
|
+
const configCount = delta.config.added.length + delta.config.removed.length + delta.config.changed.length;
|
|
15809
|
+
if (flagCount > 0) parts.push(`${flagCount} flag(s)`);
|
|
15810
|
+
if (configCount > 0) parts.push(`${configCount} config value(s)`);
|
|
15811
|
+
if (delta.release) parts.push("release");
|
|
15812
|
+
if (delta.build) parts.push("build");
|
|
15813
|
+
if (counts === 0 || parts.length === 0)
|
|
15814
|
+
return "feature flag, config, release, or build snapshot changed between sessions";
|
|
15815
|
+
return `environment delta between sessions: ${parts.join(", ")} changed`;
|
|
15816
|
+
}
|
|
15714
15817
|
function confidenceFor(divergences) {
|
|
15715
15818
|
if (divergences.length === 0) return "high";
|
|
15716
15819
|
if (divergences.some(
|
|
@@ -15737,6 +15840,9 @@ async function compareSessions(aDir, bDir, options = {}) {
|
|
|
15737
15840
|
...diffDb(a.dbWrites, b.dbWrites, rules, disabled),
|
|
15738
15841
|
...diffEnvironment(a.environment, b.environment, rules, disabled)
|
|
15739
15842
|
];
|
|
15843
|
+
const envDelta = divergences.find(
|
|
15844
|
+
(divergence) => divergence.kind === "env.snapshot"
|
|
15845
|
+
)?.envDelta;
|
|
15740
15846
|
return {
|
|
15741
15847
|
schemaVersion: SESSION_COMPARE_SCHEMA_VERSION,
|
|
15742
15848
|
verdict: divergences.length > 0 ? "regression" : "clean",
|
|
@@ -15759,20 +15865,185 @@ async function compareSessions(aDir, bDir, options = {}) {
|
|
|
15759
15865
|
divergences,
|
|
15760
15866
|
noise: { suppressedCount: rules.size, rules: [...rules].sort() },
|
|
15761
15867
|
evidence: divergencesToEvidence(divergences),
|
|
15762
|
-
intent: []
|
|
15868
|
+
intent: [],
|
|
15869
|
+
...envDelta ? { envDelta } : {}
|
|
15763
15870
|
};
|
|
15764
15871
|
}
|
|
15765
15872
|
|
|
15873
|
+
// src/compare/report.ts
|
|
15874
|
+
var PLANE_ORDER = ["flow", "network", "db", "env"];
|
|
15875
|
+
var PLANE_LABELS = {
|
|
15876
|
+
flow: "Flow steps",
|
|
15877
|
+
network: "Network calls",
|
|
15878
|
+
db: "Database rows",
|
|
15879
|
+
env: "Environment and flags"
|
|
15880
|
+
};
|
|
15881
|
+
function renderCompareReport(comparison) {
|
|
15882
|
+
const lines = [];
|
|
15883
|
+
const regressed = comparison.verdict === "regression";
|
|
15884
|
+
lines.push(
|
|
15885
|
+
`# Session comparison - ${formatSessionRef(comparison.a)} vs ${formatSessionRef(comparison.b)}`
|
|
15886
|
+
);
|
|
15887
|
+
lines.push("");
|
|
15888
|
+
lines.push(
|
|
15889
|
+
regressed ? `> **Verdict: REGRESSION** (confidence: ${comparison.confidence}) - recorded behavior changed between these sessions.` : `> **Verdict: CLEAN** (confidence: ${comparison.confidence}) - the recorded sessions show no behavioral divergence.`
|
|
15890
|
+
);
|
|
15891
|
+
lines.push("");
|
|
15892
|
+
lines.push(
|
|
15893
|
+
"A is the baseline session; B is the candidate release/build. Every row below is grounded in the recorded evidence of both sessions."
|
|
15894
|
+
);
|
|
15895
|
+
lines.push("");
|
|
15896
|
+
lines.push("## Aligned flow");
|
|
15897
|
+
lines.push("");
|
|
15898
|
+
const { matchedSteps, unmatchedA, unmatchedB } = comparison.alignment;
|
|
15899
|
+
lines.push(
|
|
15900
|
+
`${matchedSteps} step(s) matched by component identity \xB7 ${unmatchedA} only in A \xB7 ${unmatchedB} only in B`
|
|
15901
|
+
);
|
|
15902
|
+
lines.push("");
|
|
15903
|
+
if (comparison.divergences.length > 0) {
|
|
15904
|
+
lines.push("| Plane | Kind | Component | Divergence |");
|
|
15905
|
+
lines.push("|---|---|---|---|");
|
|
15906
|
+
for (const d of comparison.divergences) {
|
|
15907
|
+
const component = d.sig ? `\`${escapeCell(d.sig)}\`` : "\u2014";
|
|
15908
|
+
lines.push(
|
|
15909
|
+
`| **${d.plane}** | \`${d.kind}\` | ${component} | ${escapeCell(d.brief)} |`
|
|
15910
|
+
);
|
|
15911
|
+
}
|
|
15912
|
+
lines.push("");
|
|
15913
|
+
}
|
|
15914
|
+
for (const plane of PLANE_ORDER) {
|
|
15915
|
+
const planeDivergences = comparison.divergences.filter(
|
|
15916
|
+
(d) => d.plane === plane
|
|
15917
|
+
);
|
|
15918
|
+
if (planeDivergences.length === 0) continue;
|
|
15919
|
+
lines.push(`## ${PLANE_LABELS[plane]}`);
|
|
15920
|
+
lines.push("");
|
|
15921
|
+
for (const d of planeDivergences) {
|
|
15922
|
+
lines.push(`### \`${d.kind}\` - ${escapeCell(d.brief)}`);
|
|
15923
|
+
lines.push("");
|
|
15924
|
+
if (d.sig) lines.push(`- Component: \`${d.sig}\``);
|
|
15925
|
+
if (d.requestId) lines.push(`- Request: \`${d.requestId}\``);
|
|
15926
|
+
if (d.table)
|
|
15927
|
+
lines.push(
|
|
15928
|
+
`- Table: \`${d.table}\`${d.pk ? ` \xB7 pk \`${JSON.stringify(d.pk)}\`` : ""}`
|
|
15929
|
+
);
|
|
15930
|
+
lines.push("");
|
|
15931
|
+
if (d.envDelta) {
|
|
15932
|
+
lines.push(...renderEnvDelta(d.envDelta));
|
|
15933
|
+
} else {
|
|
15934
|
+
lines.push("```diff");
|
|
15935
|
+
lines.push(`- before: ${JSON.stringify(d.before)}`);
|
|
15936
|
+
lines.push(`+ after: ${JSON.stringify(d.after)}`);
|
|
15937
|
+
lines.push("```");
|
|
15938
|
+
}
|
|
15939
|
+
lines.push("");
|
|
15940
|
+
}
|
|
15941
|
+
}
|
|
15942
|
+
lines.push("---");
|
|
15943
|
+
lines.push("");
|
|
15944
|
+
lines.push(
|
|
15945
|
+
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.*"
|
|
15946
|
+
);
|
|
15947
|
+
lines.push("");
|
|
15948
|
+
return lines.join("\n");
|
|
15949
|
+
}
|
|
15950
|
+
function formatComparisonSummary(comparison) {
|
|
15951
|
+
const lines = [];
|
|
15952
|
+
lines.push(
|
|
15953
|
+
`crumbtrail-server compare - ${formatSessionRef(comparison.a)} vs ${formatSessionRef(comparison.b)}`
|
|
15954
|
+
);
|
|
15955
|
+
lines.push(` Schema: ${comparison.schemaVersion}`);
|
|
15956
|
+
lines.push(
|
|
15957
|
+
` Verdict: ${comparison.verdict.toUpperCase()} (confidence ${comparison.confidence})`
|
|
15958
|
+
);
|
|
15959
|
+
lines.push(
|
|
15960
|
+
` Alignment: ${comparison.alignment.matchedSteps} matched \xB7 ${comparison.alignment.unmatchedA} only in A \xB7 ${comparison.alignment.unmatchedB} only in B`
|
|
15961
|
+
);
|
|
15962
|
+
lines.push(` Divergences: ${comparison.divergences.length}`);
|
|
15963
|
+
for (const d of comparison.divergences.slice(0, 5)) {
|
|
15964
|
+
lines.push(` [${d.plane}] ${d.kind} - ${d.brief}`);
|
|
15965
|
+
}
|
|
15966
|
+
if (comparison.divergences.length > 5) {
|
|
15967
|
+
lines.push(
|
|
15968
|
+
` ... and ${comparison.divergences.length - 5} more (use --json or --report for the complete list)`
|
|
15969
|
+
);
|
|
15970
|
+
}
|
|
15971
|
+
const rules = comparison.noise.rules.length > 0 ? ` (${comparison.noise.rules.join(", ")})` : "";
|
|
15972
|
+
lines.push(
|
|
15973
|
+
` Noise: ${comparison.noise.suppressedCount} suppressed${rules}`
|
|
15974
|
+
);
|
|
15975
|
+
return lines.join("\n");
|
|
15976
|
+
}
|
|
15977
|
+
function escapeCell(text2) {
|
|
15978
|
+
return text2.replace(/\|/g, "\\|").replace(/\r?\n/g, " ");
|
|
15979
|
+
}
|
|
15980
|
+
function sessionRefLabel(ref) {
|
|
15981
|
+
return ref.release ?? ref.sessionId;
|
|
15982
|
+
}
|
|
15983
|
+
function comparisonTitle(comparison) {
|
|
15984
|
+
return `${sessionRefLabel(comparison.a)} vs ${sessionRefLabel(comparison.b)}`;
|
|
15985
|
+
}
|
|
15986
|
+
function renderEnvDelta(delta) {
|
|
15987
|
+
const lines = ["```diff"];
|
|
15988
|
+
renderEnvChannel(lines, "flags", delta.flags);
|
|
15989
|
+
renderEnvChannel(lines, "config", delta.config);
|
|
15990
|
+
if (delta.release) {
|
|
15991
|
+
lines.push(
|
|
15992
|
+
`~ release: ${JSON.stringify(delta.release.before ?? null)} -> ${JSON.stringify(delta.release.after ?? null)}`
|
|
15993
|
+
);
|
|
15994
|
+
}
|
|
15995
|
+
if (delta.build) {
|
|
15996
|
+
lines.push(
|
|
15997
|
+
`~ build: ${JSON.stringify(delta.build.before ?? null)} -> ${JSON.stringify(delta.build.after ?? null)}`
|
|
15998
|
+
);
|
|
15999
|
+
}
|
|
16000
|
+
if (lines.length === 1) lines.push(" (no field-level changes)");
|
|
16001
|
+
lines.push("```");
|
|
16002
|
+
return lines;
|
|
16003
|
+
}
|
|
16004
|
+
function renderEnvChannel(lines, channel, delta) {
|
|
16005
|
+
for (const change of delta.added) {
|
|
16006
|
+
lines.push(
|
|
16007
|
+
`+ ${channel}.${change.key}: ${JSON.stringify(change.after ?? null)}`
|
|
16008
|
+
);
|
|
16009
|
+
}
|
|
16010
|
+
for (const change of delta.removed) {
|
|
16011
|
+
lines.push(
|
|
16012
|
+
`- ${channel}.${change.key}: ${JSON.stringify(change.before ?? null)}`
|
|
16013
|
+
);
|
|
16014
|
+
}
|
|
16015
|
+
for (const change of delta.changed) {
|
|
16016
|
+
lines.push(
|
|
16017
|
+
`~ ${channel}.${change.key}: ${JSON.stringify(change.before ?? null)} -> ${JSON.stringify(change.after ?? null)}`
|
|
16018
|
+
);
|
|
16019
|
+
}
|
|
16020
|
+
}
|
|
16021
|
+
function formatSessionRef(ref) {
|
|
16022
|
+
if (ref.release) {
|
|
16023
|
+
const detail = [
|
|
16024
|
+
`session ${ref.sessionId}`,
|
|
16025
|
+
ref.build ? `build ${ref.build}` : void 0
|
|
16026
|
+
].filter(Boolean);
|
|
16027
|
+
return `${ref.release} (${detail.join(", ")})`;
|
|
16028
|
+
}
|
|
16029
|
+
return ref.build ? `${ref.sessionId} (build ${ref.build})` : ref.sessionId;
|
|
16030
|
+
}
|
|
16031
|
+
|
|
15766
16032
|
// src/compare/regression-context.ts
|
|
15767
16033
|
import fs13 from "fs";
|
|
15768
16034
|
import path13 from "path";
|
|
15769
16035
|
var REGRESSION_CONTEXT_SCHEMA_VERSION = "regression-context.v1";
|
|
15770
16036
|
function buildRegressionContext(comparison, bDir) {
|
|
15771
|
-
const requestIds = unique(
|
|
15772
|
-
|
|
16037
|
+
const requestIds = unique(
|
|
16038
|
+
comparison.divergences.map((d) => d.requestId).filter(isString)
|
|
16039
|
+
);
|
|
16040
|
+
const times = requestIds.flatMap(
|
|
16041
|
+
(requestId) => requestTimesFromIndex(bDir, requestId)
|
|
16042
|
+
);
|
|
15773
16043
|
const firstSig = comparison.divergences.map((d) => d.sig).find(isString);
|
|
15774
16044
|
return {
|
|
15775
16045
|
schemaVersion: REGRESSION_CONTEXT_SCHEMA_VERSION,
|
|
16046
|
+
title: comparisonTitle(comparison),
|
|
15776
16047
|
comparison,
|
|
15777
16048
|
divergent_interaction: firstSig ? interactionForSig(bDir, firstSig) : null,
|
|
15778
16049
|
causal_window: requestIds.length > 0 ? {
|
|
@@ -15790,6 +16061,7 @@ function buildRegressionContext(comparison, bDir) {
|
|
|
15790
16061
|
before: d.before,
|
|
15791
16062
|
after: d.after
|
|
15792
16063
|
})),
|
|
16064
|
+
env_delta: comparison.envDelta ?? null,
|
|
15793
16065
|
repro_hint: reproHint(bDir)
|
|
15794
16066
|
};
|
|
15795
16067
|
}
|
|
@@ -15834,7 +16106,8 @@ function collectRefTimes(value, times) {
|
|
|
15834
16106
|
const start = isRecord15(value.start) ? value.start : void 0;
|
|
15835
16107
|
const end = isRecord15(value.end) ? value.end : void 0;
|
|
15836
16108
|
for (const candidate of [ref, start, end]) {
|
|
15837
|
-
if (typeof candidate?.t === "number" && Number.isFinite(candidate.t))
|
|
16109
|
+
if (typeof candidate?.t === "number" && Number.isFinite(candidate.t))
|
|
16110
|
+
times.push(candidate.t);
|
|
15838
16111
|
}
|
|
15839
16112
|
}
|
|
15840
16113
|
function reproHint(sessionDir) {
|
|
@@ -16892,12 +17165,29 @@ var McpServer = class {
|
|
|
16892
17165
|
"sha"
|
|
16893
17166
|
]))
|
|
16894
17167
|
continue;
|
|
16895
|
-
sessions.push(meta);
|
|
17168
|
+
sessions.push(this.withReleaseBuild(meta));
|
|
16896
17169
|
} catch {
|
|
16897
17170
|
}
|
|
16898
17171
|
}
|
|
16899
17172
|
return textResult(sessions);
|
|
16900
17173
|
}
|
|
17174
|
+
/**
|
|
17175
|
+
* Surfaces release/build as first-class list-row fields regardless of which
|
|
17176
|
+
* alias the app used (release/releaseId/version, build/buildId/commit/sha), so
|
|
17177
|
+
* an agent can label and group sessions by release without re-reading each
|
|
17178
|
+
* meta. Additive: the raw meta keys are preserved.
|
|
17179
|
+
*/
|
|
17180
|
+
withReleaseBuild(meta) {
|
|
17181
|
+
const release = stringField5(meta.release ?? meta.releaseId ?? meta.version);
|
|
17182
|
+
const build = stringField5(
|
|
17183
|
+
meta.build ?? meta.buildId ?? meta.commit ?? meta.sha
|
|
17184
|
+
);
|
|
17185
|
+
return {
|
|
17186
|
+
...meta,
|
|
17187
|
+
...release !== void 0 ? { release } : {},
|
|
17188
|
+
...build !== void 0 ? { build } : {}
|
|
17189
|
+
};
|
|
17190
|
+
}
|
|
16901
17191
|
sessionMetadataMatches(meta, expected, keys) {
|
|
16902
17192
|
return keys.some((key) => meta[key] === expected);
|
|
16903
17193
|
}
|
|
@@ -18273,6 +18563,7 @@ var InspectError = class extends Error {
|
|
|
18273
18563
|
this.code = code;
|
|
18274
18564
|
this.name = "InspectError";
|
|
18275
18565
|
}
|
|
18566
|
+
code;
|
|
18276
18567
|
};
|
|
18277
18568
|
function inspectSession(sessionDirOrId, opts = {}) {
|
|
18278
18569
|
const sessionDir = resolveSessionDir2(sessionDirOrId, opts);
|
|
@@ -18400,117 +18691,6 @@ function isRecord17(value) {
|
|
|
18400
18691
|
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
18401
18692
|
}
|
|
18402
18693
|
|
|
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
18694
|
// src/version.ts
|
|
18515
18695
|
import fs15 from "fs";
|
|
18516
18696
|
import path16 from "path";
|
|
@@ -18895,6 +19075,10 @@ export {
|
|
|
18895
19075
|
SESSION_COMPARE_SCHEMA_VERSION,
|
|
18896
19076
|
CompareError,
|
|
18897
19077
|
compareSessions,
|
|
19078
|
+
renderCompareReport,
|
|
19079
|
+
formatComparisonSummary,
|
|
19080
|
+
sessionRefLabel,
|
|
19081
|
+
comparisonTitle,
|
|
18898
19082
|
REGRESSION_CONTEXT_SCHEMA_VERSION,
|
|
18899
19083
|
buildRegressionContext,
|
|
18900
19084
|
McpServer,
|
|
@@ -18914,7 +19098,5 @@ export {
|
|
|
18914
19098
|
InspectError,
|
|
18915
19099
|
inspectSession,
|
|
18916
19100
|
formatInspection,
|
|
18917
|
-
renderCompareReport,
|
|
18918
|
-
formatComparisonSummary,
|
|
18919
19101
|
readPackageVersion
|
|
18920
19102
|
};
|