@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,479 @@
1
+ "use strict";
2
+ // `mla stats` -- the usefulness-first local dashboard (spec §7). It reads the
3
+ // append-only ~/.meetless/events.jsonl (INV-LOCAL-STATS-1: works with all remote
4
+ // switches off) and answers "did mla help", not "did mla run". Every headline is
5
+ // value; activity (commands run, KB size) is a --verbose footnote (Thesis B).
6
+ //
7
+ // mla stats this workspace, last 30 days (the ROI default window)
8
+ // mla stats evidence the adoption join, focused (alias of `mla adoption`)
9
+ // mla stats --window 7d configurable window
10
+ // mla stats --json machine-readable
11
+ // mla stats --verbose append the activity footnote
12
+ // mla stats --global server-aggregated (handled in Phase 6 / T6.2)
13
+ //
14
+ // `mla stats evidence` and `mla adoption` route through the SAME runAdoption code
15
+ // path (INV-ADOPTION-SOURCE-1: one join, two entry points), so the §10.5 parity
16
+ // row holds by construction, not by a second implementation.
17
+ //
18
+ // The delayed-outcome trap (§7.4, INV-LOCAL-STATS-2): an inject is written
19
+ // immediately but its outcome lands only once the window closes (3 turns or 15
20
+ // minutes later). Rendering in between must count the inject in the denominator as
21
+ // `pending`, never drop it and never silently call it ignored. We window-filter
22
+ // the INJECTS (the denominator population) and attach each inject's outcome by
23
+ // inject_id regardless of when the outcome was written, so an inject near the
24
+ // window edge keeps its later verdict instead of being undercounted.
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.parseStatsArgs = parseStatsArgs;
27
+ exports.buildDashboard = buildDashboard;
28
+ exports.renderDashboard = renderDashboard;
29
+ exports.renderGlobalDashboard = renderGlobalDashboard;
30
+ exports.translateTurnAlias = translateTurnAlias;
31
+ exports.runStats = runStats;
32
+ const metrics_1 = require("../lib/analytics/metrics");
33
+ const followthrough_1 = require("../lib/analytics/followthrough");
34
+ const store_1 = require("../lib/analytics/store");
35
+ const recorder_1 = require("../lib/analytics/recorder");
36
+ const consent_1 = require("../lib/analytics/consent");
37
+ const config_1 = require("../lib/config");
38
+ const workspace_1 = require("../lib/workspace");
39
+ const http_1 = require("../lib/http");
40
+ const adoption_1 = require("./adoption");
41
+ const turn_1 = require("./turn");
42
+ const DEFAULT_WINDOW_DAYS = 30;
43
+ // Parse `--window 7d` / `--window 30` (bare integer = days). Days only in v1; the
44
+ // ROI report and §7.2 examples are all day-grained.
45
+ function parseWindow(raw) {
46
+ if (raw === undefined)
47
+ throw new Error("--window requires a value (e.g. 7d, 30d, 30)");
48
+ const m = /^(\d+)(d)?$/.exec(raw.trim());
49
+ if (!m)
50
+ throw new Error(`--window must be a positive number of days (e.g. 7d, 30d). Got: ${raw}`);
51
+ const days = Number(m[1]);
52
+ if (!Number.isInteger(days) || days <= 0) {
53
+ throw new Error(`--window must be a positive number of days. Got: ${raw}`);
54
+ }
55
+ return { days, label: `${days}d` };
56
+ }
57
+ function parseStatsArgs(argv) {
58
+ const out = {
59
+ section: null,
60
+ windowDays: DEFAULT_WINDOW_DAYS,
61
+ windowLabel: `${DEFAULT_WINDOW_DAYS}d`,
62
+ json: false,
63
+ verbose: false,
64
+ global: false,
65
+ rest: [],
66
+ };
67
+ let i = 0;
68
+ // A leading bare token is the section selector. `evidence` is the only section
69
+ // in v1; once selected, the remaining argv belongs to that section's handler.
70
+ if (argv[i] !== undefined && !argv[i].startsWith("-")) {
71
+ const section = argv[i];
72
+ if (section !== "evidence") {
73
+ throw new Error(`Unknown \`mla stats\` section: ${section} (known: evidence)`);
74
+ }
75
+ out.section = "evidence";
76
+ out.rest = argv.slice(i + 1);
77
+ return out;
78
+ }
79
+ for (; i < argv.length; i++) {
80
+ const a = argv[i];
81
+ if (a === "--json")
82
+ out.json = true;
83
+ else if (a === "--verbose")
84
+ out.verbose = true;
85
+ else if (a === "--global")
86
+ out.global = true;
87
+ else if (a === "--window") {
88
+ const w = parseWindow(argv[++i]);
89
+ out.windowDays = w.days;
90
+ out.windowLabel = w.label;
91
+ }
92
+ else
93
+ throw new Error(`Unknown flag for \`mla stats\`: ${a}`);
94
+ }
95
+ return out;
96
+ }
97
+ function inWindow(iso, startMs, nowMs) {
98
+ const t = Date.parse(iso);
99
+ return Number.isFinite(t) && t >= startMs && t <= nowMs;
100
+ }
101
+ // Latest outcome per inject_id (highest outcome_version wins; ties keep the last
102
+ // written). A recomputed outcome (bumped version) supersedes its predecessor.
103
+ function latestOutcomes(events) {
104
+ const byInject = new Map();
105
+ for (const e of events) {
106
+ if (e.event_type !== "mla_evidence_outcome")
107
+ continue;
108
+ const o = e;
109
+ const prev = byInject.get(o.inject_id);
110
+ if (!prev || o.outcome_version >= prev.outcome_version)
111
+ byInject.set(o.inject_id, o);
112
+ }
113
+ return byInject;
114
+ }
115
+ function buildDashboard(events, windowDays, nowMs) {
116
+ const startMs = nowMs - windowDays * 24 * 60 * 60 * 1000;
117
+ // Window the inject population (the denominator). Outcomes are attached by id
118
+ // below, NOT window-filtered, so a late-closing window is not undercounted.
119
+ const injects = events.filter((e) => e.event_type === "mla_evidence_inject" && inWindow(e.created_at, startMs, nowMs));
120
+ const outcomes = latestOutcomes(events);
121
+ const metricInputs = injects.map((inj) => {
122
+ const o = outcomes.get(inj.inject_id);
123
+ return {
124
+ evidence_offered: inj.evidence_offered,
125
+ offered_source_ids: inj.offered_source_ids ?? [],
126
+ referenced: o?.referenced ?? false,
127
+ referenced_source_ids: o?.referenced_source_ids ?? [],
128
+ outcome: o?.outcome ?? "pending",
129
+ };
130
+ });
131
+ const evidence = (0, metrics_1.computeMetrics)(metricInputs);
132
+ // Section 2: contradictions caught (Phase 7 emits these; empty until then).
133
+ let contradictions_surfaced = 0;
134
+ let contradictions_acted_on = 0;
135
+ for (const e of events) {
136
+ if (e.event_type !== "mla_contradiction" || !inWindow(e.created_at, startMs, nowMs))
137
+ continue;
138
+ if (e.contradiction_surfaced)
139
+ contradictions_surfaced++;
140
+ if (e.contradiction_acted_on)
141
+ contradictions_acted_on++;
142
+ }
143
+ // Section 3: local governance signal (review decisions). The authoritative
144
+ // governed-change/propagation count is server-side (--global).
145
+ let review_decisions = 0;
146
+ for (const e of events) {
147
+ if (e.event_type === "mla_review_decision" && inWindow(e.created_at, startMs, nowMs))
148
+ review_decisions++;
149
+ }
150
+ // Section 4: coverage gaps by type, sorted by demand (most frequent first).
151
+ const gapCounts = new Map();
152
+ for (const e of events) {
153
+ if (e.event_type !== "mla_coverage_gap" || !inWindow(e.created_at, startMs, nowMs))
154
+ continue;
155
+ gapCounts.set(e.coverage_gap_type, (gapCounts.get(e.coverage_gap_type) ?? 0) + 1);
156
+ }
157
+ const coverage_gaps = Array.from(gapCounts.entries())
158
+ .map(([type, count]) => ({ type, count }))
159
+ .sort((a, b) => b.count - a.count || a.type.localeCompare(b.type));
160
+ const coverage_gaps_total = coverage_gaps.reduce((s, g) => s + g.count, 0);
161
+ // Section 5: load-bearing knowledge -- referenced source ids across the
162
+ // windowed injects' outcomes, counted by the same normId rule the join uses so
163
+ // "NT:foo.md" and "NT:foo" collapse. Local renders the id; remote never sees it.
164
+ const refCounts = new Map();
165
+ for (const inj of injects) {
166
+ const o = outcomes.get(inj.inject_id);
167
+ if (!o)
168
+ continue;
169
+ for (const id of o.referenced_source_ids ?? []) {
170
+ const key = (0, followthrough_1.normId)(id);
171
+ const prev = refCounts.get(key);
172
+ if (prev)
173
+ prev.count++;
174
+ else
175
+ refCounts.set(key, { id, count: 1 });
176
+ }
177
+ }
178
+ const load_bearing = Array.from(refCounts.values())
179
+ .map((r) => ({ source_id: r.id, reference_count: r.count }))
180
+ .sort((a, b) => b.reference_count - a.reference_count || a.source_id.localeCompare(b.source_id))
181
+ .slice(0, 5);
182
+ // Section 6: activity footnote (commands run by name).
183
+ const cmdCounts = new Map();
184
+ let commands_total = 0;
185
+ for (const e of events) {
186
+ if (e.event_type !== "mla_command" || !inWindow(e.created_at, startMs, nowMs))
187
+ continue;
188
+ commands_total++;
189
+ cmdCounts.set(e.command, (cmdCounts.get(e.command) ?? 0) + 1);
190
+ }
191
+ const commands_by_name = Array.from(cmdCounts.entries())
192
+ .map(([command, count]) => ({ command, count }))
193
+ .sort((a, b) => b.count - a.count || a.command.localeCompare(b.command));
194
+ return {
195
+ window: `${windowDays}d`,
196
+ window_days: windowDays,
197
+ generated_at: new Date(nowMs).toISOString(),
198
+ evidence,
199
+ injections: injects.length,
200
+ contradictions_surfaced,
201
+ contradictions_acted_on,
202
+ review_decisions,
203
+ coverage_gaps,
204
+ coverage_gaps_total,
205
+ load_bearing,
206
+ commands_total,
207
+ commands_by_name,
208
+ };
209
+ }
210
+ // --- render -----------------------------------------------------------------
211
+ function pct(r) {
212
+ return r === null ? "n/a" : (r * 100).toFixed(0) + "%";
213
+ }
214
+ function renderDashboard(d, verbose) {
215
+ const m = d.evidence;
216
+ const lines = [];
217
+ lines.push(`mla usefulness, last ${d.window} (workspace-local):`);
218
+ lines.push("");
219
+ // 1. Evidence followthrough (headline).
220
+ lines.push("1. Evidence followthrough");
221
+ if (d.injections === 0) {
222
+ lines.push(" No evidence injections recorded in this window yet.");
223
+ }
224
+ else {
225
+ const pendingNote = m.pending > 0 ? `, ${m.pending} pending` : "";
226
+ lines.push(` mla surfaced evidence in ${d.injections} injection(s) (${m.injects_offered} offered evidence${pendingNote}).`);
227
+ lines.push(` Injection Utilization: ${pct(m.injection_utilization)} (${m.injects_referenced}/${m.injects_offered} offered injects referenced)`);
228
+ lines.push(` ${metrics_1.REFERENCE_PRECISION_V1_LABEL}: ${pct(m.reference_precision_v1)} (${m.used}/${m.used + m.ignored} closed used vs ignored)`);
229
+ lines.push(` Unknown Coverage: ${pct(m.unknown_coverage)} (${m.unknown}/${m.closed_windows} closed windows unclassified)`);
230
+ lines.push(` Evidence Item Utilization: ${pct(m.evidence_item_utilization)} (${m.distinct_referenced}/${m.distinct_offered} distinct docs)`);
231
+ }
232
+ lines.push("");
233
+ // 2. Caught before it shipped.
234
+ lines.push("2. Caught before it shipped");
235
+ if (d.contradictions_surfaced === 0) {
236
+ lines.push(" No contradictions or supersessions flagged in this window.");
237
+ }
238
+ else {
239
+ lines.push(` mla flagged ${d.contradictions_surfaced} contradiction(s)/supersession(s); you acted on ${d.contradictions_acted_on}.`);
240
+ }
241
+ lines.push("");
242
+ // 3. Decisions governed (local proxy + pointer to the authoritative --global).
243
+ lines.push("3. Decisions governed");
244
+ lines.push(` ${d.review_decisions} review decision(s) recorded locally.`);
245
+ lines.push(" For the authoritative governed-change and propagation count, run `mla stats --global`.");
246
+ lines.push("");
247
+ // 4. What mla could not help with (coverage gaps == the roadmap).
248
+ lines.push("4. Coverage gaps (the roadmap)");
249
+ if (d.coverage_gaps_total === 0) {
250
+ lines.push(" No coverage gaps recorded in this window.");
251
+ }
252
+ else {
253
+ lines.push(` ${d.coverage_gaps_total} query/queries returned nothing useful, by type:`);
254
+ for (const g of d.coverage_gaps)
255
+ lines.push(` ${g.type}: ${g.count}`);
256
+ }
257
+ lines.push("");
258
+ // 5. Load-bearing knowledge.
259
+ lines.push("5. Load-bearing knowledge");
260
+ if (d.load_bearing.length === 0) {
261
+ lines.push(" No referenced evidence recorded in this window.");
262
+ }
263
+ else {
264
+ lines.push(" Most-referenced evidence:");
265
+ for (const it of d.load_bearing)
266
+ lines.push(` ${it.source_id} (x${it.reference_count})`);
267
+ }
268
+ // 6. Activity footnote (only under --verbose; never the lead).
269
+ if (verbose) {
270
+ lines.push("");
271
+ lines.push("Activity (footnote)");
272
+ lines.push(` ${d.commands_total} command(s) run.`);
273
+ for (const c of d.commands_by_name)
274
+ lines.push(` ${c.command}: ${c.count}`);
275
+ }
276
+ return lines.join("\n");
277
+ }
278
+ // The global dashboard. Same usefulness-first ordering as the local view, but only
279
+ // the sections that have a permission-scoped, opaque-id-safe server counterpart:
280
+ // evidence followthrough, the wedge (contradictions caught), decisions governed
281
+ // (here AUTHORITATIVE, not a local proxy), and coverage gaps. Load-bearing
282
+ // knowledge and the activity footnote are local-only (spec section 9, 7.3) and have
283
+ // no global form, so they are intentionally absent rather than rendered empty.
284
+ function renderGlobalDashboard(r) {
285
+ const m = r.evidence;
286
+ const lines = [];
287
+ const wsLabel = r.workspaces === 1 ? "1 workspace" : `${r.workspaces} workspaces`;
288
+ lines.push(`mla usefulness, last ${r.window_days}d (global: ${wsLabel} you can view):`);
289
+ lines.push("");
290
+ // 1. Evidence followthrough (headline).
291
+ lines.push("1. Evidence followthrough");
292
+ if (r.injections === 0) {
293
+ lines.push(" No evidence injections recorded in this window yet.");
294
+ }
295
+ else {
296
+ const pendingNote = m.pending > 0 ? `, ${m.pending} pending` : "";
297
+ lines.push(` mla surfaced evidence in ${r.injections} injection(s) (${m.injects_offered} offered evidence${pendingNote}).`);
298
+ lines.push(` Injection Utilization: ${pct(m.injection_utilization)} (${m.injects_referenced}/${m.injects_offered} offered injects referenced)`);
299
+ lines.push(` ${metrics_1.REFERENCE_PRECISION_V1_LABEL}: ${pct(m.reference_precision_v1)} (${m.used}/${m.used + m.ignored} closed used vs ignored)`);
300
+ lines.push(` Unknown Coverage: ${pct(m.unknown_coverage)} (${m.unknown}/${m.closed_windows} closed windows unclassified)`);
301
+ lines.push(` Evidence Item Utilization: ${pct(m.evidence_item_utilization)} (${m.distinct_referenced}/${m.distinct_offered} distinct docs)`);
302
+ }
303
+ lines.push("");
304
+ // 2. Caught before it shipped (the wedge).
305
+ lines.push("2. Caught before it shipped");
306
+ if (r.contradictions_surfaced === 0) {
307
+ lines.push(" No contradictions or supersessions flagged in this window.");
308
+ }
309
+ else {
310
+ lines.push(` mla flagged ${r.contradictions_surfaced} contradiction(s)/supersession(s); acted on ${r.contradictions_acted_on}.`);
311
+ }
312
+ lines.push("");
313
+ // 3. Decisions governed (authoritative server count, not the local proxy).
314
+ lines.push("3. Decisions governed");
315
+ lines.push(` ${r.review_decisions} review decision(s) recorded across your workspaces.`);
316
+ lines.push("");
317
+ // 4. Coverage gaps (the roadmap).
318
+ lines.push("4. Coverage gaps (the roadmap)");
319
+ if (r.coverage_gaps_total === 0) {
320
+ lines.push(" No coverage gaps recorded in this window.");
321
+ }
322
+ else {
323
+ lines.push(` ${r.coverage_gaps_total} query/queries returned nothing useful, by type:`);
324
+ for (const g of r.coverage_gaps)
325
+ lines.push(` ${g.type}: ${g.count}`);
326
+ }
327
+ return lines.join("\n");
328
+ }
329
+ // INV-GLOBAL-UNKNOWN-1: telemetry off AND nothing-synced are both "unknown", never
330
+ // a misleading zero. Same human-facing message for both; in --json the machine gets
331
+ // an explicit `available:false` with the reason so it cannot read it as activity=0.
332
+ function emitGlobalUnavailable(reason, json) {
333
+ const message = "No remote telemetry available. Local stats are still available.";
334
+ if (json) {
335
+ console.log(JSON.stringify({ available: false, reason, message }, null, 2));
336
+ }
337
+ else {
338
+ console.log(message);
339
+ }
340
+ }
341
+ // Translate a `mla stats --turn [N]` argv into the `mla turn` argv: the value
342
+ // after --turn (when number-shaped) becomes the positional turn index, and --json
343
+ // / --session pass through. Window/verbose/global have no per-turn meaning and are
344
+ // dropped (the alias is a convenience, not a second per-turn flag surface).
345
+ function translateTurnAlias(argv) {
346
+ const out = [];
347
+ for (let i = 0; i < argv.length; i++) {
348
+ const a = argv[i];
349
+ if (a === "--turn") {
350
+ const v = argv[i + 1];
351
+ if (v !== undefined && /^[0-9]+$/.test(v)) {
352
+ out.push(v);
353
+ i++;
354
+ }
355
+ }
356
+ else if (a === "--json") {
357
+ out.push("--json");
358
+ }
359
+ else if (a === "--session") {
360
+ const v = argv[++i];
361
+ if (v)
362
+ out.push("--session", v);
363
+ }
364
+ }
365
+ return out;
366
+ }
367
+ // T7.2: `mla stats` is a value-checking moment -- record that someone looked
368
+ // (mla_stats_viewed answers "are people checking value"). The payload is the
369
+ // closed scope/window only (no metrics, no PII). Best-effort and fail-soft:
370
+ // analytics must never break a stats read. It only record()s into the buffer;
371
+ // run context (run_id/trace_id) is ambient from bootstrap and the cli.ts finalize
372
+ // flush ships it.
373
+ function recordStatsViewed(args, deps) {
374
+ try {
375
+ const env = deps.env ?? process.env;
376
+ const record = deps.record ?? recorder_1.recordAnalyticsEvent;
377
+ const resolveWs = deps.resolveWorkspaceId ?? workspace_1.tryResolveWorkspaceId;
378
+ const nowMs = deps.nowMs ?? Date.now();
379
+ const payload = {
380
+ scope: args.global ? "global" : "local",
381
+ window: args.windowLabel,
382
+ };
383
+ const ctx = {
384
+ workspaceId: resolveWs(),
385
+ sessionId: (env.CLAUDE_CODE_SESSION_ID || "").trim() || null,
386
+ now: new Date(nowMs).toISOString(),
387
+ };
388
+ record(ctx, { eventType: "mla_stats_viewed", payload: payload }, env);
389
+ }
390
+ catch {
391
+ // fail-soft: a stats view must never be blocked by analytics.
392
+ }
393
+ }
394
+ async function runStats(argv, deps = {}) {
395
+ // `mla stats --turn [N]` is an alias for the per-turn recap (`mla turn`). It is
396
+ // intercepted before parseStatsArgs (which has no --turn flag) and delegated to
397
+ // the SAME handler, so the two entry points are one implementation, not two.
398
+ if (argv.includes("--turn")) {
399
+ return (deps.turn ?? turn_1.runTurn)(translateTurnAlias(argv));
400
+ }
401
+ let args;
402
+ try {
403
+ args = parseStatsArgs(argv);
404
+ }
405
+ catch (e) {
406
+ console.error(e.message);
407
+ return 2;
408
+ }
409
+ // T7.2: every successful `mla stats` view is a value-checking signal -- record
410
+ // it before dispatching so local / evidence / global all count uniformly.
411
+ recordStatsViewed(args, deps);
412
+ // `mla stats evidence` is the focused adoption join -- the SAME code path as
413
+ // `mla adoption` (INV-ADOPTION-SOURCE-1). One join, two entry points.
414
+ if (args.section === "evidence") {
415
+ return (deps.adoption ?? adoption_1.runAdoption)(args.rest);
416
+ }
417
+ // `--global` reads the control rollup read-model (spec section 10.4), not PostHog.
418
+ if (args.global) {
419
+ return runGlobalStats(args, deps);
420
+ }
421
+ const read = deps.read ?? store_1.readEvents;
422
+ const nowMs = deps.nowMs ?? Date.now();
423
+ const events = read();
424
+ const dashboard = buildDashboard(events, args.windowDays, nowMs);
425
+ if (args.json) {
426
+ console.log(JSON.stringify(dashboard, null, 2));
427
+ }
428
+ else {
429
+ console.log(renderDashboard(dashboard, args.verbose));
430
+ }
431
+ return 0;
432
+ }
433
+ // `mla stats --global`: authenticated server call to control's rollup read-model.
434
+ // Reads the canonical, deduped, permission-scoped aggregate (NEVER PostHog), so the
435
+ // global numbers are ACL-correct and cannot drift from the local definitions (the
436
+ // server mirrors the same metric math). "Zero means no activity; telemetry-off
437
+ // means unknown" -- both telemetry-off and nothing-synced print the unknown
438
+ // message, never a zero (INV-GLOBAL-UNKNOWN-1).
439
+ async function runGlobalStats(args, deps) {
440
+ const env = deps.env ?? process.env;
441
+ // Telemetry off -> unknown, not zero. No server call is made (and none could
442
+ // succeed: nothing has been synced).
443
+ if (!(0, consent_1.remoteAnalyticsEnabled)(env)) {
444
+ emitGlobalUnavailable("telemetry_off", args.json);
445
+ return 0;
446
+ }
447
+ let rollup;
448
+ try {
449
+ rollup = deps.fetchGlobal
450
+ ? await deps.fetchGlobal(args.windowDays)
451
+ : await fetchGlobalRollup(args.windowDays);
452
+ }
453
+ catch (e) {
454
+ // A reachability/auth failure is NOT "no activity"; surface it as an error
455
+ // (exit 1) so it is never silently read as a zero.
456
+ console.error(`mla stats --global could not reach control: ${e.message}`);
457
+ return 1;
458
+ }
459
+ // Nothing synced yet for any visible workspace -> unknown, not zero.
460
+ if (!rollup.has_any_events) {
461
+ emitGlobalUnavailable("no_synced_data", args.json);
462
+ return 0;
463
+ }
464
+ if (args.json) {
465
+ console.log(JSON.stringify(rollup, null, 2));
466
+ }
467
+ else {
468
+ console.log(renderGlobalDashboard(rollup));
469
+ }
470
+ return 0;
471
+ }
472
+ // The real control call. `get` auto-stamps the bearer, X-Trace-ID, and
473
+ // X-Meetless-Actor (the actor the rollup endpoint resolves under INV-AUTH-1). The
474
+ // rollup is cross-workspace by design, so we read the base config (readConfig), not
475
+ // a workspace-bound one -- there is no single workspace to bind to.
476
+ async function fetchGlobalRollup(periodDays) {
477
+ const cfg = (0, config_1.readConfig)();
478
+ return (0, http_1.get)(cfg, `/internal/v1/analytics/rollups?periodDays=${periodDays}`);
479
+ }
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.renderStatus = renderStatus;
4
+ exports.runStatus = runStatus;
5
+ // src/commands/status.ts
6
+ const node_fs_1 = require("node:fs");
7
+ const node_os_1 = require("node:os");
8
+ const node_path_1 = require("node:path");
9
+ const cache_1 = require("../lib/scanner/cache");
10
+ const workspace_1 = require("../lib/workspace");
11
+ const config_1 = require("../lib/config");
12
+ function renderStatus(view) {
13
+ const cache = (0, cache_1.readScanCache)(view.home, view.workspaceId);
14
+ if (!cache) {
15
+ return `Meetless is not activated for this repo. Run \`mla activate\`.`;
16
+ }
17
+ const rules = cache.directives.length;
18
+ const pending = cache.staleSignals.length;
19
+ // `?? 0` guards a pre-M1 on-disk cache that predates the agentMemoryRules field.
20
+ const advisory = cache.inventory.agentMemoryRules ?? 0;
21
+ const hooks = view.hooksInstalled ? "hooks installed" : "hooks NOT installed (run `mla rewire`)";
22
+ const lines = [
23
+ `Meetless is active for workspace ${view.workspaceId} (${hooks}).`,
24
+ ` ${plural(rules, "confirmed rule")} injected on every prompt.`,
25
+ ` ${plural(pending, "pending review item")} (mla context list).`,
26
+ ` inventory: ${cache.inventory.instructionFiles} instruction files, ` +
27
+ `${cache.inventory.decisionDocs} docs, ${cache.inventory.legacyNotes} notes.`,
28
+ ];
29
+ // Advisory agent-memory rules are machine_inferred and NOT injected (never must-follow);
30
+ // surface them only when present, so a fresh repo with none stays quiet (no spam).
31
+ if (advisory > 0) {
32
+ lines.push(` ${plural(advisory, "advisory rule")} from agent memory (pending review; not injected).`);
33
+ }
34
+ return lines.join("\n");
35
+ }
36
+ function plural(n, noun) {
37
+ return `${n} ${noun}${n === 1 ? "" : "s"}`;
38
+ }
39
+ // Thin wrapper kept for local readability; delegates to the shared resolver
40
+ // in src/lib/workspace.ts (env override first, then .meetless.json marker walk).
41
+ function resolveWorkspaceId() {
42
+ return (0, workspace_1.resolveWorkspaceIdWithEnv)();
43
+ }
44
+ function detectHooksInstalled() {
45
+ try {
46
+ return (0, node_fs_1.existsSync)((0, node_path_1.join)(config_1.HOOKS_DIR, "user-prompt-submit.sh"));
47
+ }
48
+ catch {
49
+ return false;
50
+ }
51
+ }
52
+ async function runStatus(_argv) {
53
+ const workspaceId = resolveWorkspaceId();
54
+ if (!workspaceId) {
55
+ console.log("Meetless is not activated for this repo. Run `mla activate`.");
56
+ return 0;
57
+ }
58
+ const hooksInstalled = detectHooksInstalled();
59
+ console.log(renderStatus({ home: (0, node_os_1.homedir)(), workspaceId, hooksInstalled }));
60
+ return 0;
61
+ }