@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.
Files changed (202) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +81 -0
  3. package/dist/build-info.json +9 -0
  4. package/dist/bundles/ask-core.js +396 -0
  5. package/dist/bundles/mcp.js +16592 -0
  6. package/dist/bundles/trace-core.js +263 -0
  7. package/dist/cli.js +828 -0
  8. package/dist/commands/activate.js +781 -0
  9. package/dist/commands/adoption.js +130 -0
  10. package/dist/commands/ask.js +290 -0
  11. package/dist/commands/context.js +114 -0
  12. package/dist/commands/debug.js +313 -0
  13. package/dist/commands/doctor.js +1021 -0
  14. package/dist/commands/enrich.js +427 -0
  15. package/dist/commands/evidence.js +229 -0
  16. package/dist/commands/flush.js +184 -0
  17. package/dist/commands/graph.js +104 -0
  18. package/dist/commands/init.js +272 -0
  19. package/dist/commands/internal-active-review.js +322 -0
  20. package/dist/commands/internal-auto-index.js +188 -0
  21. package/dist/commands/internal-capture-decisions.js +320 -0
  22. package/dist/commands/internal-evidence-correlate.js +239 -0
  23. package/dist/commands/internal-evidence-hooks.js +240 -0
  24. package/dist/commands/internal-evidence-inject.js +231 -0
  25. package/dist/commands/internal-finalize.js +221 -0
  26. package/dist/commands/internal-pretool-observe.js +225 -0
  27. package/dist/commands/internal-refresh.js +136 -0
  28. package/dist/commands/internal-session-nudge.js +120 -0
  29. package/dist/commands/internal-steer-sync.js +117 -0
  30. package/dist/commands/internal-turn-recap.js +140 -0
  31. package/dist/commands/kb.js +375 -0
  32. package/dist/commands/kb_add.js +681 -0
  33. package/dist/commands/kb_forget.js +283 -0
  34. package/dist/commands/kb_move.js +45 -0
  35. package/dist/commands/kb_pending.js +410 -0
  36. package/dist/commands/kb_personal.js +149 -0
  37. package/dist/commands/kb_promote.js +188 -0
  38. package/dist/commands/kb_purge.js +168 -0
  39. package/dist/commands/kb_reingest.js +335 -0
  40. package/dist/commands/kb_retime.js +170 -0
  41. package/dist/commands/kb_review.js +391 -0
  42. package/dist/commands/kb_revision.js +179 -0
  43. package/dist/commands/kb_show.js +385 -0
  44. package/dist/commands/label.js +226 -0
  45. package/dist/commands/login.js +295 -0
  46. package/dist/commands/logout.js +108 -0
  47. package/dist/commands/mcp-supervisor.js +93 -0
  48. package/dist/commands/mcp.js +227 -0
  49. package/dist/commands/queue-prune.js +98 -0
  50. package/dist/commands/review.js +358 -0
  51. package/dist/commands/rewire.js +124 -0
  52. package/dist/commands/rules.js +728 -0
  53. package/dist/commands/scan-context.js +67 -0
  54. package/dist/commands/session.js +347 -0
  55. package/dist/commands/stats.js +479 -0
  56. package/dist/commands/status.js +61 -0
  57. package/dist/commands/summary.js +250 -0
  58. package/dist/commands/turn.js +114 -0
  59. package/dist/commands/uninstall.js +222 -0
  60. package/dist/commands/whoami.js +102 -0
  61. package/dist/commands/workspace.js +130 -0
  62. package/dist/hooks-template/ce0-post-tool-use.sh +34 -0
  63. package/dist/hooks-template/ce0-session-start.sh +49 -0
  64. package/dist/hooks-template/ce0-stop.sh +29 -0
  65. package/dist/hooks-template/ce0-user-prompt-submit.sh +38 -0
  66. package/dist/hooks-template/common.sh +934 -0
  67. package/dist/hooks-template/event-batch-filter.jq +67 -0
  68. package/dist/hooks-template/flush.sh +503 -0
  69. package/dist/hooks-template/post-tool-use.sh +423 -0
  70. package/dist/hooks-template/pre-tool-use.sh +69 -0
  71. package/dist/hooks-template/session-start.sh +140 -0
  72. package/dist/hooks-template/stop.sh +308 -0
  73. package/dist/hooks-template/user-prompt-submit.sh +1162 -0
  74. package/dist/lib/activation.js +79 -0
  75. package/dist/lib/active-conflict-cache.js +141 -0
  76. package/dist/lib/active-memory.js +59 -0
  77. package/dist/lib/active-review-runner.js +26 -0
  78. package/dist/lib/agent-decision/index.js +25 -0
  79. package/dist/lib/agent-decision/keys.js +49 -0
  80. package/dist/lib/agent-decision/normalize-claude.js +183 -0
  81. package/dist/lib/agent-decision/types.js +21 -0
  82. package/dist/lib/agent-decision/validate.js +216 -0
  83. package/dist/lib/analytics/capture.js +96 -0
  84. package/dist/lib/analytics/command-event.js +267 -0
  85. package/dist/lib/analytics/consent.js +58 -0
  86. package/dist/lib/analytics/coverage-gap.js +96 -0
  87. package/dist/lib/analytics/envelope.js +236 -0
  88. package/dist/lib/analytics/event-id.js +86 -0
  89. package/dist/lib/analytics/evidence.js +150 -0
  90. package/dist/lib/analytics/followthrough.js +194 -0
  91. package/dist/lib/analytics/forwarder.js +109 -0
  92. package/dist/lib/analytics/logs.js +78 -0
  93. package/dist/lib/analytics/metrics.js +78 -0
  94. package/dist/lib/analytics/recorder.js +92 -0
  95. package/dist/lib/analytics/review-analytics.js +75 -0
  96. package/dist/lib/analytics/sequence.js +77 -0
  97. package/dist/lib/analytics/store.js +131 -0
  98. package/dist/lib/analytics/turn-recap.js +279 -0
  99. package/dist/lib/artifact_id.js +108 -0
  100. package/dist/lib/auth-breaker.js +161 -0
  101. package/dist/lib/auto-index.js +112 -0
  102. package/dist/lib/classifier.js +88 -0
  103. package/dist/lib/config.js +298 -0
  104. package/dist/lib/conflict-advisory.js +64 -0
  105. package/dist/lib/debug-bundle.js +520 -0
  106. package/dist/lib/enrichment/ingest.js +301 -0
  107. package/dist/lib/enrichment/plan.js +253 -0
  108. package/dist/lib/enrichment/protocol.js +359 -0
  109. package/dist/lib/enrichment/scout-brief.js +176 -0
  110. package/dist/lib/failure-telemetry.js +444 -0
  111. package/dist/lib/git.js +200 -0
  112. package/dist/lib/governance-cache.js +77 -0
  113. package/dist/lib/governed-path-cache.js +76 -0
  114. package/dist/lib/http.js +677 -0
  115. package/dist/lib/identity-envelope.js +23 -0
  116. package/dist/lib/kb-candidate.js +65 -0
  117. package/dist/lib/kb_acl.js +98 -0
  118. package/dist/lib/login.js +353 -0
  119. package/dist/lib/mcp-fetchers.js +130 -0
  120. package/dist/lib/mcp-restart.js +47 -0
  121. package/dist/lib/observability.js +805 -0
  122. package/dist/lib/open-url.js +33 -0
  123. package/dist/lib/orphan-guard.js +70 -0
  124. package/dist/lib/packaged.js +21 -0
  125. package/dist/lib/reconcile-sessions.js +171 -0
  126. package/dist/lib/redactor.js +89 -0
  127. package/dist/lib/relationship-candidate-query.js +27 -0
  128. package/dist/lib/render.js +611 -0
  129. package/dist/lib/rules/applicability.js +64 -0
  130. package/dist/lib/rules/attest-code-rule-version.js +47 -0
  131. package/dist/lib/rules/attest-notes-location.js +217 -0
  132. package/dist/lib/rules/attest-rule-version.js +69 -0
  133. package/dist/lib/rules/canonical-json.js +97 -0
  134. package/dist/lib/rules/ce0-emit.js +64 -0
  135. package/dist/lib/rules/ce0-evidence.js +281 -0
  136. package/dist/lib/rules/ce0-recall-sample.js +82 -0
  137. package/dist/lib/rules/ce0-rule.js +55 -0
  138. package/dist/lib/rules/ce0-sampling-bucket.js +15 -0
  139. package/dist/lib/rules/ce0-store.js +683 -0
  140. package/dist/lib/rules/ce0-telemetry-project.js +93 -0
  141. package/dist/lib/rules/ce0-telemetry.js +158 -0
  142. package/dist/lib/rules/code-rule-registry.js +17 -0
  143. package/dist/lib/rules/command-match.js +185 -0
  144. package/dist/lib/rules/consult-evidence-binding.js +27 -0
  145. package/dist/lib/rules/consultation-capture-adapter.js +193 -0
  146. package/dist/lib/rules/content-match.js +56 -0
  147. package/dist/lib/rules/deny-admission.js +99 -0
  148. package/dist/lib/rules/durable-observation.js +190 -0
  149. package/dist/lib/rules/enforce-notes-version.js +421 -0
  150. package/dist/lib/rules/evaluation-input-hash.js +126 -0
  151. package/dist/lib/rules/evaluator.js +108 -0
  152. package/dist/lib/rules/inert-rule-families.js +51 -0
  153. package/dist/lib/rules/input-authority-resolver.js +241 -0
  154. package/dist/lib/rules/interception-schema.js +170 -0
  155. package/dist/lib/rules/interception-store.js +267 -0
  156. package/dist/lib/rules/live-input-authority.js +66 -0
  157. package/dist/lib/rules/local-matcher.js +108 -0
  158. package/dist/lib/rules/local-observe.js +79 -0
  159. package/dist/lib/rules/local-rule-version-repo.js +214 -0
  160. package/dist/lib/rules/memory-requirement.js +109 -0
  161. package/dist/lib/rules/notes-observe.js +39 -0
  162. package/dist/lib/rules/notes-path.js +261 -0
  163. package/dist/lib/rules/notes-rule.js +75 -0
  164. package/dist/lib/rules/observe-adapter.js +114 -0
  165. package/dist/lib/rules/observed-rule-hash.js +119 -0
  166. package/dist/lib/rules/prompt-submit-adapter.js +132 -0
  167. package/dist/lib/rules/requirement-subject.js +240 -0
  168. package/dist/lib/rules/rule-activity.js +67 -0
  169. package/dist/lib/rules/rule-version-hash.js +151 -0
  170. package/dist/lib/rules/runtime-scope.js +55 -0
  171. package/dist/lib/rules/stop-adapter.js +116 -0
  172. package/dist/lib/rules/stop-response-snapshot.js +174 -0
  173. package/dist/lib/rules/types.js +10 -0
  174. package/dist/lib/rules/ulid.js +46 -0
  175. package/dist/lib/rules/version-evaluation.js +156 -0
  176. package/dist/lib/scanner/agent-memory.js +99 -0
  177. package/dist/lib/scanner/bootstrap-summary.js +87 -0
  178. package/dist/lib/scanner/cache.js +59 -0
  179. package/dist/lib/scanner/frontmatter.js +42 -0
  180. package/dist/lib/scanner/parse-directives.js +69 -0
  181. package/dist/lib/scanner/parse-structured.js +72 -0
  182. package/dist/lib/scanner/render.js +73 -0
  183. package/dist/lib/scanner/scan.js +132 -0
  184. package/dist/lib/scanner/score.js +38 -0
  185. package/dist/lib/scanner/scout-mission.js +126 -0
  186. package/dist/lib/scanner/types.js +7 -0
  187. package/dist/lib/session-scope.js +195 -0
  188. package/dist/lib/spool.js +355 -0
  189. package/dist/lib/staleness.js +100 -0
  190. package/dist/lib/steer-cache.js +87 -0
  191. package/dist/lib/tagged-reference.js +20 -0
  192. package/dist/lib/temporal.js +109 -0
  193. package/dist/lib/turn-recap-emit.js +67 -0
  194. package/dist/lib/unwire.js +253 -0
  195. package/dist/lib/update-check.js +469 -0
  196. package/dist/lib/update-notifier.js +217 -0
  197. package/dist/lib/upgrade-apply.js +643 -0
  198. package/dist/lib/wire.js +1087 -0
  199. package/dist/lib/workspace.js +96 -0
  200. package/dist/lib/zip.js +154 -0
  201. package/dist/pretool-entry.js +37 -0
  202. 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
+ }