@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,79 @@
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.ACTIVATION_FILENAME = void 0;
37
+ exports.findActivation = findActivation;
38
+ const fs = __importStar(require("fs"));
39
+ const path = __importStar(require("path"));
40
+ // Per-folder activation marker (opt-in capture gate). The bash counterpart
41
+ // lives in hooks-template/common.sh (`meetless_activated`); this module is the
42
+ // TypeScript side used by `mla activate` (write) and `mla doctor` (report).
43
+ // Both sides MUST agree on the filename and the nearest-wins walk-up semantics.
44
+ // "marker" here means the folder activation marker `.meetless.json`; it is the
45
+ // only marker concept in the CLI.
46
+ exports.ACTIVATION_FILENAME = ".meetless.json";
47
+ // Walk UP from startDir looking for the nearest `.meetless.json`, nearest-wins,
48
+ // mirroring how Claude Code resolves CLAUDE.md and how common.sh's
49
+ // `meetless_activated` gate behaves. Returns null when no marker is found.
50
+ function findActivation(startDir) {
51
+ let dir = path.resolve(startDir);
52
+ // eslint-disable-next-line no-constant-condition
53
+ while (true) {
54
+ const candidate = path.join(dir, exports.ACTIVATION_FILENAME);
55
+ if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) {
56
+ const found = { path: candidate, dir };
57
+ try {
58
+ const parsed = JSON.parse(fs.readFileSync(candidate, "utf8"));
59
+ if (typeof parsed.workspaceId === "string" && parsed.workspaceId) {
60
+ found.workspaceId = parsed.workspaceId;
61
+ }
62
+ if (typeof parsed.workspaceName === "string" && parsed.workspaceName) {
63
+ found.workspaceName = parsed.workspaceName;
64
+ }
65
+ }
66
+ catch (e) {
67
+ // Matches the bash gate: a malformed marker still activates the folder
68
+ // (the file exists); the workspaceId is simply treated as absent.
69
+ found.parseError = e.message;
70
+ }
71
+ return found;
72
+ }
73
+ const parent = path.dirname(dir);
74
+ if (parent === dir)
75
+ break;
76
+ dir = parent;
77
+ }
78
+ return null;
79
+ }
@@ -0,0 +1,141 @@
1
+ "use strict";
2
+ // Active cross-session conflict snapshot: the zero-network hand-off between the
3
+ // CLI turn-boundary sync (`_internal steer-sync`, which fetches the session's
4
+ // currently-open conflicts from control on the SAME pass that pulls steers) and
5
+ // the PreToolUse hook (which surfaces a SOFT warning when one is open).
6
+ //
7
+ // G8 / D1, notes/20260626-g8-cross-session-conflict-redesign.md §11.3 (CRITICAL-5).
8
+ // The snapshot is the COMPLETE current open-conflict set, overwritten each turn
9
+ // (never appended), so a resolved conflict simply disappears on the next sync and
10
+ // the warning stops automatically. The hook reads it synchronously with NO network
11
+ // call (same hot-path constraint as the steer cache and the governance nudge).
12
+ //
13
+ // Two deliberate properties:
14
+ // - The signal source is the refreshed complete snapshot, NEVER steer-injection
15
+ // state. A steer can be injected once and then the conflict resolves; injection
16
+ // state is not conflict state (§11.3 / §11.4).
17
+ // - A snapshot that fails to refresh (sync down) FAILS OPEN: a reader past the TTL
18
+ // treats it as absent (no warning) rather than a stuck warning. The warning is
19
+ // soft, so the safe direction on staleness is to say nothing.
20
+ //
21
+ // The file lives beside the steer cache under $MEETLESS_HOME/logs/steer/ because the
22
+ // same turn-boundary pass writes both; the session id is opaque
23
+ // (CLAUDE_CODE_SESSION_ID), used verbatim like the steer cache.
24
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
25
+ if (k2 === undefined) k2 = k;
26
+ var desc = Object.getOwnPropertyDescriptor(m, k);
27
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
28
+ desc = { enumerable: true, get: function() { return m[k]; } };
29
+ }
30
+ Object.defineProperty(o, k2, desc);
31
+ }) : (function(o, m, k, k2) {
32
+ if (k2 === undefined) k2 = k;
33
+ o[k2] = m[k];
34
+ }));
35
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
36
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
37
+ }) : function(o, v) {
38
+ o["default"] = v;
39
+ });
40
+ var __importStar = (this && this.__importStar) || (function () {
41
+ var ownKeys = function(o) {
42
+ ownKeys = Object.getOwnPropertyNames || function (o) {
43
+ var ar = [];
44
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
45
+ return ar;
46
+ };
47
+ return ownKeys(o);
48
+ };
49
+ return function (mod) {
50
+ if (mod && mod.__esModule) return mod;
51
+ var result = {};
52
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
53
+ __setModuleDefault(result, mod);
54
+ return result;
55
+ };
56
+ })();
57
+ Object.defineProperty(exports, "__esModule", { value: true });
58
+ exports.DEFAULT_CONFLICT_GATE_MODE = exports.ACTIVE_CONFLICT_TTL_SECONDS = void 0;
59
+ exports.resolveConflictGateMode = resolveConflictGateMode;
60
+ exports.activeConflictCachePath = activeConflictCachePath;
61
+ exports.writeActiveConflictCache = writeActiveConflictCache;
62
+ exports.readActiveConflicts = readActiveConflicts;
63
+ const fs = __importStar(require("fs"));
64
+ const path = __importStar(require("path"));
65
+ const config_1 = require("./config");
66
+ // How long a snapshot stays trusted before a reader treats it as stale and fails
67
+ // open. The sync rewrites it every flush (Stop hook, once per turn), so a healthy
68
+ // session refreshes well inside this window; a window this wide only matters when
69
+ // the sync is genuinely down, at which point failing open is the intended safe
70
+ // outcome. Generous on purpose: a single long agent turn must not false-expire a
71
+ // real open conflict.
72
+ exports.ACTIVE_CONFLICT_TTL_SECONDS = 30 * 60;
73
+ /** The shipped default. Soft only: a default-deny that fails closed on a stale
74
+ * snapshot would brick coding sessions and burn trust (§0.1, the wedge's own
75
+ * "soft gate before hard gate" non-negotiable). wire.ts re-exports this as the
76
+ * system default so flipping to hard later is a single wired change, not a rewrite. */
77
+ exports.DEFAULT_CONFLICT_GATE_MODE = "soft";
78
+ /** Resolve the gate mode from the environment, defaulting to soft. An unknown
79
+ * value degrades to soft (fail-safe): the only behavior that can ever block a tool
80
+ * is the explicit, opted-in hard mode, which is not enabled now. */
81
+ function resolveConflictGateMode(env = process.env) {
82
+ return env.MEETLESS_D1_CONFLICT_GATE === "hard" ? "hard" : exports.DEFAULT_CONFLICT_GATE_MODE;
83
+ }
84
+ function activeConflictCachePath(sessionId, home = config_1.HOME) {
85
+ return path.join(home, "logs", "steer", `active-conflicts-${sessionId}.json`);
86
+ }
87
+ // Best-effort: a failed cache write must never break the steer-sync hop (itself
88
+ // best-effort inside flush.sh). Worst case the hook keeps reading the prior
89
+ // snapshot until it ages past the TTL and fails open.
90
+ function writeActiveConflictCache(sessionId, conflicts, home = config_1.HOME, nowSeconds = Math.floor(Date.now() / 1000)) {
91
+ try {
92
+ const file = activeConflictCachePath(sessionId, home);
93
+ fs.mkdirSync(path.dirname(file), { recursive: true });
94
+ fs.writeFileSync(file, JSON.stringify({ conflicts, ts: nowSeconds }));
95
+ }
96
+ catch {
97
+ /* non-fatal */
98
+ }
99
+ }
100
+ function coerceConflicts(raw) {
101
+ if (!Array.isArray(raw))
102
+ return [];
103
+ const out = [];
104
+ for (const item of raw) {
105
+ if (item &&
106
+ typeof item === "object" &&
107
+ typeof item.caseId === "string" &&
108
+ typeof item.openedAt === "string" &&
109
+ typeof item.reason === "string") {
110
+ const c = item;
111
+ out.push({ caseId: c.caseId, openedAt: c.openedAt, reason: c.reason });
112
+ }
113
+ }
114
+ return out;
115
+ }
116
+ /**
117
+ * Read the session's open-conflict snapshot for the PreToolUse warning. Returns []
118
+ * on ANY of: missing file, parse failure, malformed body, or a snapshot older than
119
+ * `ttlSeconds` (the fail-open staleness guard). [] means "no warning"; a non-empty
120
+ * result means at least one currently-open conflict. The reader never throws and
121
+ * never touches the network.
122
+ */
123
+ function readActiveConflicts(sessionId, opts = {}) {
124
+ const home = opts.home ?? config_1.HOME;
125
+ const nowSeconds = opts.nowSeconds ?? Math.floor(Date.now() / 1000);
126
+ const ttlSeconds = opts.ttlSeconds ?? exports.ACTIVE_CONFLICT_TTL_SECONDS;
127
+ try {
128
+ const file = activeConflictCachePath(sessionId, home);
129
+ const body = JSON.parse(fs.readFileSync(file, "utf8"));
130
+ // Staleness guard: a snapshot whose ts is missing, non-numeric, or older than
131
+ // the TTL is treated as absent so a sync-down session fails open.
132
+ const ts = typeof body.ts === "number" ? body.ts : null;
133
+ if (ts === null || nowSeconds - ts > ttlSeconds) {
134
+ return [];
135
+ }
136
+ return coerceConflicts(body.conflicts);
137
+ }
138
+ catch {
139
+ return [];
140
+ }
141
+ }
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.reduceActiveMemory = reduceActiveMemory;
4
+ exports.classifyIngestIntent = classifyIngestIntent;
5
+ // tools/meetless-agent/src/lib/active-memory.ts
6
+ // Zone 1 (Active Review) store reader. The bash PostToolUse hook appends raw
7
+ // records to ~/.meetless/logs/kb-knowledge.jsonl (fast, flock-guarded, no network).
8
+ // All dedup/TTL/debounce/caps live here, applied at READ time, so the hot path
9
+ // stays a single append and the policy is unit-testable in isolation.
10
+ // See notes/20260604-auto-propose-produced-docs-to-kb.md (active-memory store).
11
+ const fs_1 = require("fs");
12
+ const identity_envelope_1 = require("./identity-envelope");
13
+ // Read the append-only log and return the live, deduped, debounced, capped set of
14
+ // Active Review candidates. Ordering: later records win on dedup (debounce keeps
15
+ // the final content of a turn; identical content collapses). Returns at most
16
+ // maxRecords, most recent first by file order.
17
+ function reduceActiveMemory(file, opts) {
18
+ if (!(0, fs_1.existsSync)(file))
19
+ return [];
20
+ const ttlMs = opts.ttlHours * 3600 * 1000;
21
+ const lines = (0, fs_1.readFileSync)(file, "utf8").split("\n").filter((l) => l.trim().length > 0);
22
+ // Debounce within a turn: the dedup identity excludes contentHash via a separate
23
+ // path-key; collapse multiple edits to one canonicalPath in one (session,turn)
24
+ // down to the final content, then apply content-hash dedup across turns.
25
+ const turnKeyed = new Map();
26
+ for (const line of lines) {
27
+ let r;
28
+ try {
29
+ r = JSON.parse(line);
30
+ }
31
+ catch {
32
+ continue;
33
+ }
34
+ if (r.event !== "active_memory_record")
35
+ continue;
36
+ if (opts.sessionId !== undefined && r.sessionId !== opts.sessionId)
37
+ continue; // scope BEFORE dedup
38
+ const created = Date.parse(r.createdAt);
39
+ if (Number.isFinite(created) && opts.nowMs - created > ttlMs)
40
+ continue; // TTL eviction
41
+ const turnPathKey = [r.workspaceId, r.repoRootHash, r.ownerUserId, r.sessionId, r.turnIndex, r.canonicalPath, r.kind].join("|");
42
+ turnKeyed.set(turnPathKey, r); // later edit in same turn wins (debounce)
43
+ }
44
+ // Content-hash dedup across turns: collapse identical content to one record.
45
+ const deduped = new Map();
46
+ for (const r of turnKeyed.values()) {
47
+ deduped.set((0, identity_envelope_1.dedupIdentity)(r), r); // later occurrence wins
48
+ }
49
+ const all = Array.from(deduped.values());
50
+ if (all.length <= opts.maxRecords)
51
+ return all;
52
+ return all.slice(all.length - opts.maxRecords); // keep most recent
53
+ }
54
+ function classifyIngestIntent(text) {
55
+ const t = text.toLowerCase();
56
+ if (/\b(ingest|add)\b.*\bkb\b/.test(t) || /\binto (the )?kb\b/.test(t))
57
+ return "kb_ingest";
58
+ return "active_only";
59
+ }
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runActiveReview = runActiveReview;
4
+ const conflict_advisory_1 = require("./conflict-advisory");
5
+ async function runActiveReview(args) {
6
+ if (args.records.length === 0) {
7
+ return { advisories: [], degraded: false };
8
+ }
9
+ try {
10
+ const resp = await args.intel.detect({ dryRun: true, candidates: args.records });
11
+ // Defensive: Active Review must be dry-run. A response that claims it
12
+ // persisted is a contract violation; drop the advisories and degrade rather
13
+ // than act on detections that may have mutated the graph.
14
+ if (resp.persisted) {
15
+ return { advisories: [], degraded: true };
16
+ }
17
+ return {
18
+ advisories: (0, conflict_advisory_1.advisoriesFromDetections)(resp.detections, { minConfidence: args.minConfidence }),
19
+ degraded: false,
20
+ };
21
+ }
22
+ catch {
23
+ // advise-never-block (P6): any intel failure is swallowed.
24
+ return { advisories: [], degraded: true };
25
+ }
26
+ }
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ // src/lib/agent-decision/index.ts
3
+ //
4
+ // Barrel for the provider-neutral agent-human decision contract. The canonical
5
+ // types and validator (T1) are the contract; the Claude normalizer (T2+) is the
6
+ // first consumer of it. Spec: notes/20260608-agent-decision-capture-design.md.
7
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
8
+ if (k2 === undefined) k2 = k;
9
+ var desc = Object.getOwnPropertyDescriptor(m, k);
10
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
11
+ desc = { enumerable: true, get: function() { return m[k]; } };
12
+ }
13
+ Object.defineProperty(o, k2, desc);
14
+ }) : (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ o[k2] = m[k];
17
+ }));
18
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
19
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
20
+ };
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ __exportStar(require("./types"), exports);
23
+ __exportStar(require("./validate"), exports);
24
+ __exportStar(require("./keys"), exports);
25
+ __exportStar(require("./normalize-claude"), exports);
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ // src/lib/agent-decision/keys.ts
3
+ //
4
+ // Identity + dedup keys for the canonical agent-decision contract.
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.buildEventKey = buildEventKey;
7
+ exports.buildClaudeProviderEventId = buildClaudeProviderEventId;
8
+ exports.deriveFallbackProviderEventId = deriveFallbackProviderEventId;
9
+ const crypto_1 = require("crypto");
10
+ const types_1 = require("./types");
11
+ // The spool/flush dedup key (spec section 5):
12
+ // "agent_decision_captured:<provider>:<providerEventId>"
13
+ // Provider-scoped, NOT hardcoded to claude_code, so a second provider's events
14
+ // never collide with Claude's. Control independently upserts on
15
+ // (workspaceId, provider, providerEventId).
16
+ function buildEventKey(provider, providerEventId) {
17
+ return `${types_1.AGENT_DECISION_EVENT}:${provider}:${providerEventId}`;
18
+ }
19
+ // Claude's stable per-question id (spec section 5): "<tool_use.id>#<questionIndex>".
20
+ function buildClaudeProviderEventId(toolUseId, questionIndex) {
21
+ return `${toolUseId}#${questionIndex}`;
22
+ }
23
+ // NUL field separator so concatenation is unambiguous: ("a","bc") can never
24
+ // collide with ("ab","c"). NUL cannot appear in JSON text or a session id, so it
25
+ // is a safe fence (a plain space is not, since labels contain spaces).
26
+ const FIELD_SEP = "\u0000";
27
+ // Deterministic fallback providerEventId for a provider with NO stable per-event
28
+ // id (spec section 7, INV-STABLE-FALLBACK-ID):
29
+ // sha256(provider + providerSessionId + sourceOrdinal
30
+ // + normalizedPrompt + normalizedChoices + normalizedAnswer)
31
+ // CRITICAL: it must NOT depend on capture timestamp. occurredAt drifts between
32
+ // the real-time path and the transcript-scan backstop, so feeding it in would
33
+ // break dedup across the two paths. It is therefore not an input here. Claude
34
+ // uses the stable "<tool_use.id>#<i>" id and never needs this.
35
+ function deriveFallbackProviderEventId(input) {
36
+ const normalizedPrompt = JSON.stringify({ title: input.prompt.title, body: input.prompt.body });
37
+ // Order is significant (positional choice ids), so preserve it.
38
+ const normalizedChoices = JSON.stringify(input.choices.map((c) => [c.id, c.label, c.description ?? ""]));
39
+ const normalizedAnswer = JSON.stringify({ type: input.answer.type, value: input.answer.value });
40
+ const material = [
41
+ input.provider,
42
+ input.providerSessionId,
43
+ input.sourceOrdinal,
44
+ normalizedPrompt,
45
+ normalizedChoices,
46
+ normalizedAnswer,
47
+ ].join(FIELD_SEP);
48
+ return (0, crypto_1.createHash)("sha256").update(material, "utf8").digest("hex");
49
+ }
@@ -0,0 +1,183 @@
1
+ "use strict";
2
+ // src/lib/agent-decision/normalize-claude.ts
3
+ //
4
+ // The ONE normalization seam, Claude Code AskUserQuestion edition (T2-T5).
5
+ //
6
+ // Pure functions: raw AskUserQuestion (tool_input + tool_response) -> canonical
7
+ // decisions. A single tool call carrying N questions decomposes into N decisions
8
+ // (spec section 5), each with providerEventId "<tool_use.id>#<i>". Everything
9
+ // Claude-specific is confined here and to rawProviderPayload; the output is
10
+ // pure canonical contract (INV-ADAPTER-BOUNDARY, INV-NORMALIZATION).
11
+ //
12
+ // When a real second provider lands, it gets its own normalize-<provider>.ts that
13
+ // emits the same CanonicalDecisionPayload; nothing downstream changes.
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.CLAUDE_TOOL_NAME = exports.CLAUDE_PROVIDER_SOURCE = exports.CLAUDE_PROVIDER = void 0;
16
+ exports.normalizeClaudeAskUserQuestion = normalizeClaudeAskUserQuestion;
17
+ const keys_1 = require("./keys");
18
+ exports.CLAUDE_PROVIDER = "claude_code";
19
+ exports.CLAUDE_PROVIDER_SOURCE = "claude_hook";
20
+ exports.CLAUDE_TOOL_NAME = "AskUserQuestion";
21
+ // Delimiters a multiSelect answer might use IF Claude serializes it as a string.
22
+ // Claude's real multi-select serialization is UNVERIFIED (spec section 3); the
23
+ // adapter tolerates an array (preferred) or any of these delimiters.
24
+ const MULTI_DELIMITERS = /\r?\n|,|\||;/;
25
+ function toChoices(options) {
26
+ return (options ?? []).map((o, idx) => {
27
+ const choice = { id: `choice_${idx}`, label: String(o.label ?? "") };
28
+ if (o.description !== undefined)
29
+ choice.description = String(o.description);
30
+ return choice;
31
+ });
32
+ }
33
+ // Match one answer string against the offered labels. Exact match first
34
+ // (INV-CHOICE-ID resolution, spec section 6).
35
+ function matchLabel(value, choices) {
36
+ const matches = choices.filter((c) => c.label === value);
37
+ if (matches.length === 1)
38
+ return { choiceId: matches[0].id, status: "exact_unique" };
39
+ if (matches.length > 1)
40
+ return { choiceId: matches[0].id, status: "exact_ambiguous" };
41
+ return { status: "no_match" };
42
+ }
43
+ // One leading tolerated delimiter (plus any trailing whitespace) at the start of
44
+ // a string. Used to step past the separator between two offered labels.
45
+ const LEADING_DELIM = /^(?:\r?\n|[,|;])\s*/;
46
+ // A delimited multiSelect string can hide a delimiter INSIDE a label (e.g. an
47
+ // option literally named "Ship now, with a flag"). A naive split would shred that
48
+ // label and silently misreport what the human picked, which is an audit-integrity
49
+ // corruption in a governance product. So first try to read the whole string as a
50
+ // delimiter-joined run of offered labels, longest-label-first; return null (so the
51
+ // caller naive-splits) only when the string is NOT fully explained by offered
52
+ // labels. This also subsumes the whole-string-is-one-label case.
53
+ function matchOfferedLabelSequence(raw, choices) {
54
+ const labels = choices
55
+ .map((c) => c.label)
56
+ .filter((l) => l.length > 0)
57
+ .sort((a, b) => b.length - a.length);
58
+ if (labels.length === 0)
59
+ return null;
60
+ const found = [];
61
+ let rest = raw.trim();
62
+ while (rest.length > 0) {
63
+ const label = labels.find((l) => rest === l || (rest.startsWith(l) && LEADING_DELIM.test(rest.slice(l.length))));
64
+ if (label === undefined)
65
+ return null; // not a clean label run; caller naive-splits
66
+ found.push(label);
67
+ rest = rest.slice(label.length).replace(LEADING_DELIM, "").trim();
68
+ }
69
+ return found.length > 0 ? found : null;
70
+ }
71
+ // Coerce a raw multiSelect answer into an array of label strings, tolerating
72
+ // either a real array (preferred) or a delimited string.
73
+ function toMultiValues(raw, choices) {
74
+ if (Array.isArray(raw))
75
+ return raw.map((x) => String(x));
76
+ if (typeof raw !== "string")
77
+ return raw == null ? [] : [String(raw)];
78
+ // Prefer an exact decomposition into offered labels (handles a label that itself
79
+ // contains a delimiter); only naive-split when that fails.
80
+ const asLabels = matchOfferedLabelSequence(raw, choices);
81
+ if (asLabels)
82
+ return asLabels;
83
+ return raw
84
+ .split(MULTI_DELIMITERS)
85
+ .map((s) => s.trim())
86
+ .filter((s) => s.length > 0);
87
+ }
88
+ function buildAnswer(question, rawAnswer, choices) {
89
+ const multiSelect = question.multiSelect === true;
90
+ if (multiSelect) {
91
+ const values = toMultiValues(rawAnswer, choices);
92
+ const matchedIds = [];
93
+ let anyMatch = false;
94
+ let anyAmbiguous = false;
95
+ for (const v of values) {
96
+ const m = matchLabel(v, choices);
97
+ if (m.status === "no_match")
98
+ continue;
99
+ anyMatch = true;
100
+ if (m.status === "exact_ambiguous")
101
+ anyAmbiguous = true;
102
+ if (m.choiceId)
103
+ matchedIds.push(m.choiceId);
104
+ }
105
+ const status = !anyMatch ? "no_match" : anyAmbiguous ? "exact_ambiguous" : "exact_unique";
106
+ const answer = {
107
+ type: "multi_choice_labels",
108
+ value: values,
109
+ choiceMatchStatus: status,
110
+ raw: rawAnswer,
111
+ };
112
+ if (matchedIds.length > 0)
113
+ answer.choiceIds = matchedIds;
114
+ return answer;
115
+ }
116
+ // Single-select. Claude returns the chosen option label, OR free text the user
117
+ // typed for "Other". Match by exact label.
118
+ const value = typeof rawAnswer === "string" ? rawAnswer : String(rawAnswer ?? "");
119
+ const m = matchLabel(value, choices);
120
+ if (m.status === "no_match") {
121
+ return { type: "free_text", value, choiceMatchStatus: "no_match", raw: rawAnswer };
122
+ }
123
+ // Known, unfixable limitation (spec section 6): if the user typed "Other" text
124
+ // that exactly equals an offered label, this records it as a selection. Claude
125
+ // does not label answer origin, so the adapter cannot tell them apart.
126
+ return {
127
+ type: "choice_label",
128
+ value,
129
+ choiceId: m.choiceId,
130
+ choiceMatchStatus: m.status,
131
+ raw: rawAnswer,
132
+ };
133
+ }
134
+ function deriveDecisionKind(question, answer) {
135
+ if (question.multiSelect === true)
136
+ return "multi_choice";
137
+ if (answer.type === "free_text")
138
+ return "free_text";
139
+ return "choice";
140
+ }
141
+ // Decompose one AskUserQuestion call into N canonical decisions, one per question.
142
+ // Questions with no entry in the answers map are skipped: a question that was not
143
+ // answered is not a captured human decision.
144
+ function normalizeClaudeAskUserQuestion(raw, ctx) {
145
+ const out = [];
146
+ const questions = Array.isArray(raw.questions) ? raw.questions : [];
147
+ questions.forEach((question, i) => {
148
+ const key = question.question;
149
+ if (typeof key !== "string")
150
+ return;
151
+ if (!(key in (raw.answers ?? {})))
152
+ return; // unanswered -> not a decision
153
+ const rawAnswer = raw.answers[key];
154
+ const choices = toChoices(question.options);
155
+ const answer = buildAnswer(question, rawAnswer, choices);
156
+ const decisionKind = deriveDecisionKind(question, answer);
157
+ const payload = {
158
+ provider: exports.CLAUDE_PROVIDER,
159
+ providerSource: exports.CLAUDE_PROVIDER_SOURCE,
160
+ providerToolName: exports.CLAUDE_TOOL_NAME,
161
+ providerEventId: (0, keys_1.buildClaudeProviderEventId)(raw.toolUseId, i),
162
+ providerSessionId: ctx.providerSessionId,
163
+ decisionKind,
164
+ prompt: {
165
+ title: typeof question.header === "string" && question.header.trim().length > 0 ? question.header : key,
166
+ body: key,
167
+ },
168
+ choices,
169
+ answer,
170
+ multiSelect: question.multiSelect === true,
171
+ turnIndex: ctx.turnIndex ?? null,
172
+ traceId: ctx.traceId ?? null,
173
+ capturedBy: ctx.capturedBy,
174
+ actorDisplayName: ctx.actorDisplayName ?? null,
175
+ // Audit: preserve exactly what the provider gave us for this question.
176
+ rawProviderPayload: { question, answer: rawAnswer },
177
+ };
178
+ if (ctx.occurredAt !== undefined)
179
+ payload.occurredAt = ctx.occurredAt;
180
+ out.push(payload);
181
+ });
182
+ return out;
183
+ }
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ // src/lib/agent-decision/types.ts
3
+ //
4
+ // The provider-neutral canonical contract for agent-human decision capture.
5
+ // This is the ONE source the rest of the pipeline conforms to (spec
6
+ // notes/20260608-agent-decision-capture-design.md, sections 6 and 7).
7
+ //
8
+ // The domain primitive is an "agent-human decision": the agent asked a human to
9
+ // steer, the human answered. Claude Code's AskUserQuestion is the first (and for
10
+ // now only) producer behind the normalize() seam. NOTHING in these types names a
11
+ // Claude tool concept as a canonical field; provider specifics live only in the
12
+ // explicitly provider-scoped fields and in rawProviderPayload (INV-ADAPTER-BOUNDARY).
13
+ //
14
+ // Control mirrors this contract as a class-validator DTO. The CLI cannot import
15
+ // across the nested-workspace boundary into apps/control, so the two definitions
16
+ // are kept in sync by the contract tests on both sides plus the synthetic
17
+ // non-Claude ingest test (INV-CLAUDE-FIRST-NOT-ONLY). The spec is the source of
18
+ // truth for both.
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.AGENT_DECISION_EVENT = void 0;
21
+ exports.AGENT_DECISION_EVENT = "agent_decision_captured";