@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,781 @@
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.renderActivationCard = void 0;
37
+ exports.parseActivateArgs = parseActivateArgs;
38
+ exports.resolveBootstrapTier = resolveBootstrapTier;
39
+ exports.bootstrapTierEmitsMission = bootstrapTierEmitsMission;
40
+ exports.bootstrapTierIsDeepNotYet = bootstrapTierIsDeepNotYet;
41
+ exports.writeActivationMarker = writeActivationMarker;
42
+ exports.clearDeactivateSentinel = clearDeactivateSentinel;
43
+ exports.removeStaleGitignoreEntry = removeStaleGitignoreEntry;
44
+ exports.bootstrapCurrentSession = bootstrapCurrentSession;
45
+ exports.runActivate = runActivate;
46
+ exports.onboardRecommendation = onboardRecommendation;
47
+ exports.runMute = runMute;
48
+ exports.runUnmute = runUnmute;
49
+ exports.runDeactivate = runDeactivate;
50
+ const child_process_1 = require("child_process");
51
+ const fs = __importStar(require("fs"));
52
+ const path = __importStar(require("path"));
53
+ const readline = __importStar(require("readline"));
54
+ const activation_1 = require("../lib/activation");
55
+ const config_1 = require("../lib/config");
56
+ const http_1 = require("../lib/http");
57
+ const bootstrap_summary_1 = require("../lib/scanner/bootstrap-summary");
58
+ Object.defineProperty(exports, "renderActivationCard", { enumerable: true, get: function () { return bootstrap_summary_1.renderActivationCard; } });
59
+ const scout_mission_1 = require("../lib/scanner/scout-mission");
60
+ const scan_context_1 = require("./scan-context");
61
+ const workspace_1 = require("../lib/workspace");
62
+ const BOOTSTRAP_TIERS = ["fast", "agentic", "full"];
63
+ const VALUE_FLAGS = new Set(["--name", "--note", "--bootstrap"]);
64
+ const BOOLEAN_FLAGS = new Set(["--here", "--create", "--repair"]);
65
+ function parseActivateArgs(argv) {
66
+ const out = {};
67
+ for (let i = 0; i < argv.length; i++) {
68
+ const a = argv[i];
69
+ if (VALUE_FLAGS.has(a)) {
70
+ const v = argv[i + 1];
71
+ if (v === undefined || v.startsWith("-")) {
72
+ throw new Error(`Missing value for ${a}`);
73
+ }
74
+ if (a === "--name")
75
+ out.name = v;
76
+ else if (a === "--note")
77
+ out.note = v;
78
+ else if (a === "--bootstrap") {
79
+ if (!BOOTSTRAP_TIERS.includes(v)) {
80
+ throw new Error(`Invalid value for --bootstrap: ${v}. Supported tiers: ${BOOTSTRAP_TIERS.join(", ")}.`);
81
+ }
82
+ out.bootstrap = v;
83
+ }
84
+ i += 1;
85
+ continue;
86
+ }
87
+ if (BOOLEAN_FLAGS.has(a)) {
88
+ if (a === "--here")
89
+ out.here = true;
90
+ else if (a === "--create")
91
+ out.create = true;
92
+ else if (a === "--repair")
93
+ out.repair = true;
94
+ continue;
95
+ }
96
+ throw new Error(`Unknown argument: ${a}. Supported: ${[...VALUE_FLAGS, ...BOOLEAN_FLAGS].sort().join(", ")}`);
97
+ }
98
+ return out;
99
+ }
100
+ // The activation tail defaults to the `fast` tier when no `--bootstrap` was given,
101
+ // so the long-standing behavior is unchanged unless the operator opts into a
102
+ // deeper tier.
103
+ function resolveBootstrapTier(flags) {
104
+ return flags.bootstrap ?? "fast";
105
+ }
106
+ // Whether a tier emits the agentic scout mission after the review bundle. Only the
107
+ // deterministic `fast` tier stays silent; `agentic` and `full` both invite the
108
+ // deep read.
109
+ function bootstrapTierEmitsMission(tier) {
110
+ return tier !== "fast";
111
+ }
112
+ // Whether a tier asks for the deep temporal scan that is not built in this lane
113
+ // yet. Only `full` does; the activation tail uses this to print an honest "not yet,
114
+ // running agentic instead" note rather than silently under-delivering.
115
+ function bootstrapTierIsDeepNotYet(tier) {
116
+ return tier === "full";
117
+ }
118
+ // Pure marker writer (no console output). Writes a `.meetless.json` into `cwd`
119
+ // unless one already exists and `force` is not set. Returns whether a NEW marker
120
+ // was written (created=false means an existing marker was left untouched). The
121
+ // marker is strictly non-secret: workspaceId is an opaque tenant pointer and
122
+ // workspaceName is display-only, so the default note tells the human it is safe
123
+ // to commit.
124
+ function writeActivationMarker(cwd, workspaceId, opts = {}) {
125
+ const markerPath = path.join(cwd, activation_1.ACTIVATION_FILENAME);
126
+ if (fs.existsSync(markerPath) && !opts.force) {
127
+ return { markerPath, created: false };
128
+ }
129
+ const marker = {
130
+ workspaceId,
131
+ ...(opts.workspaceName ? { workspaceName: opts.workspaceName } : {}),
132
+ activatedAt: new Date().toISOString(),
133
+ note: opts.note ??
134
+ "Meetless workspace binding for this folder. Non-secret and safe to commit " +
135
+ "(it holds no credentials). Run `mla deactivate` to remove it.",
136
+ };
137
+ fs.writeFileSync(markerPath, JSON.stringify(marker, null, 2) + "\n", "utf8");
138
+ return { markerPath, created: true };
139
+ }
140
+ // Clear the per-session OFF sentinel for the CURRENT live session, if present.
141
+ // Returns the session id when a sentinel was removed (so the caller can report
142
+ // it), or null when there was nothing to clear / no live session. Pure fs; no
143
+ // console output. Used by `mla activate` to re-enable a session that was muted
144
+ // with `mla mute`.
145
+ function clearDeactivateSentinel() {
146
+ const liveSid = process.env.CLAUDE_CODE_SESSION_ID;
147
+ if (!liveSid)
148
+ return null;
149
+ const sentinel = path.join(config_1.SESSION_GATE_DIR, `${liveSid}.off`);
150
+ if (!fs.existsSync(sentinel))
151
+ return null;
152
+ fs.rmSync(sentinel, { force: true });
153
+ return liveSid;
154
+ }
155
+ // Best-effort: undo the OLD auto-gitignore behavior. The marker is committable
156
+ // and no longer force-ignored, so if a prior `mla activate` left a
157
+ // `.meetless.json` entry (and its banner comment) in the local `.gitignore`,
158
+ // strip it so the user is free to commit the marker. Returns a human message
159
+ // when something changed, else null. Never creates a `.gitignore`.
160
+ function removeStaleGitignoreEntry(dir) {
161
+ const gitignorePath = path.join(dir, ".gitignore");
162
+ if (!fs.existsSync(gitignorePath))
163
+ return null;
164
+ const body = fs.readFileSync(gitignorePath, "utf8");
165
+ const lines = body.split("\n");
166
+ const kept = lines.filter((l) => l.trim() !== activation_1.ACTIVATION_FILENAME &&
167
+ !l.startsWith("# Meetless per-folder activation marker"));
168
+ if (kept.length === lines.length)
169
+ return null;
170
+ fs.writeFileSync(gitignorePath, kept.join("\n"), "utf8");
171
+ return `removed stale ${activation_1.ACTIVATION_FILENAME} entry from ${gitignorePath}`;
172
+ }
173
+ // Bootstrap the CURRENT Claude Code session so capture takes effect NOW,
174
+ // without waiting for the next session. The current session's SessionStart
175
+ // hook already fired and exited dormant (no marker existed yet), so there is
176
+ // no AgentRun, no `session_started` spool line, and no repoPath sidecar for it.
177
+ // We reuse the installed session-start.sh as the canonical writer: with the
178
+ // marker now present its activation gate passes, and it writes the sidecar,
179
+ // spools session_started, and spawns the detached flush exactly as a real
180
+ // SessionStart would. Claude Code exports CLAUDE_CODE_SESSION_ID to hook
181
+ // subprocesses (and it equals the stdin session_id the hooks parse), so we can
182
+ // learn the live session id and feed it on stdin.
183
+ //
184
+ // Best-effort: a failure here never fails `mla activate`. The NEXT session in
185
+ // this folder still captures via the marker gate; this only buys the current
186
+ // one. Production stays dir-wise; this is the "get one session working now"
187
+ // affordance.
188
+ function bootstrapCurrentSession(dir) {
189
+ const sessionId = process.env.CLAUDE_CODE_SESSION_ID;
190
+ if (!sessionId) {
191
+ return { ok: false, detail: "not inside a Claude Code session (CLAUDE_CODE_SESSION_ID unset)" };
192
+ }
193
+ const sessionStart = path.join(config_1.HOOKS_DIR, "session-start.sh");
194
+ if (!fs.existsSync(sessionStart)) {
195
+ return {
196
+ ok: false,
197
+ sessionId,
198
+ detail: `installed hooks not found at ${sessionStart}; run 'mla init' to wire capture`,
199
+ };
200
+ }
201
+ try {
202
+ (0, child_process_1.execFileSync)("bash", [sessionStart], {
203
+ cwd: dir,
204
+ input: JSON.stringify({ session_id: sessionId, transcript_path: "" }),
205
+ env: process.env,
206
+ stdio: ["pipe", "ignore", "ignore"],
207
+ timeout: 15000,
208
+ });
209
+ return { ok: true, sessionId, detail: "bootstrapped" };
210
+ }
211
+ catch (e) {
212
+ return { ok: false, sessionId, detail: e.message };
213
+ }
214
+ }
215
+ // Probe a directory's Git context: whether cwd is inside a work tree, and the
216
+ // repo root if so. Both `git` calls swallow stderr and any non-Git failure maps
217
+ // to insideWorkTree=false, so a missing git binary or a non-repo directory is
218
+ // handled the same way (not inside Git).
219
+ function gitInfo(dir) {
220
+ try {
221
+ const inside = (0, child_process_1.execFileSync)("git", ["rev-parse", "--is-inside-work-tree"], {
222
+ cwd: dir,
223
+ encoding: "utf8",
224
+ stdio: ["ignore", "pipe", "ignore"],
225
+ }).trim();
226
+ if (inside !== "true")
227
+ return { insideWorkTree: false };
228
+ const root = (0, child_process_1.execFileSync)("git", ["rev-parse", "--show-toplevel"], {
229
+ cwd: dir,
230
+ encoding: "utf8",
231
+ stdio: ["ignore", "pipe", "ignore"],
232
+ }).trim();
233
+ return { insideWorkTree: true, root };
234
+ }
235
+ catch {
236
+ return { insideWorkTree: false };
237
+ }
238
+ }
239
+ // Compare two directory paths for identity, resolving symlinks. On macOS
240
+ // `process.cwd()` reports the physical path (/private/var/...) while
241
+ // `git rev-parse --show-toplevel` may report through the /var symlink; realpath
242
+ // on both sides makes the repo-root check robust to that.
243
+ function sameDir(a, b) {
244
+ try {
245
+ return fs.realpathSync(a) === fs.realpathSync(b);
246
+ }
247
+ catch {
248
+ return path.resolve(a) === path.resolve(b);
249
+ }
250
+ }
251
+ // Loads machine credentials (controlUrl, controlToken, actor) from
252
+ // cli-config.json. cli-config no longer carries the workspaceId (T1.1); this
253
+ // only fetches the creds the provision POST / repair probe need.
254
+ function loadCfgOrExplain() {
255
+ if (!(0, config_1.configExists)()) {
256
+ console.error(`cli-config.json not found at ${config_1.CFG_PATH}. Run 'mla init --control-token <token>' first.`);
257
+ return 2;
258
+ }
259
+ try {
260
+ return (0, config_1.readConfig)();
261
+ }
262
+ catch (e) {
263
+ console.error(e.message);
264
+ return 2;
265
+ }
266
+ }
267
+ async function runActivate(argv) {
268
+ let flags;
269
+ try {
270
+ flags = parseActivateArgs(argv);
271
+ }
272
+ catch (e) {
273
+ console.error(e.message);
274
+ return 2;
275
+ }
276
+ const cwd = process.cwd();
277
+ // `--repair` re-checks an existing binding's membership/connectivity ONLY. It
278
+ // never mints a new id (An, 2026-06-04): re-creation is an explicit
279
+ // `mla deactivate` then `mla activate`.
280
+ if (flags.repair) {
281
+ return runRepair(cwd);
282
+ }
283
+ // `--here` (in-Git subdir override) and `--create` (non-Git override) are two
284
+ // distinct flags, never overloaded (INV-FLAGS-1). Passing both is a category
285
+ // error, refused before any side effect.
286
+ if (flags.here && flags.create) {
287
+ console.error("`--here` and `--create` cannot be combined: --here is the in-Git subdir " +
288
+ "override, --create is the non-Git override.");
289
+ return 2;
290
+ }
291
+ // Create-vs-bind keys on marker PRESENCE. Under `--here` the resolution is
292
+ // narrowed to a marker exactly AT cwd (INV-ACTIVATE-1): a parent marker does
293
+ // NOT bind, so `--here` provisions a shadowing sub-project workspace even when
294
+ // a parent marker exists (the monorepo sub-project case).
295
+ const cwdMarkerPath = path.join(cwd, activation_1.ACTIVATION_FILENAME);
296
+ const existing = flags.here
297
+ ? fs.existsSync(cwdMarkerPath)
298
+ ? (0, activation_1.findActivation)(cwd)
299
+ : null
300
+ : (0, activation_1.findActivation)(cwd);
301
+ if (existing) {
302
+ return runBind(existing, cwd, resolveBootstrapTier(flags));
303
+ }
304
+ const git = gitInfo(cwd);
305
+ const guard = checkCreateGuard(flags, git, cwd);
306
+ if (guard !== 0)
307
+ return guard;
308
+ return runProvision(cwd, flags);
309
+ }
310
+ // Repo-root guard (INV-FLAGS-1). Returns 0 to allow provisioning, or a non-zero
311
+ // exit code after printing the refusal. Called only when no marker resolves.
312
+ function checkCreateGuard(flags, git, cwd) {
313
+ if (flags.here) {
314
+ // --here is the in-Git subdir override; it only applies inside a Git tree.
315
+ if (!git.insideWorkTree) {
316
+ console.error("`--here` only applies inside a Git repository.");
317
+ console.error("This directory is not inside a Git repository. To create a workspace " +
318
+ "here anyway, use `mla activate --create`.");
319
+ return 2;
320
+ }
321
+ return 0;
322
+ }
323
+ if (flags.create) {
324
+ // --create is the non-Git override; it is refused inside a Git tree, where
325
+ // the safe paths are the repo root (no flag) or a subdir (`--here`).
326
+ if (git.insideWorkTree) {
327
+ const atRoot = git.root ? sameDir(cwd, git.root) : false;
328
+ console.error("`--create` is for directories that are NOT inside a Git repository.");
329
+ if (atRoot) {
330
+ console.error("You are at a Git repo root; run `mla activate` (no flag) to provision here.");
331
+ }
332
+ else {
333
+ console.error("You are in a Git subdir; run `mla activate --here` to bind this subdir, " +
334
+ "or cd to the repo root and run `mla activate`.");
335
+ }
336
+ return 2;
337
+ }
338
+ return 0;
339
+ }
340
+ // No override flag. Outside Git, refuse and point at --create.
341
+ if (!git.insideWorkTree) {
342
+ console.error("No Meetless workspace is bound here, and this directory is not inside a Git repository.");
343
+ console.error("To create a workspace here, run `mla activate --create`.");
344
+ return 2;
345
+ }
346
+ // Inside Git with no flag: auto-create only at the repo root.
347
+ const atRoot = git.root ? sameDir(cwd, git.root) : false;
348
+ if (atRoot)
349
+ return 0;
350
+ console.error("No Meetless workspace is bound here.");
351
+ console.error("");
352
+ console.error("You are inside a Git repository but not at its root:");
353
+ console.error(` repo root: ${git.root}`);
354
+ console.error(` cwd: ${cwd}`);
355
+ console.error("");
356
+ console.error("Run one of:");
357
+ console.error(` cd ${git.root} && mla activate`);
358
+ console.error(" mla activate --here");
359
+ return 2;
360
+ }
361
+ // Provision a fresh workspace server-side and write its id into the marker at
362
+ // cwd. The owner is the authenticated caller (resolved server-side from the
363
+ // actor identity), never the request body, so a caller cannot mint a workspace
364
+ // owned by someone else.
365
+ async function runProvision(cwd, flags) {
366
+ const loaded = loadCfgOrExplain();
367
+ if (typeof loaded === "number")
368
+ return loaded;
369
+ const cfg = loaded;
370
+ const name = (flags.name && flags.name.trim()) || path.basename(cwd);
371
+ let resp;
372
+ try {
373
+ resp = await (0, http_1.post)(cfg, "/internal/v1/workspaces", { name });
374
+ }
375
+ catch (e) {
376
+ const err = e;
377
+ if (err.status === 401 || err.status === 403) {
378
+ console.error("Control rejected the provision request (not authorized). Check `mla doctor` and your token.");
379
+ }
380
+ else if (err.status !== undefined) {
381
+ console.error(`Control could not provision the workspace (HTTP ${err.status}).`);
382
+ }
383
+ else {
384
+ console.error("Could not reach control to provision the workspace. Is it running? (`mla doctor`)");
385
+ }
386
+ return 1;
387
+ }
388
+ const { markerPath } = writeActivationMarker(cwd, resp.id, {
389
+ force: true,
390
+ workspaceName: resp.name,
391
+ note: flags.note,
392
+ });
393
+ console.log(`Provisioned workspace ${resp.id} (${resp.name}).`);
394
+ console.log(` marker: ${markerPath}`);
395
+ console.log(` workspaceId: ${resp.id}`);
396
+ console.log("");
397
+ console.log("Commit guidance:");
398
+ console.log(` ${activation_1.ACTIVATION_FILENAME} is untracked and not gitignored; it holds no secrets.`);
399
+ console.log(" Commit it to share this workspace binding with the team, or leave it");
400
+ console.log(" uncommitted to keep the binding local to this clone.");
401
+ const giResult = removeStaleGitignoreEntry(cwd);
402
+ if (giResult)
403
+ console.log(` gitignore: ${giResult}`);
404
+ // Fresh workspace = empty governed KB: invite onboarding (one-time per workspace).
405
+ return finishActivate(cwd, resolveBootstrapTier(flags), true);
406
+ }
407
+ // Bind to an already-resolved marker. Provisions nothing; the marker is local
408
+ // truth for "which workspace this folder runs under".
409
+ function runBind(found, cwd, tier) {
410
+ const nameSuffix = found.workspaceName ? ` (${found.workspaceName})` : "";
411
+ const id = found.workspaceId ?? "(no workspaceId in marker)";
412
+ console.log(`Already activated: ${found.path} -> ${id}${nameSuffix}`);
413
+ console.log(" Marker unchanged; this folder is already bound to a workspace.");
414
+ const giResult = removeStaleGitignoreEntry(found.dir);
415
+ if (giResult)
416
+ console.log(` gitignore: ${giResult}`);
417
+ return finishActivate(cwd, tier);
418
+ }
419
+ // `mla activate --repair`: re-check an existing binding's health WITHOUT ever
420
+ // minting a new id (An, 2026-06-04). A missing/inaccessible workspace is
421
+ // surfaced loudly and the user is pointed at deactivate+activate to re-create;
422
+ // repair itself never re-creates.
423
+ async function runRepair(cwd) {
424
+ const found = (0, activation_1.findActivation)(cwd);
425
+ if (!found) {
426
+ console.error("Nothing to repair: no .meetless.json is bound to this folder.");
427
+ console.error(" Run `mla activate` to create or bind a workspace here.");
428
+ return 2;
429
+ }
430
+ if (!found.workspaceId) {
431
+ console.error(`Nothing to repair: ${found.path} has no usable workspaceId (stale marker).`);
432
+ console.error(" Re-create the binding with `mla deactivate` then `mla activate`.");
433
+ return 2;
434
+ }
435
+ const loaded = loadCfgOrExplain();
436
+ if (typeof loaded === "number")
437
+ return loaded;
438
+ const cfg = loaded;
439
+ console.log(`Checking binding: ${found.workspaceId} (${found.path})`);
440
+ try {
441
+ await (0, http_1.get)(cfg, `/internal/v1/workspaces/me?workspaceId=${encodeURIComponent(found.workspaceId)}`, 5000);
442
+ console.log(" Status: active (exists and reachable). Nothing to repair.");
443
+ return 0;
444
+ }
445
+ catch (e) {
446
+ const err = e;
447
+ if (err.status === 404) {
448
+ console.error(` Status: bound to ${found.workspaceId}, but the workspace does not exist or is inaccessible.`);
449
+ console.error(" `mla activate --repair` never re-creates a workspace; run `mla deactivate` " +
450
+ "then `mla activate` to mint a new one.");
451
+ return 1;
452
+ }
453
+ if (err.status === 401 || err.status === 403) {
454
+ console.error(` Status: bound to ${found.workspaceId}, but your token is not a member. ` +
455
+ "Ask a workspace owner to add you.");
456
+ return 1;
457
+ }
458
+ // Network error / control down: never fail repair on transient unreachability.
459
+ console.log(` Status: could not verify with control (${err.status ?? "offline"}). ` +
460
+ "The local binding still applies.");
461
+ return 0;
462
+ }
463
+ }
464
+ // One-time nudge toward `/mla onboard`, the agent-driven repo onboarding that seeds
465
+ // the governed KB from the repo's docs and git history (the mla-onboard skill wired
466
+ // by `mla init`/`rewire`). Pure: returns the text to print, or null to stay silent.
467
+ //
468
+ // Shown only when BOTH hold:
469
+ // - inSession: there is a live Claude Code session, so the `/mla onboard` slash
470
+ // command is actually invokable (it is a no-op suggestion from a bare shell).
471
+ // - justProvisioned: this run created a brand-new workspace, whose governed KB is
472
+ // empty: exactly the moment onboarding pays off. Re-running `mla activate` on an
473
+ // already-bound folder takes the bind path (no provision), so the nudge is
474
+ // naturally one-time per workspace without any sentinel state.
475
+ function onboardRecommendation(opts) {
476
+ if (!opts.inSession || !opts.justProvisioned)
477
+ return null;
478
+ return [
479
+ "Next: seed this workspace's governed memory from the repo.",
480
+ " Run `/mla onboard` to dispatch two read-only scouts over your docs and git",
481
+ " history. They surface constraints, decisions, conventions, boundaries, and",
482
+ " deprecations as candidates born PENDING for you to review; nothing is accepted",
483
+ " automatically. You can run it now or any time later.",
484
+ ].join("\n");
485
+ }
486
+ // Shared tail for the provision/bind paths: clear any per-session OFF sentinel,
487
+ // then bootstrap the current session so capture starts NOW (not next session). The
488
+ // bootstrap tier decides whether the activation preview also emits the agentic
489
+ // scout mission (fast = review bundle only; agentic/full = bundle + mission).
490
+ // recommendOnboard is set only by the provision path, so the `/mla onboard` nudge
491
+ // fires once per fresh workspace (see onboardRecommendation).
492
+ function finishActivate(cwd, tier, recommendOnboard = false) {
493
+ // Re-running `mla activate` inside a session that was previously muted with
494
+ // `mla mute` is one supported way to turn it back ON (the other is
495
+ // `mla unmute`): clear the per-session sentinel FIRST, so the bootstrap below
496
+ // (and every subsequent hook) is no longer short-circuited by
497
+ // meetless_session_disabled.
498
+ const clearedSid = clearDeactivateSentinel();
499
+ if (clearedSid) {
500
+ console.log("");
501
+ console.log(`Cleared a prior \`mla mute\` for this session (${clearedSid.slice(0, 8)}); capture is back ON.`);
502
+ }
503
+ // Deterministic preview (Regime 1): scan + cache, then show the review bundle.
504
+ // Never block activation on the preview; it is reassurance, not a gate.
505
+ try {
506
+ const scanWorkspaceId = (0, workspace_1.tryResolveWorkspaceId)(cwd); // existing resolver from ../lib/workspace
507
+ if (scanWorkspaceId) {
508
+ const result = (0, scan_context_1.rescanAndCache)({ cwd, workspaceId: scanWorkspaceId });
509
+ console.log("");
510
+ console.log((0, bootstrap_summary_1.renderBootstrapSummary)(result));
511
+ // Deeper tiers invite the agentic scout to read the messy Tier-2 docs the
512
+ // deterministic pass could only count. `full` additionally asks for the
513
+ // temporal legacy-note graph, which is the canonical agent's lane and not
514
+ // built here, so we say so plainly and fall back to the agentic mission.
515
+ if (bootstrapTierEmitsMission(tier)) {
516
+ if (bootstrapTierIsDeepNotYet(tier)) {
517
+ console.log("");
518
+ console.log("The `full` tier (temporal legacy-note graph) is not built yet; it depends on the");
519
+ console.log("coordination graph. Running the agentic tier below, the deepest available now.");
520
+ }
521
+ console.log("");
522
+ console.log("Agentic scout mission (hand this to a coding agent):");
523
+ console.log("");
524
+ console.log((0, scout_mission_1.renderManualScoutMission)(result));
525
+ }
526
+ else {
527
+ // Fast tier: do not hide the deeper bootstrap. When deep docs went unread,
528
+ // nudge the operator toward `mla activate --bootstrap agentic`.
529
+ const invite = (0, scout_mission_1.renderAgenticInvitation)(result);
530
+ if (invite) {
531
+ console.log("");
532
+ console.log(invite);
533
+ }
534
+ }
535
+ }
536
+ }
537
+ catch {
538
+ // swallow: the preview must never fail activation
539
+ }
540
+ const boot = bootstrapCurrentSession(cwd);
541
+ console.log("");
542
+ if (boot.ok) {
543
+ console.log(`Capture is active NOW for this session (${boot.sessionId.slice(0, 8)}); no restart needed.`);
544
+ console.log("Run `mla review` inside this session to see the console URLs + captured review.");
545
+ }
546
+ else {
547
+ console.log("Capture takes effect on the NEXT Claude Code session started from this folder.");
548
+ // Only explain when we were inside a session but the bootstrap could not
549
+ // run (e.g. hooks not installed); a plain non-session invocation needs no
550
+ // scary detail.
551
+ if (boot.sessionId) {
552
+ console.log(` (current session not bootstrapped: ${boot.detail})`);
553
+ }
554
+ }
555
+ // A live Claude Code session is what makes `/mla onboard` invokable; key off the
556
+ // session id (present even if the bootstrap hook itself could not run), not boot.ok.
557
+ const onboard = onboardRecommendation({
558
+ inSession: !!boot.sessionId,
559
+ justProvisioned: recommendOnboard,
560
+ });
561
+ if (onboard) {
562
+ console.log("");
563
+ console.log(onboard);
564
+ }
565
+ return 0;
566
+ }
567
+ // `mla mute` (per-session capture OFF, folder = workspace T2.3).
568
+ //
569
+ // Silences the CURRENT live Claude Code session, both capture AND Push, even
570
+ // inside an activated folder, by dropping a `<sid>.off` sentinel into
571
+ // SESSION_GATE_DIR. The capture hooks check meetless_session_disabled (after the
572
+ // folder gate, once the session id is parsed) and exit 0 when the sentinel
573
+ // exists. This is the dogfooding A/B affordance: run the same repo with the
574
+ // pipeline on in one session and off in another, with no folder churn.
575
+ //
576
+ // Scope is deliberately the SESSION, not the folder: `mute` never touches
577
+ // `.meetless.json`. To unbind a whole folder from its workspace, run
578
+ // `mla deactivate`. Re-enable this session with `mla unmute` (or `mla activate`).
579
+ async function runMute(argv) {
580
+ if (argv.length > 0) {
581
+ console.error(`Unknown argument: ${argv[0]}. \`mla mute\` takes no arguments.`);
582
+ return 2;
583
+ }
584
+ const sessionId = process.env.CLAUDE_CODE_SESSION_ID;
585
+ if (!sessionId) {
586
+ console.error("mla mute must run INSIDE a live Claude Code session (CLAUDE_CODE_SESSION_ID is unset).");
587
+ console.error("It silences the CURRENT session only. To unbind a whole folder from its workspace, run `mla deactivate`.");
588
+ return 2;
589
+ }
590
+ fs.mkdirSync(config_1.SESSION_GATE_DIR, { recursive: true });
591
+ const sentinel = path.join(config_1.SESSION_GATE_DIR, `${sessionId}.off`);
592
+ fs.writeFileSync(sentinel, new Date().toISOString() + "\n", "utf8");
593
+ console.log(`Muted this session (${sessionId.slice(0, 8)}): capture AND Push are now OFF.`);
594
+ console.log(` sentinel: ${sentinel}`);
595
+ console.log(" Takes effect on the next hook fire (prompt, tool use, or stop).");
596
+ console.log(" Re-run `mla unmute` (or `mla activate`) in this session to turn it back on.");
597
+ return 0;
598
+ }
599
+ // `mla unmute` (per-session capture back ON, folder = workspace T2.3).
600
+ //
601
+ // Removes the `<sid>.off` sentinel for the CURRENT live session, undoing a prior
602
+ // `mla mute`. Like `mute`, it is strictly session-scope and never touches
603
+ // `.meetless.json`. A no-op (exit 0) when the session was not muted.
604
+ async function runUnmute(argv) {
605
+ if (argv.length > 0) {
606
+ console.error(`Unknown argument: ${argv[0]}. \`mla unmute\` takes no arguments.`);
607
+ return 2;
608
+ }
609
+ const sessionId = process.env.CLAUDE_CODE_SESSION_ID;
610
+ if (!sessionId) {
611
+ console.error("mla unmute must run INSIDE a live Claude Code session (CLAUDE_CODE_SESSION_ID is unset).");
612
+ console.error("It re-enables the CURRENT session only.");
613
+ return 2;
614
+ }
615
+ const sentinel = path.join(config_1.SESSION_GATE_DIR, `${sessionId}.off`);
616
+ if (!fs.existsSync(sentinel)) {
617
+ console.log(`This session (${sessionId.slice(0, 8)}) was not muted; nothing to do.`);
618
+ return 0;
619
+ }
620
+ fs.rmSync(sentinel, { force: true });
621
+ console.log(`Unmuted this session (${sessionId.slice(0, 8)}): capture is back ON.`);
622
+ console.log(" Takes effect on the next hook fire (prompt, tool use, or stop).");
623
+ return 0;
624
+ }
625
+ function parseDeactivateArgs(argv) {
626
+ const out = {};
627
+ for (let i = 0; i < argv.length; i++) {
628
+ const a = argv[i];
629
+ if (a === "--marker") {
630
+ const v = argv[i + 1];
631
+ if (v === undefined || v.startsWith("-"))
632
+ throw new Error("Missing value for --marker");
633
+ out.marker = v;
634
+ i += 1;
635
+ continue;
636
+ }
637
+ if (a === "--yes") {
638
+ out.yes = true;
639
+ continue;
640
+ }
641
+ if (a === "--from-root") {
642
+ out.fromRoot = true;
643
+ continue;
644
+ }
645
+ throw new Error(`Unknown argument: ${a}. \`mla deactivate\` accepts --yes, --from-root, --marker <path>.`);
646
+ }
647
+ return out;
648
+ }
649
+ // Best-effort read of a marker's workspaceId for human-facing messages. A
650
+ // malformed or missing file yields undefined; never throws.
651
+ function readMarkerWorkspaceId(markerPath) {
652
+ try {
653
+ const raw = JSON.parse(fs.readFileSync(markerPath, "utf8"));
654
+ return typeof raw.workspaceId === "string" && raw.workspaceId.trim()
655
+ ? raw.workspaceId
656
+ : undefined;
657
+ }
658
+ catch {
659
+ return undefined;
660
+ }
661
+ }
662
+ // Interactive y/N prompt. Only reached on a real TTY (the non-interactive path
663
+ // refuses before calling this), so reading stdin can never hang a script.
664
+ function promptYesNo(question) {
665
+ return new Promise((resolve) => {
666
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
667
+ rl.question(question, (answer) => {
668
+ rl.close();
669
+ const a = answer.trim().toLowerCase();
670
+ resolve(a === "y" || a === "yes");
671
+ });
672
+ });
673
+ }
674
+ // `mla deactivate` (workspace-binding removal, folder = workspace T2.2).
675
+ //
676
+ // Removes the nearest `.meetless.json`, unbinding this folder from its
677
+ // workspace (future sessions under it stop capturing). This is NOT a per-session
678
+ // off switch any more; that is `mla mute`.
679
+ //
680
+ // Guards (INV-DEACTIVATE-1 + nested-dir safety):
681
+ // - Confirms before deleting; `--yes` skips the prompt. In a non-interactive
682
+ // context (no TTY) it refuses without `--yes` rather than hang.
683
+ // - When the nearest marker lives in an ANCESTOR of cwd (the monorepo case),
684
+ // a plain run refuses: removing it would unbind the whole subtree. The user
685
+ // opts in with `--from-root` (remove the resolved ancestor) or
686
+ // `--marker <path>` (target a specific marker explicitly).
687
+ async function runDeactivate(argv) {
688
+ let flags;
689
+ try {
690
+ flags = parseDeactivateArgs(argv);
691
+ }
692
+ catch (e) {
693
+ console.error(e.message);
694
+ return 2;
695
+ }
696
+ if (flags.marker && flags.fromRoot) {
697
+ console.error("`--marker` and `--from-root` cannot be combined: --marker already names an " +
698
+ "explicit target; --from-root is for the resolved ancestor marker.");
699
+ return 2;
700
+ }
701
+ const cwd = process.cwd();
702
+ // Resolve the target marker path + the directory it binds.
703
+ let targetPath;
704
+ let targetDir;
705
+ let workspaceId;
706
+ if (flags.marker) {
707
+ // Explicit path = explicit intent: no locality guard. Resolve, accept a
708
+ // directory by appending the marker filename, and require the basename to
709
+ // be the marker so we never `rm` an arbitrary file.
710
+ let p = path.resolve(cwd, flags.marker);
711
+ if (fs.existsSync(p) && fs.statSync(p).isDirectory()) {
712
+ p = path.join(p, activation_1.ACTIVATION_FILENAME);
713
+ }
714
+ if (path.basename(p) !== activation_1.ACTIVATION_FILENAME) {
715
+ console.error(`--marker must point at a ${activation_1.ACTIVATION_FILENAME} file (got ${flags.marker}).`);
716
+ return 2;
717
+ }
718
+ if (!fs.existsSync(p)) {
719
+ console.error(`No marker at ${p}.`);
720
+ return 1;
721
+ }
722
+ targetPath = p;
723
+ targetDir = path.dirname(p);
724
+ workspaceId = readMarkerWorkspaceId(p);
725
+ }
726
+ else {
727
+ const found = (0, activation_1.findActivation)(cwd);
728
+ if (!found) {
729
+ console.error("Nothing to deactivate: no .meetless.json binding resolves from here.");
730
+ console.error(" (Use `mla mute` to silence just the current session.)");
731
+ return 1;
732
+ }
733
+ // Nested-dir safety: an ancestor marker is not removed from a subdir without
734
+ // an explicit opt-in, even with `--yes` (which only skips the y/N prompt).
735
+ if (!sameDir(found.dir, cwd) && !flags.fromRoot) {
736
+ console.error("The nearest workspace binding is in a parent directory, not here:");
737
+ console.error(` marker: ${found.path}`);
738
+ console.error(` cwd: ${cwd}`);
739
+ console.error("");
740
+ console.error("Removing it would unbind the whole subtree, not just this folder.");
741
+ console.error("Re-run with `--from-root` to remove that parent binding, or");
742
+ console.error("`--marker <path>` to target a specific .meetless.json.");
743
+ return 1;
744
+ }
745
+ targetPath = found.path;
746
+ targetDir = found.dir;
747
+ workspaceId = found.workspaceId;
748
+ }
749
+ // Confirm-before-delete context (INV-DEACTIVATE-1). Shown on every path so the
750
+ // operator sees what would change even when the run refuses.
751
+ console.log("Found marker:");
752
+ console.log(` ${targetPath}`);
753
+ console.log("");
754
+ console.log("`mla deactivate` REMOVES this folder workspace binding (it no longer");
755
+ console.log("just suppresses this session; that is `mla mute`).");
756
+ console.log("");
757
+ if (!flags.yes) {
758
+ if (!process.stdin.isTTY) {
759
+ console.error("Refusing to remove a workspace binding without confirmation in a non-interactive context.");
760
+ console.error("Re-run with `--yes` to deactivate non-interactively.");
761
+ return 1;
762
+ }
763
+ const ok = await promptYesNo("Deactivate this workspace binding? [y/N] ");
764
+ if (!ok) {
765
+ console.log("Aborted; marker left in place.");
766
+ return 0;
767
+ }
768
+ }
769
+ fs.rmSync(targetPath, { force: true });
770
+ const wasBound = workspaceId ? ` (was bound to ${workspaceId})` : "";
771
+ console.log(`Removed ${targetPath}.${wasBound}`);
772
+ console.log(`Future sessions under ${targetDir} will not be captured unless another parent marker applies.`);
773
+ // Helpful for monorepos: after removing the nearer marker, re-resolve from the
774
+ // same dir to see whether a parent marker now governs the subtree, and say so.
775
+ const stillApplies = (0, activation_1.findActivation)(targetDir);
776
+ if (stillApplies) {
777
+ const sfx = stillApplies.workspaceId ? ` -> ${stillApplies.workspaceId}` : "";
778
+ console.log(` Note: a parent marker still governs this subtree: ${stillApplies.path}${sfx}`);
779
+ }
780
+ return 0;
781
+ }