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
package/dist/cli.cjs
CHANGED
|
@@ -215956,6 +215956,7 @@ var SentryError = class extends Error {
|
|
|
215956
215956
|
this.status = status;
|
|
215957
215957
|
this.name = "SentryError";
|
|
215958
215958
|
}
|
|
215959
|
+
status;
|
|
215959
215960
|
};
|
|
215960
215961
|
function sanitizeUrl2(u) {
|
|
215961
215962
|
try {
|
|
@@ -216369,6 +216370,8 @@ var CloudWatchError = class extends Error {
|
|
|
216369
216370
|
this.target = target;
|
|
216370
216371
|
this.name = "CloudWatchError";
|
|
216371
216372
|
}
|
|
216373
|
+
status;
|
|
216374
|
+
target;
|
|
216372
216375
|
};
|
|
216373
216376
|
function isTransient2(error) {
|
|
216374
216377
|
if (error instanceof CloudWatchError) {
|
|
@@ -216781,6 +216784,7 @@ var SplunkError = class extends Error {
|
|
|
216781
216784
|
this.status = status;
|
|
216782
216785
|
this.name = "SplunkError";
|
|
216783
216786
|
}
|
|
216787
|
+
status;
|
|
216784
216788
|
};
|
|
216785
216789
|
function sanitizeUrl3(u) {
|
|
216786
216790
|
try {
|
|
@@ -217131,6 +217135,7 @@ var DatadogError = class extends Error {
|
|
|
217131
217135
|
this.status = status;
|
|
217132
217136
|
this.name = "DatadogError";
|
|
217133
217137
|
}
|
|
217138
|
+
status;
|
|
217134
217139
|
};
|
|
217135
217140
|
function sanitizeUrl4(u) {
|
|
217136
217141
|
try {
|
|
@@ -217494,6 +217499,7 @@ var PostHogError = class extends Error {
|
|
|
217494
217499
|
this.status = status;
|
|
217495
217500
|
this.name = "PostHogError";
|
|
217496
217501
|
}
|
|
217502
|
+
status;
|
|
217497
217503
|
};
|
|
217498
217504
|
function sanitizeUrl5(u) {
|
|
217499
217505
|
try {
|
|
@@ -217884,6 +217890,8 @@ var CloudflareError = class extends Error {
|
|
|
217884
217890
|
this.op = op;
|
|
217885
217891
|
this.name = "CloudflareError";
|
|
217886
217892
|
}
|
|
217893
|
+
status;
|
|
217894
|
+
op;
|
|
217887
217895
|
};
|
|
217888
217896
|
function isTransient6(error) {
|
|
217889
217897
|
if (error instanceof CloudflareError) {
|
|
@@ -223902,6 +223910,8 @@ var AudioProcessingError = class extends Error {
|
|
|
223902
223910
|
this.cause = cause;
|
|
223903
223911
|
this.name = "AudioProcessingError";
|
|
223904
223912
|
}
|
|
223913
|
+
phase;
|
|
223914
|
+
cause;
|
|
223905
223915
|
};
|
|
223906
223916
|
var AudioTranscriptionUnavailableError = class extends Error {
|
|
223907
223917
|
constructor() {
|
|
@@ -225652,12 +225662,12 @@ var import_node_fs10 = __toESM(require("fs"), 1);
|
|
|
225652
225662
|
// package.json
|
|
225653
225663
|
var package_default = {
|
|
225654
225664
|
name: "crumbtrail-node",
|
|
225655
|
-
version: "0.
|
|
225665
|
+
version: "0.2.0",
|
|
225656
225666
|
description: "Local Crumbtrail server, MCP evidence tools, Express middleware, and CLI for self-hosted debugging sessions.",
|
|
225657
225667
|
license: "MIT",
|
|
225658
225668
|
repository: {
|
|
225659
225669
|
type: "git",
|
|
225660
|
-
url: "https://github.com/
|
|
225670
|
+
url: "git+https://github.com/crumbtrail-dev/crumbtrail-js.git",
|
|
225661
225671
|
directory: "packages/node"
|
|
225662
225672
|
},
|
|
225663
225673
|
keywords: [
|
|
@@ -225710,6 +225720,10 @@ var package_default = {
|
|
|
225710
225720
|
"@types/node": "^25.5.0",
|
|
225711
225721
|
express: "^5.2.1",
|
|
225712
225722
|
typescript: "^5.7.0"
|
|
225723
|
+
},
|
|
225724
|
+
homepage: "https://github.com/crumbtrail-dev/crumbtrail-js/tree/main/packages/node#readme",
|
|
225725
|
+
bugs: {
|
|
225726
|
+
url: "https://github.com/crumbtrail-dev/crumbtrail-js/issues"
|
|
225713
225727
|
}
|
|
225714
225728
|
};
|
|
225715
225729
|
|
|
@@ -226360,6 +226374,10 @@ var RequestValidationError = class extends Error {
|
|
|
226360
226374
|
this.retryable = retryable;
|
|
226361
226375
|
this.name = "RequestValidationError";
|
|
226362
226376
|
}
|
|
226377
|
+
status;
|
|
226378
|
+
publicMessage;
|
|
226379
|
+
code;
|
|
226380
|
+
retryable;
|
|
226363
226381
|
};
|
|
226364
226382
|
function serveStatic(res, staticDir, urlPath) {
|
|
226365
226383
|
const resolved = resolveStaticPath(staticDir, urlPath);
|
|
@@ -227089,6 +227107,7 @@ var BoundedTraceSessionCache = class {
|
|
|
227089
227107
|
constructor(maxEntries) {
|
|
227090
227108
|
this.maxEntries = maxEntries;
|
|
227091
227109
|
}
|
|
227110
|
+
maxEntries;
|
|
227092
227111
|
entries = /* @__PURE__ */ new Map();
|
|
227093
227112
|
get(traceId) {
|
|
227094
227113
|
const existing = this.entries.get(traceId);
|
|
@@ -227787,6 +227806,20 @@ function createServer(config) {
|
|
|
227787
227806
|
});
|
|
227788
227807
|
server2.on("close", () => sweeper.stop());
|
|
227789
227808
|
}
|
|
227809
|
+
if (config.ai?.enabled && config.ai.backfillOnStart) {
|
|
227810
|
+
const sessionDirs = sessions.listSummaries().map((summary) => sessions.getExistingSessionDir(summary.id)).filter((dir) => dir !== void 0);
|
|
227811
|
+
const timer = setTimeout(() => {
|
|
227812
|
+
void backfillAiDiagnoses(
|
|
227813
|
+
sessionDirs,
|
|
227814
|
+
config.ai
|
|
227815
|
+
).then(
|
|
227816
|
+
(result) => config.ai?.log?.(
|
|
227817
|
+
`Crumbtrail AI diagnosis backfill complete: ${JSON.stringify(result)}`
|
|
227818
|
+
)
|
|
227819
|
+
);
|
|
227820
|
+
}, 0);
|
|
227821
|
+
server2.on("close", () => clearTimeout(timer));
|
|
227822
|
+
}
|
|
227790
227823
|
if (fastFinalizer) {
|
|
227791
227824
|
server2.on("close", () => fastFinalizer.stop());
|
|
227792
227825
|
}
|
|
@@ -227836,6 +227869,7 @@ var FixContextError = class extends Error {
|
|
|
227836
227869
|
this.code = code;
|
|
227837
227870
|
this.name = "FixContextError";
|
|
227838
227871
|
}
|
|
227872
|
+
code;
|
|
227839
227873
|
};
|
|
227840
227874
|
function buildFixContext(sessionDirOrId, opts = {}) {
|
|
227841
227875
|
const sessionDir = resolveSessionDir(sessionDirOrId, opts);
|
|
@@ -228895,16 +228929,85 @@ function diffEnvironment(a, b, rules, disabled) {
|
|
|
228895
228929
|
addSuppressedRules(rules, beforeRules, afterRules, disabled);
|
|
228896
228930
|
return [];
|
|
228897
228931
|
}
|
|
228932
|
+
const envDelta = buildEnvDiff(a, b, disabled);
|
|
228898
228933
|
return [
|
|
228899
228934
|
{
|
|
228900
228935
|
plane: "env",
|
|
228901
228936
|
kind: "env.snapshot",
|
|
228902
228937
|
before: a,
|
|
228903
228938
|
after: b,
|
|
228904
|
-
brief:
|
|
228939
|
+
brief: envBrief(envDelta),
|
|
228940
|
+
envDelta
|
|
228905
228941
|
}
|
|
228906
228942
|
];
|
|
228907
228943
|
}
|
|
228944
|
+
function buildEnvDiff(a, b, disabled) {
|
|
228945
|
+
const delta = {
|
|
228946
|
+
flags: channelDelta(recordField(a.flags), recordField(b.flags), disabled),
|
|
228947
|
+
config: channelDelta(
|
|
228948
|
+
recordField(a.config),
|
|
228949
|
+
recordField(b.config),
|
|
228950
|
+
disabled
|
|
228951
|
+
)
|
|
228952
|
+
};
|
|
228953
|
+
const releaseBefore = stringOrUndefined(a.release);
|
|
228954
|
+
const releaseAfter = stringOrUndefined(b.release);
|
|
228955
|
+
if (releaseBefore !== releaseAfter) {
|
|
228956
|
+
delta.release = {
|
|
228957
|
+
...releaseBefore !== void 0 ? { before: releaseBefore } : {},
|
|
228958
|
+
...releaseAfter !== void 0 ? { after: releaseAfter } : {}
|
|
228959
|
+
};
|
|
228960
|
+
}
|
|
228961
|
+
const buildBefore = stringOrUndefined(a.build);
|
|
228962
|
+
const buildAfter = stringOrUndefined(b.build);
|
|
228963
|
+
if (buildBefore !== buildAfter) {
|
|
228964
|
+
delta.build = {
|
|
228965
|
+
...buildBefore !== void 0 ? { before: buildBefore } : {},
|
|
228966
|
+
...buildAfter !== void 0 ? { after: buildAfter } : {}
|
|
228967
|
+
};
|
|
228968
|
+
}
|
|
228969
|
+
return delta;
|
|
228970
|
+
}
|
|
228971
|
+
function recordField(value) {
|
|
228972
|
+
return isRecord14(value) ? value : {};
|
|
228973
|
+
}
|
|
228974
|
+
function channelDelta(a, b, disabled) {
|
|
228975
|
+
const added = [];
|
|
228976
|
+
const removed = [];
|
|
228977
|
+
const changed = [];
|
|
228978
|
+
for (const key of Object.keys(a).sort()) {
|
|
228979
|
+
if (!(key in b)) {
|
|
228980
|
+
removed.push({ key, before: a[key] });
|
|
228981
|
+
} else if (envValuesDiffer(a[key], b[key], disabled)) {
|
|
228982
|
+
changed.push({ key, before: a[key], after: b[key] });
|
|
228983
|
+
}
|
|
228984
|
+
}
|
|
228985
|
+
for (const key of Object.keys(b).sort()) {
|
|
228986
|
+
if (!(key in a)) added.push({ key, after: b[key] });
|
|
228987
|
+
}
|
|
228988
|
+
return { added, removed, changed };
|
|
228989
|
+
}
|
|
228990
|
+
function envValuesDiffer(before, after, disabled) {
|
|
228991
|
+
const beforeRules = /* @__PURE__ */ new Set();
|
|
228992
|
+
const afterRules = /* @__PURE__ */ new Set();
|
|
228993
|
+
if (canonicalize(before, beforeRules) !== canonicalize(after, afterRules))
|
|
228994
|
+
return true;
|
|
228995
|
+
const rawDiffers = stableStringify(before) !== stableStringify(after);
|
|
228996
|
+
return rawDiffers && hasDisabledRule(beforeRules, afterRules, disabled);
|
|
228997
|
+
}
|
|
228998
|
+
function envBrief(delta) {
|
|
228999
|
+
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);
|
|
229000
|
+
const parts = [];
|
|
229001
|
+
const flagCount = delta.flags.added.length + delta.flags.removed.length + delta.flags.changed.length;
|
|
229002
|
+
const configCount = delta.config.added.length + delta.config.removed.length + delta.config.changed.length;
|
|
229003
|
+
if (flagCount > 0) parts.push(`${flagCount} flag(s)`);
|
|
229004
|
+
if (configCount > 0) parts.push(`${configCount} config value(s)`);
|
|
229005
|
+
if (delta.release) parts.push("release");
|
|
229006
|
+
if (delta.build) parts.push("build");
|
|
229007
|
+
if (counts === 0 || parts.length === 0)
|
|
229008
|
+
return "feature flag, config, release, or build snapshot changed between sessions";
|
|
229009
|
+
return `environment delta between sessions: ${parts.join(", ")} changed`;
|
|
229010
|
+
}
|
|
228908
229011
|
function confidenceFor(divergences) {
|
|
228909
229012
|
if (divergences.length === 0) return "high";
|
|
228910
229013
|
if (divergences.some(
|
|
@@ -228931,6 +229034,9 @@ async function compareSessions(aDir, bDir, options = {}) {
|
|
|
228931
229034
|
...diffDb(a.dbWrites, b.dbWrites, rules, disabled),
|
|
228932
229035
|
...diffEnvironment(a.environment, b.environment, rules, disabled)
|
|
228933
229036
|
];
|
|
229037
|
+
const envDelta = divergences.find(
|
|
229038
|
+
(divergence) => divergence.kind === "env.snapshot"
|
|
229039
|
+
)?.envDelta;
|
|
228934
229040
|
return {
|
|
228935
229041
|
schemaVersion: SESSION_COMPARE_SCHEMA_VERSION,
|
|
228936
229042
|
verdict: divergences.length > 0 ? "regression" : "clean",
|
|
@@ -228953,20 +229059,187 @@ async function compareSessions(aDir, bDir, options = {}) {
|
|
|
228953
229059
|
divergences,
|
|
228954
229060
|
noise: { suppressedCount: rules.size, rules: [...rules].sort() },
|
|
228955
229061
|
evidence: divergencesToEvidence(divergences),
|
|
228956
|
-
intent: []
|
|
229062
|
+
intent: [],
|
|
229063
|
+
...envDelta ? { envDelta } : {}
|
|
228957
229064
|
};
|
|
228958
229065
|
}
|
|
228959
229066
|
|
|
228960
229067
|
// src/compare/regression-context.ts
|
|
228961
229068
|
var import_node_fs13 = __toESM(require("fs"), 1);
|
|
228962
229069
|
var import_node_path13 = __toESM(require("path"), 1);
|
|
229070
|
+
|
|
229071
|
+
// src/compare/report.ts
|
|
229072
|
+
var PLANE_ORDER = ["flow", "network", "db", "env"];
|
|
229073
|
+
var PLANE_LABELS = {
|
|
229074
|
+
flow: "Flow steps",
|
|
229075
|
+
network: "Network calls",
|
|
229076
|
+
db: "Database rows",
|
|
229077
|
+
env: "Environment and flags"
|
|
229078
|
+
};
|
|
229079
|
+
function renderCompareReport(comparison) {
|
|
229080
|
+
const lines = [];
|
|
229081
|
+
const regressed = comparison.verdict === "regression";
|
|
229082
|
+
lines.push(
|
|
229083
|
+
`# Session comparison - ${formatSessionRef(comparison.a)} vs ${formatSessionRef(comparison.b)}`
|
|
229084
|
+
);
|
|
229085
|
+
lines.push("");
|
|
229086
|
+
lines.push(
|
|
229087
|
+
regressed ? `> **Verdict: REGRESSION** (confidence: ${comparison.confidence}) - recorded behavior changed between these sessions.` : `> **Verdict: CLEAN** (confidence: ${comparison.confidence}) - the recorded sessions show no behavioral divergence.`
|
|
229088
|
+
);
|
|
229089
|
+
lines.push("");
|
|
229090
|
+
lines.push(
|
|
229091
|
+
"A is the baseline session; B is the candidate release/build. Every row below is grounded in the recorded evidence of both sessions."
|
|
229092
|
+
);
|
|
229093
|
+
lines.push("");
|
|
229094
|
+
lines.push("## Aligned flow");
|
|
229095
|
+
lines.push("");
|
|
229096
|
+
const { matchedSteps, unmatchedA, unmatchedB } = comparison.alignment;
|
|
229097
|
+
lines.push(
|
|
229098
|
+
`${matchedSteps} step(s) matched by component identity \xB7 ${unmatchedA} only in A \xB7 ${unmatchedB} only in B`
|
|
229099
|
+
);
|
|
229100
|
+
lines.push("");
|
|
229101
|
+
if (comparison.divergences.length > 0) {
|
|
229102
|
+
lines.push("| Plane | Kind | Component | Divergence |");
|
|
229103
|
+
lines.push("|---|---|---|---|");
|
|
229104
|
+
for (const d of comparison.divergences) {
|
|
229105
|
+
const component = d.sig ? `\`${escapeCell(d.sig)}\`` : "\u2014";
|
|
229106
|
+
lines.push(
|
|
229107
|
+
`| **${d.plane}** | \`${d.kind}\` | ${component} | ${escapeCell(d.brief)} |`
|
|
229108
|
+
);
|
|
229109
|
+
}
|
|
229110
|
+
lines.push("");
|
|
229111
|
+
}
|
|
229112
|
+
for (const plane of PLANE_ORDER) {
|
|
229113
|
+
const planeDivergences = comparison.divergences.filter(
|
|
229114
|
+
(d) => d.plane === plane
|
|
229115
|
+
);
|
|
229116
|
+
if (planeDivergences.length === 0) continue;
|
|
229117
|
+
lines.push(`## ${PLANE_LABELS[plane]}`);
|
|
229118
|
+
lines.push("");
|
|
229119
|
+
for (const d of planeDivergences) {
|
|
229120
|
+
lines.push(`### \`${d.kind}\` - ${escapeCell(d.brief)}`);
|
|
229121
|
+
lines.push("");
|
|
229122
|
+
if (d.sig) lines.push(`- Component: \`${d.sig}\``);
|
|
229123
|
+
if (d.requestId) lines.push(`- Request: \`${d.requestId}\``);
|
|
229124
|
+
if (d.table)
|
|
229125
|
+
lines.push(
|
|
229126
|
+
`- Table: \`${d.table}\`${d.pk ? ` \xB7 pk \`${JSON.stringify(d.pk)}\`` : ""}`
|
|
229127
|
+
);
|
|
229128
|
+
lines.push("");
|
|
229129
|
+
if (d.envDelta) {
|
|
229130
|
+
lines.push(...renderEnvDelta(d.envDelta));
|
|
229131
|
+
} else {
|
|
229132
|
+
lines.push("```diff");
|
|
229133
|
+
lines.push(`- before: ${JSON.stringify(d.before)}`);
|
|
229134
|
+
lines.push(`+ after: ${JSON.stringify(d.after)}`);
|
|
229135
|
+
lines.push("```");
|
|
229136
|
+
}
|
|
229137
|
+
lines.push("");
|
|
229138
|
+
}
|
|
229139
|
+
}
|
|
229140
|
+
lines.push("---");
|
|
229141
|
+
lines.push("");
|
|
229142
|
+
lines.push(
|
|
229143
|
+
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.*"
|
|
229144
|
+
);
|
|
229145
|
+
lines.push("");
|
|
229146
|
+
return lines.join("\n");
|
|
229147
|
+
}
|
|
229148
|
+
function formatComparisonSummary(comparison) {
|
|
229149
|
+
const lines = [];
|
|
229150
|
+
lines.push(
|
|
229151
|
+
`crumbtrail-server compare - ${formatSessionRef(comparison.a)} vs ${formatSessionRef(comparison.b)}`
|
|
229152
|
+
);
|
|
229153
|
+
lines.push(` Schema: ${comparison.schemaVersion}`);
|
|
229154
|
+
lines.push(
|
|
229155
|
+
` Verdict: ${comparison.verdict.toUpperCase()} (confidence ${comparison.confidence})`
|
|
229156
|
+
);
|
|
229157
|
+
lines.push(
|
|
229158
|
+
` Alignment: ${comparison.alignment.matchedSteps} matched \xB7 ${comparison.alignment.unmatchedA} only in A \xB7 ${comparison.alignment.unmatchedB} only in B`
|
|
229159
|
+
);
|
|
229160
|
+
lines.push(` Divergences: ${comparison.divergences.length}`);
|
|
229161
|
+
for (const d of comparison.divergences.slice(0, 5)) {
|
|
229162
|
+
lines.push(` [${d.plane}] ${d.kind} - ${d.brief}`);
|
|
229163
|
+
}
|
|
229164
|
+
if (comparison.divergences.length > 5) {
|
|
229165
|
+
lines.push(
|
|
229166
|
+
` ... and ${comparison.divergences.length - 5} more (use --json or --report for the complete list)`
|
|
229167
|
+
);
|
|
229168
|
+
}
|
|
229169
|
+
const rules = comparison.noise.rules.length > 0 ? ` (${comparison.noise.rules.join(", ")})` : "";
|
|
229170
|
+
lines.push(
|
|
229171
|
+
` Noise: ${comparison.noise.suppressedCount} suppressed${rules}`
|
|
229172
|
+
);
|
|
229173
|
+
return lines.join("\n");
|
|
229174
|
+
}
|
|
229175
|
+
function escapeCell(text2) {
|
|
229176
|
+
return text2.replace(/\|/g, "\\|").replace(/\r?\n/g, " ");
|
|
229177
|
+
}
|
|
229178
|
+
function sessionRefLabel(ref) {
|
|
229179
|
+
return ref.release ?? ref.sessionId;
|
|
229180
|
+
}
|
|
229181
|
+
function comparisonTitle(comparison) {
|
|
229182
|
+
return `${sessionRefLabel(comparison.a)} vs ${sessionRefLabel(comparison.b)}`;
|
|
229183
|
+
}
|
|
229184
|
+
function renderEnvDelta(delta) {
|
|
229185
|
+
const lines = ["```diff"];
|
|
229186
|
+
renderEnvChannel(lines, "flags", delta.flags);
|
|
229187
|
+
renderEnvChannel(lines, "config", delta.config);
|
|
229188
|
+
if (delta.release) {
|
|
229189
|
+
lines.push(
|
|
229190
|
+
`~ release: ${JSON.stringify(delta.release.before ?? null)} -> ${JSON.stringify(delta.release.after ?? null)}`
|
|
229191
|
+
);
|
|
229192
|
+
}
|
|
229193
|
+
if (delta.build) {
|
|
229194
|
+
lines.push(
|
|
229195
|
+
`~ build: ${JSON.stringify(delta.build.before ?? null)} -> ${JSON.stringify(delta.build.after ?? null)}`
|
|
229196
|
+
);
|
|
229197
|
+
}
|
|
229198
|
+
if (lines.length === 1) lines.push(" (no field-level changes)");
|
|
229199
|
+
lines.push("```");
|
|
229200
|
+
return lines;
|
|
229201
|
+
}
|
|
229202
|
+
function renderEnvChannel(lines, channel, delta) {
|
|
229203
|
+
for (const change of delta.added) {
|
|
229204
|
+
lines.push(
|
|
229205
|
+
`+ ${channel}.${change.key}: ${JSON.stringify(change.after ?? null)}`
|
|
229206
|
+
);
|
|
229207
|
+
}
|
|
229208
|
+
for (const change of delta.removed) {
|
|
229209
|
+
lines.push(
|
|
229210
|
+
`- ${channel}.${change.key}: ${JSON.stringify(change.before ?? null)}`
|
|
229211
|
+
);
|
|
229212
|
+
}
|
|
229213
|
+
for (const change of delta.changed) {
|
|
229214
|
+
lines.push(
|
|
229215
|
+
`~ ${channel}.${change.key}: ${JSON.stringify(change.before ?? null)} -> ${JSON.stringify(change.after ?? null)}`
|
|
229216
|
+
);
|
|
229217
|
+
}
|
|
229218
|
+
}
|
|
229219
|
+
function formatSessionRef(ref) {
|
|
229220
|
+
if (ref.release) {
|
|
229221
|
+
const detail = [
|
|
229222
|
+
`session ${ref.sessionId}`,
|
|
229223
|
+
ref.build ? `build ${ref.build}` : void 0
|
|
229224
|
+
].filter(Boolean);
|
|
229225
|
+
return `${ref.release} (${detail.join(", ")})`;
|
|
229226
|
+
}
|
|
229227
|
+
return ref.build ? `${ref.sessionId} (build ${ref.build})` : ref.sessionId;
|
|
229228
|
+
}
|
|
229229
|
+
|
|
229230
|
+
// src/compare/regression-context.ts
|
|
228963
229231
|
var REGRESSION_CONTEXT_SCHEMA_VERSION = "regression-context.v1";
|
|
228964
229232
|
function buildRegressionContext(comparison, bDir) {
|
|
228965
|
-
const requestIds = unique(
|
|
228966
|
-
|
|
229233
|
+
const requestIds = unique(
|
|
229234
|
+
comparison.divergences.map((d) => d.requestId).filter(isString)
|
|
229235
|
+
);
|
|
229236
|
+
const times = requestIds.flatMap(
|
|
229237
|
+
(requestId) => requestTimesFromIndex(bDir, requestId)
|
|
229238
|
+
);
|
|
228967
229239
|
const firstSig = comparison.divergences.map((d) => d.sig).find(isString);
|
|
228968
229240
|
return {
|
|
228969
229241
|
schemaVersion: REGRESSION_CONTEXT_SCHEMA_VERSION,
|
|
229242
|
+
title: comparisonTitle(comparison),
|
|
228970
229243
|
comparison,
|
|
228971
229244
|
divergent_interaction: firstSig ? interactionForSig(bDir, firstSig) : null,
|
|
228972
229245
|
causal_window: requestIds.length > 0 ? {
|
|
@@ -228984,6 +229257,7 @@ function buildRegressionContext(comparison, bDir) {
|
|
|
228984
229257
|
before: d.before,
|
|
228985
229258
|
after: d.after
|
|
228986
229259
|
})),
|
|
229260
|
+
env_delta: comparison.envDelta ?? null,
|
|
228987
229261
|
repro_hint: reproHint(bDir)
|
|
228988
229262
|
};
|
|
228989
229263
|
}
|
|
@@ -229028,7 +229302,8 @@ function collectRefTimes(value, times) {
|
|
|
229028
229302
|
const start = isRecord15(value.start) ? value.start : void 0;
|
|
229029
229303
|
const end = isRecord15(value.end) ? value.end : void 0;
|
|
229030
229304
|
for (const candidate of [ref, start, end]) {
|
|
229031
|
-
if (typeof candidate?.t === "number" && Number.isFinite(candidate.t))
|
|
229305
|
+
if (typeof candidate?.t === "number" && Number.isFinite(candidate.t))
|
|
229306
|
+
times.push(candidate.t);
|
|
229032
229307
|
}
|
|
229033
229308
|
}
|
|
229034
229309
|
function reproHint(sessionDir) {
|
|
@@ -229953,12 +230228,29 @@ var McpServer = class {
|
|
|
229953
230228
|
"sha"
|
|
229954
230229
|
]))
|
|
229955
230230
|
continue;
|
|
229956
|
-
sessions.push(meta);
|
|
230231
|
+
sessions.push(this.withReleaseBuild(meta));
|
|
229957
230232
|
} catch {
|
|
229958
230233
|
}
|
|
229959
230234
|
}
|
|
229960
230235
|
return textResult(sessions);
|
|
229961
230236
|
}
|
|
230237
|
+
/**
|
|
230238
|
+
* Surfaces release/build as first-class list-row fields regardless of which
|
|
230239
|
+
* alias the app used (release/releaseId/version, build/buildId/commit/sha), so
|
|
230240
|
+
* an agent can label and group sessions by release without re-reading each
|
|
230241
|
+
* meta. Additive: the raw meta keys are preserved.
|
|
230242
|
+
*/
|
|
230243
|
+
withReleaseBuild(meta) {
|
|
230244
|
+
const release = stringField5(meta.release ?? meta.releaseId ?? meta.version);
|
|
230245
|
+
const build = stringField5(
|
|
230246
|
+
meta.build ?? meta.buildId ?? meta.commit ?? meta.sha
|
|
230247
|
+
);
|
|
230248
|
+
return {
|
|
230249
|
+
...meta,
|
|
230250
|
+
...release !== void 0 ? { release } : {},
|
|
230251
|
+
...build !== void 0 ? { build } : {}
|
|
230252
|
+
};
|
|
230253
|
+
}
|
|
229962
230254
|
sessionMetadataMatches(meta, expected, keys) {
|
|
229963
230255
|
return keys.some((key) => meta[key] === expected);
|
|
229964
230256
|
}
|
|
@@ -231185,6 +231477,7 @@ var CliConfigError = class extends Error {
|
|
|
231185
231477
|
this.code = code;
|
|
231186
231478
|
this.name = "CliConfigError";
|
|
231187
231479
|
}
|
|
231480
|
+
code;
|
|
231188
231481
|
};
|
|
231189
231482
|
function defaultCliConfig() {
|
|
231190
231483
|
return {
|
|
@@ -232745,6 +233038,7 @@ var InspectError = class extends Error {
|
|
|
232745
233038
|
this.code = code;
|
|
232746
233039
|
this.name = "InspectError";
|
|
232747
233040
|
}
|
|
233041
|
+
code;
|
|
232748
233042
|
};
|
|
232749
233043
|
function inspectSession(sessionDirOrId, opts = {}) {
|
|
232750
233044
|
const sessionDir = resolveSessionDir2(sessionDirOrId, opts);
|
|
@@ -232916,119 +233210,6 @@ function resolveTarget2(target) {
|
|
|
232916
233210
|
// src/run-compare.ts
|
|
232917
233211
|
var import_node_fs20 = __toESM(require("fs"), 1);
|
|
232918
233212
|
var import_node_path24 = __toESM(require("path"), 1);
|
|
232919
|
-
|
|
232920
|
-
// src/compare/report.ts
|
|
232921
|
-
var PLANE_ORDER = ["flow", "network", "db", "env"];
|
|
232922
|
-
var PLANE_LABELS = {
|
|
232923
|
-
flow: "Flow steps",
|
|
232924
|
-
network: "Network calls",
|
|
232925
|
-
db: "Database rows",
|
|
232926
|
-
env: "Environment and flags"
|
|
232927
|
-
};
|
|
232928
|
-
function renderCompareReport(comparison) {
|
|
232929
|
-
const lines = [];
|
|
232930
|
-
const regressed = comparison.verdict === "regression";
|
|
232931
|
-
lines.push(
|
|
232932
|
-
`# Session comparison - ${formatSessionRef(comparison.a)} vs ${formatSessionRef(comparison.b)}`
|
|
232933
|
-
);
|
|
232934
|
-
lines.push("");
|
|
232935
|
-
lines.push(
|
|
232936
|
-
regressed ? `> **Verdict: REGRESSION** (confidence: ${comparison.confidence}) - recorded behavior changed between these sessions.` : `> **Verdict: CLEAN** (confidence: ${comparison.confidence}) - the recorded sessions show no behavioral divergence.`
|
|
232937
|
-
);
|
|
232938
|
-
lines.push("");
|
|
232939
|
-
lines.push(
|
|
232940
|
-
"A is the baseline session; B is the candidate release/build. Every row below is grounded in the recorded evidence of both sessions."
|
|
232941
|
-
);
|
|
232942
|
-
lines.push("");
|
|
232943
|
-
lines.push("## Aligned flow");
|
|
232944
|
-
lines.push("");
|
|
232945
|
-
const { matchedSteps, unmatchedA, unmatchedB } = comparison.alignment;
|
|
232946
|
-
lines.push(
|
|
232947
|
-
`${matchedSteps} step(s) matched by component identity \xB7 ${unmatchedA} only in A \xB7 ${unmatchedB} only in B`
|
|
232948
|
-
);
|
|
232949
|
-
lines.push("");
|
|
232950
|
-
if (comparison.divergences.length > 0) {
|
|
232951
|
-
lines.push("| Plane | Kind | Component | Divergence |");
|
|
232952
|
-
lines.push("|---|---|---|---|");
|
|
232953
|
-
for (const d of comparison.divergences) {
|
|
232954
|
-
const component = d.sig ? `\`${escapeCell(d.sig)}\`` : "\u2014";
|
|
232955
|
-
lines.push(
|
|
232956
|
-
`| **${d.plane}** | \`${d.kind}\` | ${component} | ${escapeCell(d.brief)} |`
|
|
232957
|
-
);
|
|
232958
|
-
}
|
|
232959
|
-
lines.push("");
|
|
232960
|
-
}
|
|
232961
|
-
for (const plane of PLANE_ORDER) {
|
|
232962
|
-
const planeDivergences = comparison.divergences.filter(
|
|
232963
|
-
(d) => d.plane === plane
|
|
232964
|
-
);
|
|
232965
|
-
if (planeDivergences.length === 0) continue;
|
|
232966
|
-
lines.push(`## ${PLANE_LABELS[plane]}`);
|
|
232967
|
-
lines.push("");
|
|
232968
|
-
for (const d of planeDivergences) {
|
|
232969
|
-
lines.push(`### \`${d.kind}\` - ${escapeCell(d.brief)}`);
|
|
232970
|
-
lines.push("");
|
|
232971
|
-
if (d.sig) lines.push(`- Component: \`${d.sig}\``);
|
|
232972
|
-
if (d.requestId) lines.push(`- Request: \`${d.requestId}\``);
|
|
232973
|
-
if (d.table)
|
|
232974
|
-
lines.push(
|
|
232975
|
-
`- Table: \`${d.table}\`${d.pk ? ` \xB7 pk \`${JSON.stringify(d.pk)}\`` : ""}`
|
|
232976
|
-
);
|
|
232977
|
-
lines.push("");
|
|
232978
|
-
lines.push("```diff");
|
|
232979
|
-
lines.push(`- before: ${JSON.stringify(d.before)}`);
|
|
232980
|
-
lines.push(`+ after: ${JSON.stringify(d.after)}`);
|
|
232981
|
-
lines.push("```");
|
|
232982
|
-
lines.push("");
|
|
232983
|
-
}
|
|
232984
|
-
}
|
|
232985
|
-
lines.push("---");
|
|
232986
|
-
lines.push("");
|
|
232987
|
-
lines.push(
|
|
232988
|
-
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.*"
|
|
232989
|
-
);
|
|
232990
|
-
lines.push("");
|
|
232991
|
-
return lines.join("\n");
|
|
232992
|
-
}
|
|
232993
|
-
function formatComparisonSummary(comparison) {
|
|
232994
|
-
const lines = [];
|
|
232995
|
-
lines.push(
|
|
232996
|
-
`crumbtrail-server compare - ${formatSessionRef(comparison.a)} vs ${formatSessionRef(comparison.b)}`
|
|
232997
|
-
);
|
|
232998
|
-
lines.push(` Schema: ${comparison.schemaVersion}`);
|
|
232999
|
-
lines.push(
|
|
233000
|
-
` Verdict: ${comparison.verdict.toUpperCase()} (confidence ${comparison.confidence})`
|
|
233001
|
-
);
|
|
233002
|
-
lines.push(
|
|
233003
|
-
` Alignment: ${comparison.alignment.matchedSteps} matched \xB7 ${comparison.alignment.unmatchedA} only in A \xB7 ${comparison.alignment.unmatchedB} only in B`
|
|
233004
|
-
);
|
|
233005
|
-
lines.push(` Divergences: ${comparison.divergences.length}`);
|
|
233006
|
-
for (const d of comparison.divergences.slice(0, 5)) {
|
|
233007
|
-
lines.push(` [${d.plane}] ${d.kind} - ${d.brief}`);
|
|
233008
|
-
}
|
|
233009
|
-
if (comparison.divergences.length > 5) {
|
|
233010
|
-
lines.push(
|
|
233011
|
-
` ... and ${comparison.divergences.length - 5} more (use --json or --report for the complete list)`
|
|
233012
|
-
);
|
|
233013
|
-
}
|
|
233014
|
-
const rules = comparison.noise.rules.length > 0 ? ` (${comparison.noise.rules.join(", ")})` : "";
|
|
233015
|
-
lines.push(
|
|
233016
|
-
` Noise: ${comparison.noise.suppressedCount} suppressed${rules}`
|
|
233017
|
-
);
|
|
233018
|
-
return lines.join("\n");
|
|
233019
|
-
}
|
|
233020
|
-
function escapeCell(text2) {
|
|
233021
|
-
return text2.replace(/\|/g, "\\|").replace(/\r?\n/g, " ");
|
|
233022
|
-
}
|
|
233023
|
-
function formatSessionRef(ref) {
|
|
233024
|
-
const tags = [
|
|
233025
|
-
ref.release ? `release ${ref.release}` : void 0,
|
|
233026
|
-
ref.build ? `build ${ref.build}` : void 0
|
|
233027
|
-
].filter(Boolean);
|
|
233028
|
-
return tags.length > 0 ? `${ref.sessionId} (${tags.join(", ")})` : ref.sessionId;
|
|
233029
|
-
}
|
|
233030
|
-
|
|
233031
|
-
// src/run-compare.ts
|
|
233032
233213
|
async function runCompare(rest) {
|
|
233033
233214
|
const options = parseCompareArgs(rest);
|
|
233034
233215
|
if (!options.a || !options.b) {
|
package/dist/cli.js
CHANGED
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
renderProviderCliOutput,
|
|
22
22
|
resolveLatestIssue,
|
|
23
23
|
resolveSessionDirById
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-5HPWIOG3.js";
|
|
25
25
|
import {
|
|
26
26
|
__commonJS,
|
|
27
27
|
__require,
|
|
@@ -213062,6 +213062,7 @@ var CliConfigError = class extends Error {
|
|
|
213062
213062
|
this.code = code;
|
|
213063
213063
|
this.name = "CliConfigError";
|
|
213064
213064
|
}
|
|
213065
|
+
code;
|
|
213065
213066
|
};
|
|
213066
213067
|
function defaultCliConfig() {
|
|
213067
213068
|
return {
|