@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,67 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.rescanAndCache = rescanAndCache;
4
+ exports.resolveScanRoot = resolveScanRoot;
5
+ exports.resolveScanTarget = resolveScanTarget;
6
+ exports.runScanContext = runScanContext;
7
+ // src/commands/scan-context.ts
8
+ const node_os_1 = require("node:os");
9
+ const scan_1 = require("../lib/scanner/scan");
10
+ const cache_1 = require("../lib/scanner/cache");
11
+ const workspace_1 = require("../lib/workspace");
12
+ // Pure-ish core: scan, apply current verdicts, persist. Returns the applied result.
13
+ function rescanAndCache(args) {
14
+ const home = args.home ?? (0, node_os_1.homedir)();
15
+ const now = args.now ?? (() => new Date().toISOString());
16
+ const raw = (0, scan_1.scanWorkspace)(args.cwd, { workspaceId: args.workspaceId, now });
17
+ const applied = (0, cache_1.applyVerdicts)(raw, (0, cache_1.readVerdicts)(home, args.workspaceId));
18
+ (0, cache_1.writeScanCache)(home, args.workspaceId, applied);
19
+ return applied;
20
+ }
21
+ // The directory a scan must run FROM: the nearest .meetless.json marker dir if we
22
+ // are inside an activated workspace, else the start dir unchanged. `git ls-files`
23
+ // from a package subdir only lists that subtree, so anchoring here is what keeps a
24
+ // rescan whole-workspace (all rules, root-relative + stable ids) no matter which
25
+ // subdir the command was invoked from. Shared by every caller that rescans.
26
+ function resolveScanRoot(startDir) {
27
+ return (0, workspace_1.findWorkspaceContext)(startDir)?.markerDir ?? startDir;
28
+ }
29
+ // Decide which workspace to scan and which directory to scan FROM. Pure: takes
30
+ // the start dir, env, and argv so it is fully testable without mutating process
31
+ // state. Precedence for the id is MEETLESS_WORKSPACE_ID > --workspace > the
32
+ // nearest .meetless.json marker. The scan ROOT is always the marker directory
33
+ // when a marker exists, so invoking from a package subdir (apps/control) still
34
+ // scans the whole workspace; `git ls-files` from a subdir only lists that
35
+ // subtree, which is exactly how nested instruction files would otherwise be
36
+ // missed. Only when there is no marker at all do we fall back to the start dir,
37
+ // and that path requires an explicit id from env or flag.
38
+ function resolveScanTarget(opts) {
39
+ const ctx = (0, workspace_1.findWorkspaceContext)(opts.startDir);
40
+ const envWs = (opts.env.MEETLESS_WORKSPACE_ID ?? "").trim();
41
+ const flagWs = (argFlag(opts.argv, "--workspace") ?? "").trim();
42
+ const workspaceId = envWs || flagWs || ctx?.workspaceId;
43
+ if (!workspaceId) {
44
+ return {
45
+ error: "scan-context: no workspace id (run inside an activated workspace, " +
46
+ "set MEETLESS_WORKSPACE_ID, or pass --workspace)",
47
+ };
48
+ }
49
+ return { workspaceId, scanRoot: resolveScanRoot(opts.startDir) };
50
+ }
51
+ // Thin CLI wrapper: `mla _internal scan-context`. Resolves the workspace and the
52
+ // scan root from the marker (env/flag override the id), then scans + caches.
53
+ async function runScanContext(argv) {
54
+ const target = resolveScanTarget({ startDir: process.cwd(), env: process.env, argv });
55
+ if ("error" in target) {
56
+ console.error(target.error);
57
+ return 2;
58
+ }
59
+ const result = rescanAndCache({ cwd: target.scanRoot, workspaceId: target.workspaceId });
60
+ console.log(`scanned: ${result.inventory.instructionFiles} instruction files, ` +
61
+ `${result.directives.length} rules, ${result.inventory.staleSignals} stale signals`);
62
+ return 0;
63
+ }
64
+ function argFlag(argv, flag) {
65
+ const i = argv.indexOf(flag);
66
+ return i >= 0 ? argv[i + 1] : undefined;
67
+ }
@@ -0,0 +1,347 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseShowArgs = parseShowArgs;
4
+ exports.runSessionShow = runSessionShow;
5
+ exports.parseReconcileArgs = parseReconcileArgs;
6
+ exports.runSessionReconcile = runSessionReconcile;
7
+ const config_1 = require("../lib/config");
8
+ const http_1 = require("../lib/http");
9
+ const redactor_1 = require("../lib/redactor");
10
+ const reconcile_sessions_1 = require("../lib/reconcile-sessions");
11
+ const SHOW_DEFAULT_LIMIT = 100;
12
+ const SHOW_MAX_PAGES = 50;
13
+ function parseShowArgs(argv) {
14
+ const out = { json: false };
15
+ for (let i = 0; i < argv.length; i++) {
16
+ const a = argv[i];
17
+ if (a === "--json") {
18
+ out.json = true;
19
+ continue;
20
+ }
21
+ if (a === "--last") {
22
+ const next = argv[i + 1];
23
+ if (!next)
24
+ throw new Error("--last requires a positive integer (e.g. --last 20)");
25
+ const n = Number.parseInt(next, 10);
26
+ if (!Number.isFinite(n) || n <= 0) {
27
+ throw new Error(`--last must be a positive integer, got: ${next}`);
28
+ }
29
+ out.last = n;
30
+ i++;
31
+ continue;
32
+ }
33
+ if (a.startsWith("--") || a.startsWith("-")) {
34
+ throw new Error(`Unknown flag: ${a}. Supported flags: --json, --last <N>`);
35
+ }
36
+ if (out.sessionId) {
37
+ throw new Error(`Unexpected extra argument: ${a}. Expected at most one sessionId.`);
38
+ }
39
+ out.sessionId = a;
40
+ }
41
+ return out;
42
+ }
43
+ // Session-bound resolution: positional sid -> $CLAUDE_CODE_SESSION_ID -> fail.
44
+ // No network call, no workspace-latest fallback, so this command can never
45
+ // resolve to a session other than the one named or the one it runs inside.
46
+ // CLAUDE_CODE_SESSION_ID is the only honored env var (it is what Claude Code
47
+ // exports to subprocesses, and what `mla review`/`activate` both bind to). The
48
+ // legacy $SESSION_ID was a divergent name nothing ever set, so it
49
+ // is intentionally NOT consulted (consolidated 2026-05-31).
50
+ function resolveSessionId(positional) {
51
+ const p = (positional || "").trim();
52
+ if (p)
53
+ return { sessionId: p, source: "positional" };
54
+ const envSid = (process.env.CLAUDE_CODE_SESSION_ID || "").trim();
55
+ if (envSid)
56
+ return { sessionId: envSid, source: "env" };
57
+ throw new Error("No session id provided and no $CLAUDE_CODE_SESSION_ID env. " +
58
+ "Pass a sessionId: mla session show <sid>");
59
+ }
60
+ async function fetchEventsPage(cfg, sid, cursor) {
61
+ const params = new URLSearchParams();
62
+ params.set("workspaceId", cfg.workspaceId);
63
+ params.set("limit", String(SHOW_DEFAULT_LIMIT));
64
+ if (cursor)
65
+ params.set("cursor", cursor);
66
+ return await (0, http_1.get)(cfg, `/internal/v1/agent-runs/by-session/${encodeURIComponent(sid)}/events?${params.toString()}`, 15000);
67
+ }
68
+ function clipForHuman(text, max) {
69
+ if (text.length <= max)
70
+ return text;
71
+ return text.slice(0, max) + ` ...[+${text.length - max} chars]`;
72
+ }
73
+ function renderEventHuman(ev) {
74
+ const lines = [];
75
+ const ts = ev.occurredAt;
76
+ const payload = (ev.payload || {});
77
+ if (ev.eventType === "prompt_submitted") {
78
+ const text = String(payload.prompt ?? payload.text ?? "");
79
+ lines.push(`[${ts}] prompt_submitted`);
80
+ lines.push(indent(clipForHuman(text, 2000), " > "));
81
+ return lines;
82
+ }
83
+ if (ev.eventType === "tool_used_bash") {
84
+ const cmd = String(payload.command ?? "");
85
+ const exit = payload.exitCode ?? payload.exit_code ?? "?";
86
+ const stdout = String(payload.stdout ?? "");
87
+ const stderr = String(payload.stderr ?? "");
88
+ lines.push(`[${ts}] tool_used_bash exit=${exit}`);
89
+ lines.push(indent(clipForHuman(cmd, 240), " $ "));
90
+ if (stdout)
91
+ lines.push(indent(clipForHuman(tailLines(stdout, 20), 1200), " | "));
92
+ if (stderr)
93
+ lines.push(indent(clipForHuman(tailLines(stderr, 20), 1200), " ! "));
94
+ return lines;
95
+ }
96
+ if (ev.eventType === "session_stopped") {
97
+ const final = String(payload.finalMessage ?? payload.final_message ?? payload.text ?? "");
98
+ lines.push(`[${ts}] session_stopped`);
99
+ if (final)
100
+ lines.push(indent(clipForHuman(final, 2000), " = "));
101
+ return lines;
102
+ }
103
+ lines.push(`[${ts}] ${ev.eventType}`);
104
+ return lines;
105
+ }
106
+ function tailLines(text, n) {
107
+ const parts = text.split(/\r?\n/);
108
+ if (parts.length <= n)
109
+ return text;
110
+ return parts.slice(parts.length - n).join("\n");
111
+ }
112
+ function indent(text, prefix) {
113
+ return text
114
+ .split(/\r?\n/)
115
+ .map((l) => prefix + l)
116
+ .join("\n");
117
+ }
118
+ async function runSessionShow(argv) {
119
+ const cfg = (0, config_1.loadWorkspaceConfig)();
120
+ let args;
121
+ try {
122
+ args = parseShowArgs(argv);
123
+ }
124
+ catch (e) {
125
+ console.error(e.message);
126
+ return 2;
127
+ }
128
+ let resolved;
129
+ try {
130
+ resolved = resolveSessionId(args.sessionId);
131
+ }
132
+ catch (e) {
133
+ console.error(e.message);
134
+ return 1;
135
+ }
136
+ // In --json mode the announce line goes to stderr so stdout stays a clean
137
+ // JSON document (pipeable into jq / a file). In human mode it leads stdout
138
+ // so the operator sees which rung resolved before the event stream.
139
+ const announce = `Session: ${resolved.sessionId} (source: ${resolved.source})`;
140
+ if (args.json)
141
+ console.error(announce);
142
+ else
143
+ console.log(announce);
144
+ let merged = null;
145
+ let cursor = null;
146
+ let pagesFetched = 0;
147
+ try {
148
+ while (pagesFetched < SHOW_MAX_PAGES) {
149
+ const page = await fetchEventsPage(cfg, resolved.sessionId, cursor);
150
+ pagesFetched++;
151
+ if (!merged) {
152
+ merged = {
153
+ sessionId: page.sessionId,
154
+ externalSessionId: page.externalSessionId,
155
+ runId: page.runId,
156
+ events: [...page.events],
157
+ truncated: page.truncated,
158
+ nextCursor: page.nextCursor ?? null,
159
+ };
160
+ }
161
+ else {
162
+ merged.events.push(...page.events);
163
+ merged.truncated = merged.truncated || page.truncated;
164
+ merged.nextCursor = page.nextCursor ?? null;
165
+ }
166
+ if (!page.nextCursor)
167
+ break;
168
+ cursor = page.nextCursor;
169
+ }
170
+ // Loop hit the page budget without exhausting the feed. Mark the merged
171
+ // envelope truncated so the operator never silently sees a partial dump
172
+ // (without this flag, SHOW_MAX_PAGES * SHOW_DEFAULT_LIMIT events would look
173
+ // like the complete capture). The server's own `truncated` stays sticky.
174
+ // Use merged.nextCursor (faithfully tracks last page's nextCursor incl. null)
175
+ // rather than the loop-local `cursor`, which goes stale when the last page
176
+ // exits via line 600's break before line 601 advances it.
177
+ if (merged && pagesFetched >= SHOW_MAX_PAGES && merged.nextCursor) {
178
+ merged.truncated = true;
179
+ console.error(`Note: client page budget (${SHOW_MAX_PAGES} pages of ${SHOW_DEFAULT_LIMIT}) reached; ` +
180
+ `feed may have more events. Follow nextCursor to continue.`);
181
+ }
182
+ }
183
+ catch (e) {
184
+ const err = e;
185
+ if (err.status === 404) {
186
+ console.error(`No agent run found for session ${resolved.sessionId} in this workspace. ` +
187
+ `Was it captured by the hooks?`);
188
+ return 1;
189
+ }
190
+ throw e;
191
+ }
192
+ if (!merged) {
193
+ console.error("Unexpected: no events envelope returned.");
194
+ return 1;
195
+ }
196
+ // Defense-in-depth: the control endpoint already redacts, but apply the
197
+ // shared redactor again at the render boundary (principle 7) so the failure
198
+ // mode for a bug on the server side is "double-redacted output", not
199
+ // "leaked secrets in the operator's terminal".
200
+ const redactedEvents = merged.events.map((ev) => ({
201
+ ...ev,
202
+ payload: (0, redactor_1.redactPayload)(ev.payload),
203
+ }));
204
+ const displayEvents = args.last && args.last > 0 && args.last < redactedEvents.length
205
+ ? redactedEvents.slice(redactedEvents.length - args.last)
206
+ : redactedEvents;
207
+ if (args.json) {
208
+ const out = {
209
+ sessionId: merged.sessionId,
210
+ externalSessionId: merged.externalSessionId ?? null,
211
+ runId: merged.runId ?? null,
212
+ source: resolved.source,
213
+ truncated: merged.truncated,
214
+ nextCursor: merged.nextCursor ?? null,
215
+ totalReturned: redactedEvents.length,
216
+ displayed: displayEvents.length,
217
+ events: displayEvents,
218
+ };
219
+ console.log(JSON.stringify(out, null, 2));
220
+ if (merged.truncated) {
221
+ console.error("Note: server cap clipped the event window. Re-run with `--last N` or follow nextCursor.");
222
+ }
223
+ return 0;
224
+ }
225
+ if (displayEvents.length === 0) {
226
+ console.log("(no events captured for this session)");
227
+ if (merged.truncated) {
228
+ console.log("output truncated; pass --last N or follow nextCursor to continue.");
229
+ }
230
+ return 0;
231
+ }
232
+ if (args.last && args.last < redactedEvents.length) {
233
+ console.log(`(showing last ${args.last} of ${redactedEvents.length} events)`);
234
+ }
235
+ else {
236
+ console.log(`(${redactedEvents.length} event(s) captured)`);
237
+ }
238
+ for (const ev of displayEvents) {
239
+ for (const line of renderEventHuman(ev)) {
240
+ // Defense-in-depth at the line level too: any future code path that
241
+ // pulls a string straight from payload into a render line still passes
242
+ // through the redactor here.
243
+ console.log((0, redactor_1.redact)(line) ?? line);
244
+ }
245
+ }
246
+ if (merged.truncated) {
247
+ console.log("");
248
+ console.log("output truncated; pass `--last N` or follow `nextCursor` to continue.");
249
+ }
250
+ return 0;
251
+ }
252
+ function parseReconcileArgs(argv) {
253
+ const out = { dryRun: false, json: false };
254
+ for (const a of argv) {
255
+ if (a === "--dry-run") {
256
+ out.dryRun = true;
257
+ continue;
258
+ }
259
+ if (a === "--json") {
260
+ out.json = true;
261
+ continue;
262
+ }
263
+ if (a.startsWith("-")) {
264
+ throw new Error(`Unknown flag: ${a}. Supported flags: --dry-run, --json`);
265
+ }
266
+ // reconcile sweeps the entire workspace; it intentionally takes no sid.
267
+ throw new Error(`Unexpected argument: ${a}. \`mla session reconcile\` takes no positional ` +
268
+ "arguments (it sweeps the workspace). Supported flags: --dry-run, --json");
269
+ }
270
+ return out;
271
+ }
272
+ // Cap each sweep at the server's max page (clamped to 500 server-side). The
273
+ // working set is self-limiting: once a deleted session is archived it drops out of
274
+ // the default (`archived=false`) list, so a later sweep handles any remainder.
275
+ const RECONCILE_LIST_LIMIT = 500;
276
+ async function runSessionReconcile(argv) {
277
+ let args;
278
+ try {
279
+ args = parseReconcileArgs(argv);
280
+ }
281
+ catch (e) {
282
+ console.error(e.message);
283
+ return 2;
284
+ }
285
+ const cfg = (0, config_1.loadWorkspaceConfig)();
286
+ const resolver = (0, reconcile_sessions_1.makeTranscriptStatusResolver)();
287
+ const listSessions = async () => {
288
+ const params = new URLSearchParams();
289
+ params.set("workspaceId", cfg.workspaceId);
290
+ // archived=false: never re-touch an already-archived row (idempotent sweeps).
291
+ params.set("archived", "false");
292
+ // Only Claude Code has the no-delete-event gap this sweep exists to close.
293
+ params.set("adapter", "claude_code");
294
+ params.set("limit", String(RECONCILE_LIST_LIMIT));
295
+ return await (0, http_1.get)(cfg, `/internal/v1/agent-runs?${params.toString()}`, 15000);
296
+ };
297
+ const archive = async (sid) => {
298
+ await (0, http_1.post)(cfg, `/internal/v1/agent-runs/by-session/${encodeURIComponent(sid)}/archive`, { workspaceId: cfg.workspaceId }, 15000);
299
+ };
300
+ let result;
301
+ try {
302
+ result = await (0, reconcile_sessions_1.executeSessionReconcile)({ listSessions, resolver, archive }, { dryRun: args.dryRun });
303
+ }
304
+ catch (e) {
305
+ const err = e;
306
+ console.error(`Could not list sessions to reconcile: ${err.message}` +
307
+ (err.status ? ` (HTTP ${err.status})` : ""));
308
+ return 1;
309
+ }
310
+ const { plan, archived, failed, dryRun } = result;
311
+ if (args.json) {
312
+ console.log(JSON.stringify({
313
+ workspaceId: cfg.workspaceId,
314
+ dryRun,
315
+ scanned: plan.toArchive.length + plan.skipped.length,
316
+ toArchive: plan.toArchive,
317
+ archived,
318
+ failed,
319
+ skipped: plan.skipped,
320
+ }, null, 2));
321
+ return failed.length > 0 ? 1 : 0;
322
+ }
323
+ // Human render. Lead with the headline so an operator running it ad hoc sees
324
+ // the outcome immediately; detail (which sessions, why skipped) follows.
325
+ const scanned = plan.toArchive.length + plan.skipped.length;
326
+ if (plan.toArchive.length === 0) {
327
+ console.log(`Reconcile: scanned ${scanned} claude_code session(s); none have a deleted transcript. Nothing to archive.`);
328
+ return 0;
329
+ }
330
+ if (dryRun) {
331
+ console.log(`Reconcile (dry-run): ${plan.toArchive.length} of ${scanned} session(s) have a deleted transcript and WOULD be archived:`);
332
+ for (const id of plan.toArchive)
333
+ console.log(` - ${id}`);
334
+ console.log("Re-run without --dry-run to archive them.");
335
+ return 0;
336
+ }
337
+ console.log(`Reconcile: archived ${archived.length} of ${plan.toArchive.length} session(s) with a deleted transcript (scanned ${scanned}).`);
338
+ for (const id of archived)
339
+ console.log(` archived ${id}`);
340
+ if (failed.length > 0) {
341
+ console.error(`${failed.length} archive(s) failed:`);
342
+ for (const f of failed)
343
+ console.error(` ${f.sessionId}: ${f.error}`);
344
+ return 1;
345
+ }
346
+ return 0;
347
+ }