@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,188 @@
1
+ "use strict";
2
+ // `mla kb promote <doc-id>` / `mla kb promote --reject <doc-id>` (Personal-KB
3
+ // posture promotion, Phase 3).
4
+ //
5
+ // Renamed from `kb share`: "share" read as "invite a teammate", but this verb
6
+ // has nothing to do with membership. It flips a SHADOW Personal-KB doc to LIVE,
7
+ // i.e. it PROMOTES the doc into the workspace's grounded, agent-visible corpus.
8
+ // `kb share` survives as a hidden, deprecated alias in kb.ts (see the dispatch).
9
+ //
10
+ // promote <doc-id> -> PATCH /internal/v1/kb/documents/<id>/posture with
11
+ // { workspaceId, actorUserId, posture: "LIVE" }. This
12
+ // promotes the owner's Personal-KB doc from SHADOW to
13
+ // LIVE: the "promote into the workspace corpus" action.
14
+ // promote --reject <doc-id> -> the owner declines to promote. Makes NO posture call
15
+ // and NO delete call, so the personal doc survives
16
+ // untouched at SHADOW. Records the decline locally so
17
+ // the agent can avoid re-proposing it later (test 16).
18
+ //
19
+ // Mirrors the kb_personal.ts deps-injection shape: a thin public `runKbPromote`
20
+ // that loads the real config (readKbConfig) and wires the real intelPatch +
21
+ // rejection recorder, while every collaborator is injectable so the unit test
22
+ // drives it offline without touching the network, config, or disk.
23
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
24
+ if (k2 === undefined) k2 = k;
25
+ var desc = Object.getOwnPropertyDescriptor(m, k);
26
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
27
+ desc = { enumerable: true, get: function() { return m[k]; } };
28
+ }
29
+ Object.defineProperty(o, k2, desc);
30
+ }) : (function(o, m, k, k2) {
31
+ if (k2 === undefined) k2 = k;
32
+ o[k2] = m[k];
33
+ }));
34
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
35
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
36
+ }) : function(o, v) {
37
+ o["default"] = v;
38
+ });
39
+ var __importStar = (this && this.__importStar) || (function () {
40
+ var ownKeys = function(o) {
41
+ ownKeys = Object.getOwnPropertyNames || function (o) {
42
+ var ar = [];
43
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
44
+ return ar;
45
+ };
46
+ return ownKeys(o);
47
+ };
48
+ return function (mod) {
49
+ if (mod && mod.__esModule) return mod;
50
+ var result = {};
51
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
52
+ __setModuleDefault(result, mod);
53
+ return result;
54
+ };
55
+ })();
56
+ Object.defineProperty(exports, "__esModule", { value: true });
57
+ exports.parseKbPromoteArgs = parseKbPromoteArgs;
58
+ exports.runKbPromote = runKbPromote;
59
+ const fs = __importStar(require("fs"));
60
+ const path = __importStar(require("path"));
61
+ const config_1 = require("../lib/config");
62
+ const http_1 = require("../lib/http");
63
+ const USAGE = "Usage: mla kb promote <doc-id> | mla kb promote --reject <doc-id>";
64
+ // Parse a single positional <doc-id> plus an optional --reject flag. The flag may
65
+ // appear before or after the id (`--reject doc_1` or `doc_1 --reject`). Unknown
66
+ // flags, a missing id, or a second positional are usage errors.
67
+ function parseKbPromoteArgs(argv) {
68
+ let docId = null;
69
+ let reject = false;
70
+ for (const a of argv) {
71
+ if (a === "--reject") {
72
+ reject = true;
73
+ }
74
+ else if (a.startsWith("-")) {
75
+ throw new Error(`Unknown flag: ${a}. ${USAGE}`);
76
+ }
77
+ else if (docId === null) {
78
+ docId = a;
79
+ }
80
+ else {
81
+ throw new Error(`Unexpected argument: ${a}. ${USAGE}`);
82
+ }
83
+ }
84
+ if (docId === null) {
85
+ throw new Error(`mla kb promote requires a document id. ${USAGE}`);
86
+ }
87
+ // `kb add` / `kb reingest` receipts print the id as `kbdoc:<cuid>`, so
88
+ // operators paste that exact token. The posture route keys on the bare cuid;
89
+ // a `kbdoc:` prefix flowed verbatim into the URL used to 404 with a
90
+ // misleading "intel does not expose the posture endpoint" message. Strip it
91
+ // (mirrors the kb_reingest `kbdoc:` input handling) so both spellings work.
92
+ if (docId.startsWith("kbdoc:")) {
93
+ docId = docId.slice("kbdoc:".length);
94
+ if (docId.length === 0) {
95
+ throw new Error(`mla kb promote requires a document id after 'kbdoc:'. ${USAGE}`);
96
+ }
97
+ }
98
+ return { docId, reject };
99
+ }
100
+ // The local rejections spool. Path + filename live under the SAME logs directory
101
+ // the Phase 1 Active Review store uses (HOME/logs), so both the agent and this
102
+ // command resolve their state under one MEETLESS_HOME. Forward-looking plumbing:
103
+ // the agent reads this to avoid re-proposing a doc the owner already declined.
104
+ //
105
+ // The filename + event string keep the legacy `kb-share` spelling on purpose:
106
+ // this is an append-only on-disk contract, not a user-facing surface. Renaming it
107
+ // would orphan any already-spooled declines for zero operator benefit. The command
108
+ // was renamed promote; the durable record name stays stable.
109
+ function rejectionsLogPath() {
110
+ return path.join(config_1.HOME, "logs", "kb-share-rejections.jsonl");
111
+ }
112
+ // Append one JSON line recording the decline. Best-effort by contract: every
113
+ // failure (unwritable dir, EACCES, full disk) is swallowed so the command outcome
114
+ // is never affected and the function NEVER throws (the unit test relies on this).
115
+ function recordRejectDefault(cfg, docId) {
116
+ try {
117
+ const file = rejectionsLogPath();
118
+ fs.mkdirSync(path.dirname(file), { recursive: true });
119
+ const line = JSON.stringify({
120
+ ts: new Date().toISOString(),
121
+ event: "kb_share_rejected",
122
+ workspaceId: cfg?.workspaceId ?? null,
123
+ ownerUserId: cfg?.actorUserId ?? null,
124
+ docId,
125
+ }) + "\n";
126
+ fs.appendFileSync(file, line);
127
+ }
128
+ catch {
129
+ // best-effort: never throw, never affect the command outcome.
130
+ }
131
+ }
132
+ // Surface an intel HTTP failure helpfully, mirroring kb.ts's explainIntelError
133
+ // for the postures most likely on a posture flip; falls back to the raw message.
134
+ function explainPromoteError(err, intelUrl) {
135
+ if (err.status === 404) {
136
+ return `intel returned 404 for the posture route. Document not found, or this intel does not expose the KB posture endpoint.`;
137
+ }
138
+ if (err.status === 401 || err.status === 403) {
139
+ return `intel rejected the token (HTTP ${err.status}). Check controlToken in cli-config.json.`;
140
+ }
141
+ if (err.status === undefined) {
142
+ return `intel not reachable at ${intelUrl}. Is it running? Try \`mla doctor\`.`;
143
+ }
144
+ return err.message;
145
+ }
146
+ async function runKbPromote(argv, deps) {
147
+ let cfg;
148
+ try {
149
+ cfg = deps?.cfg ?? (0, config_1.readKbConfig)();
150
+ }
151
+ catch (e) {
152
+ console.error(e.message);
153
+ return { rejected: false, code: 2 };
154
+ }
155
+ let parsed;
156
+ try {
157
+ parsed = parseKbPromoteArgs(argv);
158
+ }
159
+ catch (e) {
160
+ console.error(e.message);
161
+ return { rejected: false, code: 2 };
162
+ }
163
+ const patch = deps?.http?.intelPatch ?? http_1.intelPatch;
164
+ const recordReject = deps?.recordReject ?? recordRejectDefault;
165
+ // REJECT path: no posture call, no delete. Record the decline and confirm the
166
+ // personal doc is unchanged.
167
+ if (parsed.reject) {
168
+ recordReject(cfg, parsed.docId);
169
+ console.log(`Declined to promote ${parsed.docId}. Your personal copy is unchanged (still SHADOW, not deleted).`);
170
+ return { rejected: true, code: 0 };
171
+ }
172
+ // PROMOTE path: flip the posture to LIVE.
173
+ const body = {
174
+ workspaceId: cfg.workspaceId,
175
+ actorUserId: cfg.actorUserId,
176
+ posture: "LIVE",
177
+ };
178
+ try {
179
+ await patch(cfg, `/internal/v1/kb/documents/${parsed.docId}/posture`, body);
180
+ }
181
+ catch (e) {
182
+ const intelUrl = cfg.intelUrl || http_1.DEFAULT_INTEL_URL;
183
+ console.error(explainPromoteError(e, intelUrl));
184
+ return { rejected: false, code: 1 };
185
+ }
186
+ console.log(`Promoted ${parsed.docId} to the workspace corpus (posture is now LIVE).`);
187
+ return { rejected: false, code: 0 };
188
+ }
@@ -0,0 +1,168 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseKbPurgeArgs = parseKbPurgeArgs;
4
+ exports.runKbPurge = runKbPurge;
5
+ const config_1 = require("../lib/config");
6
+ const kb_acl_1 = require("../lib/kb_acl");
7
+ const render_1 = require("../lib/render");
8
+ const http_1 = require("../lib/http");
9
+ const kb_forget_1 = require("./kb_forget");
10
+ const kb_reingest_1 = require("./kb_reingest");
11
+ const VALUE_FLAGS = new Set(["--workspace", "--reason"]);
12
+ const MIN_REASON_CHARS = 16;
13
+ const PURGE_TIMEOUT_MS = 30_000;
14
+ function parseKbPurgeArgs(argv) {
15
+ const out = {};
16
+ let positional = null;
17
+ for (let i = 0; i < argv.length; i++) {
18
+ const a = argv[i];
19
+ if (VALUE_FLAGS.has(a)) {
20
+ const v = argv[i + 1];
21
+ if (v === undefined) {
22
+ throw new Error(`Missing value for ${a}`);
23
+ }
24
+ if (v.startsWith("--") || v.startsWith("-")) {
25
+ throw new Error(`Missing value for ${a} (got the next flag ${v} instead)`);
26
+ }
27
+ switch (a) {
28
+ case "--workspace":
29
+ out.workspace = v;
30
+ break;
31
+ case "--reason":
32
+ out.reason = v;
33
+ break;
34
+ }
35
+ i += 1;
36
+ continue;
37
+ }
38
+ if (a.startsWith("--") || a.startsWith("-")) {
39
+ throw new Error(`Unknown flag: ${a}. Supported flags: ${[...VALUE_FLAGS].sort().join(", ")}`);
40
+ }
41
+ if (positional !== null) {
42
+ throw new Error(`\`mla kb purge\` takes exactly one positional input (got '${positional}' and '${a}')`);
43
+ }
44
+ positional = a;
45
+ }
46
+ if (positional === null) {
47
+ throw new Error("`mla kb purge` requires a positional input: kbdoc:<id>, note:<path>, or a bare note path");
48
+ }
49
+ if (!out.reason || !out.reason.trim()) {
50
+ throw new Error("--reason \"...\" is required: purge redacts every revision, which is irreversible in slice A");
51
+ }
52
+ if (out.reason.trim().length < MIN_REASON_CHARS) {
53
+ throw new Error(`--reason must be at least ${MIN_REASON_CHARS} characters of rationale (purge is irreversible)`);
54
+ }
55
+ return {
56
+ input: positional,
57
+ workspace: out.workspace,
58
+ reason: out.reason,
59
+ };
60
+ }
61
+ function printPreflight(flags, cfg) {
62
+ const ws = flags.workspace || cfg.workspaceId;
63
+ console.log(`mla kb purge workspace=${ws} input=${flags.input} reason=${JSON.stringify(flags.reason)}`);
64
+ }
65
+ // Map an HTTP/network failure to the worker's exit semantics: a 404 (unknown
66
+ // doc), 409 (PURGED terminal), or 422 (bad args / short reason) is a precondition
67
+ // -> 2; a network error (no status), a 5xx, or the 500 write-time state race is an
68
+ // operational failure -> 1. Identical to forget's mapping.
69
+ function exitForHttpError(e) {
70
+ const status = e?.status;
71
+ if (status === 404 || status === 409 || status === 422)
72
+ return 2;
73
+ return 1;
74
+ }
75
+ async function runKbPurge(argv) {
76
+ // Parse flags BEFORE loading config so `--workspace <id>` can override the
77
+ // marker-resolved workspace (T1.1 folder = workspace) without requiring the
78
+ // current directory to be activated.
79
+ let flags;
80
+ try {
81
+ flags = parseKbPurgeArgs(argv);
82
+ }
83
+ catch (e) {
84
+ console.error(e.message);
85
+ return 2;
86
+ }
87
+ let cfg;
88
+ try {
89
+ cfg = (0, config_1.readKbConfig)(flags.workspace);
90
+ }
91
+ catch (e) {
92
+ console.error(e.message);
93
+ return 2;
94
+ }
95
+ // §13.14 owner-only ACL: purge is the most destructive KB write; the gate runs
96
+ // before the route so a non-owner cannot trigger redact-all + tombstone.
97
+ try {
98
+ await (0, kb_acl_1.verifyKbActorIsOwner)(cfg);
99
+ }
100
+ catch (e) {
101
+ if (e instanceof kb_acl_1.KbOwnerCheckError) {
102
+ console.error(e.message);
103
+ return 2;
104
+ }
105
+ throw e;
106
+ }
107
+ const workspaceId = flags.workspace || cfg.workspaceId;
108
+ printPreflight(flags, cfg);
109
+ // Pick the server-resolvable handle, SHARED with forget so `purge X` and
110
+ // `forget X` produce the same handle. A real local file maps to its
111
+ // vault-relative path (vault root resolved client-side); an unresolved vault
112
+ // root for a real file is a precondition (-> 2).
113
+ let handle;
114
+ try {
115
+ handle = (0, kb_forget_1.resolveForgetHandle)(flags.input);
116
+ }
117
+ catch (e) {
118
+ if (e instanceof kb_reingest_1.ReingestPreconditionError) {
119
+ console.error(e.message);
120
+ return 2;
121
+ }
122
+ throw e;
123
+ }
124
+ const body = {
125
+ workspaceId,
126
+ actor: cfg.actorUserId,
127
+ reason: flags.reason,
128
+ ...handle,
129
+ };
130
+ let receipt;
131
+ try {
132
+ const res = await (0, http_1.intelPost)(cfg, "/internal/v1/kb/purge", body, PURGE_TIMEOUT_MS);
133
+ receipt = res.receipt;
134
+ }
135
+ catch (e) {
136
+ console.error(`kb purge failed: ${e.message}`);
137
+ return exitForHttpError(e);
138
+ }
139
+ if (!receipt) {
140
+ console.error("kb purge: the route returned no receipt.");
141
+ return 1;
142
+ }
143
+ console.log((0, render_1.renderKbPurgeReceipt)(receipt));
144
+ console.log("");
145
+ // Cascade: a purged doc is dead the same way a forgotten one is, so its PENDING
146
+ // relationship candidates point at a dead artifact and should not linger in the
147
+ // review queue. Only a fresh purge needs it; an already_purged doc's candidates
148
+ // were cleared on the first purge/forget. Reuse forget's best-effort cascade.
149
+ if (receipt.outcome === "purged") {
150
+ const cascadeDeps = {
151
+ fetchPending: (qs) => (0, http_1.get)(cfg, `/internal/v1/relationship-candidates?${qs}`, 12000),
152
+ submitReject: async (id, rejectBody) => {
153
+ await (0, http_1.post)(cfg, `/internal/v1/relationship-candidates/${encodeURIComponent(id)}/reject`, rejectBody, 12000);
154
+ },
155
+ };
156
+ const c = await (0, kb_forget_1.cascadeRejectForDoc)(receipt.canonicalPath, { workspaceId: cfg.workspaceId, actorUserId: cfg.actorUserId }, cascadeDeps);
157
+ if (c.rejected > 0) {
158
+ console.log(`Also cleared ${c.rejected} orphaned relationship candidate${c.rejected === 1 ? "" : "s"} that referenced this doc.`);
159
+ }
160
+ if (c.fetchFailed) {
161
+ console.error("Warning: could not check for orphaned relationship candidates; some may remain in the review queue.");
162
+ }
163
+ else if (c.failed > 0) {
164
+ console.error(`Warning: ${c.failed} orphaned relationship candidate${c.failed === 1 ? "" : "s"} could not be cleared; clear them with mla kb review <id> --reject.`);
165
+ }
166
+ }
167
+ return 0;
168
+ }
@@ -0,0 +1,335 @@
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.ReingestPreconditionError = void 0;
37
+ exports.parseKbReingestArgs = parseKbReingestArgs;
38
+ exports.resolveReingestVaultRoot = resolveReingestVaultRoot;
39
+ exports.reverseMapEoidToFile = reverseMapEoidToFile;
40
+ exports.runKbReingest = runKbReingest;
41
+ const fs = __importStar(require("fs"));
42
+ const os = __importStar(require("os"));
43
+ const path = __importStar(require("path"));
44
+ const config_1 = require("../lib/config");
45
+ const http_1 = require("../lib/http");
46
+ const kb_acl_1 = require("../lib/kb_acl");
47
+ const observability_1 = require("../lib/observability");
48
+ const render_1 = require("../lib/render");
49
+ const kb_add_1 = require("./kb_add");
50
+ const VALUE_FLAGS = new Set([
51
+ "--workspace",
52
+ "--profile",
53
+ "--ingest-run-id",
54
+ "--reason",
55
+ "--agent-session",
56
+ ]);
57
+ const DEFAULT_PROFILE = "markdown_atomic_v1";
58
+ const NOTES_IDENTITY_ROOT = "notes";
59
+ const KBDOC_PREFIX = "kbdoc:";
60
+ const NOTE_PREFIX = "note:";
61
+ // RESOLVE mints nothing (a DB lookup + guards), so it is fast. APPLY runs the
62
+ // heavy inline LDM body + embeds for a body change, so it gets the kb-add
63
+ // single-file floor.
64
+ const RESOLVE_TIMEOUT_MS = 15_000;
65
+ const REINGEST_TIMEOUT_MS = 120_000;
66
+ function parseKbReingestArgs(argv) {
67
+ const out = {};
68
+ let positional = null;
69
+ for (let i = 0; i < argv.length; i++) {
70
+ const a = argv[i];
71
+ if (VALUE_FLAGS.has(a)) {
72
+ const v = argv[i + 1];
73
+ if (v === undefined) {
74
+ throw new Error(`Missing value for ${a}`);
75
+ }
76
+ if (v.startsWith("--") || v.startsWith("-")) {
77
+ throw new Error(`Missing value for ${a} (got the next flag ${v} instead)`);
78
+ }
79
+ switch (a) {
80
+ case "--workspace":
81
+ out.workspace = v;
82
+ break;
83
+ case "--profile":
84
+ out.profile = v;
85
+ break;
86
+ case "--ingest-run-id":
87
+ out.ingestRunId = v;
88
+ break;
89
+ case "--reason":
90
+ out.reason = v;
91
+ break;
92
+ case "--agent-session":
93
+ out.agentSession = v;
94
+ break;
95
+ }
96
+ i += 1;
97
+ continue;
98
+ }
99
+ if (a.startsWith("--") || a.startsWith("-")) {
100
+ throw new Error(`Unknown flag: ${a}. Supported flags: ${[...VALUE_FLAGS].sort().join(", ")}`);
101
+ }
102
+ if (positional !== null) {
103
+ throw new Error(`\`mla kb reingest\` takes exactly one positional input (got '${positional}' and '${a}')`);
104
+ }
105
+ positional = a;
106
+ }
107
+ if (positional === null) {
108
+ throw new Error("`mla kb reingest` requires a positional input: kbdoc:<id>, note:<externalObjectId>, or a bare note path");
109
+ }
110
+ return {
111
+ input: positional,
112
+ workspace: out.workspace,
113
+ profile: out.profile,
114
+ ingestRunId: out.ingestRunId,
115
+ reason: out.reason,
116
+ agentSession: out.agentSession,
117
+ };
118
+ }
119
+ // ---------------------------------------------------------------------------
120
+ // Client-side source resolution (was tools/mla_kb_reingest.py)
121
+ //
122
+ // The governed identity is the vault-relative POSIX path under a single
123
+ // `notes/` root. The CLIENT alone holds the filesystem, so it resolves the
124
+ // vault root and reverse-maps the stored externalObjectId to a file here,
125
+ // exactly mirroring the python worker's `_resolve_vault_root` /
126
+ // `_abs_path_from_external_object_id`.
127
+ // ---------------------------------------------------------------------------
128
+ // A client-side precondition (unresolved vault root, missing source file, a
129
+ // malformed identity). Maps to exit code 2, distinct from an HTTP failure.
130
+ class ReingestPreconditionError extends Error {
131
+ constructor(message) {
132
+ super(message);
133
+ this.name = "ReingestPreconditionError";
134
+ }
135
+ }
136
+ exports.ReingestPreconditionError = ReingestPreconditionError;
137
+ function expandHome(p) {
138
+ if (p === "~")
139
+ return os.homedir();
140
+ if (p.startsWith("~/"))
141
+ return path.join(os.homedir(), p.slice(2));
142
+ return p;
143
+ }
144
+ // Resolve the notes vault root the governed identity is relative to. Order
145
+ // (mirrors the worker, minus the removed `--vault-root` flag): MEETLESS_NOTES_ROOT,
146
+ // else a git-repo-root walk-up from `anchor` (the source file's directory for a
147
+ // path input, else cwd). Throws a precondition when neither resolves: reingest
148
+ // cannot read the source otherwise.
149
+ function resolveReingestVaultRoot(anchor) {
150
+ const envRoot = process.env.MEETLESS_NOTES_ROOT;
151
+ if (envRoot) {
152
+ const expanded = path.resolve(expandHome(envRoot));
153
+ if (!fs.existsSync(expanded) || !fs.statSync(expanded).isDirectory()) {
154
+ throw new ReingestPreconditionError(`MEETLESS_NOTES_ROOT=${envRoot} is not a directory`);
155
+ }
156
+ return fs.realpathSync(expanded);
157
+ }
158
+ const gitRoot = (0, kb_add_1.gitRootForVault)(anchor);
159
+ if (gitRoot)
160
+ return gitRoot;
161
+ throw new ReingestPreconditionError("could not resolve a notes vault root to read the source file; set MEETLESS_NOTES_ROOT or run inside a git repo");
162
+ }
163
+ // Reverse the governed identity mapping: `notes/<rel>` -> <vaultRoot>/<rel>.
164
+ // Mirrors the worker's `_abs_path_from_external_object_id`. The stored id is
165
+ // NFC + (case-insensitive fs) casefolded; lookup on such a fs is case-
166
+ // insensitive, so the reverse-mapped path still reads the real file. Guards a
167
+ // non-notes-rooted id and a `..` escape, and that the target is a real file.
168
+ function reverseMapEoidToFile(externalObjectId, vaultRoot) {
169
+ const prefix = `${NOTES_IDENTITY_ROOT}/`;
170
+ if (!externalObjectId.startsWith(prefix)) {
171
+ throw new ReingestPreconditionError(`externalObjectId ${JSON.stringify(externalObjectId)} is not under the '${NOTES_IDENTITY_ROOT}/' identity root; reingest only supports notes-sourced documents`);
172
+ }
173
+ const rel = externalObjectId.slice(prefix.length);
174
+ if (!rel) {
175
+ throw new ReingestPreconditionError(`externalObjectId ${JSON.stringify(externalObjectId)} has an empty relative path`);
176
+ }
177
+ const root = fs.realpathSync(vaultRoot);
178
+ const abs = path.resolve(root, rel);
179
+ const relCheck = path.relative(root, abs);
180
+ if (relCheck.startsWith("..") || path.isAbsolute(relCheck)) {
181
+ throw new ReingestPreconditionError(`resolved source path ${abs} escapes vault root ${root}`);
182
+ }
183
+ if (!fs.existsSync(abs) || !fs.statSync(abs).isFile()) {
184
+ throw new ReingestPreconditionError(`source file for ${JSON.stringify(externalObjectId)} does not resolve to a readable file at ${abs}. Set MEETLESS_NOTES_ROOT, or re-add it with \`mla kb add\`.`);
185
+ }
186
+ return abs;
187
+ }
188
+ // RESOLVE an identity reference (kbdoc:<id> or a stored note:<eoid>) server-side
189
+ // -- the SAME canonicalize + guards the worker ran -- then reverse-map the
190
+ // returned externalObjectId to a file and read its bytes for an APPLY-by-id.
191
+ async function resolveViaServer(cfg, workspaceId, ref, anchor) {
192
+ const resolved = await (0, http_1.intelPost)(cfg, "/internal/v1/kb/reingest", { workspaceId, actor: cfg.actorUserId, ref }, RESOLVE_TIMEOUT_MS);
193
+ // RESOLVE already ran the PURGED/TOMBSTONED guards (409) and unknown-doc (404),
194
+ // so a terminal/missing doc threw before we touch the filesystem.
195
+ const vaultRoot = resolveReingestVaultRoot(anchor);
196
+ const file = reverseMapEoidToFile(resolved.externalObjectId, vaultRoot);
197
+ const content = fs.readFileSync(file, "utf8");
198
+ return { documentId: resolved.documentId, content };
199
+ }
200
+ // Map the operator's input to an APPLY target. A real local file is read
201
+ // directly (APPLY by relPath); an opaque/identity reference round-trips through
202
+ // RESOLVE (APPLY by documentId). This mirrors the worker's two resolution
203
+ // candidates (filesystem form vs identity form), split across the wire.
204
+ async function resolveApplyTarget(cfg, flags, workspaceId) {
205
+ const raw = flags.input.trim();
206
+ if (!raw) {
207
+ throw new ReingestPreconditionError("`mla kb reingest` requires a non-empty input");
208
+ }
209
+ // kbdoc:<id>: opaque, never a file. Resolve server-side, anchored on cwd.
210
+ if (raw.startsWith(KBDOC_PREFIX)) {
211
+ const id = raw.slice(KBDOC_PREFIX.length).trim();
212
+ if (!id) {
213
+ throw new ReingestPreconditionError("kbdoc: prefix requires an id");
214
+ }
215
+ return resolveViaServer(cfg, workspaceId, raw, process.cwd());
216
+ }
217
+ // note:<X> or bare <X>. If X is a real local file, the client holds it: read
218
+ // it and APPLY by relPath. Otherwise it is a stored externalObjectId string;
219
+ // resolve it server-side (anchored on cwd) and APPLY by documentId.
220
+ const rawPath = raw.startsWith(NOTE_PREFIX)
221
+ ? raw.slice(NOTE_PREFIX.length)
222
+ : raw;
223
+ const abs = path.resolve(expandHome(rawPath));
224
+ let isFile = false;
225
+ try {
226
+ isFile = fs.existsSync(abs) && fs.statSync(abs).isFile();
227
+ }
228
+ catch {
229
+ isFile = false;
230
+ }
231
+ if (isFile) {
232
+ const vaultRoot = resolveReingestVaultRoot(path.dirname(abs));
233
+ const relPath = (0, kb_add_1.vaultRelPath)(vaultRoot, abs);
234
+ const content = fs.readFileSync(abs, "utf8");
235
+ return { relPath, content };
236
+ }
237
+ return resolveViaServer(cfg, workspaceId, raw, process.cwd());
238
+ }
239
+ function printPreflight(flags, cfg) {
240
+ const ws = flags.workspace || cfg.workspaceId;
241
+ const reasonHint = flags.reason ? ` reason=${JSON.stringify(flags.reason)}` : "";
242
+ console.log(`mla kb reingest workspace=${ws} input=${flags.input}${reasonHint}`);
243
+ }
244
+ // Map an HTTP/network failure to the worker's exit semantics: a 404 (unknown
245
+ // doc), 409 (terminal state), or 422 (bad args) is a precondition -> 2; a
246
+ // network error (no status) or a 5xx is an operational failure -> 1.
247
+ function exitForHttpError(e) {
248
+ const status = e?.status;
249
+ if (status === 404 || status === 409 || status === 422)
250
+ return 2;
251
+ return 1;
252
+ }
253
+ async function runKbReingest(argv) {
254
+ // Parse flags BEFORE loading config so `--workspace <id>` can override the
255
+ // marker-resolved workspace (T1.1 folder = workspace) without requiring the
256
+ // current directory to be activated.
257
+ let flags;
258
+ try {
259
+ flags = parseKbReingestArgs(argv);
260
+ }
261
+ catch (e) {
262
+ console.error(e.message);
263
+ return 2;
264
+ }
265
+ let cfg;
266
+ try {
267
+ cfg = (0, config_1.readKbConfig)(flags.workspace);
268
+ }
269
+ catch (e) {
270
+ console.error(e.message);
271
+ return 2;
272
+ }
273
+ // §13.14 owner-only ACL: reingest mints new revisions and activates them.
274
+ try {
275
+ await (0, kb_acl_1.verifyKbActorIsOwner)(cfg);
276
+ }
277
+ catch (e) {
278
+ if (e instanceof kb_acl_1.KbOwnerCheckError) {
279
+ console.error(e.message);
280
+ return 2;
281
+ }
282
+ throw e;
283
+ }
284
+ const workspaceId = flags.workspace || cfg.workspaceId;
285
+ printPreflight(flags, cfg);
286
+ // Resolve the source file + the document handle. A RESOLVE round-trip (for an
287
+ // opaque/identity reference) can fail with an HTTP status; a client-side
288
+ // precondition (vault root, missing file) throws ReingestPreconditionError.
289
+ let target;
290
+ try {
291
+ target = await resolveApplyTarget(cfg, flags, workspaceId);
292
+ }
293
+ catch (e) {
294
+ if (e instanceof ReingestPreconditionError) {
295
+ console.error(e.message);
296
+ return 2;
297
+ }
298
+ console.error(`kb reingest failed: ${e.message}`);
299
+ return exitForHttpError(e);
300
+ }
301
+ // Relay the session UUID, canonicalized (defense in depth: a direct
302
+ // `mla kb reingest --agent-session X` may carry a non-canonical value). The
303
+ // server canonicalizes again and is the authoritative gate; an invalid value
304
+ // yields no session, never a composed value.
305
+ const agentSession = (0, observability_1.canonicalizeSessionId)(flags.agentSession ?? null);
306
+ const body = {
307
+ workspaceId,
308
+ actor: cfg.actorUserId,
309
+ profile: flags.profile || DEFAULT_PROFILE,
310
+ reason: flags.reason ?? undefined,
311
+ agentSession: agentSession ?? undefined,
312
+ content: target.content,
313
+ ...(target.documentId
314
+ ? { documentId: target.documentId }
315
+ : { relPath: target.relPath }),
316
+ };
317
+ let receipt;
318
+ try {
319
+ const res = await (0, http_1.intelPost)(cfg, "/internal/v1/kb/reingest", body, REINGEST_TIMEOUT_MS);
320
+ receipt = res.receipt;
321
+ }
322
+ catch (e) {
323
+ console.error(`kb reingest failed: ${e.message}`);
324
+ return exitForHttpError(e);
325
+ }
326
+ if (!receipt) {
327
+ console.error("kb reingest: the route returned no receipt.");
328
+ return 1;
329
+ }
330
+ console.log((0, render_1.renderKbReingestReceipt)(receipt));
331
+ console.log("");
332
+ // A per-doc intake failure is reported in the receipt, not the HTTP status;
333
+ // mirror the worker's exit (failed -> 1, ingested / noop_unchanged -> 0).
334
+ return receipt.outcome === "failed" ? 1 : 0;
335
+ }