@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,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Per-session command sequence fields (spec section 6.2: command_index_in_session,
|
|
3
|
+
// preceded_by, session_idle_gap_ms). These unlock PostHog path analysis (section
|
|
4
|
+
// 8: the `preceded_by` Sankey) without any second on-disk tracker: the local
|
|
5
|
+
// events.jsonl is already the single source of truth for `mla stats`, so the
|
|
6
|
+
// prior commands of a session are simply the `mla_command` rows that already
|
|
7
|
+
// carry the same session_id. Deriving from the jsonl keeps one source of truth
|
|
8
|
+
// and cannot drift from what `mla stats` sees.
|
|
9
|
+
//
|
|
10
|
+
// Computed BEFORE the current event is appended, so the rows read here are the
|
|
11
|
+
// strictly-prior commands of this session.
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.computeSequence = computeSequence;
|
|
14
|
+
const store_1 = require("./store");
|
|
15
|
+
// Sequence fields are null for an unbound run (no session to order within).
|
|
16
|
+
const NO_SEQUENCE = {
|
|
17
|
+
command_index_in_session: null,
|
|
18
|
+
preceded_by: null,
|
|
19
|
+
session_idle_gap_ms: null,
|
|
20
|
+
};
|
|
21
|
+
function eventMillis(ev) {
|
|
22
|
+
const raw = ev.emitted_at || ev.created_at;
|
|
23
|
+
if (!raw)
|
|
24
|
+
return null;
|
|
25
|
+
const ms = Date.parse(raw);
|
|
26
|
+
return Number.isNaN(ms) ? null : ms;
|
|
27
|
+
}
|
|
28
|
+
// Derive the sequence fields for the command about to be recorded. `readEvents`
|
|
29
|
+
// is the injectable read so a test can drive it off a tmp MEETLESS_HOME; when
|
|
30
|
+
// local stats are off it returns [], which correctly yields index 1 / no
|
|
31
|
+
// predecessor (we have no local working set to order against).
|
|
32
|
+
function computeSequence(sessionId, commandStartedAtMs, env = process.env) {
|
|
33
|
+
if (!sessionId)
|
|
34
|
+
return NO_SEQUENCE;
|
|
35
|
+
const prior = [];
|
|
36
|
+
for (const ev of (0, store_1.readEvents)(env)) {
|
|
37
|
+
if (ev.event_type === "mla_command" && ev.session_id === sessionId) {
|
|
38
|
+
prior.push(ev);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// 1-based position of this command in the session.
|
|
42
|
+
const index = prior.length + 1;
|
|
43
|
+
if (prior.length === 0) {
|
|
44
|
+
// First command of the session: no predecessor, no idle gap to measure.
|
|
45
|
+
return {
|
|
46
|
+
command_index_in_session: index,
|
|
47
|
+
preceded_by: null,
|
|
48
|
+
session_idle_gap_ms: null,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
// The immediately-preceding command is the latest prior row by ship time.
|
|
52
|
+
let latest = prior[0];
|
|
53
|
+
let latestMs = eventMillis(prior[0]);
|
|
54
|
+
for (const ev of prior) {
|
|
55
|
+
const ms = eventMillis(ev);
|
|
56
|
+
if (ms !== null && (latestMs === null || ms >= latestMs)) {
|
|
57
|
+
latest = ev;
|
|
58
|
+
latestMs = ms;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const precededBy = typeof latest.command === "string"
|
|
62
|
+
? latest.command
|
|
63
|
+
: null;
|
|
64
|
+
// Idle gap = this command's start minus the prior command's recorded time.
|
|
65
|
+
// Clamp a negative result (clock skew, out-of-order rows) to null rather than
|
|
66
|
+
// emit a nonsense negative duration.
|
|
67
|
+
let idleGap = null;
|
|
68
|
+
if (latestMs !== null) {
|
|
69
|
+
const gap = commandStartedAtMs - latestMs;
|
|
70
|
+
idleGap = gap >= 0 ? gap : null;
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
command_index_in_session: index,
|
|
74
|
+
preceded_by: precededBy,
|
|
75
|
+
session_idle_gap_ms: idleGap,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Local analytics store: the append-only ~/.meetless/events.jsonl (spec section
|
|
3
|
+
// 7.4, INV-LOCAL-STATS-1/2). This file is the source of truth for `mla stats`
|
|
4
|
+
// (offline, OSS-safe, no backend required) and the local correlator's working
|
|
5
|
+
// set. It is NEVER the source of truth for `--global` (that reads control
|
|
6
|
+
// rollups).
|
|
7
|
+
//
|
|
8
|
+
// Every write is gated by localStatsEnabled() so a user who turns local stats
|
|
9
|
+
// off leaves no trace on disk. Reads are lenient: a malformed line is skipped,
|
|
10
|
+
// never fatal, so a half-written line (crash mid-append) can't brick `mla stats`.
|
|
11
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
12
|
+
if (k2 === undefined) k2 = k;
|
|
13
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
14
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
15
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
16
|
+
}
|
|
17
|
+
Object.defineProperty(o, k2, desc);
|
|
18
|
+
}) : (function(o, m, k, k2) {
|
|
19
|
+
if (k2 === undefined) k2 = k;
|
|
20
|
+
o[k2] = m[k];
|
|
21
|
+
}));
|
|
22
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
23
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
24
|
+
}) : function(o, v) {
|
|
25
|
+
o["default"] = v;
|
|
26
|
+
});
|
|
27
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
28
|
+
var ownKeys = function(o) {
|
|
29
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
30
|
+
var ar = [];
|
|
31
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
32
|
+
return ar;
|
|
33
|
+
};
|
|
34
|
+
return ownKeys(o);
|
|
35
|
+
};
|
|
36
|
+
return function (mod) {
|
|
37
|
+
if (mod && mod.__esModule) return mod;
|
|
38
|
+
var result = {};
|
|
39
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
40
|
+
__setModuleDefault(result, mod);
|
|
41
|
+
return result;
|
|
42
|
+
};
|
|
43
|
+
})();
|
|
44
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
+
exports.eventsPath = eventsPath;
|
|
46
|
+
exports.machineId = machineId;
|
|
47
|
+
exports.appendEvent = appendEvent;
|
|
48
|
+
exports.appendEventLine = appendEventLine;
|
|
49
|
+
exports.readEvents = readEvents;
|
|
50
|
+
const fs = __importStar(require("fs"));
|
|
51
|
+
const os = __importStar(require("os"));
|
|
52
|
+
const path = __importStar(require("path"));
|
|
53
|
+
const crypto = __importStar(require("crypto"));
|
|
54
|
+
const config_1 = require("../config");
|
|
55
|
+
const consent_1 = require("./consent");
|
|
56
|
+
function eventsPath() {
|
|
57
|
+
return path.join(config_1.HOME, "events.jsonl");
|
|
58
|
+
}
|
|
59
|
+
// A stable, non-identifying machine id for distinct_id when no workspace user is
|
|
60
|
+
// resolved. Derived from the hostname + home dir so it is consistent per machine
|
|
61
|
+
// but carries no account data. Hashed so the raw hostname never lands in jsonl.
|
|
62
|
+
let cachedMachineId = null;
|
|
63
|
+
function machineId() {
|
|
64
|
+
if (cachedMachineId)
|
|
65
|
+
return cachedMachineId;
|
|
66
|
+
const seed = `${os.hostname()}::${os.homedir()}`;
|
|
67
|
+
cachedMachineId = "m_" + crypto.createHash("sha256").update(seed).digest("hex").slice(0, 24);
|
|
68
|
+
return cachedMachineId;
|
|
69
|
+
}
|
|
70
|
+
function ensureHome() {
|
|
71
|
+
if (!fs.existsSync(config_1.HOME)) {
|
|
72
|
+
fs.mkdirSync(config_1.HOME, { recursive: true });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// Append one event as a single jsonl line. No-op when local stats are disabled.
|
|
76
|
+
// Best-effort: a disk error is swallowed (analytics must never break a command),
|
|
77
|
+
// but we surface it on the debug channel via the optional onError hook.
|
|
78
|
+
function appendEvent(ev, env = process.env, onError) {
|
|
79
|
+
if (!(0, consent_1.localStatsEnabled)(env))
|
|
80
|
+
return;
|
|
81
|
+
try {
|
|
82
|
+
ensureHome();
|
|
83
|
+
fs.appendFileSync(eventsPath(), JSON.stringify(ev) + "\n", "utf8");
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
if (onError)
|
|
87
|
+
onError(err);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// Append a raw pre-serialized line (used by the correlator when it rewrites an
|
|
91
|
+
// outcome). Same gating + best-effort semantics.
|
|
92
|
+
function appendEventLine(line, env = process.env, onError) {
|
|
93
|
+
if (!(0, consent_1.localStatsEnabled)(env))
|
|
94
|
+
return;
|
|
95
|
+
try {
|
|
96
|
+
ensureHome();
|
|
97
|
+
const normalized = line.endsWith("\n") ? line : line + "\n";
|
|
98
|
+
fs.appendFileSync(eventsPath(), normalized, "utf8");
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
if (onError)
|
|
102
|
+
onError(err);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// Read all events from the local jsonl. Lenient parser: blank and malformed
|
|
106
|
+
// lines are skipped. Returns [] when the file is absent or local stats are off.
|
|
107
|
+
function readEvents(env = process.env) {
|
|
108
|
+
if (!(0, consent_1.localStatsEnabled)(env))
|
|
109
|
+
return [];
|
|
110
|
+
const file = eventsPath();
|
|
111
|
+
let raw;
|
|
112
|
+
try {
|
|
113
|
+
raw = fs.readFileSync(file, "utf8");
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return [];
|
|
117
|
+
}
|
|
118
|
+
const out = [];
|
|
119
|
+
for (const line of raw.split("\n")) {
|
|
120
|
+
const trimmed = line.trim();
|
|
121
|
+
if (!trimmed)
|
|
122
|
+
continue;
|
|
123
|
+
try {
|
|
124
|
+
out.push(JSON.parse(trimmed));
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
// Skip a half-written or corrupt line; never fail the read.
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return out;
|
|
131
|
+
}
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Per-turn assist recap (Layer A of notes/20260609-mla-per-turn-assist-recap-plan.md).
|
|
3
|
+
//
|
|
4
|
+
// The session-level `mla stats` / `mla adoption` reader answers "across this
|
|
5
|
+
// session, how often was injected evidence used?" This module is the per-TURN
|
|
6
|
+
// analog: for one (session_id, turn_index) it answers the two operator questions
|
|
7
|
+
// An asked for at the end of every turn:
|
|
8
|
+
//
|
|
9
|
+
// liveness -- did mla run this turn, or silently not fire? (ran / not_run_reason)
|
|
10
|
+
// usefulness -- if it offered evidence, was the evidence pulled or cited, or
|
|
11
|
+
// ignored? (verdict USED / IGNORED / NO_OFFER / NOT_RUN)
|
|
12
|
+
//
|
|
13
|
+
// It reuses the SAME three local spool files and the SAME overlap math as the
|
|
14
|
+
// followthrough reader (INV-ADOPTION-SOURCE-1) so a per-turn USED is exactly the
|
|
15
|
+
// per-turn instance of the session-level A1c. The only new parse is a single-turn
|
|
16
|
+
// ask-traces reader; mcp-calls and report-citations go through followthrough's
|
|
17
|
+
// parsers unchanged. Window is 0 (same turn): this is computed at Stop, when all
|
|
18
|
+
// of the turn's pulls (written during the turn) and citations (written by stop.sh
|
|
19
|
+
// moments earlier) are already on disk, so the cross-turn window the session
|
|
20
|
+
// reader needs does not apply here.
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
exports.parseAskTrace = parseAskTrace;
|
|
23
|
+
exports.computeTurnRecap = computeTurnRecap;
|
|
24
|
+
exports.renderFooter = renderFooter;
|
|
25
|
+
exports.renderBlockContext = renderBlockContext;
|
|
26
|
+
exports.renderBlock = renderBlock;
|
|
27
|
+
const logs_1 = require("./logs");
|
|
28
|
+
const followthrough_1 = require("./followthrough");
|
|
29
|
+
function asObj(v) {
|
|
30
|
+
return v && typeof v === "object" ? v : {};
|
|
31
|
+
}
|
|
32
|
+
function asStr(v) {
|
|
33
|
+
return typeof v === "string" ? v : "";
|
|
34
|
+
}
|
|
35
|
+
function asNum(v) {
|
|
36
|
+
return typeof v === "number" && Number.isFinite(v) ? v : null;
|
|
37
|
+
}
|
|
38
|
+
const NOT_RUN_REASONS = ["muted", "not_activated", "suppressed", "timeout", "error"];
|
|
39
|
+
function asNotRunReason(v) {
|
|
40
|
+
return typeof v === "string" && NOT_RUN_REASONS.includes(v) ? v : null;
|
|
41
|
+
}
|
|
42
|
+
// parseAskTrace reads ONE ask-traces.jsonl line into the fields the recap needs.
|
|
43
|
+
// Returns null when the line cannot join (no session_id or non-numeric turn).
|
|
44
|
+
// The offered set mirrors parseInjectTurns: enrichment.context_items[] with
|
|
45
|
+
// injected===true and a non-empty source_id (the ids mla actually PUSHED).
|
|
46
|
+
function parseAskTrace(line) {
|
|
47
|
+
const session_id = asStr(line.session_id);
|
|
48
|
+
const turn_index = asNum(line.turn_index);
|
|
49
|
+
if (!session_id || turn_index === null)
|
|
50
|
+
return null;
|
|
51
|
+
const hook = asObj(line.hook);
|
|
52
|
+
const arbitration = asObj(line.arbitration);
|
|
53
|
+
const enrichment = asObj(line.enrichment);
|
|
54
|
+
const items = Array.isArray(enrichment.context_items) ? enrichment.context_items : [];
|
|
55
|
+
const offered = [];
|
|
56
|
+
for (const raw of items) {
|
|
57
|
+
const item = asObj(raw);
|
|
58
|
+
if (item.injected !== true)
|
|
59
|
+
continue;
|
|
60
|
+
const sid = asStr(item.source_id);
|
|
61
|
+
if (sid)
|
|
62
|
+
offered.push(sid);
|
|
63
|
+
}
|
|
64
|
+
const failOpen = asStr(hook.fail_open_reason);
|
|
65
|
+
// The early-exit minimal line (Phase 1 §5 enabling change) names its reason
|
|
66
|
+
// explicitly; accept it from hook or top-level so the writer has either home.
|
|
67
|
+
const explicitReason = asNotRunReason(hook.not_run_reason) ?? asNotRunReason(line.not_run_reason);
|
|
68
|
+
return {
|
|
69
|
+
session_id,
|
|
70
|
+
turn_index,
|
|
71
|
+
trace_id: typeof line.trace_id === "string" && line.trace_id ? line.trace_id : null,
|
|
72
|
+
injected_floor: hook.injected === true,
|
|
73
|
+
injected_evidence: hook.layer2_injected === true,
|
|
74
|
+
enrich_latency_ms: asNum(hook.enrich_latency_ms),
|
|
75
|
+
offered_source_ids: Array.from(new Set(offered)),
|
|
76
|
+
arb_reason: asStr(arbitration.reason),
|
|
77
|
+
fail_open_reason: failOpen || null,
|
|
78
|
+
not_run_reason: explicitReason,
|
|
79
|
+
has_error: line.error != null,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
// Why mla did not RUN (or suppressed injection) this turn. Only meaningful when
|
|
83
|
+
// the floor was not injected (a control, suppression, or early-exit). An explicit
|
|
84
|
+
// minimal-line reason wins; otherwise the injected=false control maps to
|
|
85
|
+
// "suppressed". An injected floor means it ran, so this is null there.
|
|
86
|
+
function deriveNotRunReason(t) {
|
|
87
|
+
if (!t)
|
|
88
|
+
return null; // no line at all: reason unknown
|
|
89
|
+
if (t.not_run_reason)
|
|
90
|
+
return t.not_run_reason;
|
|
91
|
+
const arb = t.arb_reason.toLowerCase();
|
|
92
|
+
if (arb.includes("pull_only") || arb.includes("suppress"))
|
|
93
|
+
return "suppressed";
|
|
94
|
+
if (t.fail_open_reason === "timeout")
|
|
95
|
+
return "timeout";
|
|
96
|
+
if (t.fail_open_reason === "error" || t.has_error)
|
|
97
|
+
return "error";
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
// Why nothing was OFFERED this turn though mla ran (floor injected, no evidence).
|
|
101
|
+
// Feeds the NO_OFFER footer and the session-level "coverage gaps" vocabulary.
|
|
102
|
+
function deriveCoverageGap(t) {
|
|
103
|
+
const arb = t.arb_reason.toLowerCase();
|
|
104
|
+
const fail = (t.fail_open_reason ?? "").toLowerCase();
|
|
105
|
+
if (arb.includes("no_relevant_context"))
|
|
106
|
+
return "no_relevant_context";
|
|
107
|
+
// Auth rejection (expired/revoked CLI token) is checked before the generic
|
|
108
|
+
// error so a dead session reads as "re-auth", not "enrichment failed".
|
|
109
|
+
if (fail === "unauthorized" || arb.includes("unauthorized"))
|
|
110
|
+
return "enrich_unauthorized";
|
|
111
|
+
if (fail === "timeout" || arb.includes("timeout"))
|
|
112
|
+
return "enrich_timeout";
|
|
113
|
+
if (fail === "error" || arb.includes("error"))
|
|
114
|
+
return "enrich_error";
|
|
115
|
+
if (arb.includes("missing_token"))
|
|
116
|
+
return "missing_token";
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
// --- the join ----------------------------------------------------------------
|
|
120
|
+
function computeTurnRecap(sessionId, turnIndex, deps = {}) {
|
|
121
|
+
const readLog = deps.readLog ?? logs_1.readLogJsonl;
|
|
122
|
+
const askLines = readLog("ask-traces.jsonl");
|
|
123
|
+
const mcpLines = (0, followthrough_1.parseMcpCalls)(readLog("mcp-calls.jsonl"));
|
|
124
|
+
const citeLines = (0, followthrough_1.parseReportCitations)(readLog("report-citations.jsonl"));
|
|
125
|
+
// The single ask-traces line for this turn. One line per turn is the invariant
|
|
126
|
+
// (write_trace is the sole emitter); take the last match so a re-emit wins.
|
|
127
|
+
let trace = null;
|
|
128
|
+
for (const raw of askLines) {
|
|
129
|
+
const t = parseAskTrace(raw);
|
|
130
|
+
if (t && t.session_id === sessionId && t.turn_index === turnIndex)
|
|
131
|
+
trace = t;
|
|
132
|
+
}
|
|
133
|
+
const ran = trace !== null;
|
|
134
|
+
const injected_floor = trace?.injected_floor ?? false;
|
|
135
|
+
const injected_evidence = trace?.injected_evidence ?? false;
|
|
136
|
+
const offered_source_ids = trace?.offered_source_ids ?? [];
|
|
137
|
+
const evidence_offered = offered_source_ids.length > 0;
|
|
138
|
+
// Same-turn, same-session pulls and citations (window=0).
|
|
139
|
+
const pulledIds = [];
|
|
140
|
+
const toolSet = new Set();
|
|
141
|
+
let pull_count = 0;
|
|
142
|
+
for (const c of mcpLines) {
|
|
143
|
+
if (c.session_id !== sessionId || c.turn_index !== turnIndex || !c.evidence_tool)
|
|
144
|
+
continue;
|
|
145
|
+
pull_count += 1;
|
|
146
|
+
pulledIds.push(...c.source_ids);
|
|
147
|
+
if (c.tool)
|
|
148
|
+
toolSet.add(c.tool);
|
|
149
|
+
}
|
|
150
|
+
const cited_source_ids = [];
|
|
151
|
+
for (const r of citeLines) {
|
|
152
|
+
if (r.session_id !== sessionId || r.turn_index !== turnIndex)
|
|
153
|
+
continue;
|
|
154
|
+
cited_source_ids.push(...r.source_ids);
|
|
155
|
+
}
|
|
156
|
+
// Referenced = offered ids the agent pulled (via an evidence tool) or cited.
|
|
157
|
+
const referenced_source_ids = (0, followthrough_1.overlap)(offered_source_ids, [...pulledIds, ...cited_source_ids]);
|
|
158
|
+
const notRun = !ran || !injected_floor;
|
|
159
|
+
const not_run_reason = notRun ? deriveNotRunReason(trace) : null;
|
|
160
|
+
const coverage_gap_type = !notRun && !evidence_offered ? deriveCoverageGap(trace) : null;
|
|
161
|
+
let verdict;
|
|
162
|
+
if (notRun)
|
|
163
|
+
verdict = "NOT_RUN";
|
|
164
|
+
else if (!evidence_offered)
|
|
165
|
+
verdict = "NO_OFFER";
|
|
166
|
+
else if (referenced_source_ids.length === 0)
|
|
167
|
+
verdict = "IGNORED";
|
|
168
|
+
else
|
|
169
|
+
verdict = "USED";
|
|
170
|
+
return {
|
|
171
|
+
session_id: sessionId,
|
|
172
|
+
turn_index: turnIndex,
|
|
173
|
+
trace_id: trace?.trace_id ?? null,
|
|
174
|
+
ran,
|
|
175
|
+
injected_floor,
|
|
176
|
+
injected_evidence,
|
|
177
|
+
not_run_reason,
|
|
178
|
+
enrich_latency_ms: trace?.enrich_latency_ms ?? null,
|
|
179
|
+
evidence_offered,
|
|
180
|
+
offered_source_ids,
|
|
181
|
+
zero_results: !evidence_offered,
|
|
182
|
+
coverage_gap_type,
|
|
183
|
+
evidence_tools_pulled: Array.from(toolSet),
|
|
184
|
+
pull_count,
|
|
185
|
+
referenced_source_ids,
|
|
186
|
+
cited_source_ids,
|
|
187
|
+
verdict,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
// --- render ------------------------------------------------------------------
|
|
191
|
+
function gapPhrase(t) {
|
|
192
|
+
switch (t) {
|
|
193
|
+
case "no_relevant_context":
|
|
194
|
+
return "no candidate matched your prompt";
|
|
195
|
+
case "enrich_timeout":
|
|
196
|
+
return "enrichment timed out";
|
|
197
|
+
case "enrich_unauthorized":
|
|
198
|
+
return "Meetless session expired, run `mla login`";
|
|
199
|
+
case "enrich_error":
|
|
200
|
+
return "enrichment failed";
|
|
201
|
+
case "missing_token":
|
|
202
|
+
return "no auth token";
|
|
203
|
+
default:
|
|
204
|
+
return "nothing relevant offered";
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
function notRunPhrase(r) {
|
|
208
|
+
switch (r) {
|
|
209
|
+
case "muted":
|
|
210
|
+
return "muted this session";
|
|
211
|
+
case "not_activated":
|
|
212
|
+
return "not activated for this repo";
|
|
213
|
+
case "suppressed":
|
|
214
|
+
return "injection suppressed";
|
|
215
|
+
case "timeout":
|
|
216
|
+
return "hook timed out";
|
|
217
|
+
case "error":
|
|
218
|
+
return "hook error";
|
|
219
|
+
default:
|
|
220
|
+
return "did not run (reason unknown)";
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
function pulledPhrase(r) {
|
|
224
|
+
if (r.pull_count === 0)
|
|
225
|
+
return "0";
|
|
226
|
+
const names = r.evidence_tools_pulled.length ? r.evidence_tools_pulled.join("+") : "evidence";
|
|
227
|
+
return `${names} ×${r.pull_count}`;
|
|
228
|
+
}
|
|
229
|
+
function citedPhrase(r) {
|
|
230
|
+
return r.cited_source_ids.length ? r.cited_source_ids.join(", ") : "0";
|
|
231
|
+
}
|
|
232
|
+
// The single-line, scannable footer (Section 7). Also used as the Langfuse score
|
|
233
|
+
// comment (Layer D) and the `mla turn` headline, so all three surfaces agree.
|
|
234
|
+
function renderFooter(r) {
|
|
235
|
+
const head = `🔎 mla · turn ${r.turn_index}`;
|
|
236
|
+
if (r.verdict === "NOT_RUN")
|
|
237
|
+
return `${head} · ${notRunPhrase(r.not_run_reason)} · NOT_RUN`;
|
|
238
|
+
if (r.verdict === "NO_OFFER")
|
|
239
|
+
return `${head} · floor only · ${gapPhrase(r.coverage_gap_type)} · NO_OFFER`;
|
|
240
|
+
const latency = r.enrich_latency_ms != null ? `${r.enrich_latency_ms}ms` : "?ms";
|
|
241
|
+
const offer = `evidence injected (${r.offered_source_ids.length} src, ${latency})`;
|
|
242
|
+
return `${head} · ${offer} · pulled ${pulledPhrase(r)} · cited ${citedPhrase(r)} · ${r.verdict}`;
|
|
243
|
+
}
|
|
244
|
+
// The C-lite injection payload: the footer wrapped in a context block with one
|
|
245
|
+
// soft, optional nudge. Best-effort surfacing; never a command (D3).
|
|
246
|
+
function renderBlockContext(r) {
|
|
247
|
+
return [
|
|
248
|
+
`<meetless-context kind="turn-recap" for-turn="${r.turn_index}">`,
|
|
249
|
+
renderFooter(r),
|
|
250
|
+
"You may surface this assist recap to the operator as a one-line footer if useful.",
|
|
251
|
+
"</meetless-context>",
|
|
252
|
+
].join("\n");
|
|
253
|
+
}
|
|
254
|
+
// The full multi-line expansion for `mla turn` human output.
|
|
255
|
+
function renderBlock(r) {
|
|
256
|
+
const ranDesc = !r.ran
|
|
257
|
+
? "no (no trace for this turn)"
|
|
258
|
+
: !r.injected_floor
|
|
259
|
+
? `suppressed (${r.not_run_reason ?? "control"})`
|
|
260
|
+
: r.injected_evidence
|
|
261
|
+
? "floor + evidence"
|
|
262
|
+
: "floor only";
|
|
263
|
+
const offeredDesc = r.evidence_offered
|
|
264
|
+
? `${r.offered_source_ids.length} source(s): ${r.offered_source_ids.join(", ")}`
|
|
265
|
+
: `none${r.coverage_gap_type ? ` (${r.coverage_gap_type})` : ""}`;
|
|
266
|
+
const lines = [
|
|
267
|
+
`🔎 mla turn ${r.turn_index} recap`,
|
|
268
|
+
` ran: ${ranDesc}`,
|
|
269
|
+
` offered: ${offeredDesc}`,
|
|
270
|
+
` latency: ${r.enrich_latency_ms != null ? `${r.enrich_latency_ms}ms` : "n/a"}`,
|
|
271
|
+
` pulled: ${pulledPhrase(r)}`,
|
|
272
|
+
` cited: ${citedPhrase(r)}`,
|
|
273
|
+
` referenced: ${r.referenced_source_ids.length ? r.referenced_source_ids.join(", ") : "none"}`,
|
|
274
|
+
` verdict: ${r.verdict}`,
|
|
275
|
+
];
|
|
276
|
+
if (r.trace_id)
|
|
277
|
+
lines.push(` trace: ${r.trace_id}`);
|
|
278
|
+
return lines.join("\n");
|
|
279
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Artifact ID resolver for `mla kb` commands.
|
|
3
|
+
// Source: notes/20260530-mla-kb-curation-cli-proposal-v2.md §3.5 + §4.2.
|
|
4
|
+
//
|
|
5
|
+
// Inputs accepted on the command line:
|
|
6
|
+
// kbdoc:<id> the canonical id of a KbDocument row.
|
|
7
|
+
// kbdocrev:<id> the id of a specific KbDocumentRevision row.
|
|
8
|
+
// note:<canonicalPath> legacy form during the deprecation window (§3.4).
|
|
9
|
+
// <bare path> treated as a canonical path the server will resolve.
|
|
10
|
+
//
|
|
11
|
+
// Client-side this layer does NOT canonicalize paths. Canonicalization lives
|
|
12
|
+
// server-side in intel's canonicalize_note_path() so that the case-fold rule
|
|
13
|
+
// and unicode NFC step apply consistently across CLI, MCP, and worker
|
|
14
|
+
// callers. The helper exists to (a) classify the operator's input shape, and
|
|
15
|
+
// (b) build display strings for receipts.
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.ArtifactInputError = void 0;
|
|
18
|
+
exports.parseArtifactInput = parseArtifactInput;
|
|
19
|
+
exports.formatKbDocId = formatKbDocId;
|
|
20
|
+
exports.formatKbDocRevId = formatKbDocRevId;
|
|
21
|
+
exports.formatArtifactInput = formatArtifactInput;
|
|
22
|
+
exports.toResolverQuery = toResolverQuery;
|
|
23
|
+
class ArtifactInputError extends Error {
|
|
24
|
+
constructor(message) {
|
|
25
|
+
super(message);
|
|
26
|
+
this.name = "ArtifactInputError";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
exports.ArtifactInputError = ArtifactInputError;
|
|
30
|
+
const KBDOC_PREFIX = "kbdoc:";
|
|
31
|
+
const KBDOCREV_PREFIX = "kbdocrev:";
|
|
32
|
+
const NOTE_PREFIX = "note:";
|
|
33
|
+
// Parse a positional input from `mla kb show / forget / reingest / purge /
|
|
34
|
+
// move`. Empty strings are rejected because they would silently degrade to a
|
|
35
|
+
// bare-path branch that resolves nothing.
|
|
36
|
+
function parseArtifactInput(raw) {
|
|
37
|
+
if (typeof raw !== "string") {
|
|
38
|
+
throw new ArtifactInputError("artifact input must be a string");
|
|
39
|
+
}
|
|
40
|
+
const trimmed = raw.trim();
|
|
41
|
+
if (!trimmed) {
|
|
42
|
+
throw new ArtifactInputError("artifact input is empty");
|
|
43
|
+
}
|
|
44
|
+
if (trimmed.startsWith(KBDOC_PREFIX)) {
|
|
45
|
+
const id = trimmed.slice(KBDOC_PREFIX.length).trim();
|
|
46
|
+
if (!id) {
|
|
47
|
+
throw new ArtifactInputError("kbdoc: prefix requires an id");
|
|
48
|
+
}
|
|
49
|
+
return { kind: "kbdoc", id };
|
|
50
|
+
}
|
|
51
|
+
if (trimmed.startsWith(KBDOCREV_PREFIX)) {
|
|
52
|
+
const id = trimmed.slice(KBDOCREV_PREFIX.length).trim();
|
|
53
|
+
if (!id) {
|
|
54
|
+
throw new ArtifactInputError("kbdocrev: prefix requires an id");
|
|
55
|
+
}
|
|
56
|
+
return { kind: "kbdocrev", id };
|
|
57
|
+
}
|
|
58
|
+
if (trimmed.startsWith(NOTE_PREFIX)) {
|
|
59
|
+
const p = trimmed.slice(NOTE_PREFIX.length).trim();
|
|
60
|
+
if (!p) {
|
|
61
|
+
throw new ArtifactInputError("note: prefix requires a path");
|
|
62
|
+
}
|
|
63
|
+
return { kind: "note", path: p };
|
|
64
|
+
}
|
|
65
|
+
return { kind: "path", path: trimmed };
|
|
66
|
+
}
|
|
67
|
+
// Receipts and renderers always express ids in the canonical `kbdoc:<id>`
|
|
68
|
+
// shape so audit log lookups and MCP queries can use the same string.
|
|
69
|
+
function formatKbDocId(id) {
|
|
70
|
+
if (!id)
|
|
71
|
+
throw new ArtifactInputError("formatKbDocId: id is required");
|
|
72
|
+
return `${KBDOC_PREFIX}${id}`;
|
|
73
|
+
}
|
|
74
|
+
function formatKbDocRevId(id) {
|
|
75
|
+
if (!id)
|
|
76
|
+
throw new ArtifactInputError("formatKbDocRevId: id is required");
|
|
77
|
+
return `${KBDOCREV_PREFIX}${id}`;
|
|
78
|
+
}
|
|
79
|
+
// Inverse of parseArtifactInput for display. Bare paths render as-is (no
|
|
80
|
+
// `note:` prefix) because the deprecation-window form is operator-typed
|
|
81
|
+
// input, not output the CLI should emit.
|
|
82
|
+
function formatArtifactInput(input) {
|
|
83
|
+
switch (input.kind) {
|
|
84
|
+
case "kbdoc":
|
|
85
|
+
return formatKbDocId(input.id);
|
|
86
|
+
case "kbdocrev":
|
|
87
|
+
return formatKbDocRevId(input.id);
|
|
88
|
+
case "note":
|
|
89
|
+
return `${NOTE_PREFIX}${input.path}`;
|
|
90
|
+
case "path":
|
|
91
|
+
return input.path;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// HTTP query-param shape: the intel routes that resolve operator input accept
|
|
95
|
+
// either `documentId=<id>` (when the CLI parsed a kbdoc/kbdocrev) or
|
|
96
|
+
// `path=<value>` (when the CLI saw a bare path or legacy note:). Centralized
|
|
97
|
+
// here so command files don't reinvent the encoding.
|
|
98
|
+
function toResolverQuery(input) {
|
|
99
|
+
switch (input.kind) {
|
|
100
|
+
case "kbdoc":
|
|
101
|
+
return { documentId: input.id };
|
|
102
|
+
case "kbdocrev":
|
|
103
|
+
return { revisionId: input.id };
|
|
104
|
+
case "note":
|
|
105
|
+
case "path":
|
|
106
|
+
return { path: input.path };
|
|
107
|
+
}
|
|
108
|
+
}
|