@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,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.openUrl = openUrl;
|
|
4
|
+
const child_process_1 = require("child_process");
|
|
5
|
+
const defaultRunner = (cmd, args) => {
|
|
6
|
+
const r = (0, child_process_1.spawnSync)(cmd, args, { stdio: "ignore" });
|
|
7
|
+
return { error: r.error ?? undefined, status: r.status };
|
|
8
|
+
};
|
|
9
|
+
function launcherFor(platform, url) {
|
|
10
|
+
if (platform === "darwin")
|
|
11
|
+
return { cmd: "open", args: [url] };
|
|
12
|
+
if (platform === "win32")
|
|
13
|
+
return { cmd: "cmd", args: ["/c", "start", "", url] };
|
|
14
|
+
return { cmd: "xdg-open", args: [url] };
|
|
15
|
+
}
|
|
16
|
+
function openUrl(url, opts = {}) {
|
|
17
|
+
// Hand the OS launcher only vetted http(s) URLs. The Console URL is the only thing
|
|
18
|
+
// we ever open; rejecting everything else keeps a malformed config or a crafted
|
|
19
|
+
// path from reaching the shell.
|
|
20
|
+
if (!/^https?:\/\/\S+$/i.test(url)) {
|
|
21
|
+
return { ok: false, error: `refusing to open a non-http(s) URL: ${url}` };
|
|
22
|
+
}
|
|
23
|
+
const platform = opts.platform ?? process.platform;
|
|
24
|
+
const run = opts.run ?? defaultRunner;
|
|
25
|
+
const { cmd, args } = launcherFor(platform, url);
|
|
26
|
+
const r = run(cmd, args);
|
|
27
|
+
if (r.error)
|
|
28
|
+
return { ok: false, error: r.error.message };
|
|
29
|
+
if (typeof r.status === "number" && r.status !== 0) {
|
|
30
|
+
return { ok: false, error: `${cmd} exited ${r.status}` };
|
|
31
|
+
}
|
|
32
|
+
return { ok: true };
|
|
33
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Reaping guard for the long-lived `mla mcp` process tree (notes/20260622-mla-
|
|
3
|
+
// mcp-process-leak-findings-and-fix.md, Tier 1). Shared by BOTH levels:
|
|
4
|
+
// - the worker (runMcp), which blocks on stdin EOF, and
|
|
5
|
+
// - the supervisor (runMcpSupervisor), which blocks on `await spawnChild`.
|
|
6
|
+
//
|
|
7
|
+
// The worker's MCP server blocks on stdin EOF (server.onclose in @meetless/mcp's
|
|
8
|
+
// runStdioServer) as its ONLY exit path. So if the client dies WITHOUT closing
|
|
9
|
+
// the stdin pipe (force-quit, crash, or a SIGTERM aimed at a parent in the
|
|
10
|
+
// spawn chain that does not cascade), the worker is orphaned, reparented to
|
|
11
|
+
// launchd (pid 1), and blocks on a stdin whose EOF will never arrive. The
|
|
12
|
+
// supervisor has the mirror-image problem: if ITS parent dies but the worker
|
|
13
|
+
// underneath it stays alive (so the worker's ppid is the still-running
|
|
14
|
+
// supervisor, NOT 1, and the worker's own watchdog never fires), the supervisor
|
|
15
|
+
// sits forever in `await spawnChild`. That orphaned-supervisor case was in fact
|
|
16
|
+
// the MAJORITY of the measured leak (148 supervisors vs 61 workers), so the
|
|
17
|
+
// watchdog has to live at every level, not just the leaf.
|
|
18
|
+
//
|
|
19
|
+
// Two backstops:
|
|
20
|
+
// 1. SIGTERM/SIGINT/SIGHUP handlers: a direct signal tears the process down
|
|
21
|
+
// cleanly instead of being ignored while it waits. They run onTerminate
|
|
22
|
+
// first (the supervisor uses this to SIGTERM its in-flight worker so the
|
|
23
|
+
// whole tree collapses) and exit with signalExitCode.
|
|
24
|
+
// 2. A parent-death watchdog: poll process.ppid; once it is 1 the original
|
|
25
|
+
// parent (client or supervisor) is gone, so run onTerminate and exit 0.
|
|
26
|
+
// macOS and Linux reparent orphans to pid 1, which makes this a reliable
|
|
27
|
+
// orphan signal. The timer is unref'd so it never, by itself, keeps the
|
|
28
|
+
// process alive (a unref'd timer does not hold the event loop open, so the
|
|
29
|
+
// clean disconnect/await path still ends the process exactly on time).
|
|
30
|
+
//
|
|
31
|
+
// Everything is injectable so the guard's behaviour is unit-tested without
|
|
32
|
+
// registering real process listeners or wall-clock timers in the jest runner.
|
|
33
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
34
|
+
exports.ORPHAN_POLL_MS = void 0;
|
|
35
|
+
exports.installOrphanGuard = installOrphanGuard;
|
|
36
|
+
exports.ORPHAN_POLL_MS = 5000;
|
|
37
|
+
// The signals a terminal/editor or an OS shutdown sends to ask a process to
|
|
38
|
+
// stop. Without these handlers Node's defaults still terminate on SIGTERM/SIGINT
|
|
39
|
+
// in many cases, but an installed handler guarantees a clean, deterministic
|
|
40
|
+
// teardown (run onTerminate, then exit with our chosen code) and covers SIGHUP,
|
|
41
|
+
// which a closing terminal session delivers.
|
|
42
|
+
const GUARD_SIGNALS = ["SIGTERM", "SIGINT", "SIGHUP"];
|
|
43
|
+
// Install a level's death backstops. Call once, right before the long-lived
|
|
44
|
+
// wait (the worker's serve loop, or the supervisor's spawn loop). Idempotency is
|
|
45
|
+
// the caller's concern: in production each is invoked once per process, and the
|
|
46
|
+
// unit tests inject a no-op.
|
|
47
|
+
function installOrphanGuard(deps = {}) {
|
|
48
|
+
const on = deps.on ?? ((signal, handler) => void process.on(signal, handler));
|
|
49
|
+
const exit = deps.exit ?? ((code) => process.exit(code));
|
|
50
|
+
const getPpid = deps.getPpid ?? (() => process.ppid);
|
|
51
|
+
const setIntervalFn = deps.setIntervalFn ?? ((fn, ms) => setInterval(fn, ms));
|
|
52
|
+
const pollMs = deps.pollMs ?? exports.ORPHAN_POLL_MS;
|
|
53
|
+
const onTerminate = deps.onTerminate ?? (() => { });
|
|
54
|
+
const signalExitCode = deps.signalExitCode ?? 0;
|
|
55
|
+
for (const signal of GUARD_SIGNALS) {
|
|
56
|
+
on(signal, () => {
|
|
57
|
+
onTerminate();
|
|
58
|
+
exit(signalExitCode);
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
const timer = setIntervalFn(() => {
|
|
62
|
+
if (getPpid() === 1) {
|
|
63
|
+
onTerminate();
|
|
64
|
+
exit(0);
|
|
65
|
+
}
|
|
66
|
+
}, pollMs);
|
|
67
|
+
// Never let the watchdog itself hold the process open; the server's stdin
|
|
68
|
+
// pipe / the spawn await is the thing that should keep it alive, not this poll.
|
|
69
|
+
timer.unref();
|
|
70
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isPackagedBinary = isPackagedBinary;
|
|
4
|
+
// Runtime detection of the pkg-compiled standalone binary.
|
|
5
|
+
//
|
|
6
|
+
// `mla` ships two ways: a @yao-pkg/pkg-compiled single-file binary (curl|sh and
|
|
7
|
+
// Homebrew) and a source/npm install on a real Node. The binary embeds its files
|
|
8
|
+
// in a V8 snapshot rooted at /snapshot, and @yao-pkg/pkg sets `process.pkg` in
|
|
9
|
+
// the packaged process. That snapshot has NO ESM dynamic-import callback, so any
|
|
10
|
+
// true runtime import() of an ESM-only module fails with "A dynamic import
|
|
11
|
+
// callback was not specified". `mla mcp` is exactly such a path (it
|
|
12
|
+
// dynamic-imports the ESM-only @meetless/mcp), so the dispatcher uses this to
|
|
13
|
+
// refuse cleanly in the binary rather than crash cryptically.
|
|
14
|
+
//
|
|
15
|
+
// From a source/npm install `process.pkg` is undefined and __dirname is a real
|
|
16
|
+
// path, so this returns false and every code path behaves exactly as before.
|
|
17
|
+
function isPackagedBinary() {
|
|
18
|
+
if (process.pkg != null)
|
|
19
|
+
return true;
|
|
20
|
+
return typeof __dirname === "string" && __dirname.startsWith("/snapshot");
|
|
21
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
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.planSessionReconcile = planSessionReconcile;
|
|
37
|
+
exports.projectDirForRepoPath = projectDirForRepoPath;
|
|
38
|
+
exports.makeTranscriptStatusResolver = makeTranscriptStatusResolver;
|
|
39
|
+
exports.executeSessionReconcile = executeSessionReconcile;
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const os = __importStar(require("os"));
|
|
42
|
+
const path = __importStar(require("path"));
|
|
43
|
+
// Pure decision core. Walks the captured sessions and partitions them into
|
|
44
|
+
// "archive" (provably-deleted transcript) and "skip" (everything else, with the
|
|
45
|
+
// reason). Order-preserving so the human/JSON output is stable.
|
|
46
|
+
function planSessionReconcile(sessions, transcriptStatus) {
|
|
47
|
+
const plan = { toArchive: [], skipped: [] };
|
|
48
|
+
for (const s of sessions) {
|
|
49
|
+
const id = s.externalSessionId;
|
|
50
|
+
// The gates are ordered cheapest/safest first so the skip reason is the most
|
|
51
|
+
// specific true statement about why this session was left alone.
|
|
52
|
+
if (s.adapter !== "claude_code") {
|
|
53
|
+
plan.skipped.push({ sessionId: id, reason: "not-claude-code" });
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (s.archivedAt != null) {
|
|
57
|
+
plan.skipped.push({ sessionId: id, reason: "already-archived" });
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (s.liveness === "LIVE") {
|
|
61
|
+
plan.skipped.push({ sessionId: id, reason: "live" });
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
const status = transcriptStatus(id, s.repoPath);
|
|
65
|
+
if (status === "deleted") {
|
|
66
|
+
plan.toArchive.push(id);
|
|
67
|
+
}
|
|
68
|
+
else if (status === "present") {
|
|
69
|
+
plan.skipped.push({ sessionId: id, reason: "transcript-present" });
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
plan.skipped.push({ sessionId: id, reason: "transcript-unknown" });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return plan;
|
|
76
|
+
}
|
|
77
|
+
// Claude Code names a project dir by collapsing the cwd's path separators (and
|
|
78
|
+
// dots) to '-'. Verified empirically against the live ~/.claude/projects:
|
|
79
|
+
// /Users/alice/projects/acme/web
|
|
80
|
+
// -> -Users-alice-projects-acme-web
|
|
81
|
+
// /private/tmp/ml-q6-pg-rV03lX -> -private-tmp-ml-q6-pg-rV03lX
|
|
82
|
+
// We only use this to PROVE the project still exists on this machine; if our
|
|
83
|
+
// encoding ever diverges from Claude Code's the dir simply will not be found, so
|
|
84
|
+
// the session resolves to "unknown" (skip) rather than a false "deleted".
|
|
85
|
+
function projectDirForRepoPath(repoPath) {
|
|
86
|
+
return repoPath.replace(/[/.]/g, "-");
|
|
87
|
+
}
|
|
88
|
+
// Build the default disk-backed resolver. Performs ONE upfront scan of every
|
|
89
|
+
// project dir under projectsRoot, recording the set of session ids that have a
|
|
90
|
+
// `<id>.jsonl` transcript ANYWHERE (an encoding-independent presence check: even
|
|
91
|
+
// if the server's repoPath disagrees with the real project dir, a transcript that
|
|
92
|
+
// exists is still found and the session is reported present, never archived).
|
|
93
|
+
//
|
|
94
|
+
// A session is "deleted" ONLY when it is absent from that global set AND the
|
|
95
|
+
// project dir derived from its repoPath still exists on disk (proof we are on the
|
|
96
|
+
// capture host and that project is real here). Otherwise it is "unknown".
|
|
97
|
+
function makeTranscriptStatusResolver(deps = {}) {
|
|
98
|
+
const projectsRoot = deps.projectsRoot ?? path.join(os.homedir(), ".claude", "projects");
|
|
99
|
+
const present = new Set();
|
|
100
|
+
let rootEntries = [];
|
|
101
|
+
try {
|
|
102
|
+
rootEntries = fs.readdirSync(projectsRoot);
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
rootEntries = [];
|
|
106
|
+
}
|
|
107
|
+
for (const entry of rootEntries) {
|
|
108
|
+
const dir = path.join(projectsRoot, entry);
|
|
109
|
+
let isDir = false;
|
|
110
|
+
try {
|
|
111
|
+
isDir = fs.statSync(dir).isDirectory();
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
isDir = false;
|
|
115
|
+
}
|
|
116
|
+
if (!isDir)
|
|
117
|
+
continue;
|
|
118
|
+
let files = [];
|
|
119
|
+
try {
|
|
120
|
+
files = fs.readdirSync(dir);
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
files = [];
|
|
124
|
+
}
|
|
125
|
+
for (const f of files) {
|
|
126
|
+
if (f.endsWith(".jsonl"))
|
|
127
|
+
present.add(f.slice(0, -".jsonl".length));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const dirExists = (dir) => {
|
|
131
|
+
try {
|
|
132
|
+
return fs.statSync(dir).isDirectory();
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
return (sessionId, repoPath) => {
|
|
139
|
+
if (present.has(sessionId))
|
|
140
|
+
return "present";
|
|
141
|
+
const rp = (repoPath || "").trim();
|
|
142
|
+
if (!rp)
|
|
143
|
+
return "unknown";
|
|
144
|
+
const projectDir = path.join(projectsRoot, projectDirForRepoPath(rp));
|
|
145
|
+
return dirExists(projectDir) ? "deleted" : "unknown";
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
// Orchestrate one reconcile sweep: fetch the captured sessions, plan which have a
|
|
149
|
+
// provably-deleted transcript, and (unless dry-run) archive exactly those. Archive
|
|
150
|
+
// is fail-SOFT per session: a single failure is recorded in `failed` and the sweep
|
|
151
|
+
// continues, because hiding one stale session must never depend on hiding another.
|
|
152
|
+
// Under dry-run the plan is computed and returned but `archive` is never called, so
|
|
153
|
+
// `--dry-run` is a faithful, side-effect-free preview of what a real run would do.
|
|
154
|
+
async function executeSessionReconcile(deps, opts) {
|
|
155
|
+
const sessions = await deps.listSessions();
|
|
156
|
+
const plan = planSessionReconcile(sessions, deps.resolver);
|
|
157
|
+
const archived = [];
|
|
158
|
+
const failed = [];
|
|
159
|
+
if (!opts.dryRun) {
|
|
160
|
+
for (const id of plan.toArchive) {
|
|
161
|
+
try {
|
|
162
|
+
await deps.archive(id);
|
|
163
|
+
archived.push(id);
|
|
164
|
+
}
|
|
165
|
+
catch (e) {
|
|
166
|
+
failed.push({ sessionId: id, error: e.message });
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return { plan, archived, failed, dryRun: opts.dryRun };
|
|
171
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Shared secret redactor for the mla CLI. Mirror of
|
|
3
|
+
// intel/app/observability/redaction.py and apps/control/src/core/services/redactor.ts.
|
|
4
|
+
// Principle 7 of notes/20260528-mla-logging-and-tracing-proposal.md:
|
|
5
|
+
// exactly one redactor, applied at the three places an operator can see
|
|
6
|
+
// captured content. Cross-plane parity is locked by a shared fixture test
|
|
7
|
+
// (tools/meetless-agent/test/lib/redactor-parity.spec.ts).
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.REDACTED = void 0;
|
|
10
|
+
exports.redact = redact;
|
|
11
|
+
exports.redactPayload = redactPayload;
|
|
12
|
+
exports.REDACTED = "[REDACTED]";
|
|
13
|
+
// Order matters: env_assignment runs first so KEY=value pairs are redacted
|
|
14
|
+
// whole, not just the value half. Token literals come second for cases
|
|
15
|
+
// without an = sign. High-entropy heuristic runs last to catch generic
|
|
16
|
+
// session tokens that the prefix matchers miss.
|
|
17
|
+
const PATTERNS = [
|
|
18
|
+
[
|
|
19
|
+
"env_assignment",
|
|
20
|
+
/\b([A-Z][A-Z0-9_]*_(?:TOKEN|KEY|SECRET|PASSWORD|PWD|API[_-]?KEY|ACCESS[_-]?KEY)|SECRET_[A-Z0-9_]+|PASSWORD|PASSWD|AWS_(?:ACCESS|SECRET)_(?:ACCESS_)?KEY(?:_ID)?|GH_TOKEN|GITHUB_TOKEN|OPENAI_API_KEY|ANTHROPIC_API_KEY)\s*[:=]\s*('[^']*'|"[^"]*"|\S+)/gim,
|
|
21
|
+
],
|
|
22
|
+
["bearer", /\b(Bearer|Basic)\s+[A-Za-z0-9._\-+/=]+/gi],
|
|
23
|
+
[
|
|
24
|
+
"provider_token",
|
|
25
|
+
/\b(sk-(?:proj-|ant-)?[A-Za-z0-9_\-]{16,}|ghp_[A-Za-z0-9]{20,}|gho_[A-Za-z0-9]{20,}|ghs_[A-Za-z0-9]{20,}|github_pat_[A-Za-z0-9_]{20,}|xox[baprs]-[A-Za-z0-9\-]{10,}|AKIA[0-9A-Z]{16}|ASIA[0-9A-Z]{16}|AIza[0-9A-Za-z_\-]{35}|hf_[A-Za-z0-9]{20,}|lf_(?:sk|pk)_[A-Za-z0-9]{20,})\b/g,
|
|
26
|
+
],
|
|
27
|
+
["cookie", /(Set-)?Cookie:\s*[^\r\n]+/gi],
|
|
28
|
+
[
|
|
29
|
+
"pem_key",
|
|
30
|
+
/-----BEGIN (?:[A-Z ]+ )?PRIVATE KEY-----[\s\S]*?-----END (?:[A-Z ]+ )?PRIVATE KEY-----/g,
|
|
31
|
+
],
|
|
32
|
+
];
|
|
33
|
+
const ENTROPY_TOKEN = /\b[A-Za-z0-9_\-+/=]{32,}\b/g;
|
|
34
|
+
function shannonEntropy(s) {
|
|
35
|
+
if (!s)
|
|
36
|
+
return 0;
|
|
37
|
+
const counts = {};
|
|
38
|
+
for (const ch of s)
|
|
39
|
+
counts[ch] = (counts[ch] ?? 0) + 1;
|
|
40
|
+
const n = s.length;
|
|
41
|
+
let h = 0;
|
|
42
|
+
for (const c of Object.values(counts)) {
|
|
43
|
+
const p = c / n;
|
|
44
|
+
h -= p * Math.log2(p);
|
|
45
|
+
}
|
|
46
|
+
return h;
|
|
47
|
+
}
|
|
48
|
+
function looksHighEntropy(token) {
|
|
49
|
+
if (token.length < 32)
|
|
50
|
+
return false;
|
|
51
|
+
let lower = false, upper = false, digit = false, sep = false;
|
|
52
|
+
for (const ch of token) {
|
|
53
|
+
if (ch >= "a" && ch <= "z")
|
|
54
|
+
lower = true;
|
|
55
|
+
else if (ch >= "A" && ch <= "Z")
|
|
56
|
+
upper = true;
|
|
57
|
+
else if (ch >= "0" && ch <= "9")
|
|
58
|
+
digit = true;
|
|
59
|
+
else if ("_-+/=".includes(ch))
|
|
60
|
+
sep = true;
|
|
61
|
+
}
|
|
62
|
+
const classes = [lower, upper, digit, sep].filter(Boolean).length;
|
|
63
|
+
if (classes < 2)
|
|
64
|
+
return false;
|
|
65
|
+
return shannonEntropy(token) >= 3.5;
|
|
66
|
+
}
|
|
67
|
+
function redact(text) {
|
|
68
|
+
if (text === null || text === undefined || text === "")
|
|
69
|
+
return text;
|
|
70
|
+
let out = text;
|
|
71
|
+
for (const [, pat] of PATTERNS)
|
|
72
|
+
out = out.replace(pat, exports.REDACTED);
|
|
73
|
+
out = out.replace(ENTROPY_TOKEN, (m) => (looksHighEntropy(m) ? exports.REDACTED : m));
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
76
|
+
function redactPayload(value) {
|
|
77
|
+
if (typeof value === "string")
|
|
78
|
+
return redact(value);
|
|
79
|
+
if (Array.isArray(value))
|
|
80
|
+
return value.map((v) => redactPayload(v));
|
|
81
|
+
if (value !== null && typeof value === "object") {
|
|
82
|
+
const out = {};
|
|
83
|
+
for (const [k, v] of Object.entries(value)) {
|
|
84
|
+
out[k] = redactPayload(v);
|
|
85
|
+
}
|
|
86
|
+
return out;
|
|
87
|
+
}
|
|
88
|
+
return value;
|
|
89
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildPendingCandidateQuery = buildPendingCandidateQuery;
|
|
4
|
+
// The single query-string builder for control's relationship-candidate list route
|
|
5
|
+
// (GET /internal/v1/relationship-candidates). Shared by the kb review listing
|
|
6
|
+
// (cursor pagination) and the kb forget cascade so neither command module imports
|
|
7
|
+
// the other. Pins the default review view: PENDING_REVIEW + LIVE.
|
|
8
|
+
function buildPendingCandidateQuery(workspaceId, doc, limit, cursor) {
|
|
9
|
+
const qs = new URLSearchParams();
|
|
10
|
+
qs.set("workspaceId", workspaceId);
|
|
11
|
+
qs.set("statusId", "PENDING_REVIEW");
|
|
12
|
+
qs.set("posture", "LIVE");
|
|
13
|
+
qs.set("limit", String(limit));
|
|
14
|
+
if (doc) {
|
|
15
|
+
// `--doc note:foo.md` is an exact artifactId; a bare `foo.md` is a notePath the
|
|
16
|
+
// route resolves to a basename server-side (relationship-candidate.dto.ts).
|
|
17
|
+
if (doc.includes(":"))
|
|
18
|
+
qs.set("artifactId", doc);
|
|
19
|
+
else
|
|
20
|
+
qs.set("notePath", doc);
|
|
21
|
+
}
|
|
22
|
+
if (cursor) {
|
|
23
|
+
qs.set("cursorId", cursor.id);
|
|
24
|
+
qs.set("cursorCreatedAt", cursor.createdAt);
|
|
25
|
+
}
|
|
26
|
+
return qs.toString();
|
|
27
|
+
}
|