@meetless/mla 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +81 -0
- package/dist/build-info.json +9 -0
- package/dist/bundles/ask-core.js +396 -0
- package/dist/bundles/mcp.js +16592 -0
- package/dist/bundles/trace-core.js +263 -0
- package/dist/cli.js +828 -0
- package/dist/commands/activate.js +781 -0
- package/dist/commands/adoption.js +130 -0
- package/dist/commands/ask.js +290 -0
- package/dist/commands/context.js +114 -0
- package/dist/commands/debug.js +313 -0
- package/dist/commands/doctor.js +1021 -0
- package/dist/commands/enrich.js +427 -0
- package/dist/commands/evidence.js +229 -0
- package/dist/commands/flush.js +184 -0
- package/dist/commands/graph.js +104 -0
- package/dist/commands/init.js +272 -0
- package/dist/commands/internal-active-review.js +322 -0
- package/dist/commands/internal-auto-index.js +188 -0
- package/dist/commands/internal-capture-decisions.js +320 -0
- package/dist/commands/internal-evidence-correlate.js +239 -0
- package/dist/commands/internal-evidence-hooks.js +240 -0
- package/dist/commands/internal-evidence-inject.js +231 -0
- package/dist/commands/internal-finalize.js +221 -0
- package/dist/commands/internal-pretool-observe.js +225 -0
- package/dist/commands/internal-refresh.js +136 -0
- package/dist/commands/internal-session-nudge.js +120 -0
- package/dist/commands/internal-steer-sync.js +117 -0
- package/dist/commands/internal-turn-recap.js +140 -0
- package/dist/commands/kb.js +375 -0
- package/dist/commands/kb_add.js +681 -0
- package/dist/commands/kb_forget.js +283 -0
- package/dist/commands/kb_move.js +45 -0
- package/dist/commands/kb_pending.js +410 -0
- package/dist/commands/kb_personal.js +149 -0
- package/dist/commands/kb_promote.js +188 -0
- package/dist/commands/kb_purge.js +168 -0
- package/dist/commands/kb_reingest.js +335 -0
- package/dist/commands/kb_retime.js +170 -0
- package/dist/commands/kb_review.js +391 -0
- package/dist/commands/kb_revision.js +179 -0
- package/dist/commands/kb_show.js +385 -0
- package/dist/commands/label.js +226 -0
- package/dist/commands/login.js +295 -0
- package/dist/commands/logout.js +108 -0
- package/dist/commands/mcp-supervisor.js +93 -0
- package/dist/commands/mcp.js +227 -0
- package/dist/commands/queue-prune.js +98 -0
- package/dist/commands/review.js +358 -0
- package/dist/commands/rewire.js +124 -0
- package/dist/commands/rules.js +728 -0
- package/dist/commands/scan-context.js +67 -0
- package/dist/commands/session.js +347 -0
- package/dist/commands/stats.js +479 -0
- package/dist/commands/status.js +61 -0
- package/dist/commands/summary.js +250 -0
- package/dist/commands/turn.js +114 -0
- package/dist/commands/uninstall.js +222 -0
- package/dist/commands/whoami.js +102 -0
- package/dist/commands/workspace.js +130 -0
- package/dist/hooks-template/ce0-post-tool-use.sh +34 -0
- package/dist/hooks-template/ce0-session-start.sh +49 -0
- package/dist/hooks-template/ce0-stop.sh +29 -0
- package/dist/hooks-template/ce0-user-prompt-submit.sh +38 -0
- package/dist/hooks-template/common.sh +934 -0
- package/dist/hooks-template/event-batch-filter.jq +67 -0
- package/dist/hooks-template/flush.sh +503 -0
- package/dist/hooks-template/post-tool-use.sh +423 -0
- package/dist/hooks-template/pre-tool-use.sh +69 -0
- package/dist/hooks-template/session-start.sh +140 -0
- package/dist/hooks-template/stop.sh +308 -0
- package/dist/hooks-template/user-prompt-submit.sh +1162 -0
- package/dist/lib/activation.js +79 -0
- package/dist/lib/active-conflict-cache.js +141 -0
- package/dist/lib/active-memory.js +59 -0
- package/dist/lib/active-review-runner.js +26 -0
- package/dist/lib/agent-decision/index.js +25 -0
- package/dist/lib/agent-decision/keys.js +49 -0
- package/dist/lib/agent-decision/normalize-claude.js +183 -0
- package/dist/lib/agent-decision/types.js +21 -0
- package/dist/lib/agent-decision/validate.js +216 -0
- package/dist/lib/analytics/capture.js +96 -0
- package/dist/lib/analytics/command-event.js +267 -0
- package/dist/lib/analytics/consent.js +58 -0
- package/dist/lib/analytics/coverage-gap.js +96 -0
- package/dist/lib/analytics/envelope.js +236 -0
- package/dist/lib/analytics/event-id.js +86 -0
- package/dist/lib/analytics/evidence.js +150 -0
- package/dist/lib/analytics/followthrough.js +194 -0
- package/dist/lib/analytics/forwarder.js +109 -0
- package/dist/lib/analytics/logs.js +78 -0
- package/dist/lib/analytics/metrics.js +78 -0
- package/dist/lib/analytics/recorder.js +92 -0
- package/dist/lib/analytics/review-analytics.js +75 -0
- package/dist/lib/analytics/sequence.js +77 -0
- package/dist/lib/analytics/store.js +131 -0
- package/dist/lib/analytics/turn-recap.js +279 -0
- package/dist/lib/artifact_id.js +108 -0
- package/dist/lib/auth-breaker.js +161 -0
- package/dist/lib/auto-index.js +112 -0
- package/dist/lib/classifier.js +88 -0
- package/dist/lib/config.js +298 -0
- package/dist/lib/conflict-advisory.js +64 -0
- package/dist/lib/debug-bundle.js +520 -0
- package/dist/lib/enrichment/ingest.js +301 -0
- package/dist/lib/enrichment/plan.js +253 -0
- package/dist/lib/enrichment/protocol.js +359 -0
- package/dist/lib/enrichment/scout-brief.js +176 -0
- package/dist/lib/failure-telemetry.js +444 -0
- package/dist/lib/git.js +200 -0
- package/dist/lib/governance-cache.js +77 -0
- package/dist/lib/governed-path-cache.js +76 -0
- package/dist/lib/http.js +677 -0
- package/dist/lib/identity-envelope.js +23 -0
- package/dist/lib/kb-candidate.js +65 -0
- package/dist/lib/kb_acl.js +98 -0
- package/dist/lib/login.js +353 -0
- package/dist/lib/mcp-fetchers.js +130 -0
- package/dist/lib/mcp-restart.js +47 -0
- package/dist/lib/observability.js +805 -0
- package/dist/lib/open-url.js +33 -0
- package/dist/lib/orphan-guard.js +70 -0
- package/dist/lib/packaged.js +21 -0
- package/dist/lib/reconcile-sessions.js +171 -0
- package/dist/lib/redactor.js +89 -0
- package/dist/lib/relationship-candidate-query.js +27 -0
- package/dist/lib/render.js +611 -0
- package/dist/lib/rules/applicability.js +64 -0
- package/dist/lib/rules/attest-code-rule-version.js +47 -0
- package/dist/lib/rules/attest-notes-location.js +217 -0
- package/dist/lib/rules/attest-rule-version.js +69 -0
- package/dist/lib/rules/canonical-json.js +97 -0
- package/dist/lib/rules/ce0-emit.js +64 -0
- package/dist/lib/rules/ce0-evidence.js +281 -0
- package/dist/lib/rules/ce0-recall-sample.js +82 -0
- package/dist/lib/rules/ce0-rule.js +55 -0
- package/dist/lib/rules/ce0-sampling-bucket.js +15 -0
- package/dist/lib/rules/ce0-store.js +683 -0
- package/dist/lib/rules/ce0-telemetry-project.js +93 -0
- package/dist/lib/rules/ce0-telemetry.js +158 -0
- package/dist/lib/rules/code-rule-registry.js +17 -0
- package/dist/lib/rules/command-match.js +185 -0
- package/dist/lib/rules/consult-evidence-binding.js +27 -0
- package/dist/lib/rules/consultation-capture-adapter.js +193 -0
- package/dist/lib/rules/content-match.js +56 -0
- package/dist/lib/rules/deny-admission.js +99 -0
- package/dist/lib/rules/durable-observation.js +190 -0
- package/dist/lib/rules/enforce-notes-version.js +421 -0
- package/dist/lib/rules/evaluation-input-hash.js +126 -0
- package/dist/lib/rules/evaluator.js +108 -0
- package/dist/lib/rules/inert-rule-families.js +51 -0
- package/dist/lib/rules/input-authority-resolver.js +241 -0
- package/dist/lib/rules/interception-schema.js +170 -0
- package/dist/lib/rules/interception-store.js +267 -0
- package/dist/lib/rules/live-input-authority.js +66 -0
- package/dist/lib/rules/local-matcher.js +108 -0
- package/dist/lib/rules/local-observe.js +79 -0
- package/dist/lib/rules/local-rule-version-repo.js +214 -0
- package/dist/lib/rules/memory-requirement.js +109 -0
- package/dist/lib/rules/notes-observe.js +39 -0
- package/dist/lib/rules/notes-path.js +261 -0
- package/dist/lib/rules/notes-rule.js +75 -0
- package/dist/lib/rules/observe-adapter.js +114 -0
- package/dist/lib/rules/observed-rule-hash.js +119 -0
- package/dist/lib/rules/prompt-submit-adapter.js +132 -0
- package/dist/lib/rules/requirement-subject.js +240 -0
- package/dist/lib/rules/rule-activity.js +67 -0
- package/dist/lib/rules/rule-version-hash.js +151 -0
- package/dist/lib/rules/runtime-scope.js +55 -0
- package/dist/lib/rules/stop-adapter.js +116 -0
- package/dist/lib/rules/stop-response-snapshot.js +174 -0
- package/dist/lib/rules/types.js +10 -0
- package/dist/lib/rules/ulid.js +46 -0
- package/dist/lib/rules/version-evaluation.js +156 -0
- package/dist/lib/scanner/agent-memory.js +99 -0
- package/dist/lib/scanner/bootstrap-summary.js +87 -0
- package/dist/lib/scanner/cache.js +59 -0
- package/dist/lib/scanner/frontmatter.js +42 -0
- package/dist/lib/scanner/parse-directives.js +69 -0
- package/dist/lib/scanner/parse-structured.js +72 -0
- package/dist/lib/scanner/render.js +73 -0
- package/dist/lib/scanner/scan.js +132 -0
- package/dist/lib/scanner/score.js +38 -0
- package/dist/lib/scanner/scout-mission.js +126 -0
- package/dist/lib/scanner/types.js +7 -0
- package/dist/lib/session-scope.js +195 -0
- package/dist/lib/spool.js +355 -0
- package/dist/lib/staleness.js +100 -0
- package/dist/lib/steer-cache.js +87 -0
- package/dist/lib/tagged-reference.js +20 -0
- package/dist/lib/temporal.js +109 -0
- package/dist/lib/turn-recap-emit.js +67 -0
- package/dist/lib/unwire.js +253 -0
- package/dist/lib/update-check.js +469 -0
- package/dist/lib/update-notifier.js +217 -0
- package/dist/lib/upgrade-apply.js +643 -0
- package/dist/lib/wire.js +1087 -0
- package/dist/lib/workspace.js +96 -0
- package/dist/lib/zip.js +154 -0
- package/dist/pretool-entry.js +37 -0
- package/package.json +75 -0
|
@@ -0,0 +1,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
|
+
}
|