@meetless/mla 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (202) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +81 -0
  3. package/dist/build-info.json +9 -0
  4. package/dist/bundles/ask-core.js +396 -0
  5. package/dist/bundles/mcp.js +16592 -0
  6. package/dist/bundles/trace-core.js +263 -0
  7. package/dist/cli.js +828 -0
  8. package/dist/commands/activate.js +781 -0
  9. package/dist/commands/adoption.js +130 -0
  10. package/dist/commands/ask.js +290 -0
  11. package/dist/commands/context.js +114 -0
  12. package/dist/commands/debug.js +313 -0
  13. package/dist/commands/doctor.js +1021 -0
  14. package/dist/commands/enrich.js +427 -0
  15. package/dist/commands/evidence.js +229 -0
  16. package/dist/commands/flush.js +184 -0
  17. package/dist/commands/graph.js +104 -0
  18. package/dist/commands/init.js +272 -0
  19. package/dist/commands/internal-active-review.js +322 -0
  20. package/dist/commands/internal-auto-index.js +188 -0
  21. package/dist/commands/internal-capture-decisions.js +320 -0
  22. package/dist/commands/internal-evidence-correlate.js +239 -0
  23. package/dist/commands/internal-evidence-hooks.js +240 -0
  24. package/dist/commands/internal-evidence-inject.js +231 -0
  25. package/dist/commands/internal-finalize.js +221 -0
  26. package/dist/commands/internal-pretool-observe.js +225 -0
  27. package/dist/commands/internal-refresh.js +136 -0
  28. package/dist/commands/internal-session-nudge.js +120 -0
  29. package/dist/commands/internal-steer-sync.js +117 -0
  30. package/dist/commands/internal-turn-recap.js +140 -0
  31. package/dist/commands/kb.js +375 -0
  32. package/dist/commands/kb_add.js +681 -0
  33. package/dist/commands/kb_forget.js +283 -0
  34. package/dist/commands/kb_move.js +45 -0
  35. package/dist/commands/kb_pending.js +410 -0
  36. package/dist/commands/kb_personal.js +149 -0
  37. package/dist/commands/kb_promote.js +188 -0
  38. package/dist/commands/kb_purge.js +168 -0
  39. package/dist/commands/kb_reingest.js +335 -0
  40. package/dist/commands/kb_retime.js +170 -0
  41. package/dist/commands/kb_review.js +391 -0
  42. package/dist/commands/kb_revision.js +179 -0
  43. package/dist/commands/kb_show.js +385 -0
  44. package/dist/commands/label.js +226 -0
  45. package/dist/commands/login.js +295 -0
  46. package/dist/commands/logout.js +108 -0
  47. package/dist/commands/mcp-supervisor.js +93 -0
  48. package/dist/commands/mcp.js +227 -0
  49. package/dist/commands/queue-prune.js +98 -0
  50. package/dist/commands/review.js +358 -0
  51. package/dist/commands/rewire.js +124 -0
  52. package/dist/commands/rules.js +728 -0
  53. package/dist/commands/scan-context.js +67 -0
  54. package/dist/commands/session.js +347 -0
  55. package/dist/commands/stats.js +479 -0
  56. package/dist/commands/status.js +61 -0
  57. package/dist/commands/summary.js +250 -0
  58. package/dist/commands/turn.js +114 -0
  59. package/dist/commands/uninstall.js +222 -0
  60. package/dist/commands/whoami.js +102 -0
  61. package/dist/commands/workspace.js +130 -0
  62. package/dist/hooks-template/ce0-post-tool-use.sh +34 -0
  63. package/dist/hooks-template/ce0-session-start.sh +49 -0
  64. package/dist/hooks-template/ce0-stop.sh +29 -0
  65. package/dist/hooks-template/ce0-user-prompt-submit.sh +38 -0
  66. package/dist/hooks-template/common.sh +934 -0
  67. package/dist/hooks-template/event-batch-filter.jq +67 -0
  68. package/dist/hooks-template/flush.sh +503 -0
  69. package/dist/hooks-template/post-tool-use.sh +423 -0
  70. package/dist/hooks-template/pre-tool-use.sh +69 -0
  71. package/dist/hooks-template/session-start.sh +140 -0
  72. package/dist/hooks-template/stop.sh +308 -0
  73. package/dist/hooks-template/user-prompt-submit.sh +1162 -0
  74. package/dist/lib/activation.js +79 -0
  75. package/dist/lib/active-conflict-cache.js +141 -0
  76. package/dist/lib/active-memory.js +59 -0
  77. package/dist/lib/active-review-runner.js +26 -0
  78. package/dist/lib/agent-decision/index.js +25 -0
  79. package/dist/lib/agent-decision/keys.js +49 -0
  80. package/dist/lib/agent-decision/normalize-claude.js +183 -0
  81. package/dist/lib/agent-decision/types.js +21 -0
  82. package/dist/lib/agent-decision/validate.js +216 -0
  83. package/dist/lib/analytics/capture.js +96 -0
  84. package/dist/lib/analytics/command-event.js +267 -0
  85. package/dist/lib/analytics/consent.js +58 -0
  86. package/dist/lib/analytics/coverage-gap.js +96 -0
  87. package/dist/lib/analytics/envelope.js +236 -0
  88. package/dist/lib/analytics/event-id.js +86 -0
  89. package/dist/lib/analytics/evidence.js +150 -0
  90. package/dist/lib/analytics/followthrough.js +194 -0
  91. package/dist/lib/analytics/forwarder.js +109 -0
  92. package/dist/lib/analytics/logs.js +78 -0
  93. package/dist/lib/analytics/metrics.js +78 -0
  94. package/dist/lib/analytics/recorder.js +92 -0
  95. package/dist/lib/analytics/review-analytics.js +75 -0
  96. package/dist/lib/analytics/sequence.js +77 -0
  97. package/dist/lib/analytics/store.js +131 -0
  98. package/dist/lib/analytics/turn-recap.js +279 -0
  99. package/dist/lib/artifact_id.js +108 -0
  100. package/dist/lib/auth-breaker.js +161 -0
  101. package/dist/lib/auto-index.js +112 -0
  102. package/dist/lib/classifier.js +88 -0
  103. package/dist/lib/config.js +298 -0
  104. package/dist/lib/conflict-advisory.js +64 -0
  105. package/dist/lib/debug-bundle.js +520 -0
  106. package/dist/lib/enrichment/ingest.js +301 -0
  107. package/dist/lib/enrichment/plan.js +253 -0
  108. package/dist/lib/enrichment/protocol.js +359 -0
  109. package/dist/lib/enrichment/scout-brief.js +176 -0
  110. package/dist/lib/failure-telemetry.js +444 -0
  111. package/dist/lib/git.js +200 -0
  112. package/dist/lib/governance-cache.js +77 -0
  113. package/dist/lib/governed-path-cache.js +76 -0
  114. package/dist/lib/http.js +677 -0
  115. package/dist/lib/identity-envelope.js +23 -0
  116. package/dist/lib/kb-candidate.js +65 -0
  117. package/dist/lib/kb_acl.js +98 -0
  118. package/dist/lib/login.js +353 -0
  119. package/dist/lib/mcp-fetchers.js +130 -0
  120. package/dist/lib/mcp-restart.js +47 -0
  121. package/dist/lib/observability.js +805 -0
  122. package/dist/lib/open-url.js +33 -0
  123. package/dist/lib/orphan-guard.js +70 -0
  124. package/dist/lib/packaged.js +21 -0
  125. package/dist/lib/reconcile-sessions.js +171 -0
  126. package/dist/lib/redactor.js +89 -0
  127. package/dist/lib/relationship-candidate-query.js +27 -0
  128. package/dist/lib/render.js +611 -0
  129. package/dist/lib/rules/applicability.js +64 -0
  130. package/dist/lib/rules/attest-code-rule-version.js +47 -0
  131. package/dist/lib/rules/attest-notes-location.js +217 -0
  132. package/dist/lib/rules/attest-rule-version.js +69 -0
  133. package/dist/lib/rules/canonical-json.js +97 -0
  134. package/dist/lib/rules/ce0-emit.js +64 -0
  135. package/dist/lib/rules/ce0-evidence.js +281 -0
  136. package/dist/lib/rules/ce0-recall-sample.js +82 -0
  137. package/dist/lib/rules/ce0-rule.js +55 -0
  138. package/dist/lib/rules/ce0-sampling-bucket.js +15 -0
  139. package/dist/lib/rules/ce0-store.js +683 -0
  140. package/dist/lib/rules/ce0-telemetry-project.js +93 -0
  141. package/dist/lib/rules/ce0-telemetry.js +158 -0
  142. package/dist/lib/rules/code-rule-registry.js +17 -0
  143. package/dist/lib/rules/command-match.js +185 -0
  144. package/dist/lib/rules/consult-evidence-binding.js +27 -0
  145. package/dist/lib/rules/consultation-capture-adapter.js +193 -0
  146. package/dist/lib/rules/content-match.js +56 -0
  147. package/dist/lib/rules/deny-admission.js +99 -0
  148. package/dist/lib/rules/durable-observation.js +190 -0
  149. package/dist/lib/rules/enforce-notes-version.js +421 -0
  150. package/dist/lib/rules/evaluation-input-hash.js +126 -0
  151. package/dist/lib/rules/evaluator.js +108 -0
  152. package/dist/lib/rules/inert-rule-families.js +51 -0
  153. package/dist/lib/rules/input-authority-resolver.js +241 -0
  154. package/dist/lib/rules/interception-schema.js +170 -0
  155. package/dist/lib/rules/interception-store.js +267 -0
  156. package/dist/lib/rules/live-input-authority.js +66 -0
  157. package/dist/lib/rules/local-matcher.js +108 -0
  158. package/dist/lib/rules/local-observe.js +79 -0
  159. package/dist/lib/rules/local-rule-version-repo.js +214 -0
  160. package/dist/lib/rules/memory-requirement.js +109 -0
  161. package/dist/lib/rules/notes-observe.js +39 -0
  162. package/dist/lib/rules/notes-path.js +261 -0
  163. package/dist/lib/rules/notes-rule.js +75 -0
  164. package/dist/lib/rules/observe-adapter.js +114 -0
  165. package/dist/lib/rules/observed-rule-hash.js +119 -0
  166. package/dist/lib/rules/prompt-submit-adapter.js +132 -0
  167. package/dist/lib/rules/requirement-subject.js +240 -0
  168. package/dist/lib/rules/rule-activity.js +67 -0
  169. package/dist/lib/rules/rule-version-hash.js +151 -0
  170. package/dist/lib/rules/runtime-scope.js +55 -0
  171. package/dist/lib/rules/stop-adapter.js +116 -0
  172. package/dist/lib/rules/stop-response-snapshot.js +174 -0
  173. package/dist/lib/rules/types.js +10 -0
  174. package/dist/lib/rules/ulid.js +46 -0
  175. package/dist/lib/rules/version-evaluation.js +156 -0
  176. package/dist/lib/scanner/agent-memory.js +99 -0
  177. package/dist/lib/scanner/bootstrap-summary.js +87 -0
  178. package/dist/lib/scanner/cache.js +59 -0
  179. package/dist/lib/scanner/frontmatter.js +42 -0
  180. package/dist/lib/scanner/parse-directives.js +69 -0
  181. package/dist/lib/scanner/parse-structured.js +72 -0
  182. package/dist/lib/scanner/render.js +73 -0
  183. package/dist/lib/scanner/scan.js +132 -0
  184. package/dist/lib/scanner/score.js +38 -0
  185. package/dist/lib/scanner/scout-mission.js +126 -0
  186. package/dist/lib/scanner/types.js +7 -0
  187. package/dist/lib/session-scope.js +195 -0
  188. package/dist/lib/spool.js +355 -0
  189. package/dist/lib/staleness.js +100 -0
  190. package/dist/lib/steer-cache.js +87 -0
  191. package/dist/lib/tagged-reference.js +20 -0
  192. package/dist/lib/temporal.js +109 -0
  193. package/dist/lib/turn-recap-emit.js +67 -0
  194. package/dist/lib/unwire.js +253 -0
  195. package/dist/lib/update-check.js +469 -0
  196. package/dist/lib/update-notifier.js +217 -0
  197. package/dist/lib/upgrade-apply.js +643 -0
  198. package/dist/lib/wire.js +1087 -0
  199. package/dist/lib/workspace.js +96 -0
  200. package/dist/lib/zip.js +154 -0
  201. package/dist/pretool-entry.js +37 -0
  202. package/package.json +75 -0
@@ -0,0 +1,174 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.selectParentAssistantText = selectParentAssistantText;
4
+ exports.readStopResponseSnapshot = readStopResponseSnapshot;
5
+ const fs_1 = require("fs");
6
+ const canonical_json_1 = require("./canonical-json");
7
+ // CE0 §2.3 Stage B, the best-effort response snapshot
8
+ // (notes/20260617-evidence-consultation-forcing-function-proposal.md §2.3, lines 1119-1160).
9
+ //
10
+ // Stage B is layered ON TOP of the Stage A deadline claim, never inside it: a transcript read must
11
+ // never delay Stop, fail Stop, or roll back the deadline. This module holds the PURE selector
12
+ // (PARENT_ASSISTANT_TEXT_V1, with no filesystem surface) and the bounded backward reader that feeds
13
+ // it real transcript records. The reader is best-effort: every failure mode resolves to a stable
14
+ // labelability reason, never a throw.
15
+ const MAX_WINDOW_BYTES = 2 * 1024 * 1024; // 2 MiB (§2.3)
16
+ const MAX_RECORDS = 256; // at most 256 records from the tail (§2.3)
17
+ const NEWLINE = 0x0a; // JSONL is newline-delimited; 0x0a never appears inside a UTF-8 continuation byte
18
+ /**
19
+ * The §2.3 PARENT_ASSISTANT_TEXT_V1 selector, as a pure function over already-parsed transcript
20
+ * records given in file order (oldest first). It returns the canonical answer of the latest top-level
21
+ * parent assistant record, or null when the scanned window holds no such record
22
+ * (NO_PARENT_ASSISTANT_RECORD).
23
+ *
24
+ * A "top-level parent assistant record" is one whose `type` is "assistant" and which is NOT a
25
+ * sidechain / subagent record (`isSidechain === true`). User, system, progress, and tool-result
26
+ * records are excluded by that same `type` filter. The canonical answer is the selected record's text
27
+ * blocks (`block.type === "text"`) joined with a single literal newline, preserving each block's text
28
+ * exactly; a record with no text blocks yields the empty answer "" rather than skipping to an earlier
29
+ * record. The latest such record wins, so the scan runs from the newest record backward and stops at
30
+ * the first match.
31
+ */
32
+ function selectParentAssistantText(records) {
33
+ const idx = findLatestParentAssistantIndex(records);
34
+ return idx < 0 ? null : joinTextBlocks(records[idx]);
35
+ }
36
+ /** The latest (highest-index) top-level parent assistant record, or -1 when there is none. The byte
37
+ * reader and the pure text selector share this one selection predicate. */
38
+ function findLatestParentAssistantIndex(records) {
39
+ for (let i = records.length - 1; i >= 0; i--) {
40
+ if (isTopLevelParentAssistant(records[i]))
41
+ return i;
42
+ }
43
+ return -1;
44
+ }
45
+ function isTopLevelParentAssistant(rec) {
46
+ if (typeof rec !== "object" || rec === null)
47
+ return false;
48
+ const r = rec;
49
+ return r.type === "assistant" && r.isSidechain !== true;
50
+ }
51
+ /** Extract `message.content[]` text blocks in order and join them with a single literal newline.
52
+ * A missing / malformed message, a non-array content, or an absence of text blocks all yield "". */
53
+ function joinTextBlocks(rec) {
54
+ const message = rec.message;
55
+ if (typeof message !== "object" || message === null)
56
+ return "";
57
+ const content = message.content;
58
+ if (!Array.isArray(content))
59
+ return "";
60
+ const texts = [];
61
+ for (const block of content) {
62
+ if (typeof block === "object" && block !== null) {
63
+ const b = block;
64
+ if (b.type === "text" && typeof b.text === "string") {
65
+ texts.push(b.text);
66
+ }
67
+ }
68
+ }
69
+ return texts.join("\n");
70
+ }
71
+ /**
72
+ * Read the §2.3 Stage B response snapshot from the tail of a Claude transcript. Reads at most 2 MiB
73
+ * and at most 256 records backward from the end, selects the latest top-level parent assistant record,
74
+ * and returns its `responseHash = sha256Hex(canonicalAnswer)` plus a byte-exact `ResponseSourceRefV1`
75
+ * pointer the offline exporter can rehydrate deterministically. Any unreadable / absent transcript or
76
+ * empty selection window resolves to a stable reason; this function NEVER throws.
77
+ */
78
+ function readStopResponseSnapshot(transcriptPath) {
79
+ if (!transcriptPath)
80
+ return { ok: false, reason: "TRANSCRIPT_MISSING" };
81
+ let isFile;
82
+ try {
83
+ isFile = (0, fs_1.statSync)(transcriptPath).isFile();
84
+ }
85
+ catch (err) {
86
+ return { ok: false, reason: isMissingError(err) ? "TRANSCRIPT_MISSING" : "TRANSCRIPT_UNREADABLE" };
87
+ }
88
+ // A path that exists but is not a regular file (e.g. a directory) is not MISSING; it simply cannot
89
+ // be read as a transcript.
90
+ if (!isFile)
91
+ return { ok: false, reason: "TRANSCRIPT_UNREADABLE" };
92
+ let coords;
93
+ try {
94
+ coords = readTailRecords(transcriptPath);
95
+ }
96
+ catch {
97
+ return { ok: false, reason: "TRANSCRIPT_UNREADABLE" };
98
+ }
99
+ const idx = findLatestParentAssistantIndex(coords.map((c) => c.record));
100
+ if (idx < 0)
101
+ return { ok: false, reason: "NO_PARENT_ASSISTANT_RECORD" };
102
+ const chosen = coords[idx];
103
+ const canonicalAnswer = joinTextBlocks(chosen.record);
104
+ const responseHash = (0, canonical_json_1.sha256Hex)(canonicalAnswer);
105
+ const responseSourceRef = {
106
+ kind: "CLAUDE_TRANSCRIPT_JSONL",
107
+ version: 1,
108
+ transcriptPath,
109
+ recordByteOffset: chosen.byteOffset,
110
+ recordByteLength: chosen.byteLength,
111
+ // The line's exact bytes; the line is valid UTF-8 (it parsed), so hashing the decoded string
112
+ // re-encodes to the identical bytes the exporter will read at recordByteOffset.
113
+ recordSha256: (0, canonical_json_1.sha256Hex)(chosen.bytes),
114
+ selector: "PARENT_ASSISTANT_TEXT_V1",
115
+ };
116
+ return { ok: true, responseHash, responseSourceRef };
117
+ }
118
+ function isMissingError(err) {
119
+ return (typeof err === "object" &&
120
+ err !== null &&
121
+ err.code === "ENOENT");
122
+ }
123
+ /**
124
+ * Read the tail window (at most 2 MiB) and parse it into records with exact byte coordinates, keeping
125
+ * at most the last 256. When the window starts mid-file it almost certainly starts mid-record, so the
126
+ * partial first line is dropped to keep every recorded byte span complete. Unparseable lines in the
127
+ * live window are skipped (the selected record, a top-level assistant, parses by construction).
128
+ */
129
+ function readTailRecords(transcriptPath) {
130
+ const fd = (0, fs_1.openSync)(transcriptPath, "r");
131
+ let windowStart;
132
+ let buf;
133
+ let read;
134
+ try {
135
+ const size = (0, fs_1.fstatSync)(fd).size;
136
+ const windowSize = Math.min(size, MAX_WINDOW_BYTES);
137
+ windowStart = size - windowSize;
138
+ buf = Buffer.allocUnsafe(windowSize);
139
+ read = 0;
140
+ while (read < windowSize) {
141
+ const n = (0, fs_1.readSync)(fd, buf, read, windowSize - read, windowStart + read);
142
+ if (n === 0)
143
+ break;
144
+ read += n;
145
+ }
146
+ }
147
+ finally {
148
+ (0, fs_1.closeSync)(fd);
149
+ }
150
+ let cursor = 0;
151
+ if (windowStart > 0) {
152
+ const firstNl = buf.indexOf(NEWLINE, 0);
153
+ cursor = firstNl === -1 ? read : firstNl + 1;
154
+ }
155
+ const coords = [];
156
+ while (cursor < read) {
157
+ const nl = buf.indexOf(NEWLINE, cursor);
158
+ const lineEnd = nl === -1 ? read : nl; // exclusive; excludes the trailing newline
159
+ if (lineEnd > cursor) {
160
+ const bytes = buf.subarray(cursor, lineEnd).toString("utf8");
161
+ try {
162
+ const record = JSON.parse(bytes);
163
+ coords.push({ record, byteOffset: windowStart + cursor, byteLength: lineEnd - cursor, bytes });
164
+ }
165
+ catch {
166
+ // not valid JSONL at this offset: skip it, it cannot be the selected assistant record
167
+ }
168
+ }
169
+ if (nl === -1)
170
+ break;
171
+ cursor = nl + 1;
172
+ }
173
+ return coords.length > MAX_RECORDS ? coords.slice(coords.length - MAX_RECORDS) : coords;
174
+ }
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ // Schema-independent R0 core for "rules as node and action interception"
3
+ // (notes/20260615-rules-as-node-and-action-interception-consolidated-proposal.md).
4
+ //
5
+ // This module declares ONLY the in-memory shapes the pure logic operates on. It
6
+ // deliberately contains no persistence, no SQLite rows, no deny behavior, and no
7
+ // rollout state: those are the documented seam left for a later slice. Everything
8
+ // here is the value-level contract the parser, selector, evaluator, and the
9
+ // observe-only adapter agree on.
10
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.encodeTime = encodeTime;
4
+ exports.encodeRandom = encodeRandom;
5
+ exports.ulid = ulid;
6
+ const crypto_1 = require("crypto");
7
+ /**
8
+ * A locally-minted ULID, the primary key for R0 interception records.
9
+ *
10
+ * PreToolUse hooks receive no tool_use_id, so the interception path mints its own
11
+ * attempt_id and evaluation_id. A ULID gives a 26-char Crockford base32 identifier: a
12
+ * 48-bit millisecond timestamp encoded as the leading 10 chars (so keys sort
13
+ * lexicographically by mint time, matching created_at order) followed by 80 bits of
14
+ * randomness (16 chars) so the attempt row and its observed-arm evaluation row, minted in
15
+ * the same transaction within one millisecond, never collide.
16
+ *
17
+ * `now` and the random source are parameters so the durable-observation slice and its
18
+ * tests stay deterministic; production callers pass nothing and get Date.now() + a CSPRNG.
19
+ */
20
+ // Crockford base32: digits 0-9 then A-Z excluding I, L, O, U (32 symbols).
21
+ const CROCKFORD = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
22
+ // 256 is an exact multiple of 32, so a single random byte modulo 32 is unbiased.
23
+ const defaultRand = () => (0, crypto_1.randomBytes)(1)[0] % 32;
24
+ /** Encode a non-negative millisecond timestamp as `len` Crockford base32 chars (big-endian). */
25
+ function encodeTime(ms, len = 10) {
26
+ let value = ms;
27
+ let out = "";
28
+ for (let i = 0; i < len; i++) {
29
+ const mod = value % 32;
30
+ out = CROCKFORD[mod] + out;
31
+ value = (value - mod) / 32;
32
+ }
33
+ return out;
34
+ }
35
+ /** Encode `len` Crockford base32 chars drawn from a 0..31 integer source. */
36
+ function encodeRandom(len = 16, rand = defaultRand) {
37
+ let out = "";
38
+ for (let i = 0; i < len; i++) {
39
+ out += CROCKFORD[rand() % 32];
40
+ }
41
+ return out;
42
+ }
43
+ /** Mint a 26-char ULID: a 10-char time prefix followed by 16 chars of randomness. */
44
+ function ulid(now = Date.now(), rand = defaultRand) {
45
+ return encodeTime(now, 10) + encodeRandom(16, rand);
46
+ }
@@ -0,0 +1,156 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.versionBackedVerdict = versionBackedVerdict;
4
+ exports.recordVersionEvaluation = recordVersionEvaluation;
5
+ exports.evaluateAndRecordNotesVersion = evaluateAndRecordNotesVersion;
6
+ const evaluation_input_hash_1 = require("./evaluation-input-hash");
7
+ const interception_store_1 = require("./interception-store");
8
+ const local_rule_version_repo_1 = require("./local-rule-version-repo");
9
+ const durable_observation_1 = require("./durable-observation");
10
+ const notes_path_1 = require("./notes-path");
11
+ const observe_adapter_1 = require("./observe-adapter");
12
+ const evaluator_1 = require("./evaluator");
13
+ const attest_notes_location_1 = require("./attest-notes-location");
14
+ const ulid_1 = require("./ulid");
15
+ /**
16
+ * The verdict computed FROM the attested payload. First it checks that the version's declared
17
+ * compliance triple is the one this MLA build can reproduce; on any mismatch it refuses with UNKNOWN
18
+ * / EVALUATOR_UNSUPPORTED WITHOUT reading the path (it cannot honor semantics it does not implement).
19
+ * On a match it delegates to the same snapshot-pure rule R0 uses, over the payload's forbidden root,
20
+ * so a supported-triple version evaluates byte-for-byte like the observed rule it was attested from.
21
+ */
22
+ function versionBackedVerdict(payload, target) {
23
+ const c = payload.compliance;
24
+ const supported = c.evaluatorContractVersion === durable_observation_1.EVALUATOR_CONTRACT_VERSION &&
25
+ c.matcherSchemaVersion === durable_observation_1.MATCHER_SCHEMA_VERSION &&
26
+ c.pathCanonicalizerVersion === durable_observation_1.PATH_CANONICALIZER_VERSION;
27
+ if (!supported) {
28
+ return { result: "UNKNOWN", verdictReasonCode: "EVALUATOR_UNSUPPORTED" };
29
+ }
30
+ return (0, durable_observation_1.verdictFromEvaluationInput)(target, c.config.forbiddenRootRelativePath);
31
+ }
32
+ /**
33
+ * Persist one version-backed evaluation as the two-record pair, atomically. Mints two distinct ULIDs
34
+ * (one attempt, one evaluation), parses the version's immutable payload for its forbidden root,
35
+ * computes the version-backed verdict, builds the canonical evaluation-input-v1 snapshot + hash, and
36
+ * writes a tool_attempt (NO_DECISION / NOT_APPLICABLE deny status) plus one VERSION-arm
37
+ * rule_evaluation_record inside a single BEGIN IMMEDIATE transaction so an interception is never
38
+ * half-recorded.
39
+ *
40
+ * The evaluation-input-v1 snapshot carries the RUNNING evaluator's supported triple, exactly like
41
+ * R0: those fields denote the evaluator that ran, and the version's own declared triple is recoverable
42
+ * from the version payload behind the recorded canonicalPayloadHash. Consequently R0's snapshot-only
43
+ * replay reproduces the stored verdict for any supported-triple version (the entire pilot); for a
44
+ * foreign-triple version the verdict is UNKNOWN / EVALUATOR_UNSUPPORTED and its authoritative replay
45
+ * basis is the referenced version row, not the snapshot alone.
46
+ */
47
+ function recordVersionEvaluation(store, subject, ctx) {
48
+ const payload = JSON.parse(subject.version.rulePayload);
49
+ const attemptId = (0, ulid_1.ulid)(ctx.now, ctx.rand);
50
+ const evaluationId = (0, ulid_1.ulid)(ctx.now, ctx.rand);
51
+ const evaluationInput = {
52
+ toolName: subject.toolName,
53
+ target: subject.target,
54
+ forbiddenRootRelativePath: payload.compliance.config.forbiddenRootRelativePath,
55
+ evaluatorContractVersion: durable_observation_1.EVALUATOR_CONTRACT_VERSION,
56
+ matcherSchemaVersion: durable_observation_1.MATCHER_SCHEMA_VERSION,
57
+ pathCanonicalizerVersion: durable_observation_1.PATH_CANONICALIZER_VERSION,
58
+ };
59
+ const verdict = versionBackedVerdict(payload, subject.target);
60
+ const attempt = {
61
+ attemptId,
62
+ runtimeScopeId: ctx.runtimeScopeId,
63
+ sessionId: ctx.sessionId,
64
+ toolName: subject.toolName,
65
+ evaluationInputSnapshot: (0, evaluation_input_hash_1.serializeEvaluationInput)(evaluationInput),
66
+ evaluationInputHash: (0, evaluation_input_hash_1.evaluationInputHash)(evaluationInput),
67
+ aggregateDecision: "NO_DECISION",
68
+ denyEmissionStatus: "NOT_APPLICABLE",
69
+ inputAuthorityConfigHash: null,
70
+ createdAt: ctx.createdAt,
71
+ };
72
+ const evaluation = {
73
+ evaluationId,
74
+ attemptId,
75
+ runtimeScopeId: ctx.runtimeScopeId,
76
+ result: verdict.result,
77
+ eligibleEnforcement: "OBSERVE",
78
+ effectiveEnforcement: "OBSERVE",
79
+ verdictReasonCode: verdict.verdictReasonCode,
80
+ gateReasonCode: null,
81
+ evaluatorContractVersion: durable_observation_1.EVALUATOR_CONTRACT_VERSION,
82
+ ruleVersionId: subject.version.versionId,
83
+ canonicalPayloadHash: subject.version.canonicalPayloadHash,
84
+ createdAt: ctx.createdAt,
85
+ };
86
+ store.db
87
+ .transaction(() => {
88
+ (0, interception_store_1.insertToolAttempt)(store, attempt);
89
+ (0, local_rule_version_repo_1.insertVersionEvaluationRecord)(store, evaluation);
90
+ })
91
+ .immediate();
92
+ return {
93
+ attemptId,
94
+ evaluationId,
95
+ result: verdict.result,
96
+ verdictReasonCode: verdict.verdictReasonCode,
97
+ ruleVersionId: subject.version.versionId,
98
+ canonicalPayloadHash: subject.version.canonicalPayloadHash,
99
+ };
100
+ }
101
+ const NO_DECISION = {};
102
+ /**
103
+ * The version-backed PreToolUse seam for the notes-location pilot. Parses the hook payload, resolves
104
+ * the LIVE attested version for the scope, runs the pure selector against the version's applicability,
105
+ * classifies the target, and on an applicable call persists the version-arm evaluation. Always returns
106
+ * the empty, decision-free hook response; the durable outcome travels on the side channel.
107
+ *
108
+ * Skip semantics (persist nothing): a malformed payload or a missing session id is INFRA (we refuse
109
+ * to fabricate the NOT NULL session_id); no LIVE version for the scope is NO_LIVE_VERSION (nothing has
110
+ * been attested, so there is no version to evaluate against); a non-Write/Edit tool or a glob non-match
111
+ * is NOT_APPLICABLE. Resolution order puts the infrastructure faults first, then the absent version,
112
+ * then applicability, so an unattested-but-applicable call reports NO_LIVE_VERSION (not NOT_APPLICABLE).
113
+ */
114
+ async function evaluateAndRecordNotesVersion(store, input) {
115
+ const parsed = (0, observe_adapter_1.parsePreToolUseInput)(input.rawStdin);
116
+ if (!parsed) {
117
+ return { response: NO_DECISION, outcome: { kind: "INFRA", diagnostic: "malformed hook input" } };
118
+ }
119
+ if (parsed.session_id === undefined) {
120
+ return { response: NO_DECISION, outcome: { kind: "INFRA", diagnostic: "missing session_id" } };
121
+ }
122
+ const version = (0, local_rule_version_repo_1.getLiveLocalRuleVersion)(store, input.runtimeScopeId, attest_notes_location_1.NOTES_LOCATION_RULE_ID);
123
+ if (!version) {
124
+ return { response: NO_DECISION, outcome: { kind: "NO_LIVE_VERSION" } };
125
+ }
126
+ const payload = JSON.parse(version.rulePayload);
127
+ const call = { toolName: parsed.tool_name, toolInput: parsed.tool_input };
128
+ if ((0, evaluator_1.selectRule)(call, payload.applicability) === "NOT_APPLICABLE" || payload.applicability.mode !== "action") {
129
+ return { response: NO_DECISION, outcome: { kind: "NOT_APPLICABLE" } };
130
+ }
131
+ // The selector proved the tool is in the version's {Write, Edit} list and the matcher field holds a
132
+ // *.md string, so this narrowing is sound.
133
+ const toolName = parsed.tool_name;
134
+ const rawFilePath = parsed.tool_input[payload.applicability.matcher.field];
135
+ const classify = input.classifyRuntime ?? notes_path_1.classifyRuntimeTarget;
136
+ const target = await classify(rawFilePath, input.runtimeProjectRoot);
137
+ const persisted = recordVersionEvaluation(store, { toolName, target, version }, {
138
+ runtimeScopeId: input.runtimeScopeId,
139
+ sessionId: parsed.session_id,
140
+ createdAt: input.createdAt,
141
+ now: input.now,
142
+ rand: input.rand,
143
+ });
144
+ return {
145
+ response: NO_DECISION,
146
+ outcome: {
147
+ kind: "RECORDED",
148
+ attemptId: persisted.attemptId,
149
+ evaluationId: persisted.evaluationId,
150
+ result: persisted.result,
151
+ verdictReasonCode: persisted.verdictReasonCode,
152
+ ruleVersionId: persisted.ruleVersionId,
153
+ canonicalPayloadHash: persisted.canonicalPayloadHash,
154
+ },
155
+ };
156
+ }
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.agentMemoryDir = agentMemoryDir;
4
+ exports.readAgentMemoryFiles = readAgentMemoryFiles;
5
+ exports.parseAgentMemoryDirectives = parseAgentMemoryDirectives;
6
+ exports.discoverAgentMemoryDirectives = discoverAgentMemoryDirectives;
7
+ // src/lib/scanner/agent-memory.ts
8
+ //
9
+ // Claude Code stores per-project agent memory at
10
+ // `~/.claude/projects/<cwd-with-slashes-and-dots-as-dashes>/memory/`. Past sessions
11
+ // distill the rules the user taught them into `feedback_*.md` topic files there. That
12
+ // directory is NOT git-tracked, so `scanWorkspace`'s `git ls-files` enumeration
13
+ // structurally misses it. This module discovers those files so the cold-start scan can
14
+ // surface "the other things we need to support" beyond the committed instruction files.
15
+ //
16
+ // Hard trust gate: everything minted here is `machine_inferred` (untracked, per-machine,
17
+ // agent-distilled). Per the cold-start proposal (§54, §225, §305) untracked content is
18
+ // "not attested" and can NEVER earn must-follow; it rides advisory until a human attests.
19
+ // `render.ts` already enforces this (must-follow requires `human_attested`), and
20
+ // `scanWorkspace` keeps these out of the auto-injected `confirmedRulesXml` pack entirely.
21
+ const node_fs_1 = require("node:fs");
22
+ const node_os_1 = require("node:os");
23
+ const node_path_1 = require("node:path");
24
+ const types_1 = require("./types");
25
+ const frontmatter_1 = require("./frontmatter");
26
+ // A description that SHOUTS a normative modal is a MUST; everything else is a SHOULD.
27
+ // Mirrors parse-directives.ts MUST_TOKENS so strength is consistent across sources.
28
+ const MUST_TOKENS = /\b(MUST|NEVER|ALWAYS|REQUIRED|DO NOT|DON'?T|FORBIDDEN|NON-NEGOTIABLE)\b/;
29
+ // Resolve the agent-memory dir for a workspace cwd. Replicates Claude Code's projects-dir
30
+ // encoding (slashes AND dots become dashes). `home` is injectable for tests.
31
+ function agentMemoryDir(cwd, home = (0, node_os_1.homedir)()) {
32
+ const encoded = cwd.replace(/[/.]/g, "-");
33
+ return (0, node_path_1.join)(home, ".claude", "projects", encoded, "memory");
34
+ }
35
+ // Read the `feedback_*.md` topic files (the "rules the user gave" bucket) from an
36
+ // agent-memory dir, sorted for a stable/diffable worklist. Fails open to []: a missing
37
+ // dir (fresh machine, no prior agent memory) is the common case and must never abort the
38
+ // scan. The MEMORY.md index and project_/reference_ topic files are intentionally skipped
39
+ // here: the index is one-line pointers, and only feedback memories are coordination rules.
40
+ function readAgentMemoryFiles(dir) {
41
+ let names;
42
+ try {
43
+ names = (0, node_fs_1.readdirSync)(dir);
44
+ }
45
+ catch {
46
+ return [];
47
+ }
48
+ const out = [];
49
+ for (const name of names) {
50
+ if (!name.startsWith("feedback_") || !name.endsWith(".md"))
51
+ continue;
52
+ try {
53
+ out.push({ name, text: (0, node_fs_1.readFileSync)((0, node_path_1.join)(dir, name), "utf8") });
54
+ }
55
+ catch {
56
+ // An unreadable single file must not abort discovery of the rest.
57
+ }
58
+ }
59
+ return out.sort((a, b) => a.name.localeCompare(b.name));
60
+ }
61
+ // One advisory directive per feedback memory: its frontmatter `description` is the
62
+ // distilled one-line rule, which is exactly the grain a review worklist wants (§316,
63
+ // "one-line reason per file"). Files without a description are skipped; identical
64
+ // descriptions collapse to one.
65
+ function parseAgentMemoryDirectives(files) {
66
+ const out = [];
67
+ const seen = new Set();
68
+ for (const f of files) {
69
+ const { data } = (0, frontmatter_1.parseFrontmatter)(f.text);
70
+ const desc = (data.description ?? "").trim();
71
+ if (!desc)
72
+ continue;
73
+ const text = desc.replace(/\s+/g, " ");
74
+ if (seen.has(text))
75
+ continue;
76
+ seen.add(text);
77
+ const source = `agent-memory:${f.name}`;
78
+ out.push({
79
+ id: (0, types_1.directiveId)(source, text),
80
+ text,
81
+ source,
82
+ kind: "RULE",
83
+ strength: MUST_TOKENS.test(text) ? "MUST_FOLLOW" : "SHOULD_FOLLOW",
84
+ attestation: "machine_inferred",
85
+ });
86
+ }
87
+ return out;
88
+ }
89
+ // The default cap is a pathological-directory guard (e.g. a symlink loop or an unrelated
90
+ // tool dumping thousands of files under ~/.claude/projects), NOT a curation limit. A real
91
+ // feedback corpus is tens to low-hundreds of files (the dogfood repo has ~55) and must
92
+ // surface in full; bounding scan-time I/O only matters at the absurd end. Scan runs on
93
+ // `mla activate`, not the per-Write hot path, so reading a few hundred small files is cheap.
94
+ const DEFAULT_AGENT_MEMORY_CAP = 500;
95
+ // Discover + parse the agent-memory rules for a workspace cwd. The advisory worklist is
96
+ // surfaced for review, never bulk-injected. Fully fail-open via readAgentMemoryFiles.
97
+ function discoverAgentMemoryDirectives(cwd, home = (0, node_os_1.homedir)(), cap = DEFAULT_AGENT_MEMORY_CAP) {
98
+ return parseAgentMemoryDirectives(readAgentMemoryFiles(agentMemoryDir(cwd, home))).slice(0, cap);
99
+ }
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.renderActivationCard = renderActivationCard;
4
+ exports.renderBootstrapSummary = renderBootstrapSummary;
5
+ // GAP1 Slice 1: the activation "what we found" surface.
6
+ //
7
+ // `mla activate` already runs the deterministic Tier-1 scan, extracts directives,
8
+ // builds provisional context, and injects the high-confidence rules into the hot
9
+ // path (the M-slices + scanner + injector cover steps 2-5 of the design's
10
+ // `mla activate --bootstrap fast`, notes/20260611-onboarding-mla.md:1917). What was
11
+ // missing is step 6: the "Active agent instructions" review bundle that lets the
12
+ // human SEE what was found and what Meetless will do with it. That is the first-
13
+ // session magic moment; until now the card showed only raw file counts.
14
+ //
15
+ // Everything here is pure rendering over the existing ScanResult. It introduces NO
16
+ // new store: it reads the same three lists the scan already produces, split on the
17
+ // two-axis model:
18
+ // - directives : human-authored / high-confidence, injected NOW.
19
+ // - advisoryDirectives : machine_inferred, awaiting review, NEVER auto-injected.
20
+ // - staleSignals : need a keep/drop verdict.
21
+ const MAX_DIRECTIVES_SHOWN = 5;
22
+ function pluralize(count, noun) {
23
+ return `${count} ${noun}${count === 1 ? "" : "s"}`;
24
+ }
25
+ // The inventory headline. Kept identical to the long-standing card so its callers
26
+ // and golden assertions are unchanged; renderBootstrapSummary leads with it.
27
+ function renderActivationCard(inv) {
28
+ return [
29
+ `Found: ${pluralize(inv.instructionFiles, "agent-instruction file")} · ` +
30
+ `${pluralize(inv.decisionDocs, "decision/spec doc")} · ` +
31
+ `${pluralize(inv.legacyNotes, "legacy note")} · ` +
32
+ `${pluralize(inv.staleSignals, "likely-stale signal")}.`,
33
+ "For your first run, Meetless will use high-confidence project instructions and mark everything else provisional.",
34
+ ].join("\n");
35
+ }
36
+ // MUST_FOLLOW before SHOULD_FOLLOW; otherwise stable (the scan's own order). A
37
+ // stable sort keeps equal-strength directives in discovery order.
38
+ function byStrength(a, b) {
39
+ const rank = (d) => (d.strength === "MUST_FOLLOW" ? 0 : 1);
40
+ return rank(a) - rank(b);
41
+ }
42
+ function directiveBullet(d) {
43
+ return ` • ${d.text} (${d.source})`;
44
+ }
45
+ /**
46
+ * Render the full "Active agent instructions" bundle for `mla activate`. Leads with
47
+ * the inventory headline, then (only when non-empty):
48
+ * - the high-confidence directives guiding the session now (capped, with an "and N
49
+ * more" tail), MUST_FOLLOW first;
50
+ * - the count of machine_inferred advisory candidates awaiting review, with the
51
+ * `mla context advisory` pointer and an explicit "not injected" note;
52
+ * - the count of likely-stale signals needing a verdict, with `mla context list`.
53
+ * An empty graph degrades to a calm "nothing high-confidence yet" line; it never
54
+ * prints an empty section header or a stray bullet.
55
+ */
56
+ function renderBootstrapSummary(scan) {
57
+ const lines = [renderActivationCard(scan.inventory)];
58
+ const directives = [...scan.directives].sort(byStrength);
59
+ if (directives.length > 0) {
60
+ lines.push("");
61
+ lines.push("Guiding this session now (high-confidence, injected):");
62
+ const shown = directives.slice(0, MAX_DIRECTIVES_SHOWN);
63
+ for (const d of shown) {
64
+ lines.push(directiveBullet(d));
65
+ }
66
+ const remaining = directives.length - shown.length;
67
+ if (remaining > 0) {
68
+ lines.push(` …and ${pluralize(remaining, "more rule")}.`);
69
+ }
70
+ }
71
+ else {
72
+ lines.push("");
73
+ lines.push("No high-confidence agent instructions found yet; this first run stays provisional.");
74
+ }
75
+ if (scan.advisoryDirectives.length > 0) {
76
+ lines.push("");
77
+ lines.push(`Awaiting your review: ${pluralize(scan.advisoryDirectives.length, "advisory rule")} ` +
78
+ "from agent memory (machine_inferred, NOT injected).");
79
+ lines.push(" See them with `mla context advisory`.");
80
+ }
81
+ if (scan.staleSignals.length > 0) {
82
+ lines.push("");
83
+ lines.push(`Possibly stale: ${pluralize(scan.staleSignals.length, "signal")} that may no longer apply.`);
84
+ lines.push(" Review and keep/drop with `mla context list`.");
85
+ }
86
+ return lines.join("\n");
87
+ }
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.scanCachePath = scanCachePath;
4
+ exports.verdictsPath = verdictsPath;
5
+ exports.writeScanCache = writeScanCache;
6
+ exports.readScanCache = readScanCache;
7
+ exports.readVerdicts = readVerdicts;
8
+ exports.writeVerdicts = writeVerdicts;
9
+ exports.applyVerdicts = applyVerdicts;
10
+ const node_fs_1 = require("node:fs");
11
+ const node_os_1 = require("node:os");
12
+ const node_path_1 = require("node:path");
13
+ const render_1 = require("./render");
14
+ function wsDir(home, workspaceId) {
15
+ return (0, node_path_1.join)(home, ".meetless", "workspaces", workspaceId);
16
+ }
17
+ function scanCachePath(workspaceId, home = (0, node_os_1.homedir)()) {
18
+ return (0, node_path_1.join)(wsDir(home, workspaceId), "scan-cache.json");
19
+ }
20
+ function verdictsPath(workspaceId, home = (0, node_os_1.homedir)()) {
21
+ return (0, node_path_1.join)(wsDir(home, workspaceId), "scanner-verdicts.json");
22
+ }
23
+ function writeJson(path, value) {
24
+ (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(path), { recursive: true });
25
+ (0, node_fs_1.writeFileSync)(path, JSON.stringify(value, null, 2), "utf8");
26
+ }
27
+ function readJson(path) {
28
+ try {
29
+ return JSON.parse((0, node_fs_1.readFileSync)(path, "utf8"));
30
+ }
31
+ catch {
32
+ return null;
33
+ }
34
+ }
35
+ function writeScanCache(home, workspaceId, result) {
36
+ writeJson(scanCachePath(workspaceId, home), result);
37
+ }
38
+ function readScanCache(home, workspaceId) {
39
+ return readJson(scanCachePath(workspaceId, home));
40
+ }
41
+ const EMPTY_VERDICTS = { schemaVersion: 1, accepted: [], dismissed: [] };
42
+ function readVerdicts(home, workspaceId) {
43
+ return readJson(verdictsPath(workspaceId, home)) ?? { ...EMPTY_VERDICTS };
44
+ }
45
+ function writeVerdicts(home, workspaceId, v) {
46
+ writeJson(verdictsPath(workspaceId, home), v);
47
+ }
48
+ // Dismissed signals are removed; the stale block + inventory are re-derived so the
49
+ // cache the hot path reads always reflects the latest verdicts.
50
+ function applyVerdicts(result, verdicts) {
51
+ const dismissed = new Set(verdicts.dismissed);
52
+ const staleSignals = result.staleSignals.filter((s) => !dismissed.has(s.id));
53
+ return {
54
+ ...result,
55
+ staleSignals,
56
+ staleContextXml: (0, render_1.renderStaleContextXml)(staleSignals),
57
+ inventory: { ...result.inventory, staleSignals: staleSignals.length },
58
+ };
59
+ }