@meetless/mla 0.1.4
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/LICENSE +201 -0
- package/README.md +81 -0
- package/dist/build-info.json +9 -0
- package/dist/bundles/ask-core.js +396 -0
- package/dist/bundles/mcp.js +16592 -0
- package/dist/bundles/trace-core.js +263 -0
- package/dist/cli.js +828 -0
- package/dist/commands/activate.js +781 -0
- package/dist/commands/adoption.js +130 -0
- package/dist/commands/ask.js +290 -0
- package/dist/commands/context.js +114 -0
- package/dist/commands/debug.js +313 -0
- package/dist/commands/doctor.js +1021 -0
- package/dist/commands/enrich.js +427 -0
- package/dist/commands/evidence.js +229 -0
- package/dist/commands/flush.js +184 -0
- package/dist/commands/graph.js +104 -0
- package/dist/commands/init.js +272 -0
- package/dist/commands/internal-active-review.js +322 -0
- package/dist/commands/internal-auto-index.js +188 -0
- package/dist/commands/internal-capture-decisions.js +320 -0
- package/dist/commands/internal-evidence-correlate.js +239 -0
- package/dist/commands/internal-evidence-hooks.js +240 -0
- package/dist/commands/internal-evidence-inject.js +231 -0
- package/dist/commands/internal-finalize.js +221 -0
- package/dist/commands/internal-pretool-observe.js +225 -0
- package/dist/commands/internal-refresh.js +136 -0
- package/dist/commands/internal-session-nudge.js +120 -0
- package/dist/commands/internal-steer-sync.js +117 -0
- package/dist/commands/internal-turn-recap.js +140 -0
- package/dist/commands/kb.js +375 -0
- package/dist/commands/kb_add.js +681 -0
- package/dist/commands/kb_forget.js +283 -0
- package/dist/commands/kb_move.js +45 -0
- package/dist/commands/kb_pending.js +410 -0
- package/dist/commands/kb_personal.js +149 -0
- package/dist/commands/kb_promote.js +188 -0
- package/dist/commands/kb_purge.js +168 -0
- package/dist/commands/kb_reingest.js +335 -0
- package/dist/commands/kb_retime.js +170 -0
- package/dist/commands/kb_review.js +391 -0
- package/dist/commands/kb_revision.js +179 -0
- package/dist/commands/kb_show.js +385 -0
- package/dist/commands/label.js +226 -0
- package/dist/commands/login.js +295 -0
- package/dist/commands/logout.js +108 -0
- package/dist/commands/mcp-supervisor.js +93 -0
- package/dist/commands/mcp.js +227 -0
- package/dist/commands/queue-prune.js +98 -0
- package/dist/commands/review.js +358 -0
- package/dist/commands/rewire.js +124 -0
- package/dist/commands/rules.js +728 -0
- package/dist/commands/scan-context.js +67 -0
- package/dist/commands/session.js +347 -0
- package/dist/commands/stats.js +479 -0
- package/dist/commands/status.js +61 -0
- package/dist/commands/summary.js +250 -0
- package/dist/commands/turn.js +114 -0
- package/dist/commands/uninstall.js +222 -0
- package/dist/commands/whoami.js +102 -0
- package/dist/commands/workspace.js +130 -0
- package/dist/hooks-template/ce0-post-tool-use.sh +34 -0
- package/dist/hooks-template/ce0-session-start.sh +49 -0
- package/dist/hooks-template/ce0-stop.sh +29 -0
- package/dist/hooks-template/ce0-user-prompt-submit.sh +38 -0
- package/dist/hooks-template/common.sh +934 -0
- package/dist/hooks-template/event-batch-filter.jq +67 -0
- package/dist/hooks-template/flush.sh +503 -0
- package/dist/hooks-template/post-tool-use.sh +423 -0
- package/dist/hooks-template/pre-tool-use.sh +69 -0
- package/dist/hooks-template/session-start.sh +140 -0
- package/dist/hooks-template/stop.sh +308 -0
- package/dist/hooks-template/user-prompt-submit.sh +1162 -0
- package/dist/lib/activation.js +79 -0
- package/dist/lib/active-conflict-cache.js +141 -0
- package/dist/lib/active-memory.js +59 -0
- package/dist/lib/active-review-runner.js +26 -0
- package/dist/lib/agent-decision/index.js +25 -0
- package/dist/lib/agent-decision/keys.js +49 -0
- package/dist/lib/agent-decision/normalize-claude.js +183 -0
- package/dist/lib/agent-decision/types.js +21 -0
- package/dist/lib/agent-decision/validate.js +216 -0
- package/dist/lib/analytics/capture.js +96 -0
- package/dist/lib/analytics/command-event.js +267 -0
- package/dist/lib/analytics/consent.js +58 -0
- package/dist/lib/analytics/coverage-gap.js +96 -0
- package/dist/lib/analytics/envelope.js +236 -0
- package/dist/lib/analytics/event-id.js +86 -0
- package/dist/lib/analytics/evidence.js +150 -0
- package/dist/lib/analytics/followthrough.js +194 -0
- package/dist/lib/analytics/forwarder.js +109 -0
- package/dist/lib/analytics/logs.js +78 -0
- package/dist/lib/analytics/metrics.js +78 -0
- package/dist/lib/analytics/recorder.js +92 -0
- package/dist/lib/analytics/review-analytics.js +75 -0
- package/dist/lib/analytics/sequence.js +77 -0
- package/dist/lib/analytics/store.js +131 -0
- package/dist/lib/analytics/turn-recap.js +279 -0
- package/dist/lib/artifact_id.js +108 -0
- package/dist/lib/auth-breaker.js +161 -0
- package/dist/lib/auto-index.js +112 -0
- package/dist/lib/classifier.js +88 -0
- package/dist/lib/config.js +298 -0
- package/dist/lib/conflict-advisory.js +64 -0
- package/dist/lib/debug-bundle.js +520 -0
- package/dist/lib/enrichment/ingest.js +301 -0
- package/dist/lib/enrichment/plan.js +253 -0
- package/dist/lib/enrichment/protocol.js +359 -0
- package/dist/lib/enrichment/scout-brief.js +176 -0
- package/dist/lib/failure-telemetry.js +444 -0
- package/dist/lib/git.js +200 -0
- package/dist/lib/governance-cache.js +77 -0
- package/dist/lib/governed-path-cache.js +76 -0
- package/dist/lib/http.js +677 -0
- package/dist/lib/identity-envelope.js +23 -0
- package/dist/lib/kb-candidate.js +65 -0
- package/dist/lib/kb_acl.js +98 -0
- package/dist/lib/login.js +353 -0
- package/dist/lib/mcp-fetchers.js +130 -0
- package/dist/lib/mcp-restart.js +47 -0
- package/dist/lib/observability.js +805 -0
- package/dist/lib/open-url.js +33 -0
- package/dist/lib/orphan-guard.js +70 -0
- package/dist/lib/packaged.js +21 -0
- package/dist/lib/reconcile-sessions.js +171 -0
- package/dist/lib/redactor.js +89 -0
- package/dist/lib/relationship-candidate-query.js +27 -0
- package/dist/lib/render.js +611 -0
- package/dist/lib/rules/applicability.js +64 -0
- package/dist/lib/rules/attest-code-rule-version.js +47 -0
- package/dist/lib/rules/attest-notes-location.js +217 -0
- package/dist/lib/rules/attest-rule-version.js +69 -0
- package/dist/lib/rules/canonical-json.js +97 -0
- package/dist/lib/rules/ce0-emit.js +64 -0
- package/dist/lib/rules/ce0-evidence.js +281 -0
- package/dist/lib/rules/ce0-recall-sample.js +82 -0
- package/dist/lib/rules/ce0-rule.js +55 -0
- package/dist/lib/rules/ce0-sampling-bucket.js +15 -0
- package/dist/lib/rules/ce0-store.js +683 -0
- package/dist/lib/rules/ce0-telemetry-project.js +93 -0
- package/dist/lib/rules/ce0-telemetry.js +158 -0
- package/dist/lib/rules/code-rule-registry.js +17 -0
- package/dist/lib/rules/command-match.js +185 -0
- package/dist/lib/rules/consult-evidence-binding.js +27 -0
- package/dist/lib/rules/consultation-capture-adapter.js +193 -0
- package/dist/lib/rules/content-match.js +56 -0
- package/dist/lib/rules/deny-admission.js +99 -0
- package/dist/lib/rules/durable-observation.js +190 -0
- package/dist/lib/rules/enforce-notes-version.js +421 -0
- package/dist/lib/rules/evaluation-input-hash.js +126 -0
- package/dist/lib/rules/evaluator.js +108 -0
- package/dist/lib/rules/inert-rule-families.js +51 -0
- package/dist/lib/rules/input-authority-resolver.js +241 -0
- package/dist/lib/rules/interception-schema.js +170 -0
- package/dist/lib/rules/interception-store.js +267 -0
- package/dist/lib/rules/live-input-authority.js +66 -0
- package/dist/lib/rules/local-matcher.js +108 -0
- package/dist/lib/rules/local-observe.js +79 -0
- package/dist/lib/rules/local-rule-version-repo.js +214 -0
- package/dist/lib/rules/memory-requirement.js +109 -0
- package/dist/lib/rules/notes-observe.js +39 -0
- package/dist/lib/rules/notes-path.js +261 -0
- package/dist/lib/rules/notes-rule.js +75 -0
- package/dist/lib/rules/observe-adapter.js +114 -0
- package/dist/lib/rules/observed-rule-hash.js +119 -0
- package/dist/lib/rules/prompt-submit-adapter.js +132 -0
- package/dist/lib/rules/requirement-subject.js +240 -0
- package/dist/lib/rules/rule-activity.js +67 -0
- package/dist/lib/rules/rule-version-hash.js +151 -0
- package/dist/lib/rules/runtime-scope.js +55 -0
- package/dist/lib/rules/stop-adapter.js +116 -0
- package/dist/lib/rules/stop-response-snapshot.js +174 -0
- package/dist/lib/rules/types.js +10 -0
- package/dist/lib/rules/ulid.js +46 -0
- package/dist/lib/rules/version-evaluation.js +156 -0
- package/dist/lib/scanner/agent-memory.js +99 -0
- package/dist/lib/scanner/bootstrap-summary.js +87 -0
- package/dist/lib/scanner/cache.js +59 -0
- package/dist/lib/scanner/frontmatter.js +42 -0
- package/dist/lib/scanner/parse-directives.js +69 -0
- package/dist/lib/scanner/parse-structured.js +72 -0
- package/dist/lib/scanner/render.js +73 -0
- package/dist/lib/scanner/scan.js +132 -0
- package/dist/lib/scanner/score.js +38 -0
- package/dist/lib/scanner/scout-mission.js +126 -0
- package/dist/lib/scanner/types.js +7 -0
- package/dist/lib/session-scope.js +195 -0
- package/dist/lib/spool.js +355 -0
- package/dist/lib/staleness.js +100 -0
- package/dist/lib/steer-cache.js +87 -0
- package/dist/lib/tagged-reference.js +20 -0
- package/dist/lib/temporal.js +109 -0
- package/dist/lib/turn-recap-emit.js +67 -0
- package/dist/lib/unwire.js +253 -0
- package/dist/lib/update-check.js +469 -0
- package/dist/lib/update-notifier.js +217 -0
- package/dist/lib/upgrade-apply.js +643 -0
- package/dist/lib/wire.js +1087 -0
- package/dist/lib/workspace.js +96 -0
- package/dist/lib/zip.js +154 -0
- package/dist/pretool-entry.js +37 -0
- package/package.json +75 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// tools/meetless-agent/src/lib/conflict-advisory.ts
|
|
3
|
+
//
|
|
4
|
+
// NORMATIVE V1 conflict-advisory flag policy (Phase 1, Active Review).
|
|
5
|
+
//
|
|
6
|
+
// Active Review reviews the PRIOR turn's produced docs against the workspace's
|
|
7
|
+
// governed knowledge and turns intel's relationship detections into AT MOST one
|
|
8
|
+
// terse advisory per cited document. This module is the single source of truth
|
|
9
|
+
// for WHICH detections become advisories. The policy is deliberately narrow and
|
|
10
|
+
// silent by default:
|
|
11
|
+
//
|
|
12
|
+
// 1. ONLY conflict relation types flag. CONTRADICTS, SUPERSEDES, and
|
|
13
|
+
// STALE_RELIES_ON are the conflict set. Everything else (REFERENCES,
|
|
14
|
+
// RELATES_TO, plausibly-helpful "see also" edges, etc.) is silent: a
|
|
15
|
+
// related doc is not a conflict, and surfacing it would be noise.
|
|
16
|
+
// 2. ONLY over an APPROVED, VISIBLE cited document. The cited doc must be
|
|
17
|
+
// either a LIVE posture (published / governed) OR a SHADOW posture that is
|
|
18
|
+
// ACCEPTED (an approved private edge). A PENDING_REVIEW or REJECTED edge,
|
|
19
|
+
// or an un-approved SHADOW, never flags: we do not warn the agent about a
|
|
20
|
+
// contradiction with something nobody has signed off on yet.
|
|
21
|
+
// 3. ONLY at or above the confidence floor. A detection below minConfidence is
|
|
22
|
+
// a weak signal; the caller may log it for tuning, but it produces no
|
|
23
|
+
// advisory.
|
|
24
|
+
// 4. ONE advisory per cited doc. A single document can yield many chunk-level
|
|
25
|
+
// detections; they collapse to one flag (the highest-confidence one) so the
|
|
26
|
+
// agent sees one line per conflicting doc, never a chunk storm.
|
|
27
|
+
//
|
|
28
|
+
// Pure and side-effect-free so the policy is unit-testable in isolation; the
|
|
29
|
+
// runner (active-review-runner.ts) supplies the detections and renders the
|
|
30
|
+
// advisory text. advise-never-block (P6): an advisory is informational only.
|
|
31
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
32
|
+
exports.CONFLICT_RELATION_TYPES = void 0;
|
|
33
|
+
exports.advisoriesFromDetections = advisoriesFromDetections;
|
|
34
|
+
// The conflict relation types. Only these flag; every other relation type is
|
|
35
|
+
// silent (a related doc is not a conflict).
|
|
36
|
+
exports.CONFLICT_RELATION_TYPES = new Set(["CONTRADICTS", "SUPERSEDES", "STALE_RELIES_ON"]);
|
|
37
|
+
// A cited doc is eligible to flag only when it is approved AND visible: a LIVE
|
|
38
|
+
// posture, or a SHADOW posture that has been ACCEPTED (an approved private edge).
|
|
39
|
+
function isEligible(d) {
|
|
40
|
+
return d.posture === "LIVE" || (d.posture === "SHADOW" && d.status === "ACCEPTED");
|
|
41
|
+
}
|
|
42
|
+
function advisoriesFromDetections(detections, opts) {
|
|
43
|
+
// Collapse to one advisory per cited doc, keeping the highest-confidence one.
|
|
44
|
+
const byCitedId = new Map();
|
|
45
|
+
for (const d of detections) {
|
|
46
|
+
if (!exports.CONFLICT_RELATION_TYPES.has(d.relationType))
|
|
47
|
+
continue; // related is not conflict
|
|
48
|
+
if (d.confidence < opts.minConfidence)
|
|
49
|
+
continue; // below the floor: log only, no advisory
|
|
50
|
+
if (!isEligible(d))
|
|
51
|
+
continue; // not over an approved, visible cited doc
|
|
52
|
+
const existing = byCitedId.get(d.citedKbId);
|
|
53
|
+
if (existing && existing.confidence >= d.confidence)
|
|
54
|
+
continue; // keep the strongest
|
|
55
|
+
byCitedId.set(d.citedKbId, {
|
|
56
|
+
citedKbId: d.citedKbId,
|
|
57
|
+
relationType: d.relationType,
|
|
58
|
+
candidatePath: d.candidatePath,
|
|
59
|
+
citedQuote: d.citedQuote,
|
|
60
|
+
confidence: d.confidence,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
return Array.from(byCitedId.values());
|
|
64
|
+
}
|
|
@@ -0,0 +1,520 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.redactValue = redactValue;
|
|
37
|
+
exports.collectLocalLogs = collectLocalLogs;
|
|
38
|
+
exports.hashWorkspaceId = hashWorkspaceId;
|
|
39
|
+
exports.deepLinks = deepLinks;
|
|
40
|
+
exports.buildManifest = buildManifest;
|
|
41
|
+
exports.buildRedactionReport = buildRedactionReport;
|
|
42
|
+
exports.buildReadme = buildReadme;
|
|
43
|
+
exports.buildBundle = buildBundle;
|
|
44
|
+
const crypto = __importStar(require("crypto"));
|
|
45
|
+
const fs = __importStar(require("fs"));
|
|
46
|
+
const path = __importStar(require("path"));
|
|
47
|
+
const zip_1 = require("./zip");
|
|
48
|
+
const observability_1 = require("./observability");
|
|
49
|
+
const redactor_1 = require("./redactor");
|
|
50
|
+
// mla debug bundle core (Phase 5 / gap 6.7). Pure-ish builders kept separate
|
|
51
|
+
// from the command's IO so they are unit-testable without a filesystem or a
|
|
52
|
+
// backend. The command (commands/debug.ts) does the IO (read local logs,
|
|
53
|
+
// best-effort backend fetch, write the zip) and calls buildBundle().
|
|
54
|
+
//
|
|
55
|
+
// Safe-by-construction guarantees, all enforced here:
|
|
56
|
+
// - Raw payloads (document bodies, prompts, diffs, tool payloads, raw user
|
|
57
|
+
// requests) are NOT in the bundle by default. They enter only behind the
|
|
58
|
+
// includePrompts / includeDiffs opt-ins (the command gates those on a
|
|
59
|
+
// confirmation). redactValue does the stripping and counts every drop.
|
|
60
|
+
// - Redaction is layered, not a single denylist (a key denylist can never be
|
|
61
|
+
// complete). Layer 1: a key denylist drops whole payload categories (diffs,
|
|
62
|
+
// prompts, secrets) by key name. Layer 2: the value-level secret scrubber
|
|
63
|
+
// (lib/redactor.ts) runs on EVERY remaining string value, regardless of key
|
|
64
|
+
// name or include flags, so credentials (sk-..., ghp_..., Bearer ..., env
|
|
65
|
+
// assignments, PEM blocks, high-entropy tokens) never leak through an unknown
|
|
66
|
+
// key or even inside a deliberately-included raw payload. Correlation ids are
|
|
67
|
+
// allowlisted past Layer 2 so the bundle stays joinable.
|
|
68
|
+
// - The redaction report is mandatory and lists which redactors ran + counts.
|
|
69
|
+
// - manifest.json is the first thing in the bundle: read it, know the bundle.
|
|
70
|
+
// - Backend summaries are best-effort: their absence yields a partial bundle
|
|
71
|
+
// with a warning, never a failure (the command never requires backend access).
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
// Redaction
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
// Keys whose values are source diffs / patches. Gated by includeDiffs.
|
|
76
|
+
const DIFF_KEYS = new Set(["diff", "diffs", "patch", "patches", "source_diff", "sourcediff"]);
|
|
77
|
+
// Keys whose values are prompts, retrieved document bodies, LLM output, tool
|
|
78
|
+
// payloads, or raw user requests. Gated by includePrompts. (Two opt-in flags
|
|
79
|
+
// cover the gated payload categories: --include-diffs for diffs, --include-prompts
|
|
80
|
+
// for the rest, matching the spec's flag surface in gap 6.7.)
|
|
81
|
+
const PROMPT_KEYS = new Set([
|
|
82
|
+
// prompts
|
|
83
|
+
"prompt",
|
|
84
|
+
"prompts",
|
|
85
|
+
"system_prompt",
|
|
86
|
+
"systemprompt",
|
|
87
|
+
"messages",
|
|
88
|
+
// retrieved document bodies / evidence
|
|
89
|
+
"evidence",
|
|
90
|
+
"document",
|
|
91
|
+
"documents",
|
|
92
|
+
"body",
|
|
93
|
+
"bodies",
|
|
94
|
+
"content",
|
|
95
|
+
"text",
|
|
96
|
+
"chunk",
|
|
97
|
+
"chunks",
|
|
98
|
+
"passage",
|
|
99
|
+
"passages",
|
|
100
|
+
"snippet",
|
|
101
|
+
"snippets",
|
|
102
|
+
// LLM output / generated content (model responses are raw payloads too: the
|
|
103
|
+
// denylist must name them or they leak through under an unlisted key)
|
|
104
|
+
"completion",
|
|
105
|
+
"completions",
|
|
106
|
+
"llm_response",
|
|
107
|
+
"llmresponse",
|
|
108
|
+
"llm_output",
|
|
109
|
+
"llmoutput",
|
|
110
|
+
"model_output",
|
|
111
|
+
"modeloutput",
|
|
112
|
+
"response",
|
|
113
|
+
"responses",
|
|
114
|
+
"output",
|
|
115
|
+
"outputs",
|
|
116
|
+
"answer",
|
|
117
|
+
"answers",
|
|
118
|
+
"generation",
|
|
119
|
+
"generations",
|
|
120
|
+
"retrieved_text",
|
|
121
|
+
"retrievedtext",
|
|
122
|
+
"retrieval",
|
|
123
|
+
"retrievals",
|
|
124
|
+
"reasoning",
|
|
125
|
+
// tool payloads
|
|
126
|
+
"tool_payload",
|
|
127
|
+
"toolpayload",
|
|
128
|
+
"tool_input",
|
|
129
|
+
"tool_output",
|
|
130
|
+
"payload",
|
|
131
|
+
// raw user requests
|
|
132
|
+
"raw_request",
|
|
133
|
+
"rawrequest",
|
|
134
|
+
"request_body",
|
|
135
|
+
"requestbody",
|
|
136
|
+
"query",
|
|
137
|
+
"question",
|
|
138
|
+
"user_input",
|
|
139
|
+
"userinput",
|
|
140
|
+
]);
|
|
141
|
+
// Keys whose values are credentials. ALWAYS redacted, never re-exposed by the
|
|
142
|
+
// include flags: --include-prompts is for content, never for secrets. The
|
|
143
|
+
// value-level scrubber (Layer 2) is the pattern-based backstop for credentials
|
|
144
|
+
// under any other key; this set is the belt-and-suspenders for credential keys
|
|
145
|
+
// whose value may not match a known token pattern (short/custom tokens).
|
|
146
|
+
const SECRET_KEYS = new Set([
|
|
147
|
+
"api_key",
|
|
148
|
+
"apikey",
|
|
149
|
+
"api_secret",
|
|
150
|
+
"apisecret",
|
|
151
|
+
"token",
|
|
152
|
+
"access_token",
|
|
153
|
+
"accesstoken",
|
|
154
|
+
"refresh_token",
|
|
155
|
+
"refreshtoken",
|
|
156
|
+
"id_token",
|
|
157
|
+
"idtoken",
|
|
158
|
+
"session_token",
|
|
159
|
+
"sessiontoken",
|
|
160
|
+
"secret",
|
|
161
|
+
"client_secret",
|
|
162
|
+
"clientsecret",
|
|
163
|
+
"password",
|
|
164
|
+
"passwd",
|
|
165
|
+
"pwd",
|
|
166
|
+
"authorization",
|
|
167
|
+
"auth_header",
|
|
168
|
+
"authheader",
|
|
169
|
+
"cookie",
|
|
170
|
+
"set_cookie",
|
|
171
|
+
"setcookie",
|
|
172
|
+
"credentials",
|
|
173
|
+
"credential",
|
|
174
|
+
"private_key",
|
|
175
|
+
"privatekey",
|
|
176
|
+
]);
|
|
177
|
+
// Correlation / id fields are pointers, not secrets (OBS-9: ids and URLs only).
|
|
178
|
+
// The Layer 2 high-entropy heuristic would otherwise flag hex/uuid ids and nuke
|
|
179
|
+
// exactly the join keys an operator needs the bundle for. These keys bypass
|
|
180
|
+
// Layer 2; they are never a payload category, so the denylist (Layer 1) is
|
|
181
|
+
// unaffected and a credential never hides here (those keys are in SECRET_KEYS).
|
|
182
|
+
const ID_PASSTHROUGH_KEYS = new Set([
|
|
183
|
+
"id",
|
|
184
|
+
"trace_id",
|
|
185
|
+
"traceid",
|
|
186
|
+
"langfuse_trace_id",
|
|
187
|
+
"langfusetraceid",
|
|
188
|
+
"run_id",
|
|
189
|
+
"runid",
|
|
190
|
+
"session_id",
|
|
191
|
+
"sessionid",
|
|
192
|
+
"span_id",
|
|
193
|
+
"spanid",
|
|
194
|
+
"parent_span_id",
|
|
195
|
+
"parentspanid",
|
|
196
|
+
"request_id",
|
|
197
|
+
"requestid",
|
|
198
|
+
"correlation_id",
|
|
199
|
+
"correlationid",
|
|
200
|
+
"workspace_id",
|
|
201
|
+
"workspaceid",
|
|
202
|
+
"tenant_id",
|
|
203
|
+
"tenantid",
|
|
204
|
+
"event_id",
|
|
205
|
+
"eventid",
|
|
206
|
+
"user_id",
|
|
207
|
+
"userid",
|
|
208
|
+
"diff_id",
|
|
209
|
+
"diffid",
|
|
210
|
+
]);
|
|
211
|
+
function categoryFor(key) {
|
|
212
|
+
const k = key.toLowerCase();
|
|
213
|
+
if (SECRET_KEYS.has(k))
|
|
214
|
+
return "secrets";
|
|
215
|
+
if (DIFF_KEYS.has(k))
|
|
216
|
+
return "diffs";
|
|
217
|
+
if (PROMPT_KEYS.has(k))
|
|
218
|
+
return "prompts";
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
function shouldRedact(category, opts) {
|
|
222
|
+
if (category === "secrets")
|
|
223
|
+
return true; // never re-exposed by include flags
|
|
224
|
+
if (category === "diffs")
|
|
225
|
+
return !opts.includeDiffs;
|
|
226
|
+
return !opts.includePrompts;
|
|
227
|
+
}
|
|
228
|
+
// Recursively redact an arbitrary JSON value with two layers. Layer 1: drop
|
|
229
|
+
// payload-bearing keys by category (replaced with a marker, key kept so the
|
|
230
|
+
// structure stays inspectable, counted). Layer 2: run the value-level secret
|
|
231
|
+
// scrubber on every remaining string value so credentials never leak through an
|
|
232
|
+
// unlisted key or a deliberately-included raw payload. Correlation ids are
|
|
233
|
+
// allowlisted past Layer 2 so the bundle stays joinable.
|
|
234
|
+
function redactValue(value, opts, counts) {
|
|
235
|
+
if (Array.isArray(value)) {
|
|
236
|
+
return value.map((v) => redactValue(v, opts, counts));
|
|
237
|
+
}
|
|
238
|
+
if (value && typeof value === "object") {
|
|
239
|
+
const out = {};
|
|
240
|
+
for (const [k, v] of Object.entries(value)) {
|
|
241
|
+
const category = categoryFor(k);
|
|
242
|
+
if (category && shouldRedact(category, opts)) {
|
|
243
|
+
out[k] = `[REDACTED:${category}]`;
|
|
244
|
+
counts[category] += 1;
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
// Layer 2 bypass: keep correlation ids readable (pointers, not secrets).
|
|
248
|
+
if (typeof v === "string" && ID_PASSTHROUGH_KEYS.has(k.toLowerCase())) {
|
|
249
|
+
out[k] = v;
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
out[k] = redactValue(v, opts, counts);
|
|
253
|
+
}
|
|
254
|
+
return out;
|
|
255
|
+
}
|
|
256
|
+
// Layer 2: scalar string backstop. Scrub credential patterns from every
|
|
257
|
+
// string value regardless of key or include flags. A non-id string that the
|
|
258
|
+
// scrubber changed contained a secret => count it.
|
|
259
|
+
if (typeof value === "string") {
|
|
260
|
+
return scrubSecret(value, counts);
|
|
261
|
+
}
|
|
262
|
+
return value;
|
|
263
|
+
}
|
|
264
|
+
// Apply the value-level secret scrubber, counting a hit when it changes the
|
|
265
|
+
// string. redact() returns its input unchanged for null/undefined/""; a string
|
|
266
|
+
// in always returns a string out, so the `?? value` is a pure type-narrowing
|
|
267
|
+
// guard, never a runtime branch for real input.
|
|
268
|
+
function scrubSecret(value, counts) {
|
|
269
|
+
const scrubbed = (0, redactor_1.redact)(value);
|
|
270
|
+
if (scrubbed !== value)
|
|
271
|
+
counts.secrets += 1;
|
|
272
|
+
return scrubbed ?? value;
|
|
273
|
+
}
|
|
274
|
+
// Scan logsDir recursively for *.jsonl / *.log files, keep only lines that
|
|
275
|
+
// mention the trace_id, redact each (JSON lines parsed + two-layer redacted;
|
|
276
|
+
// plaintext lines get the value-level secret scrubber), and return the
|
|
277
|
+
// trace-scoped slice per file. The scoped trace_id itself is preserved in every
|
|
278
|
+
// line so the bundle stays greppable by trace_id. Missing dir => empty (offline
|
|
279
|
+
// / fresh box is normal, never an error).
|
|
280
|
+
function collectLocalLogs(logsDir, traceId, opts, counts) {
|
|
281
|
+
const out = [];
|
|
282
|
+
let files = [];
|
|
283
|
+
try {
|
|
284
|
+
files = walkLogFiles(logsDir);
|
|
285
|
+
}
|
|
286
|
+
catch {
|
|
287
|
+
return out;
|
|
288
|
+
}
|
|
289
|
+
for (const file of files) {
|
|
290
|
+
let raw;
|
|
291
|
+
try {
|
|
292
|
+
raw = fs.readFileSync(file, "utf8");
|
|
293
|
+
}
|
|
294
|
+
catch {
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
const matched = [];
|
|
298
|
+
for (const line of raw.split("\n")) {
|
|
299
|
+
if (!line.includes(traceId))
|
|
300
|
+
continue;
|
|
301
|
+
matched.push(redactLogLine(line, traceId, opts, counts));
|
|
302
|
+
}
|
|
303
|
+
if (matched.length === 0)
|
|
304
|
+
continue;
|
|
305
|
+
const rel = path.relative(logsDir, file).split(path.sep).join("/");
|
|
306
|
+
out.push({
|
|
307
|
+
name: `logs/${rel}`,
|
|
308
|
+
data: matched.join("\n") + "\n",
|
|
309
|
+
lineCount: matched.length,
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
return out;
|
|
313
|
+
}
|
|
314
|
+
function walkLogFiles(dir) {
|
|
315
|
+
const result = [];
|
|
316
|
+
for (const ent of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
317
|
+
const full = path.join(dir, ent.name);
|
|
318
|
+
if (ent.isDirectory()) {
|
|
319
|
+
result.push(...walkLogFiles(full));
|
|
320
|
+
}
|
|
321
|
+
else if (ent.isFile() && (ent.name.endsWith(".jsonl") || ent.name.endsWith(".log"))) {
|
|
322
|
+
result.push(full);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
return result;
|
|
326
|
+
}
|
|
327
|
+
function redactLogLine(line, traceId, opts, counts) {
|
|
328
|
+
const trimmed = line.trim();
|
|
329
|
+
if (!trimmed.startsWith("{")) {
|
|
330
|
+
// Non-JSON line: no structure to category-redact, but it can still carry a
|
|
331
|
+
// credential in plaintext (e.g. "Authorization: Bearer ghp_..."). Run the
|
|
332
|
+
// value-level scrubber so secrets never pass through verbatim.
|
|
333
|
+
return scrubPlaintext(line, traceId, counts);
|
|
334
|
+
}
|
|
335
|
+
try {
|
|
336
|
+
const parsed = JSON.parse(trimmed);
|
|
337
|
+
return JSON.stringify(redactValue(parsed, opts, counts));
|
|
338
|
+
}
|
|
339
|
+
catch {
|
|
340
|
+
// Looked like JSON but did not parse: scrub it as plaintext rather than
|
|
341
|
+
// keep it verbatim (a half-written log line can still hold a secret).
|
|
342
|
+
return scrubPlaintext(line, traceId, counts);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
// Scrub a plaintext line while preserving the scoped trace_id. The value
|
|
346
|
+
// scrubber's high-entropy heuristic would otherwise redact a real hex trace_id
|
|
347
|
+
// (a 32-hex correlation pointer reads as a generic token), erasing the join key
|
|
348
|
+
// the bundle exists to expose. There is no JSON key to allowlist on here, so we
|
|
349
|
+
// mask the trace_id with a control-char sentinel (never matches a token pattern,
|
|
350
|
+
// never appears in real logs), scrub, then restore it.
|
|
351
|
+
function scrubPlaintext(line, traceId, counts) {
|
|
352
|
+
const SENTINEL = "\x00\x00MLA_TRACE_ID\x00\x00";
|
|
353
|
+
const masked = traceId ? line.split(traceId).join(SENTINEL) : line;
|
|
354
|
+
const scrubbed = scrubSecret(masked, counts);
|
|
355
|
+
return traceId ? scrubbed.split(SENTINEL).join(traceId) : scrubbed;
|
|
356
|
+
}
|
|
357
|
+
// Hash the workspace id so a shared bundle never leaks the raw tenant id. The
|
|
358
|
+
// raw id is a pointer, not a secret, but a bundle is meant to be attached to a
|
|
359
|
+
// public issue, so the conservative default is a hash.
|
|
360
|
+
function hashWorkspaceId(workspaceId) {
|
|
361
|
+
if (!workspaceId)
|
|
362
|
+
return null;
|
|
363
|
+
return "sha256:" + crypto.createHash("sha256").update(workspaceId).digest("hex").slice(0, 16);
|
|
364
|
+
}
|
|
365
|
+
// Deep links / search hints for this trace. Concrete URLs are emitted when the
|
|
366
|
+
// inputs carry the pieces to build them (Langfuse needs a project id, Sentry a
|
|
367
|
+
// dashboard URL), both of which depend on backend/workspace config that may be
|
|
368
|
+
// absent offline. The trace-id search hints are ALWAYS emitted: they need
|
|
369
|
+
// nothing but the id, so the bundle stays useful even fully offline (spec 6.7:
|
|
370
|
+
// "known deep-links ... are always included").
|
|
371
|
+
function deepLinks(inputs) {
|
|
372
|
+
const links = [];
|
|
373
|
+
if (inputs.langfuseProjectId) {
|
|
374
|
+
links.push(`langfuse: ${(0, observability_1.langfuseTraceUrl)(inputs.langfuseProjectId, inputs.traceId)}`);
|
|
375
|
+
}
|
|
376
|
+
if (inputs.sentryUrl) {
|
|
377
|
+
links.push(`sentry: ${inputs.sentryUrl}`);
|
|
378
|
+
}
|
|
379
|
+
links.push(`search Langfuse for trace_id: ${inputs.traceId}`);
|
|
380
|
+
links.push(`search Sentry for the tag trace_id == ${inputs.traceId}`);
|
|
381
|
+
links.push(`grep your CLI logs for: ${inputs.traceId}`);
|
|
382
|
+
return links;
|
|
383
|
+
}
|
|
384
|
+
function buildManifest(inputs, fileList) {
|
|
385
|
+
return {
|
|
386
|
+
trace_id: inputs.traceId,
|
|
387
|
+
created_at: inputs.createdAt,
|
|
388
|
+
mla_version: inputs.mlaVersion,
|
|
389
|
+
release_sha: inputs.releaseSha,
|
|
390
|
+
workspace_id_hash: hashWorkspaceId(inputs.workspaceId),
|
|
391
|
+
command: inputs.command,
|
|
392
|
+
run_id: inputs.runId,
|
|
393
|
+
session_id: inputs.sessionId,
|
|
394
|
+
telemetry_enabled: inputs.telemetryEnabled,
|
|
395
|
+
files: fileList,
|
|
396
|
+
redaction: {
|
|
397
|
+
raw_payloads_included: {
|
|
398
|
+
prompts: inputs.opts.includePrompts,
|
|
399
|
+
diffs: inputs.opts.includeDiffs,
|
|
400
|
+
},
|
|
401
|
+
redacted_counts: inputs.redactionCounts,
|
|
402
|
+
},
|
|
403
|
+
backend_summary_present: inputs.backendSummary !== null,
|
|
404
|
+
warnings: inputs.warnings,
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
function buildRedactionReport(inputs) {
|
|
408
|
+
const ranDiffs = !inputs.opts.includeDiffs;
|
|
409
|
+
const ranPrompts = !inputs.opts.includePrompts;
|
|
410
|
+
return {
|
|
411
|
+
redactors_run: [
|
|
412
|
+
ranDiffs ? "diffs" : null,
|
|
413
|
+
ranPrompts ? "prompts (document bodies, prompts, LLM output, tool payloads, raw requests)" : null,
|
|
414
|
+
"secrets (credentials, tokens, API keys, env assignments, PEM blocks; ALWAYS on, even with include flags)",
|
|
415
|
+
].filter(Boolean),
|
|
416
|
+
redacted_counts: inputs.redactionCounts,
|
|
417
|
+
raw_payloads_included: {
|
|
418
|
+
prompts: inputs.opts.includePrompts,
|
|
419
|
+
diffs: inputs.opts.includeDiffs,
|
|
420
|
+
// secrets are never included; there is no flag to re-expose them.
|
|
421
|
+
secrets: false,
|
|
422
|
+
},
|
|
423
|
+
warnings: inputs.warnings,
|
|
424
|
+
note: "Redaction is layered. Layer 1 (key denylist) replaces payload-bearing " +
|
|
425
|
+
"values with [REDACTED:<category>] and keeps the surrounding structure. " +
|
|
426
|
+
"Layer 2 (value-level secret scrubber) runs on every remaining string and " +
|
|
427
|
+
"strips credential patterns regardless of key name or include flags. A " +
|
|
428
|
+
"non-zero count means content was removed before this bundle was written. " +
|
|
429
|
+
"Silent redaction is avoided on purpose: review this report before sharing " +
|
|
430
|
+
"the bundle. The include flags re-expose content categories only; secrets " +
|
|
431
|
+
"are never re-exposed.",
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
function buildReadme(inputs) {
|
|
435
|
+
const lines = [
|
|
436
|
+
"mla debug bundle",
|
|
437
|
+
"================",
|
|
438
|
+
"",
|
|
439
|
+
`trace_id: ${inputs.traceId}`,
|
|
440
|
+
`created: ${inputs.createdAt}`,
|
|
441
|
+
"",
|
|
442
|
+
"SHARE BOUNDARY",
|
|
443
|
+
"--------------",
|
|
444
|
+
"This bundle was generated locally by `mla debug bundle`. Nothing was",
|
|
445
|
+
"uploaded. You are in control of where it goes.",
|
|
446
|
+
"",
|
|
447
|
+
"Before attaching it to a GitHub issue or support ticket:",
|
|
448
|
+
" 1. Read manifest.json (it summarizes everything inside).",
|
|
449
|
+
" 2. Read redaction-report.json (it states what was stripped).",
|
|
450
|
+
" 3. Confirm you are comfortable sharing what remains.",
|
|
451
|
+
"",
|
|
452
|
+
"By default, raw payloads (document bodies, prompts, source diffs, tool",
|
|
453
|
+
"payloads, and raw user requests) are NOT included. They are present only if",
|
|
454
|
+
"you passed --include-prompts and/or --include-diffs.",
|
|
455
|
+
"",
|
|
456
|
+
`raw prompts included: ${inputs.opts.includePrompts}`,
|
|
457
|
+
`raw diffs included: ${inputs.opts.includeDiffs}`,
|
|
458
|
+
"",
|
|
459
|
+
"CONTENTS",
|
|
460
|
+
"--------",
|
|
461
|
+
" manifest.json machine-readable summary (read this first)",
|
|
462
|
+
" redaction-report.json what was redacted and which redactors ran",
|
|
463
|
+
" deep-links.txt Langfuse / Sentry links for this trace_id",
|
|
464
|
+
];
|
|
465
|
+
// Only list files that are actually in this bundle (logs and the backend
|
|
466
|
+
// summary are both best-effort; an offline / fresh box has neither).
|
|
467
|
+
if (inputs.localLogs.length > 0) {
|
|
468
|
+
lines.push(" logs/ trace-scoped, redacted local CLI log lines");
|
|
469
|
+
}
|
|
470
|
+
if (inputs.backendSummary !== null) {
|
|
471
|
+
lines.push(" backend-summary.json best-effort control/intel summary");
|
|
472
|
+
}
|
|
473
|
+
lines.push("");
|
|
474
|
+
if (inputs.warnings.length > 0) {
|
|
475
|
+
lines.push("WARNINGS", "--------");
|
|
476
|
+
for (const w of inputs.warnings)
|
|
477
|
+
lines.push(` - ${w}`);
|
|
478
|
+
lines.push("");
|
|
479
|
+
}
|
|
480
|
+
return lines.join("\n");
|
|
481
|
+
}
|
|
482
|
+
// Assemble the full bundle as an in-memory zip. Deterministic given its inputs
|
|
483
|
+
// (no clock, no IO): createdAt and all collection results are passed in.
|
|
484
|
+
function buildBundle(inputs) {
|
|
485
|
+
const entries = [];
|
|
486
|
+
// deep links (always non-empty: deepLinks always emits the trace-id search
|
|
487
|
+
// hints, even fully offline).
|
|
488
|
+
const links = deepLinks(inputs);
|
|
489
|
+
entries.push({
|
|
490
|
+
name: "deep-links.txt",
|
|
491
|
+
data: Buffer.from(links.join("\n") + "\n", "utf8"),
|
|
492
|
+
});
|
|
493
|
+
// local logs (trace-scoped, redacted)
|
|
494
|
+
for (const log of inputs.localLogs) {
|
|
495
|
+
entries.push({ name: log.name, data: Buffer.from(log.data, "utf8") });
|
|
496
|
+
}
|
|
497
|
+
// backend summary (best-effort)
|
|
498
|
+
if (inputs.backendSummary !== null) {
|
|
499
|
+
entries.push({
|
|
500
|
+
name: "backend-summary.json",
|
|
501
|
+
data: Buffer.from(JSON.stringify(inputs.backendSummary, null, 2) + "\n", "utf8"),
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
// redaction report (mandatory)
|
|
505
|
+
entries.push({
|
|
506
|
+
name: "redaction-report.json",
|
|
507
|
+
data: Buffer.from(JSON.stringify(buildRedactionReport(inputs), null, 2) + "\n", "utf8"),
|
|
508
|
+
});
|
|
509
|
+
// README (share boundary)
|
|
510
|
+
entries.push({ name: "README.txt", data: Buffer.from(buildReadme(inputs), "utf8") });
|
|
511
|
+
// file list excludes the manifest itself (the manifest describes the rest).
|
|
512
|
+
const fileList = entries.map((e) => e.name).sort();
|
|
513
|
+
// manifest (first, but built last so it can list the files)
|
|
514
|
+
const manifest = buildManifest(inputs, fileList);
|
|
515
|
+
entries.unshift({
|
|
516
|
+
name: "manifest.json",
|
|
517
|
+
data: Buffer.from(JSON.stringify(manifest, null, 2) + "\n", "utf8"),
|
|
518
|
+
});
|
|
519
|
+
return { zip: (0, zip_1.createZip)(entries), fileList, manifest };
|
|
520
|
+
}
|