@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.
- package/LICENSE +201 -0
- package/README.md +81 -0
- package/dist/build-info.json +9 -0
- package/dist/bundles/ask-core.js +396 -0
- package/dist/bundles/mcp.js +16592 -0
- package/dist/bundles/trace-core.js +263 -0
- package/dist/cli.js +828 -0
- package/dist/commands/activate.js +781 -0
- package/dist/commands/adoption.js +130 -0
- package/dist/commands/ask.js +290 -0
- package/dist/commands/context.js +114 -0
- package/dist/commands/debug.js +313 -0
- package/dist/commands/doctor.js +1021 -0
- package/dist/commands/enrich.js +427 -0
- package/dist/commands/evidence.js +229 -0
- package/dist/commands/flush.js +184 -0
- package/dist/commands/graph.js +104 -0
- package/dist/commands/init.js +272 -0
- package/dist/commands/internal-active-review.js +322 -0
- package/dist/commands/internal-auto-index.js +188 -0
- package/dist/commands/internal-capture-decisions.js +320 -0
- package/dist/commands/internal-evidence-correlate.js +239 -0
- package/dist/commands/internal-evidence-hooks.js +240 -0
- package/dist/commands/internal-evidence-inject.js +231 -0
- package/dist/commands/internal-finalize.js +221 -0
- package/dist/commands/internal-pretool-observe.js +225 -0
- package/dist/commands/internal-refresh.js +136 -0
- package/dist/commands/internal-session-nudge.js +120 -0
- package/dist/commands/internal-steer-sync.js +117 -0
- package/dist/commands/internal-turn-recap.js +140 -0
- package/dist/commands/kb.js +375 -0
- package/dist/commands/kb_add.js +681 -0
- package/dist/commands/kb_forget.js +283 -0
- package/dist/commands/kb_move.js +45 -0
- package/dist/commands/kb_pending.js +410 -0
- package/dist/commands/kb_personal.js +149 -0
- package/dist/commands/kb_promote.js +188 -0
- package/dist/commands/kb_purge.js +168 -0
- package/dist/commands/kb_reingest.js +335 -0
- package/dist/commands/kb_retime.js +170 -0
- package/dist/commands/kb_review.js +391 -0
- package/dist/commands/kb_revision.js +179 -0
- package/dist/commands/kb_show.js +385 -0
- package/dist/commands/label.js +226 -0
- package/dist/commands/login.js +295 -0
- package/dist/commands/logout.js +108 -0
- package/dist/commands/mcp-supervisor.js +93 -0
- package/dist/commands/mcp.js +227 -0
- package/dist/commands/queue-prune.js +98 -0
- package/dist/commands/review.js +358 -0
- package/dist/commands/rewire.js +124 -0
- package/dist/commands/rules.js +728 -0
- package/dist/commands/scan-context.js +67 -0
- package/dist/commands/session.js +347 -0
- package/dist/commands/stats.js +479 -0
- package/dist/commands/status.js +61 -0
- package/dist/commands/summary.js +250 -0
- package/dist/commands/turn.js +114 -0
- package/dist/commands/uninstall.js +222 -0
- package/dist/commands/whoami.js +102 -0
- package/dist/commands/workspace.js +130 -0
- package/dist/hooks-template/ce0-post-tool-use.sh +34 -0
- package/dist/hooks-template/ce0-session-start.sh +49 -0
- package/dist/hooks-template/ce0-stop.sh +29 -0
- package/dist/hooks-template/ce0-user-prompt-submit.sh +38 -0
- package/dist/hooks-template/common.sh +934 -0
- package/dist/hooks-template/event-batch-filter.jq +67 -0
- package/dist/hooks-template/flush.sh +503 -0
- package/dist/hooks-template/post-tool-use.sh +423 -0
- package/dist/hooks-template/pre-tool-use.sh +69 -0
- package/dist/hooks-template/session-start.sh +140 -0
- package/dist/hooks-template/stop.sh +308 -0
- package/dist/hooks-template/user-prompt-submit.sh +1162 -0
- package/dist/lib/activation.js +79 -0
- package/dist/lib/active-conflict-cache.js +141 -0
- package/dist/lib/active-memory.js +59 -0
- package/dist/lib/active-review-runner.js +26 -0
- package/dist/lib/agent-decision/index.js +25 -0
- package/dist/lib/agent-decision/keys.js +49 -0
- package/dist/lib/agent-decision/normalize-claude.js +183 -0
- package/dist/lib/agent-decision/types.js +21 -0
- package/dist/lib/agent-decision/validate.js +216 -0
- package/dist/lib/analytics/capture.js +96 -0
- package/dist/lib/analytics/command-event.js +267 -0
- package/dist/lib/analytics/consent.js +58 -0
- package/dist/lib/analytics/coverage-gap.js +96 -0
- package/dist/lib/analytics/envelope.js +236 -0
- package/dist/lib/analytics/event-id.js +86 -0
- package/dist/lib/analytics/evidence.js +150 -0
- package/dist/lib/analytics/followthrough.js +194 -0
- package/dist/lib/analytics/forwarder.js +109 -0
- package/dist/lib/analytics/logs.js +78 -0
- package/dist/lib/analytics/metrics.js +78 -0
- package/dist/lib/analytics/recorder.js +92 -0
- package/dist/lib/analytics/review-analytics.js +75 -0
- package/dist/lib/analytics/sequence.js +77 -0
- package/dist/lib/analytics/store.js +131 -0
- package/dist/lib/analytics/turn-recap.js +279 -0
- package/dist/lib/artifact_id.js +108 -0
- package/dist/lib/auth-breaker.js +161 -0
- package/dist/lib/auto-index.js +112 -0
- package/dist/lib/classifier.js +88 -0
- package/dist/lib/config.js +298 -0
- package/dist/lib/conflict-advisory.js +64 -0
- package/dist/lib/debug-bundle.js +520 -0
- package/dist/lib/enrichment/ingest.js +301 -0
- package/dist/lib/enrichment/plan.js +253 -0
- package/dist/lib/enrichment/protocol.js +359 -0
- package/dist/lib/enrichment/scout-brief.js +176 -0
- package/dist/lib/failure-telemetry.js +444 -0
- package/dist/lib/git.js +200 -0
- package/dist/lib/governance-cache.js +77 -0
- package/dist/lib/governed-path-cache.js +76 -0
- package/dist/lib/http.js +677 -0
- package/dist/lib/identity-envelope.js +23 -0
- package/dist/lib/kb-candidate.js +65 -0
- package/dist/lib/kb_acl.js +98 -0
- package/dist/lib/login.js +353 -0
- package/dist/lib/mcp-fetchers.js +130 -0
- package/dist/lib/mcp-restart.js +47 -0
- package/dist/lib/observability.js +805 -0
- package/dist/lib/open-url.js +33 -0
- package/dist/lib/orphan-guard.js +70 -0
- package/dist/lib/packaged.js +21 -0
- package/dist/lib/reconcile-sessions.js +171 -0
- package/dist/lib/redactor.js +89 -0
- package/dist/lib/relationship-candidate-query.js +27 -0
- package/dist/lib/render.js +611 -0
- package/dist/lib/rules/applicability.js +64 -0
- package/dist/lib/rules/attest-code-rule-version.js +47 -0
- package/dist/lib/rules/attest-notes-location.js +217 -0
- package/dist/lib/rules/attest-rule-version.js +69 -0
- package/dist/lib/rules/canonical-json.js +97 -0
- package/dist/lib/rules/ce0-emit.js +64 -0
- package/dist/lib/rules/ce0-evidence.js +281 -0
- package/dist/lib/rules/ce0-recall-sample.js +82 -0
- package/dist/lib/rules/ce0-rule.js +55 -0
- package/dist/lib/rules/ce0-sampling-bucket.js +15 -0
- package/dist/lib/rules/ce0-store.js +683 -0
- package/dist/lib/rules/ce0-telemetry-project.js +93 -0
- package/dist/lib/rules/ce0-telemetry.js +158 -0
- package/dist/lib/rules/code-rule-registry.js +17 -0
- package/dist/lib/rules/command-match.js +185 -0
- package/dist/lib/rules/consult-evidence-binding.js +27 -0
- package/dist/lib/rules/consultation-capture-adapter.js +193 -0
- package/dist/lib/rules/content-match.js +56 -0
- package/dist/lib/rules/deny-admission.js +99 -0
- package/dist/lib/rules/durable-observation.js +190 -0
- package/dist/lib/rules/enforce-notes-version.js +421 -0
- package/dist/lib/rules/evaluation-input-hash.js +126 -0
- package/dist/lib/rules/evaluator.js +108 -0
- package/dist/lib/rules/inert-rule-families.js +51 -0
- package/dist/lib/rules/input-authority-resolver.js +241 -0
- package/dist/lib/rules/interception-schema.js +170 -0
- package/dist/lib/rules/interception-store.js +267 -0
- package/dist/lib/rules/live-input-authority.js +66 -0
- package/dist/lib/rules/local-matcher.js +108 -0
- package/dist/lib/rules/local-observe.js +79 -0
- package/dist/lib/rules/local-rule-version-repo.js +214 -0
- package/dist/lib/rules/memory-requirement.js +109 -0
- package/dist/lib/rules/notes-observe.js +39 -0
- package/dist/lib/rules/notes-path.js +261 -0
- package/dist/lib/rules/notes-rule.js +75 -0
- package/dist/lib/rules/observe-adapter.js +114 -0
- package/dist/lib/rules/observed-rule-hash.js +119 -0
- package/dist/lib/rules/prompt-submit-adapter.js +132 -0
- package/dist/lib/rules/requirement-subject.js +240 -0
- package/dist/lib/rules/rule-activity.js +67 -0
- package/dist/lib/rules/rule-version-hash.js +151 -0
- package/dist/lib/rules/runtime-scope.js +55 -0
- package/dist/lib/rules/stop-adapter.js +116 -0
- package/dist/lib/rules/stop-response-snapshot.js +174 -0
- package/dist/lib/rules/types.js +10 -0
- package/dist/lib/rules/ulid.js +46 -0
- package/dist/lib/rules/version-evaluation.js +156 -0
- package/dist/lib/scanner/agent-memory.js +99 -0
- package/dist/lib/scanner/bootstrap-summary.js +87 -0
- package/dist/lib/scanner/cache.js +59 -0
- package/dist/lib/scanner/frontmatter.js +42 -0
- package/dist/lib/scanner/parse-directives.js +69 -0
- package/dist/lib/scanner/parse-structured.js +72 -0
- package/dist/lib/scanner/render.js +73 -0
- package/dist/lib/scanner/scan.js +132 -0
- package/dist/lib/scanner/score.js +38 -0
- package/dist/lib/scanner/scout-mission.js +126 -0
- package/dist/lib/scanner/types.js +7 -0
- package/dist/lib/session-scope.js +195 -0
- package/dist/lib/spool.js +355 -0
- package/dist/lib/staleness.js +100 -0
- package/dist/lib/steer-cache.js +87 -0
- package/dist/lib/tagged-reference.js +20 -0
- package/dist/lib/temporal.js +109 -0
- package/dist/lib/turn-recap-emit.js +67 -0
- package/dist/lib/unwire.js +253 -0
- package/dist/lib/update-check.js +469 -0
- package/dist/lib/update-notifier.js +217 -0
- package/dist/lib/upgrade-apply.js +643 -0
- package/dist/lib/wire.js +1087 -0
- package/dist/lib/workspace.js +96 -0
- package/dist/lib/zip.js +154 -0
- package/dist/pretool-entry.js +37 -0
- 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
|
+
}
|