@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,170 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// `mla kb retime <source-item-id> --effective-date <date>` (Phase 5.3).
|
|
3
|
+
//
|
|
4
|
+
// retime corrects the trusted EFFECTIVE DATE of a source item (a note, a diff, a
|
|
5
|
+
// thread) and regenerates the derived relations it anchors. It is the operator's
|
|
6
|
+
// front door to the Phase 4 correction path:
|
|
7
|
+
//
|
|
8
|
+
// POST /internal/v1/kb/retime -> create_temporal_correction -> regeneration
|
|
9
|
+
//
|
|
10
|
+
// which honours the Option-3 invariant: an accepted relation is NEVER edited in
|
|
11
|
+
// place nor physically deleted. A correction records a new anchor for the source
|
|
12
|
+
// item, stales that item's live derived relations (sets invalidated_at, leaving
|
|
13
|
+
// valid_at and row identity intact), and re-inserts fresh live edges under the
|
|
14
|
+
// corrected anchor through the resolver. So retime edits the SOURCE ITEM, not a
|
|
15
|
+
// relation; you never hand-edit a relation's valid_at.
|
|
16
|
+
//
|
|
17
|
+
// Mirrors the kb_promote.ts deps-injection shape: a thin public `runKbRetime` that
|
|
18
|
+
// loads the real config (readKbConfig) and wires the real intelPost, while every
|
|
19
|
+
// collaborator is injectable so the unit test drives it offline (no network,
|
|
20
|
+
// config, or disk).
|
|
21
|
+
//
|
|
22
|
+
// Auth + actor: the POST carries Authorization via the shared intel HTTP layer;
|
|
23
|
+
// the actor rides in the BODY (intel stamps only Authorization + X-Trace-ID,
|
|
24
|
+
// never X-Meetless-Actor), so `actor` comes from cfg.actorUserId in the payload.
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.parseKbRetimeArgs = parseKbRetimeArgs;
|
|
27
|
+
exports.runKbRetime = runKbRetime;
|
|
28
|
+
const config_1 = require("../lib/config");
|
|
29
|
+
const http_1 = require("../lib/http");
|
|
30
|
+
const temporal_1 = require("../lib/temporal");
|
|
31
|
+
const USAGE = "Usage: mla kb retime <source-item-id> --effective-date <date> [--reason <s>] [--anchor-type <t>] [--json]";
|
|
32
|
+
const VALUE_FLAGS = new Set(["--effective-date", "--reason", "--anchor-type"]);
|
|
33
|
+
const BOOLEAN_FLAGS = new Set(["--json"]);
|
|
34
|
+
// Parse a single positional <source-item-id>, the required --effective-date, plus
|
|
35
|
+
// optional --reason / --anchor-type / --json. A value flag with no following
|
|
36
|
+
// value, a missing source id or effective date, a second positional, or an
|
|
37
|
+
// unknown flag are all usage errors (the caller maps them to exit 2).
|
|
38
|
+
function parseKbRetimeArgs(argv) {
|
|
39
|
+
let sourceItemId = null;
|
|
40
|
+
let effectiveDate = null;
|
|
41
|
+
let reason;
|
|
42
|
+
let anchorType;
|
|
43
|
+
let json = false;
|
|
44
|
+
for (let i = 0; i < argv.length; i++) {
|
|
45
|
+
const a = argv[i];
|
|
46
|
+
if (VALUE_FLAGS.has(a)) {
|
|
47
|
+
const next = argv[i + 1];
|
|
48
|
+
if (next === undefined || next.startsWith("-")) {
|
|
49
|
+
throw new Error(`Missing value for ${a}. ${USAGE}`);
|
|
50
|
+
}
|
|
51
|
+
if (a === "--effective-date")
|
|
52
|
+
effectiveDate = next;
|
|
53
|
+
else if (a === "--reason")
|
|
54
|
+
reason = next;
|
|
55
|
+
else if (a === "--anchor-type")
|
|
56
|
+
anchorType = next;
|
|
57
|
+
i++;
|
|
58
|
+
}
|
|
59
|
+
else if (BOOLEAN_FLAGS.has(a)) {
|
|
60
|
+
json = true;
|
|
61
|
+
}
|
|
62
|
+
else if (a.startsWith("-")) {
|
|
63
|
+
const supported = [...VALUE_FLAGS, ...BOOLEAN_FLAGS].sort().join(", ");
|
|
64
|
+
throw new Error(`Unknown flag: ${a}. Supported: ${supported}. ${USAGE}`);
|
|
65
|
+
}
|
|
66
|
+
else if (sourceItemId === null) {
|
|
67
|
+
sourceItemId = a;
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
throw new Error(`Unexpected argument: ${a}. ${USAGE}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (sourceItemId === null) {
|
|
74
|
+
throw new Error(`mla kb retime requires a source item id. ${USAGE}`);
|
|
75
|
+
}
|
|
76
|
+
if (effectiveDate === null) {
|
|
77
|
+
throw new Error(`mla kb retime requires --effective-date. ${USAGE}`);
|
|
78
|
+
}
|
|
79
|
+
return { sourceItemId, effectiveDate, reason, anchorType, json };
|
|
80
|
+
}
|
|
81
|
+
// Surface an intel HTTP failure helpfully, mirroring kb_promote.ts's explainPromoteError.
|
|
82
|
+
function explainRetimeError(err, intelUrl) {
|
|
83
|
+
if (err.status === 404) {
|
|
84
|
+
return `intel returned 404 for the retime route. The source item was not found, or this intel does not expose the KB retime endpoint.`;
|
|
85
|
+
}
|
|
86
|
+
if (err.status === 422) {
|
|
87
|
+
return `intel rejected the correction (HTTP 422): ${err.body ?? err.message}`;
|
|
88
|
+
}
|
|
89
|
+
if (err.status === 401 || err.status === 403) {
|
|
90
|
+
return `intel rejected the token (HTTP ${err.status}). Check controlToken in cli-config.json.`;
|
|
91
|
+
}
|
|
92
|
+
if (err.status === undefined) {
|
|
93
|
+
return `intel not reachable at ${intelUrl}. Is it running? Try \`mla doctor\`.`;
|
|
94
|
+
}
|
|
95
|
+
return err.message;
|
|
96
|
+
}
|
|
97
|
+
// Render the correction receipt in plain words. No double-dash range separators
|
|
98
|
+
// (An's AI-smell rule); each line spells out what changed so an operator reads a
|
|
99
|
+
// receipt, not a guess.
|
|
100
|
+
function renderReceipt(r) {
|
|
101
|
+
const out = [];
|
|
102
|
+
out.push(`Retimed ${r.sourceItemId}: effective date corrected to ${r.effectiveDate}.`);
|
|
103
|
+
out.push(` new anchor: ${r.newAnchorId}`);
|
|
104
|
+
if (r.priorAnchorId)
|
|
105
|
+
out.push(` prior anchor: ${r.priorAnchorId} (superseded, kept for audit)`);
|
|
106
|
+
out.push(` staled relations: ${r.staledRelationIds.length}`);
|
|
107
|
+
if (r.regenerated) {
|
|
108
|
+
out.push(` regenerated: ${r.regeneratedRelationIds.length} live edge(s) under the corrected anchor`);
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
out.push(` regenerated: pending (a concurrent drainer claimed the event; it will regenerate)`);
|
|
112
|
+
}
|
|
113
|
+
out.push("");
|
|
114
|
+
out.push("retime edits the SOURCE ITEM's effective date and regenerates derived relations; it does not edit accepted relations in place.");
|
|
115
|
+
return out.join("\n");
|
|
116
|
+
}
|
|
117
|
+
async function runKbRetime(argv, deps) {
|
|
118
|
+
let cfg;
|
|
119
|
+
try {
|
|
120
|
+
cfg = deps?.cfg ?? (0, config_1.readKbConfig)();
|
|
121
|
+
}
|
|
122
|
+
catch (e) {
|
|
123
|
+
console.error(e.message);
|
|
124
|
+
return 2;
|
|
125
|
+
}
|
|
126
|
+
let parsed;
|
|
127
|
+
try {
|
|
128
|
+
parsed = parseKbRetimeArgs(argv);
|
|
129
|
+
}
|
|
130
|
+
catch (e) {
|
|
131
|
+
console.error(e.message);
|
|
132
|
+
return 2;
|
|
133
|
+
}
|
|
134
|
+
// Validate + normalize the effective date client-side so a typo never silently
|
|
135
|
+
// anchors to "now". parseAsOf throws on anything malformed (exit 2, no POST).
|
|
136
|
+
let effectiveDate;
|
|
137
|
+
try {
|
|
138
|
+
effectiveDate = (0, temporal_1.parseAsOf)(parsed.effectiveDate);
|
|
139
|
+
}
|
|
140
|
+
catch (e) {
|
|
141
|
+
console.error(e.message);
|
|
142
|
+
return 2;
|
|
143
|
+
}
|
|
144
|
+
const post = deps?.http?.intelPost ?? http_1.intelPost;
|
|
145
|
+
const body = {
|
|
146
|
+
workspaceId: cfg.workspaceId,
|
|
147
|
+
sourceItemId: parsed.sourceItemId,
|
|
148
|
+
effectiveDate,
|
|
149
|
+
actor: cfg.actorUserId,
|
|
150
|
+
};
|
|
151
|
+
if (parsed.reason !== undefined)
|
|
152
|
+
body.reason = parsed.reason;
|
|
153
|
+
if (parsed.anchorType !== undefined)
|
|
154
|
+
body.anchorType = parsed.anchorType;
|
|
155
|
+
let res;
|
|
156
|
+
try {
|
|
157
|
+
res = (await post(cfg, "/internal/v1/kb/retime", body));
|
|
158
|
+
}
|
|
159
|
+
catch (e) {
|
|
160
|
+
const intelUrl = cfg.intelUrl || http_1.DEFAULT_INTEL_URL;
|
|
161
|
+
console.error(explainRetimeError(e, intelUrl));
|
|
162
|
+
return 1;
|
|
163
|
+
}
|
|
164
|
+
if (parsed.json) {
|
|
165
|
+
console.log(JSON.stringify(res, null, 2));
|
|
166
|
+
return 0;
|
|
167
|
+
}
|
|
168
|
+
console.log(renderReceipt(res));
|
|
169
|
+
return 0;
|
|
170
|
+
}
|
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// `mla kb review <candidate-id> (--accept | --reject | --reclassify <TYPE>
|
|
3
|
+
// [--scope-section <text>] | --no-relation) [--note <text>] [--agent]`
|
|
4
|
+
//
|
|
5
|
+
// B5 core (notes/20260603-mla-kb-agent-proxy-and-evidence-adoption.md §3, §7.4).
|
|
6
|
+
// Routes a verdict through the EXISTING control finalize primitive
|
|
7
|
+
// (POST /internal/v1/relationship-candidates/<id>/{accept,reject}) -- the same
|
|
8
|
+
// primitive the Console and the meetless__relationship_verdict MCP tool already
|
|
9
|
+
// use. The CLI never writes the knowledge graph and never bypasses the promotion
|
|
10
|
+
// gate (status==ACCEPTED && posture==LIVE is the control service's job; the CLI
|
|
11
|
+
// only records the verdict, which control then transitions + propagates via outbox).
|
|
12
|
+
//
|
|
13
|
+
// A-2 write-side: --reclassify / --scope-section / --no-relation map to the
|
|
14
|
+
// propose-correction verb (POST .../propose-correction). A correction is a
|
|
15
|
+
// PROPOSED training label, not a live-graph edit: it captures "the right answer is
|
|
16
|
+
// X" so a human can apply it later ("agent proposes; user applies"). Because it
|
|
17
|
+
// never mutates the graph, propose-correction is ALWAYS allowed -- for a human and
|
|
18
|
+
// for an automated proxy alike -- unlike accept (human-only) and reject (an agent
|
|
19
|
+
// may only auto-reject mechanically-invalid candidates).
|
|
20
|
+
//
|
|
21
|
+
// Auto-resolution policy (P2, MVP): REJECT-ONLY. The `--agent` flag declares that an
|
|
22
|
+
// automated proxy (Claude Code acting on An's behalf) is the caller:
|
|
23
|
+
// * `--accept --agent` is ALWAYS refused. Accepting a relationship is institutional
|
|
24
|
+
// memory; a wrong auto-accept manufactures false governance and poisons
|
|
25
|
+
// retrieval, which is strictly worse than leaving the edge unreviewed. Only a
|
|
26
|
+
// human may accept.
|
|
27
|
+
// * `--reject --agent` is allowed ONLY when the candidate is mechanically invalid
|
|
28
|
+
// (classifyMechanicalInvalidity); otherwise it is refused and surfaced for human
|
|
29
|
+
// review. The auto-reject reason is stamped into the verdict note for audit.
|
|
30
|
+
// A human (no `--agent`) is the authority: both verbs proceed unconditionally.
|
|
31
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
32
|
+
exports.parseKbReviewArgs = parseKbReviewArgs;
|
|
33
|
+
exports.evaluateReviewPolicy = evaluateReviewPolicy;
|
|
34
|
+
exports.runKbReviewWith = runKbReviewWith;
|
|
35
|
+
exports.renderReviewFooter = renderReviewFooter;
|
|
36
|
+
exports.renderCorrectionFooter = renderCorrectionFooter;
|
|
37
|
+
exports.runKbReview = runKbReview;
|
|
38
|
+
const config_1 = require("../lib/config");
|
|
39
|
+
const http_1 = require("../lib/http");
|
|
40
|
+
const kb_candidate_1 = require("../lib/kb-candidate");
|
|
41
|
+
const recorder_1 = require("../lib/analytics/recorder");
|
|
42
|
+
const review_analytics_1 = require("../lib/analytics/review-analytics");
|
|
43
|
+
const USAGE = "Usage: mla kb review <candidate-id> (--accept | --reject | --reclassify <TYPE> [--scope-section <text>] | --no-relation) [--note <text>] [--agent]";
|
|
44
|
+
// Validate the SHAPE of a --reclassify value and normalize it to the canonical
|
|
45
|
+
// SCREAMING_SNAKE the relation-type registry uses. We deliberately do NOT check it
|
|
46
|
+
// against the registry here (control is the authority; coupling the CLI to the
|
|
47
|
+
// enum would risk false rejects on registry drift). A typo surfaces as a clean
|
|
48
|
+
// control error after one round-trip.
|
|
49
|
+
function normalizeCorrectedRelationType(raw) {
|
|
50
|
+
const t = raw.trim().toUpperCase();
|
|
51
|
+
if (!/^[A-Z][A-Z0-9_]*$/.test(t)) {
|
|
52
|
+
throw new Error(`--reclassify expects a relation type like REFINES or SUPERSEDES (got "${raw}"). ` +
|
|
53
|
+
`The authoritative set is validated by control.`);
|
|
54
|
+
}
|
|
55
|
+
return t;
|
|
56
|
+
}
|
|
57
|
+
function parseKbReviewArgs(argv) {
|
|
58
|
+
let candidateId;
|
|
59
|
+
let accept = false;
|
|
60
|
+
let reject = false;
|
|
61
|
+
let agent = false;
|
|
62
|
+
let note = null;
|
|
63
|
+
let reclassify;
|
|
64
|
+
let noRelation = false;
|
|
65
|
+
let scopeSection;
|
|
66
|
+
for (let i = 0; i < argv.length; i++) {
|
|
67
|
+
const a = argv[i];
|
|
68
|
+
if (a === "--accept") {
|
|
69
|
+
accept = true;
|
|
70
|
+
}
|
|
71
|
+
else if (a === "--reject") {
|
|
72
|
+
reject = true;
|
|
73
|
+
}
|
|
74
|
+
else if (a === "--agent") {
|
|
75
|
+
agent = true;
|
|
76
|
+
}
|
|
77
|
+
else if (a === "--no-relation") {
|
|
78
|
+
noRelation = true;
|
|
79
|
+
}
|
|
80
|
+
else if (a === "--note") {
|
|
81
|
+
const v = argv[++i];
|
|
82
|
+
if (v === undefined)
|
|
83
|
+
throw new Error("--note requires a value");
|
|
84
|
+
note = v;
|
|
85
|
+
}
|
|
86
|
+
else if (a === "--reclassify") {
|
|
87
|
+
const v = argv[++i];
|
|
88
|
+
if (v === undefined || v.startsWith("-")) {
|
|
89
|
+
throw new Error("--reclassify requires a relation type (e.g. REFINES)");
|
|
90
|
+
}
|
|
91
|
+
reclassify = v;
|
|
92
|
+
}
|
|
93
|
+
else if (a === "--scope-section") {
|
|
94
|
+
const v = argv[++i];
|
|
95
|
+
if (v === undefined)
|
|
96
|
+
throw new Error("--scope-section requires a value");
|
|
97
|
+
scopeSection = v;
|
|
98
|
+
}
|
|
99
|
+
else if (a.startsWith("-")) {
|
|
100
|
+
throw new Error(`Unknown flag: ${a}. ${USAGE}`);
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
if (candidateId !== undefined)
|
|
104
|
+
throw new Error(`Unexpected extra argument: ${a}. ${USAGE}`);
|
|
105
|
+
candidateId = a;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (candidateId === undefined)
|
|
109
|
+
throw new Error(USAGE);
|
|
110
|
+
const primaries = (accept ? 1 : 0) + (reject ? 1 : 0) + (reclassify !== undefined ? 1 : 0) + (noRelation ? 1 : 0);
|
|
111
|
+
if (primaries === 0) {
|
|
112
|
+
// Keep the legacy "--accept or --reject" phrasing (callers/tests key on it)
|
|
113
|
+
// while pointing at the new correction verbs.
|
|
114
|
+
throw new Error(`Pass one of --accept or --reject (or --reclassify <TYPE> / --no-relation to propose a correction). ${USAGE}`);
|
|
115
|
+
}
|
|
116
|
+
if (primaries > 1) {
|
|
117
|
+
throw new Error("Pass exactly one of --accept, --reject, --reclassify, or --no-relation, not several");
|
|
118
|
+
}
|
|
119
|
+
if (scopeSection !== undefined && reclassify === undefined) {
|
|
120
|
+
throw new Error("--scope-section requires --reclassify <TYPE> (a section-scoped correction still needs the corrected relation type)");
|
|
121
|
+
}
|
|
122
|
+
if (reclassify !== undefined) {
|
|
123
|
+
const correctedRelationType = normalizeCorrectedRelationType(reclassify);
|
|
124
|
+
const correction = scopeSection !== undefined
|
|
125
|
+
? {
|
|
126
|
+
correctionKind: "SCOPE_CORRECTION",
|
|
127
|
+
correctedRelationType,
|
|
128
|
+
scopeKind: "SECTION",
|
|
129
|
+
sourceSectionPath: scopeSection,
|
|
130
|
+
}
|
|
131
|
+
: { correctionKind: "RELATION_TYPE_CORRECTION", correctedRelationType };
|
|
132
|
+
return { candidateId, verdict: "propose-correction", note, agent, correction };
|
|
133
|
+
}
|
|
134
|
+
if (noRelation) {
|
|
135
|
+
return {
|
|
136
|
+
candidateId,
|
|
137
|
+
verdict: "propose-correction",
|
|
138
|
+
note,
|
|
139
|
+
agent,
|
|
140
|
+
correction: { correctionKind: "NO_RELATION" },
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
return { candidateId, verdict: accept ? "accept" : "reject", note, agent };
|
|
144
|
+
}
|
|
145
|
+
// The P2 policy gate. Pure; the candidate is consulted only for the agent-reject
|
|
146
|
+
// path (to classify mechanical invalidity). Returns the verdict note to persist on
|
|
147
|
+
// proceed, or a refusal with an exit code and an operator-facing message.
|
|
148
|
+
function evaluateReviewPolicy(opts) {
|
|
149
|
+
const { candidateId, verdict, agent, note } = opts;
|
|
150
|
+
// A-2: a correction is propose-only (no live-graph edit; a human applies it
|
|
151
|
+
// later), so it is always allowed regardless of caller. This is checked BEFORE
|
|
152
|
+
// the agent gates below precisely so an automated proxy is NOT blocked from
|
|
153
|
+
// proposing a correction the way it is blocked from accept/reject.
|
|
154
|
+
if (verdict === "propose-correction") {
|
|
155
|
+
return { action: "proceed", note: note ?? undefined };
|
|
156
|
+
}
|
|
157
|
+
// Human caller: full authority over both verbs.
|
|
158
|
+
if (!agent) {
|
|
159
|
+
return { action: "proceed", note: note ?? undefined };
|
|
160
|
+
}
|
|
161
|
+
// Automated proxy: reject-only, mechanically gated.
|
|
162
|
+
if (verdict === "accept") {
|
|
163
|
+
return {
|
|
164
|
+
action: "refuse",
|
|
165
|
+
exitCode: 2,
|
|
166
|
+
message: `Auto-accept is disallowed for an automated proxy (P2). Accepting a relationship ` +
|
|
167
|
+
`is institutional memory; a wrong auto-accept creates false governance and ` +
|
|
168
|
+
`poisons retrieval. A human must accept ${candidateId}. Surface it with ` +
|
|
169
|
+
`\`mla kb pending\` and ask the operator to run \`mla kb review ${candidateId} --accept\`.`,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
// verdict === "reject" && agent
|
|
173
|
+
const cand = opts.candidate;
|
|
174
|
+
if (!cand) {
|
|
175
|
+
return {
|
|
176
|
+
action: "refuse",
|
|
177
|
+
exitCode: 1,
|
|
178
|
+
message: `Candidate ${candidateId} not found; cannot evaluate auto-reject eligibility.`,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
const mech = (0, kb_candidate_1.classifyMechanicalInvalidity)(cand);
|
|
182
|
+
if (!mech.autoRejectable) {
|
|
183
|
+
return {
|
|
184
|
+
action: "refuse",
|
|
185
|
+
exitCode: 2,
|
|
186
|
+
message: `Candidate ${candidateId} is not mechanically invalid, so an automated proxy may ` +
|
|
187
|
+
`not reject it (P2: agent auto-resolution is reject-only AND limited to ` +
|
|
188
|
+
`mechanically-invalid candidates: self-edge or unsupported low-confidence). ` +
|
|
189
|
+
`Surface it for a human decision with \`mla kb pending\`.`,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
const auto = `[auto-reject:${mech.reasonCode}] ${mech.reason}`;
|
|
193
|
+
const combined = note ? `${auto}; ${note}` : auto;
|
|
194
|
+
return { action: "proceed", note: combined };
|
|
195
|
+
}
|
|
196
|
+
// T7.2: emit the value/trust analytics for a RECORDED review decision
|
|
197
|
+
// (mla_review_decision always; mla_contradiction only for a CONTRADICTS/SUPERSEDES
|
|
198
|
+
// edge). Called AFTER a successful submission, so a recorded decision is the
|
|
199
|
+
// trigger and a refused operation never emits. Fully best-effort: it resolves the
|
|
200
|
+
// reviewed candidate's relation type (reusing the row the agent-reject path already
|
|
201
|
+
// loaded; otherwise a best-effort fetch so the human paths still carry relation_type
|
|
202
|
+
// and the contradiction signal) and records each event into the run buffer. Any
|
|
203
|
+
// failure -- a fetch error, an absent run context -- is swallowed so analytics can
|
|
204
|
+
// never block or revert a verdict.
|
|
205
|
+
async function emitReviewAnalytics(parsed, ctx, deps, alreadyFetched) {
|
|
206
|
+
try {
|
|
207
|
+
const record = deps.record ?? recorder_1.recordAnalyticsEvent;
|
|
208
|
+
const env = deps.env ?? process.env;
|
|
209
|
+
const nowMs = deps.nowMs ?? Date.now();
|
|
210
|
+
let relationTypeId = null;
|
|
211
|
+
if (alreadyFetched !== undefined) {
|
|
212
|
+
// The agent-reject path already loaded the row; reuse it (no second fetch).
|
|
213
|
+
relationTypeId = alreadyFetched?.relationTypeId ?? null;
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
try {
|
|
217
|
+
const c = await deps.fetchCandidate(parsed.candidateId);
|
|
218
|
+
relationTypeId = c?.relationTypeId ?? null;
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
// best-effort: degrade to relation_type="unknown"; still record the decision.
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
const events = (0, review_analytics_1.buildReviewAnalyticsEvents)({
|
|
225
|
+
candidateId: parsed.candidateId,
|
|
226
|
+
verdict: parsed.verdict,
|
|
227
|
+
correctionKind: parsed.correction?.correctionKind,
|
|
228
|
+
relationTypeId,
|
|
229
|
+
});
|
|
230
|
+
const recordCtx = {
|
|
231
|
+
workspaceId: ctx.workspaceId,
|
|
232
|
+
sessionId: (env.CLAUDE_CODE_SESSION_ID || "").trim() || null,
|
|
233
|
+
now: new Date(nowMs).toISOString(),
|
|
234
|
+
};
|
|
235
|
+
for (const ev of events) {
|
|
236
|
+
record(recordCtx, ev, env);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
// fail-soft: a verdict must never be blocked or reverted by analytics.
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
async function runKbReviewWith(argv, ctx, deps) {
|
|
244
|
+
let parsed;
|
|
245
|
+
try {
|
|
246
|
+
parsed = parseKbReviewArgs(argv);
|
|
247
|
+
}
|
|
248
|
+
catch (e) {
|
|
249
|
+
console.error(e.message);
|
|
250
|
+
return 2;
|
|
251
|
+
}
|
|
252
|
+
// Only the agent-reject path needs the candidate row (to classify). Human verdicts
|
|
253
|
+
// and the agent-accept refusal short-circuit without a fetch.
|
|
254
|
+
let candidate;
|
|
255
|
+
if (parsed.agent && parsed.verdict === "reject") {
|
|
256
|
+
try {
|
|
257
|
+
candidate = await deps.fetchCandidate(parsed.candidateId);
|
|
258
|
+
}
|
|
259
|
+
catch (e) {
|
|
260
|
+
console.error(`Failed to load candidate ${parsed.candidateId}: ${e.message}`);
|
|
261
|
+
return 1;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
const decision = evaluateReviewPolicy({
|
|
265
|
+
candidateId: parsed.candidateId,
|
|
266
|
+
verdict: parsed.verdict,
|
|
267
|
+
agent: parsed.agent,
|
|
268
|
+
note: parsed.note,
|
|
269
|
+
candidate,
|
|
270
|
+
});
|
|
271
|
+
if (decision.action === "refuse") {
|
|
272
|
+
console.error(decision.message);
|
|
273
|
+
return decision.exitCode;
|
|
274
|
+
}
|
|
275
|
+
// A-2: a correction takes the propose-correction boundary, not the verdict one.
|
|
276
|
+
if (parsed.verdict === "propose-correction" && parsed.correction) {
|
|
277
|
+
const cbody = {
|
|
278
|
+
workspaceId: ctx.workspaceId,
|
|
279
|
+
userId: ctx.actorUserId,
|
|
280
|
+
correction: parsed.correction,
|
|
281
|
+
};
|
|
282
|
+
if (decision.note !== undefined)
|
|
283
|
+
cbody.note = decision.note;
|
|
284
|
+
let cresult;
|
|
285
|
+
try {
|
|
286
|
+
cresult = await deps.submitCorrection(parsed.candidateId, cbody);
|
|
287
|
+
}
|
|
288
|
+
catch (e) {
|
|
289
|
+
const err = e;
|
|
290
|
+
if (err.status === 404) {
|
|
291
|
+
console.error(`Candidate ${parsed.candidateId} not found.`);
|
|
292
|
+
return 1;
|
|
293
|
+
}
|
|
294
|
+
console.error(`Failed to propose a correction for ${parsed.candidateId}: ${err.message}`);
|
|
295
|
+
return 1;
|
|
296
|
+
}
|
|
297
|
+
const footer = renderCorrectionFooter(parsed.candidateId, cresult, (0, kb_candidate_1.candidateConsoleUrl)(ctx.consoleBase, parsed.candidateId));
|
|
298
|
+
for (const line of footer)
|
|
299
|
+
console.log(line);
|
|
300
|
+
await emitReviewAnalytics(parsed, ctx, deps, candidate);
|
|
301
|
+
return 0;
|
|
302
|
+
}
|
|
303
|
+
// The propose-correction case returned above, so the only verdicts left are the
|
|
304
|
+
// two finalize verbs.
|
|
305
|
+
const verdict = parsed.verdict;
|
|
306
|
+
const body = {
|
|
307
|
+
workspaceId: ctx.workspaceId,
|
|
308
|
+
userId: ctx.actorUserId,
|
|
309
|
+
};
|
|
310
|
+
if (decision.note !== undefined)
|
|
311
|
+
body.note = decision.note;
|
|
312
|
+
let result;
|
|
313
|
+
try {
|
|
314
|
+
result = await deps.submitVerdict(parsed.candidateId, verdict, body);
|
|
315
|
+
}
|
|
316
|
+
catch (e) {
|
|
317
|
+
const err = e;
|
|
318
|
+
if (err.status === 404) {
|
|
319
|
+
console.error(`Candidate ${parsed.candidateId} not found.`);
|
|
320
|
+
return 1;
|
|
321
|
+
}
|
|
322
|
+
console.error(`Failed to record verdict for ${parsed.candidateId}: ${err.message}`);
|
|
323
|
+
return 1;
|
|
324
|
+
}
|
|
325
|
+
const verb = verdict === "accept" ? "accepted" : parsed.agent ? "auto-rejected" : "rejected";
|
|
326
|
+
const footer = renderReviewFooter(verb, parsed.candidateId, result.statusId, (0, kb_candidate_1.candidateConsoleUrl)(ctx.consoleBase, parsed.candidateId));
|
|
327
|
+
for (const line of footer)
|
|
328
|
+
console.log(line);
|
|
329
|
+
await emitReviewAnalytics(parsed, ctx, deps, candidate);
|
|
330
|
+
return 0;
|
|
331
|
+
}
|
|
332
|
+
// A-0 (A4 surface 1): the success footer. The CLI caller is UNKNOWN (a human and a
|
|
333
|
+
// coding agent run the identical command), so an ACCEPT -- the one governed verb --
|
|
334
|
+
// gets a dual-audience note: it states the accept carried the user's authority and
|
|
335
|
+
// reminds an agent that the UX default is propose-first (it should not have run
|
|
336
|
+
// --accept directly). A reject/auto-reject is freely allowed and gets no such note.
|
|
337
|
+
function renderReviewFooter(verb, candidateId, statusId, consoleUrl) {
|
|
338
|
+
const lines = [`${verb} relationship candidate ${candidateId} (now ${statusId}).`, ` ${consoleUrl}`];
|
|
339
|
+
if (verb === "accepted") {
|
|
340
|
+
lines.push("Recorded under the user's authority (a governed change). If you are an agent, the default is to propose accepts and let the user confirm rather than run --accept directly.");
|
|
341
|
+
}
|
|
342
|
+
return lines;
|
|
343
|
+
}
|
|
344
|
+
// A-2: the propose-correction footer. A correction is propose-only, so it must NOT
|
|
345
|
+
// borrow the accept footer's "user's authority" language: nothing in the live graph
|
|
346
|
+
// changed. It names the proposed correction, points at the console, and states that
|
|
347
|
+
// a human applies it later (and flags a deduped re-proposal so the caller does not
|
|
348
|
+
// think a fresh record was created).
|
|
349
|
+
function renderCorrectionFooter(candidateId, result, consoleUrl) {
|
|
350
|
+
const what = result.correctionKindId === "NO_RELATION"
|
|
351
|
+
? "no relation (the edge should not exist)"
|
|
352
|
+
: `${result.correctedRelationTypeKey}`;
|
|
353
|
+
const lines = [
|
|
354
|
+
`proposed correction for relationship candidate ${candidateId}: ${what} ` +
|
|
355
|
+
`(${result.curationStatusId.toLowerCase()}, not yet applied to the graph).`,
|
|
356
|
+
` ${consoleUrl}`,
|
|
357
|
+
];
|
|
358
|
+
if (result.deduped) {
|
|
359
|
+
lines.push("This matches a correction already proposed for this candidate, so no new record was created (deduped).");
|
|
360
|
+
}
|
|
361
|
+
lines.push("A human applies or dismisses proposed corrections; this did not change the live knowledge graph.");
|
|
362
|
+
return lines;
|
|
363
|
+
}
|
|
364
|
+
// Public entrypoint: wires real config + the control HTTP boundary.
|
|
365
|
+
async function runKbReview(argv) {
|
|
366
|
+
let cfg;
|
|
367
|
+
try {
|
|
368
|
+
cfg = (0, config_1.readKbConfig)();
|
|
369
|
+
}
|
|
370
|
+
catch (e) {
|
|
371
|
+
console.error(e.message);
|
|
372
|
+
return 2;
|
|
373
|
+
}
|
|
374
|
+
const consoleBase = (0, config_1.getConsoleUrl)(cfg);
|
|
375
|
+
const deps = {
|
|
376
|
+
fetchCandidate: async (id) => {
|
|
377
|
+
try {
|
|
378
|
+
const r = await (0, http_1.get)(cfg, `/internal/v1/relationship-candidates/${encodeURIComponent(id)}?workspaceId=${encodeURIComponent(cfg.workspaceId)}`, 10000);
|
|
379
|
+
return r.candidate ?? null;
|
|
380
|
+
}
|
|
381
|
+
catch (e) {
|
|
382
|
+
if (e.status === 404)
|
|
383
|
+
return null;
|
|
384
|
+
throw e;
|
|
385
|
+
}
|
|
386
|
+
},
|
|
387
|
+
submitVerdict: async (id, verdict, body) => (0, http_1.post)(cfg, `/internal/v1/relationship-candidates/${encodeURIComponent(id)}/${verdict}`, body, 15000),
|
|
388
|
+
submitCorrection: async (id, body) => (0, http_1.post)(cfg, `/internal/v1/relationship-candidates/${encodeURIComponent(id)}/propose-correction`, body, 15000),
|
|
389
|
+
};
|
|
390
|
+
return runKbReviewWith(argv, { workspaceId: cfg.workspaceId, actorUserId: cfg.actorUserId, consoleBase }, deps);
|
|
391
|
+
}
|