@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,250 @@
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.parseSummaryArgs = parseSummaryArgs;
37
+ exports.runSummary = runSummary;
38
+ const fs = __importStar(require("fs"));
39
+ const os = __importStar(require("os"));
40
+ const path = __importStar(require("path"));
41
+ // `mla summary` -- aggregate view over the hook's enrichment trace JSONL
42
+ // (T18b, §6.9). Counts/latency/cost/labels across the most recent N prompts.
43
+ //
44
+ // mla summary [--last N] [--json] [--all]
45
+ //
46
+ // By default it auto-scopes to the CURRENT session via CLAUDE_CODE_SESSION_ID
47
+ // (the env var the parent Claude Code shell exports, same as `mla review`), so
48
+ // there is deliberately NO `--session` flag: the shell already told us which
49
+ // session this is. `--all` opts back out to the cross-session aggregate; when
50
+ // run outside a session (var unset) it defaults to global.
51
+ //
52
+ // Was `mla traces summarize`; the `traces show`/`traces label` subcommands were
53
+ // removed 2026-05-31. Per-trace inspection is unnecessary from the CLI: every
54
+ // enrichment writes its trace_id to ~/.meetless/logs/ask-traces.jsonl AND the
55
+ // same id is printed live on each prompt's `<meetless-context trace="...">`
56
+ // block, so the operator pins a run via the prompt or `jq`/`tail` over the
57
+ // JSONL and opens it in the Langfuse dashboard. This command stays as the only
58
+ // thing the JSONL can't trivially give you by hand: the rolled-up aggregates.
59
+ //
60
+ // The trace line schema is produced by user-prompt-submit.sh write_trace(); we
61
+ // read only the fields we tally and treat everything as optional/best-effort so
62
+ // a partially-written or future-extended line never crashes the summary.
63
+ // Paths are resolved lazily from MEETLESS_HOME (same fallback as lib/config's
64
+ // HOME) so the short-lived CLI process picks up the operator's env, and tests
65
+ // can point at a temp dir without module-cache tricks.
66
+ function logDir() {
67
+ return path.join(process.env.MEETLESS_HOME || path.join(os.homedir(), ".meetless"), "logs");
68
+ }
69
+ function tracesFile() {
70
+ return path.join(logDir(), "ask-traces.jsonl");
71
+ }
72
+ function readLines() {
73
+ if (!fs.existsSync(tracesFile()))
74
+ return [];
75
+ return fs
76
+ .readFileSync(tracesFile(), "utf8")
77
+ .split("\n")
78
+ .filter((l) => l.trim().length > 0);
79
+ }
80
+ function parse(line) {
81
+ try {
82
+ const o = JSON.parse(line);
83
+ return o && typeof o === "object" ? o : null;
84
+ }
85
+ catch {
86
+ return null;
87
+ }
88
+ }
89
+ function num(v) {
90
+ return typeof v === "number" && Number.isFinite(v) ? v : null;
91
+ }
92
+ function percentile(sortedAsc, p) {
93
+ if (sortedAsc.length === 0)
94
+ return 0;
95
+ const idx = Math.ceil(p * sortedAsc.length) - 1;
96
+ return sortedAsc[Math.min(sortedAsc.length - 1, Math.max(0, idx))];
97
+ }
98
+ function isLabeled(l) {
99
+ if (!l)
100
+ return false;
101
+ return (l.useful === true ||
102
+ l.noisy === true ||
103
+ l.harmful === true ||
104
+ l.prevented_mistake === true ||
105
+ (typeof l.notes === "string" && l.notes.trim().length > 0));
106
+ }
107
+ function parseSummaryArgs(argv) {
108
+ const out = { last: 20, json: false, all: false };
109
+ for (let i = 0; i < argv.length; i++) {
110
+ const a = argv[i];
111
+ if (a === "--json")
112
+ out.json = true;
113
+ else if (a === "--all")
114
+ out.all = true;
115
+ else if (a === "--last") {
116
+ const v = argv[++i];
117
+ const parsed = Number(v);
118
+ if (!v || !Number.isInteger(parsed) || parsed <= 0) {
119
+ throw new Error(`--last requires a positive integer (got: ${v ?? "(none)"})`);
120
+ }
121
+ out.last = parsed;
122
+ }
123
+ else
124
+ throw new Error(`Unknown flag for \`mla summary\`: ${a}`);
125
+ }
126
+ return out;
127
+ }
128
+ function buildSummary(traces) {
129
+ let injected = 0;
130
+ let discarded = 0;
131
+ let failOpen = 0;
132
+ let timeouts = 0;
133
+ let totalCost = 0;
134
+ const latencies = [];
135
+ const injectedChars = [];
136
+ const strategies = {};
137
+ let useful = 0;
138
+ let noisy = 0;
139
+ let harmful = 0;
140
+ let labeled = 0;
141
+ for (const t of traces) {
142
+ const decision = t.arbitration?.decision ?? "";
143
+ if (decision === "injected")
144
+ injected++;
145
+ if (t.arbitration?.discarded_after_compute === true)
146
+ discarded++;
147
+ if (decision === "fail_open")
148
+ failOpen++;
149
+ const failReason = t.hook?.fail_open_reason ?? "";
150
+ if (failReason === "timeout" || t.enrichment?.status === "timeout")
151
+ timeouts++;
152
+ const lat = num(t.enrichment?.latency_ms);
153
+ if (lat !== null)
154
+ latencies.push(lat);
155
+ const cost = num(t.enrichment?.cost_usd);
156
+ if (cost !== null)
157
+ totalCost += cost;
158
+ if (decision === "injected") {
159
+ const chars = num(t.hook?.injected_chars);
160
+ if (chars !== null)
161
+ injectedChars.push(chars);
162
+ }
163
+ const strat = t.experiment?.variant || t.enrichment?.strategy || "unknown";
164
+ strategies[strat] = (strategies[strat] ?? 0) + 1;
165
+ const ol = t.operator_label;
166
+ if (ol?.useful === true)
167
+ useful++;
168
+ if (ol?.noisy === true)
169
+ noisy++;
170
+ if (ol?.harmful === true)
171
+ harmful++;
172
+ if (isLabeled(ol))
173
+ labeled++;
174
+ }
175
+ latencies.sort((a, b) => a - b);
176
+ const avgLat = latencies.length ? latencies.reduce((s, v) => s + v, 0) / latencies.length : 0;
177
+ const p95Lat = percentile(latencies, 0.95);
178
+ const avgChars = injectedChars.length
179
+ ? Math.round(injectedChars.reduce((s, v) => s + v, 0) / injectedChars.length)
180
+ : 0;
181
+ const timeoutRate = traces.length ? timeouts / traces.length : 0;
182
+ return {
183
+ prompt_count: traces.length,
184
+ injected,
185
+ discarded_after_compute: discarded,
186
+ fail_open: failOpen,
187
+ avg_enrichment_latency_ms: Math.round(avgLat),
188
+ p95_enrichment_latency_ms: p95Lat,
189
+ timeout_rate: timeoutRate,
190
+ total_cost_usd: totalCost,
191
+ avg_injected_chars: avgChars,
192
+ strategies,
193
+ operator_labels: { useful, noisy, harmful, unlabeled: traces.length - labeled },
194
+ };
195
+ }
196
+ function renderSummary(s) {
197
+ const secs = (ms) => (ms / 1000).toFixed(1) + "s";
198
+ const pct = (r) => (r * 100).toFixed(0) + "%";
199
+ const strat = Object.entries(s.strategies)
200
+ .map(([k, v]) => `${k}=${v}`)
201
+ .join(" ");
202
+ return [
203
+ `Prompt count: ${s.prompt_count}`,
204
+ `Injected: ${s.injected} Discarded after compute: ${s.discarded_after_compute} Fail-open: ${s.fail_open}`,
205
+ `Avg enrichment latency: ${secs(s.avg_enrichment_latency_ms)} P95: ${secs(s.p95_enrichment_latency_ms)} Timeout rate: ${pct(s.timeout_rate)}`,
206
+ `Total cost: $${s.total_cost_usd.toFixed(2)} Avg injected chars: ${s.avg_injected_chars} Strategies: ${strat || "(none)"}`,
207
+ `Operator labels: ${s.operator_labels.useful} useful / ${s.operator_labels.noisy} noisy / ${s.operator_labels.harmful} harmful / ${s.operator_labels.unlabeled} unlabeled`,
208
+ ].join("\n");
209
+ }
210
+ function runSummary(argv) {
211
+ let args;
212
+ try {
213
+ args = parseSummaryArgs(argv);
214
+ }
215
+ catch (e) {
216
+ console.error(e.message);
217
+ return 2;
218
+ }
219
+ const lines = readLines();
220
+ if (lines.length === 0) {
221
+ console.error(`No traces found at ${tracesFile()}.`);
222
+ return 1;
223
+ }
224
+ let traces = lines.map(parse).filter((t) => t !== null);
225
+ // Auto-scope to the current live session. The parent shell (Claude Code)
226
+ // exports CLAUDE_CODE_SESSION_ID, the same var `mla review` binds to, so the
227
+ // operator never passes a session id by hand. `--all` opts back out to the
228
+ // cross-session aggregate; outside a session (var unset) we default to global
229
+ // since there is no current session to scope to. Scope BEFORE --last so the
230
+ // window is "last N of this session", not "last N overall then filtered".
231
+ const session = (process.env.CLAUDE_CODE_SESSION_ID || "").trim();
232
+ const scoped = !args.all && session.length > 0;
233
+ if (scoped)
234
+ traces = traces.filter((t) => t.session_id === session);
235
+ if (traces.length === 0) {
236
+ console.error(scoped
237
+ ? `No traces for the current session (${session}) at ${tracesFile()}. Use --all for every session.`
238
+ : `No traces found at ${tracesFile()}.`);
239
+ return 1;
240
+ }
241
+ traces = traces.slice(-args.last);
242
+ const summary = buildSummary(traces);
243
+ if (args.json) {
244
+ console.log(JSON.stringify(summary, null, 2));
245
+ }
246
+ else {
247
+ console.log(renderSummary(summary));
248
+ }
249
+ return 0;
250
+ }
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ // `mla turn [N]` -- the operator-facing per-turn assist recap (Layer B of
3
+ // notes/20260609-mla-per-turn-assist-recap-plan.md). The cheap, always-available,
4
+ // zero-model-cost answer to "how did mla do?" for a single turn:
5
+ //
6
+ // mla turn recap the latest completed turn of the current session
7
+ // mla turn 5 recap turn 5
8
+ // mla turn --session X target another session
9
+ // mla turn 5 --json machine output (the full TurnRecap)
10
+ //
11
+ // It reads the same three local spool files as `mla stats` and computes the recap
12
+ // via Layer A computeTurnRecap. `mla stats --turn [N]` is wired to this same
13
+ // handler (commands/stats.ts) so the feature is discoverable from the stats surface
14
+ // An framed it against. Unlike `mla _internal turn-recap` (fail-soft for the hook),
15
+ // this is a human read: a strict argv error exits 2, a missing session exits 1.
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.parseTurnArgs = parseTurnArgs;
18
+ exports.latestTurnIndex = latestTurnIndex;
19
+ exports.runTurn = runTurn;
20
+ const followthrough_1 = require("../lib/analytics/followthrough");
21
+ const logs_1 = require("../lib/analytics/logs");
22
+ const turn_recap_1 = require("../lib/analytics/turn-recap");
23
+ function parseTurnArgs(argv) {
24
+ const out = { session: null, turn: null, json: false };
25
+ for (let i = 0; i < argv.length; i++) {
26
+ const a = argv[i];
27
+ switch (a) {
28
+ case "--session":
29
+ out.session = argv[++i] ?? "";
30
+ if (!out.session)
31
+ throw new Error("--session requires a value");
32
+ break;
33
+ case "--json":
34
+ out.json = true;
35
+ break;
36
+ default: {
37
+ // A number-shaped token (including a leading '-' or a decimal) is a
38
+ // turn-index candidate, so `mla turn -1` / `mla turn 1.5` report a clear
39
+ // turn error rather than the misleading "unknown flag". A non-numeric
40
+ // leading-dash token is a genuine unknown flag.
41
+ const numeric = /^-?\d*\.?\d+$/.test(a);
42
+ if (!numeric && a.startsWith("-"))
43
+ throw new Error(`Unknown flag for \`mla turn\`: ${a}`);
44
+ if (out.turn !== null)
45
+ throw new Error("`mla turn` takes one turn index at most");
46
+ if (!/^[0-9]+$/.test(a) || Number(a) < 1) {
47
+ throw new Error(`turn index must be a positive integer: ${a}`);
48
+ }
49
+ out.turn = Number(a);
50
+ }
51
+ }
52
+ }
53
+ return out;
54
+ }
55
+ // The latest turn that left ANY trace on disk for this session: the max turn_index
56
+ // across the three spool files (the inject side, the pull side, the cite side).
57
+ // Reading all three (not just ask-traces) means a turn that only produced a pull or
58
+ // a citation still counts as the latest, and a NOT_RUN turn whose minimal trace
59
+ // line landed is found too. Returns null when the session has no turns yet.
60
+ function latestTurnIndex(sessionId, readLog) {
61
+ let max = null;
62
+ const bump = (t) => {
63
+ if (max === null || t > max)
64
+ max = t;
65
+ };
66
+ for (const raw of readLog("ask-traces.jsonl")) {
67
+ const t = (0, turn_recap_1.parseAskTrace)(raw);
68
+ if (t && t.session_id === sessionId)
69
+ bump(t.turn_index);
70
+ }
71
+ for (const c of (0, followthrough_1.parseMcpCalls)(readLog("mcp-calls.jsonl"))) {
72
+ if (c.session_id === sessionId)
73
+ bump(c.turn_index);
74
+ }
75
+ for (const r of (0, followthrough_1.parseReportCitations)(readLog("report-citations.jsonl"))) {
76
+ if (r.session_id === sessionId)
77
+ bump(r.turn_index);
78
+ }
79
+ return max;
80
+ }
81
+ async function runTurn(argv, deps = {}) {
82
+ const log = deps.log ?? ((l) => console.log(l));
83
+ const err = deps.err ?? ((l) => console.error(l));
84
+ let args;
85
+ try {
86
+ args = parseTurnArgs(argv);
87
+ }
88
+ catch (e) {
89
+ err(e.message);
90
+ return 2;
91
+ }
92
+ const env = deps.env ?? process.env;
93
+ const session = (args.session ?? env.CLAUDE_CODE_SESSION_ID ?? "").trim();
94
+ if (!session) {
95
+ err("No session id provided and no $CLAUDE_CODE_SESSION_ID env. " +
96
+ "Run `mla turn` inside a Claude Code session, or pass --session <sid>.");
97
+ return 1;
98
+ }
99
+ const readLog = deps.readLog ?? logs_1.readLogJsonl;
100
+ const turn = args.turn ?? latestTurnIndex(session, readLog);
101
+ if (turn === null) {
102
+ log(`No turns recorded for session ${session} yet.`);
103
+ return 0;
104
+ }
105
+ const compute = deps.compute ?? ((s, t) => (0, turn_recap_1.computeTurnRecap)(s, t, { readLog }));
106
+ const recap = compute(session, turn);
107
+ if (args.json) {
108
+ log(JSON.stringify(recap, null, 2));
109
+ }
110
+ else {
111
+ log((0, turn_recap_1.renderBlock)(recap));
112
+ }
113
+ return 0;
114
+ }
@@ -0,0 +1,222 @@
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.runUninstall = runUninstall;
37
+ const os = __importStar(require("os"));
38
+ const path = __importStar(require("path"));
39
+ const readline = __importStar(require("readline"));
40
+ const fs = __importStar(require("fs"));
41
+ const config_1 = require("../lib/config");
42
+ const unwire_1 = require("../lib/unwire");
43
+ const flush_1 = require("./flush");
44
+ function defaultConfirm(prompt) {
45
+ const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
46
+ return new Promise((resolve) => {
47
+ rl.question(prompt, (answer) => {
48
+ rl.close();
49
+ const n = answer.trim().toLowerCase();
50
+ resolve(n === "y" || n === "yes");
51
+ });
52
+ });
53
+ }
54
+ function defaultChoose(prompt) {
55
+ const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
56
+ return new Promise((resolve) => {
57
+ rl.question(prompt, (answer) => {
58
+ rl.close();
59
+ const n = answer.trim().toLowerCase();
60
+ if (n === "f" || n === "flush")
61
+ resolve("flush");
62
+ else if (n === "d" || n === "delete")
63
+ resolve("delete");
64
+ else
65
+ resolve("cancel");
66
+ });
67
+ });
68
+ }
69
+ async function runUninstall(argv, deps = {}) {
70
+ const log = deps.log ?? ((m) => console.log(m));
71
+ const errlog = deps.errlog ?? ((m) => console.error(m));
72
+ let dryRun = false;
73
+ let yes = false;
74
+ for (const a of argv) {
75
+ if (a === "--dry-run")
76
+ dryRun = true;
77
+ else if (a === "--yes" || a === "-y")
78
+ yes = true;
79
+ else {
80
+ errlog(`Unknown flag for \`mla uninstall\`: ${a}. Usage: mla uninstall [--dry-run] [--yes]`);
81
+ return 2;
82
+ }
83
+ }
84
+ const home = deps.home ?? config_1.HOME;
85
+ const settingsPath = deps.settingsPath ?? path.join(os.homedir(), ".claude", "settings.json");
86
+ const claudeJsonPath = deps.claudeJsonPath ?? path.join(os.homedir(), ".claude.json");
87
+ const skillDir = deps.skillDir ?? path.join(os.homedir(), ".claude", "skills", "mla");
88
+ const queueDir = deps.queueDir ?? config_1.QUEUE_DIR;
89
+ const isTTY = deps.isTTY ?? Boolean(process.stdin.isTTY);
90
+ const env = deps.env ?? process.env;
91
+ const countQueued = deps.countQueued ?? unwire_1.countQueuedSessions;
92
+ const countEvents = deps.countEvents ?? unwire_1.countQueuedEvents;
93
+ const homeExists = deps.homeExists ?? ((p) => fs.existsSync(p));
94
+ const skillExists = deps.skillExists ?? ((p) => fs.existsSync(p));
95
+ const resolveBinary = deps.resolveBinary ?? unwire_1.resolveMlaBinary;
96
+ const removeHooks = deps.removeHooks ?? unwire_1.removeMeetlessHooks;
97
+ const removeMcp = deps.removeMcp ?? unwire_1.removeMeetlessMcp;
98
+ const removeDir = deps.removeDir ?? unwire_1.removeDir;
99
+ const confirm = deps.confirm ?? defaultConfirm;
100
+ const choose = deps.choose ?? defaultChoose;
101
+ const flush = deps.flush ?? flush_1.runFlush;
102
+ const queued = countQueued(queueDir);
103
+ const events = countEvents(queueDir);
104
+ const { binPath, realPath } = resolveBinary(env);
105
+ // 1. Render the plan.
106
+ log("mla uninstall will remove the local Meetless footprint:");
107
+ log(` - ${home} (config, credentials, queue, hooks, logs, telemetry)`);
108
+ log(` - the Meetless hook entries in ${settingsPath}`);
109
+ log(` - the "meetless" MCP server in ${claudeJsonPath}`);
110
+ if (skillExists(skillDir))
111
+ log(` - ${skillDir} (the /mla skill)`);
112
+ log("");
113
+ log("Left in place on purpose:");
114
+ log(" - any of your own hooks or MCP servers (only Meetless's entries are removed)");
115
+ log(" - .meetless.json + the CLAUDE.md rule block in other repos (non-secret).");
116
+ log(" Remove those per repo with: rm .meetless.json (and delete the");
117
+ log(" `BEGIN MEETLESS RULES` ... `END MEETLESS RULES` block from CLAUDE.md)");
118
+ log("");
119
+ if (dryRun) {
120
+ // Disclose un-flushed captured events the real run would put at risk, so the
121
+ // preview names what is lost by its honest magnitude (event count, not file
122
+ // count). Stay silent when no un-flushed events remain: a counted session
123
+ // file can be fully drained and hold nothing to lose.
124
+ if (events > 0) {
125
+ log(`Note: the local queue holds ${events} captured event(s) across ${queued} session(s) not yet flushed to the server.`);
126
+ log("The real run will offer to flush, delete, or cancel before anything is removed.");
127
+ log("");
128
+ }
129
+ // Preview the one manual step uninstall leaves behind, so --dry-run shows
130
+ // the complete footprint and never undersells what stays on disk.
131
+ log("After removal, one manual step remains: remove the `mla` binary yourself:");
132
+ for (const line of (0, unwire_1.detectBinaryRemovalHint)(binPath, realPath))
133
+ log(line);
134
+ log("");
135
+ log("(dry run: nothing was changed.)");
136
+ return 0;
137
+ }
138
+ // 2. Gate. Non-interactive requires --yes.
139
+ if (!yes && !isTTY) {
140
+ errlog("Refusing to uninstall non-interactively. Re-run with --yes to proceed (or --dry-run to preview).");
141
+ return 2;
142
+ }
143
+ // 3. Un-flushed-event safety (interactive only; --yes warns and proceeds).
144
+ // Gate on the event count, not the file count: drained-but-lingering session
145
+ // files carry nothing to lose, so they must not trigger a data-loss prompt.
146
+ if (events > 0) {
147
+ log(`Warning: the local queue holds ${events} captured event(s) across ${queued} session(s) not yet flushed to the server.`);
148
+ if (yes) {
149
+ log("Proceeding under --yes; this unflushed data will be deleted.");
150
+ }
151
+ else {
152
+ const choice = await choose("[f]lush now, [d]elete anyway, or [c]ancel? ");
153
+ if (choice === "cancel") {
154
+ log("Cancelled. Nothing was changed.");
155
+ return 0;
156
+ }
157
+ if (choice === "flush") {
158
+ log("Flushing queued sessions...");
159
+ const rc = await flush([]);
160
+ if (rc !== 0) {
161
+ errlog("Flush did not complete cleanly. Cancelling uninstall so no data is lost. Resolve the flush, then re-run.");
162
+ return 1;
163
+ }
164
+ }
165
+ }
166
+ }
167
+ // 4. Final confirmation (skipped under --yes).
168
+ if (!yes) {
169
+ const ok = await confirm("Remove the Meetless local install? [y/N] ");
170
+ if (!ok) {
171
+ log("Cancelled. Nothing was changed.");
172
+ return 0;
173
+ }
174
+ }
175
+ // 5. Execute. Strip wiring BEFORE deleting HOME. Each step best-effort.
176
+ let hadError = false;
177
+ const hooksRes = removeHooks(settingsPath);
178
+ if (hooksRes.changed) {
179
+ log(`Removed Meetless hooks (${hooksRes.removed.join(", ")}) from ${settingsPath}` +
180
+ (hooksRes.backupPath ? ` (backup: ${hooksRes.backupPath})` : ""));
181
+ }
182
+ else {
183
+ log(`No Meetless hooks found in ${settingsPath}.`);
184
+ }
185
+ const mcpRes = removeMcp(claudeJsonPath);
186
+ if (mcpRes.changed) {
187
+ log(`Removed the "meetless" MCP server from ${claudeJsonPath}` +
188
+ (mcpRes.backupPath ? ` (backup: ${mcpRes.backupPath})` : ""));
189
+ }
190
+ else {
191
+ log(`No "meetless" MCP server found in ${claudeJsonPath}.`);
192
+ }
193
+ if (skillExists(skillDir)) {
194
+ const r = removeDir(skillDir);
195
+ if (r.error) {
196
+ hadError = true;
197
+ errlog(`Could not remove ${skillDir}: ${r.error}`);
198
+ }
199
+ else {
200
+ log(`Removed ${skillDir}.`);
201
+ }
202
+ }
203
+ if (homeExists(home)) {
204
+ const r = removeDir(home);
205
+ if (r.error) {
206
+ hadError = true;
207
+ errlog(`Could not remove ${home}: ${r.error}`);
208
+ }
209
+ else {
210
+ log(`Removed ${home}.`);
211
+ }
212
+ }
213
+ else {
214
+ log(`${home} was already absent.`);
215
+ }
216
+ // 6. Binary: print, never self-delete.
217
+ log("");
218
+ log("Local state is gone. One step left, to remove the `mla` binary itself:");
219
+ for (const line of (0, unwire_1.detectBinaryRemovalHint)(binPath, realPath))
220
+ log(line);
221
+ return hadError ? 1 : 0;
222
+ }
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runWhoami = runWhoami;
4
+ const config_1 = require("../lib/config");
5
+ const http_1 = require("../lib/http");
6
+ // Best-effort humanizer for an ISO expiry. Mirrors login.ts formatRemaining:
7
+ // null for an unparseable/absent timestamp so callers degrade gracefully.
8
+ function formatExpiry(iso) {
9
+ if (!iso)
10
+ return "unknown";
11
+ const ms = Date.parse(iso) - Date.now();
12
+ if (Number.isNaN(ms))
13
+ return "unknown";
14
+ if (ms <= 0)
15
+ return "expired";
16
+ const hours = Math.floor(ms / (60 * 60 * 1000));
17
+ if (hours < 48)
18
+ return `in ~${hours}h`;
19
+ return `in ~${Math.floor(hours / 24)}d`;
20
+ }
21
+ async function runWhoami(argv, deps = {}) {
22
+ if (argv.length > 0) {
23
+ console.error(`\`mla whoami\` takes no arguments (got: ${argv.join(" ")}).`);
24
+ return 2;
25
+ }
26
+ const log = deps.log ?? ((m) => console.log(m));
27
+ const getMeFn = deps.getMeFn ?? ((cfg) => (0, http_1.get)(cfg, "/internal/v1/auth/me"));
28
+ // `auth.mode: none` is also the shape of a box that never ran `mla init`.
29
+ if (!(0, config_1.configExists)()) {
30
+ log("Not configured (no cli-config.json).");
31
+ log("Run `mla init --control-token <T>` or `mla login` to get started.");
32
+ return 1;
33
+ }
34
+ let cfg;
35
+ try {
36
+ cfg = (0, config_1.readConfig)();
37
+ }
38
+ catch (e) {
39
+ console.error(e.message);
40
+ return 1;
41
+ }
42
+ if (cfg.auth.mode === "none") {
43
+ log("Not configured (auth.mode: none).");
44
+ log("Run `mla init --control-token <T>` or `mla login` to log in.");
45
+ return 1;
46
+ }
47
+ if (cfg.auth.mode === "shared-key") {
48
+ // A shared key authenticates AS the workspace's internal key; there is no
49
+ // user behind it. Print the mode + the folder/config workspace without a
50
+ // network call (the §6.6 contract: "does NOT call /auth/me").
51
+ log("auth.mode: shared-key (no user identity)");
52
+ log(` Control: ${cfg.controlUrl}`);
53
+ if (cfg.workspaceId) {
54
+ log(` Workspace: ${cfg.workspaceId} (from cli-config.json)`);
55
+ }
56
+ else {
57
+ log(" Workspace: resolved per-folder from .meetless.json (run `mla workspace`)");
58
+ }
59
+ return 0;
60
+ }
61
+ // user-token: resolve live identity from control.
62
+ let me;
63
+ try {
64
+ me = await getMeFn(cfg);
65
+ }
66
+ catch (e) {
67
+ const err = e;
68
+ if (err.status === 401) {
69
+ // After T27's auto-refresh has tried and failed, a 401 means the session
70
+ // is gone (expired or revoked). Point the operator at re-login.
71
+ console.error("Your CLI session has expired or was revoked. Run `mla login`.");
72
+ return 1;
73
+ }
74
+ // Network failure, control down, or unexpected status. The cached identity
75
+ // is still useful, so print it with a clear "could not reach control" note
76
+ // rather than failing blind.
77
+ console.error(`Could not reach control to verify the session (${err.message}).`);
78
+ const who = cfg.auth.user.displayName || cfg.auth.user.id;
79
+ const email = cfg.auth.user.email ? ` <${cfg.auth.user.email}>` : "";
80
+ log(`Cached identity: ${who}${email} (role ${cfg.auth.user.role}, unverified).`);
81
+ return 1;
82
+ }
83
+ if (me.mode === "shared-key" || !me.user) {
84
+ // Should not happen for a user-token bearer, but stay honest if control
85
+ // reports shared-key (e.g. the token happened to equal INTERNAL_API_KEY).
86
+ log("auth.mode: shared-key (no user identity)");
87
+ return 0;
88
+ }
89
+ const email = me.user.email ? ` <${me.user.email}>` : "";
90
+ log(`Logged in as ${me.user.displayName}${email}.`);
91
+ log(` User: ${me.user.id}`);
92
+ log(` Role: ${me.user.role}`);
93
+ if (me.workspace) {
94
+ log(` Workspace: ${me.workspace.name} (${me.workspace.slug})`);
95
+ }
96
+ if (me.sessionId) {
97
+ log(` Session: ${me.sessionId}`);
98
+ }
99
+ log(` Access token expires ${formatExpiry(me.accessExpiresAt)}.`);
100
+ log(` Refresh token expires ${formatExpiry(me.refreshExpiresAt)}.`);
101
+ return 0;
102
+ }