@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,410 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// `mla kb review [--all | --session <sid|current|latest> | --doc <id>] [--json]`
|
|
3
|
+
// (list mode; the same runner also backs the deprecated `mla kb pending` alias).
|
|
4
|
+
//
|
|
5
|
+
// B5 (notes/20260603-mla-kb-agent-proxy-and-evidence-adoption.md §3). Lists the
|
|
6
|
+
// PENDING_REVIEW relationship candidates a human (or an agent proxy) must decide on.
|
|
7
|
+
// Reads the control list route (GET /internal/v1/relationship-candidates) at its
|
|
8
|
+
// default view (PENDING_REVIEW + LIVE posture) -- the same queue the Console review
|
|
9
|
+
// inbox shows.
|
|
10
|
+
//
|
|
11
|
+
// Scope (notes/20260607-mla-kb-pending-session-scope-and-bulk-discard-plan.md):
|
|
12
|
+
// * default (no flag): the CURRENT session if $CLAUDE_CODE_SESSION_ID is set, else
|
|
13
|
+
// the full workspace queue. Parallel coding agents each see only the candidates
|
|
14
|
+
// touching docs THEIR session produced.
|
|
15
|
+
// * `--all`: the full workspace queue.
|
|
16
|
+
// * `--session <sid|current|latest>`: an explicit session scope.
|
|
17
|
+
// * `--doc <id>`: scope to one artifact. A value containing ":" is treated as a
|
|
18
|
+
// fully-qualified artifactId (e.g. "note:notes/foo.md", "jira:PDM-9"); a bare
|
|
19
|
+
// path is treated as a notePath the route resolves to "note:<rel>".
|
|
20
|
+
//
|
|
21
|
+
// * Human view: the "needs your decision" digest (one line per candidate: type,
|
|
22
|
+
// source -> target, confidence, detector, evidence snippet, Console deep link).
|
|
23
|
+
// No in-terminal relationship graph; the Console is the deep-work surface.
|
|
24
|
+
// * `--json`: structured for an automated proxy, each candidate annotated with its
|
|
25
|
+
// mechanical-validity verdict so the agent knows which ones it may auto-reject
|
|
26
|
+
// via `mla kb review --reject --agent` (P2 reject-only policy).
|
|
27
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
28
|
+
exports.runKbReviewList = exports.USER_CONFIRM_ACTIONS = exports.ALLOWED_AGENT_ACTIONS = void 0;
|
|
29
|
+
exports.parseKbPendingArgs = parseKbPendingArgs;
|
|
30
|
+
exports.buildPendingView = buildPendingView;
|
|
31
|
+
exports.renderPendingJson = renderPendingJson;
|
|
32
|
+
exports.renderPendingHuman = renderPendingHuman;
|
|
33
|
+
exports.fetchAllPending = fetchAllPending;
|
|
34
|
+
exports.runKbPendingWith = runKbPendingWith;
|
|
35
|
+
exports.runKbPending = runKbPending;
|
|
36
|
+
const config_1 = require("../lib/config");
|
|
37
|
+
const http_1 = require("../lib/http");
|
|
38
|
+
const governance_cache_1 = require("../lib/governance-cache");
|
|
39
|
+
const kb_candidate_1 = require("../lib/kb-candidate");
|
|
40
|
+
const session_scope_1 = require("../lib/session-scope");
|
|
41
|
+
const relationship_candidate_query_1 = require("../lib/relationship-candidate-query");
|
|
42
|
+
const USAGE = "Usage: mla kb review [--all | --session <sid|current|latest> | --doc <id>] [--json]";
|
|
43
|
+
// Verdict flags belong on `mla kb review <id> <flag>`; if one leads (so the
|
|
44
|
+
// overload routes here to list mode), give a targeted hint instead of a generic
|
|
45
|
+
// "unknown flag".
|
|
46
|
+
const VERDICT_FLAGS = new Set(["--accept", "--reject", "--reclassify", "--no-relation"]);
|
|
47
|
+
function parseKbPendingArgs(argv) {
|
|
48
|
+
let json = false;
|
|
49
|
+
let all = false;
|
|
50
|
+
let session = null;
|
|
51
|
+
let doc = null;
|
|
52
|
+
for (let i = 0; i < argv.length; i++) {
|
|
53
|
+
const a = argv[i];
|
|
54
|
+
if (a === "--json") {
|
|
55
|
+
json = true;
|
|
56
|
+
}
|
|
57
|
+
else if (a === "--all") {
|
|
58
|
+
all = true;
|
|
59
|
+
}
|
|
60
|
+
else if (a === "--session") {
|
|
61
|
+
const v = argv[++i];
|
|
62
|
+
if (v === undefined)
|
|
63
|
+
throw new Error("--session requires a value");
|
|
64
|
+
session = v;
|
|
65
|
+
}
|
|
66
|
+
else if (a === "--doc") {
|
|
67
|
+
const v = argv[++i];
|
|
68
|
+
if (v === undefined)
|
|
69
|
+
throw new Error("--doc requires a value");
|
|
70
|
+
doc = v;
|
|
71
|
+
}
|
|
72
|
+
else if (VERDICT_FLAGS.has(a)) {
|
|
73
|
+
throw new Error(`To record a verdict, pass the candidate id first: mla kb review <candidate-id> ${a}`);
|
|
74
|
+
}
|
|
75
|
+
else if (a.startsWith("-")) {
|
|
76
|
+
throw new Error(`Unknown flag: ${a}. ${USAGE}`);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
throw new Error(`mla kb review (list mode) takes no positional args; pass a scope flag. ${USAGE}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const set = (all ? 1 : 0) + (session !== null ? 1 : 0) + (doc !== null ? 1 : 0);
|
|
83
|
+
if (set > 1) {
|
|
84
|
+
throw new Error(`Pass at most one of --all, --session, or --doc. ${USAGE}`);
|
|
85
|
+
}
|
|
86
|
+
let scope;
|
|
87
|
+
if (all)
|
|
88
|
+
scope = { kind: "workspace" };
|
|
89
|
+
else if (session !== null)
|
|
90
|
+
scope = { kind: "session", value: session };
|
|
91
|
+
else if (doc !== null)
|
|
92
|
+
scope = { kind: "doc", doc };
|
|
93
|
+
else
|
|
94
|
+
scope = { kind: "default" };
|
|
95
|
+
return { scope, json };
|
|
96
|
+
}
|
|
97
|
+
// A-0 (A4): the governance action vocabulary. The agent may propose / triage /
|
|
98
|
+
// auto-clear (the LEFT list); ACCEPT and APPLY_CORRECTION are governed changes
|
|
99
|
+
// made under the user's authority and are NOT in the agent's allowed set (a
|
|
100
|
+
// scoped AGENT_PROXY credential is denied them outright, A-1b). Defined once and
|
|
101
|
+
// reused by the --json output (surface 3) here; the static <meetless-context>
|
|
102
|
+
// hook block (surface 2, A-0c) mirrors this exact vocabulary so the agent reads
|
|
103
|
+
// one policy across both channels.
|
|
104
|
+
exports.ALLOWED_AGENT_ACTIONS = [
|
|
105
|
+
"triage",
|
|
106
|
+
"recommend",
|
|
107
|
+
"defer",
|
|
108
|
+
"propose_correction",
|
|
109
|
+
"auto_reject_mechanical_only",
|
|
110
|
+
];
|
|
111
|
+
exports.USER_CONFIRM_ACTIONS = ["accept", "apply_correction"];
|
|
112
|
+
const GOVERNANCE_NOTE = "accept and apply_correction are governed changes made under the user's authority; " +
|
|
113
|
+
"by default propose and let the user confirm. A scoped AGENT_PROXY credential is denied these outright.";
|
|
114
|
+
// Per-candidate verbs an agent may take WITHOUT user confirmation. "triage" is the
|
|
115
|
+
// umbrella activity (top-level only); the concrete per-candidate verbs are these,
|
|
116
|
+
// plus auto_reject_mechanical_only ONLY when the candidate is mechanically invalid.
|
|
117
|
+
function agentActionsFor(mechanical) {
|
|
118
|
+
const allowed = ["recommend", "defer", "propose_correction"];
|
|
119
|
+
if (mechanical.autoRejectable)
|
|
120
|
+
allowed.push("auto_reject_mechanical_only");
|
|
121
|
+
return { allowed, userConfirm: [...exports.USER_CONFIRM_ACTIONS] };
|
|
122
|
+
}
|
|
123
|
+
function buildPendingView(items, ctx) {
|
|
124
|
+
return {
|
|
125
|
+
workspaceId: ctx.workspaceId,
|
|
126
|
+
consoleBase: ctx.consoleBase,
|
|
127
|
+
truncated: ctx.truncated,
|
|
128
|
+
scope: ctx.scope,
|
|
129
|
+
scopeNote: ctx.scopeNote,
|
|
130
|
+
rows: items.map((candidate) => ({
|
|
131
|
+
candidate,
|
|
132
|
+
mechanical: (0, kb_candidate_1.classifyMechanicalInvalidity)(candidate),
|
|
133
|
+
consoleUrl: (0, kb_candidate_1.candidateConsoleUrl)(ctx.consoleBase, candidate.id),
|
|
134
|
+
})),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
function renderPendingJson(view) {
|
|
138
|
+
return JSON.stringify({
|
|
139
|
+
workspaceId: view.workspaceId,
|
|
140
|
+
scope: view.scope,
|
|
141
|
+
count: view.rows.length,
|
|
142
|
+
truncated: view.truncated,
|
|
143
|
+
// A-0 surface 3: a top-level policy summary so a programmatic agent reads
|
|
144
|
+
// the governance rule once instead of inferring it from per-row prose. This
|
|
145
|
+
// mirrors the static hook block's compact form (A-0c).
|
|
146
|
+
governance: {
|
|
147
|
+
pendingCount: view.rows.length,
|
|
148
|
+
allowedAgentActions: [...exports.ALLOWED_AGENT_ACTIONS],
|
|
149
|
+
userConfirmActions: [...exports.USER_CONFIRM_ACTIONS],
|
|
150
|
+
note: GOVERNANCE_NOTE,
|
|
151
|
+
},
|
|
152
|
+
candidates: view.rows.map((r) => ({
|
|
153
|
+
id: r.candidate.id,
|
|
154
|
+
relationType: r.candidate.relationTypeId,
|
|
155
|
+
source: { type: r.candidate.sourceType, artifactId: r.candidate.sourceArtifactId },
|
|
156
|
+
target: { type: r.candidate.targetType, artifactId: r.candidate.targetArtifactId },
|
|
157
|
+
confidence: r.candidate.confidence,
|
|
158
|
+
status: r.candidate.statusId,
|
|
159
|
+
posture: r.candidate.postureId,
|
|
160
|
+
reviewMode: r.candidate.reviewModeId,
|
|
161
|
+
detector: r.candidate.detectorFamily,
|
|
162
|
+
detectorVersion: r.candidate.detectorVersion,
|
|
163
|
+
createdAt: r.candidate.createdAt,
|
|
164
|
+
evidence: {
|
|
165
|
+
sourceQuote: r.candidate.evidenceJson?.sourceQuote ?? null,
|
|
166
|
+
targetQuote: r.candidate.evidenceJson?.targetQuote ?? null,
|
|
167
|
+
reasoning: r.candidate.evidenceJson?.reasoning ?? null,
|
|
168
|
+
},
|
|
169
|
+
autoRejectable: r.mechanical.autoRejectable,
|
|
170
|
+
autoRejectReasonCode: r.mechanical.reasonCode,
|
|
171
|
+
autoRejectReason: r.mechanical.reason,
|
|
172
|
+
// Per-candidate verbs: what THIS agent may do to THIS candidate without a
|
|
173
|
+
// user confirmation (auto_reject_mechanical_only appears only when the row
|
|
174
|
+
// is mechanically invalid), vs the governed verbs that need the user.
|
|
175
|
+
agentActions: agentActionsFor(r.mechanical),
|
|
176
|
+
consoleUrl: r.consoleUrl,
|
|
177
|
+
})),
|
|
178
|
+
}, null, 2);
|
|
179
|
+
}
|
|
180
|
+
function snippet(s, max = 80) {
|
|
181
|
+
if (!s)
|
|
182
|
+
return "";
|
|
183
|
+
const flat = s.replace(/\s+/g, " ").trim();
|
|
184
|
+
return flat.length > max ? `${flat.slice(0, max - 1)}...` : flat;
|
|
185
|
+
}
|
|
186
|
+
function renderPendingHuman(view) {
|
|
187
|
+
if (view.rows.length === 0) {
|
|
188
|
+
const base = `No relationship candidates pending review (workspace ${view.workspaceId}).`;
|
|
189
|
+
return view.scopeNote ? `${view.scopeNote}\n${base}` : base;
|
|
190
|
+
}
|
|
191
|
+
const lines = [];
|
|
192
|
+
if (view.scopeNote) {
|
|
193
|
+
lines.push(view.scopeNote);
|
|
194
|
+
lines.push("");
|
|
195
|
+
}
|
|
196
|
+
const n = view.rows.length;
|
|
197
|
+
lines.push(`${n} relationship candidate${n === 1 ? "" : "s"} need${n === 1 ? "s" : ""} your decision (workspace ${view.workspaceId}):`);
|
|
198
|
+
lines.push("");
|
|
199
|
+
for (const r of view.rows) {
|
|
200
|
+
const c = r.candidate;
|
|
201
|
+
const target = c.targetArtifactId ?? "(unary)";
|
|
202
|
+
lines.push(` [${c.relationTypeId}] ${c.sourceArtifactId} -> ${target} conf ${c.confidence.toFixed(2)} ${c.detectorFamily}`);
|
|
203
|
+
const src = snippet(c.evidenceJson?.sourceQuote);
|
|
204
|
+
const tgt = snippet(c.evidenceJson?.targetQuote);
|
|
205
|
+
if (src || tgt)
|
|
206
|
+
lines.push(` "${src}" vs "${tgt}"`);
|
|
207
|
+
lines.push(` id ${c.id}`);
|
|
208
|
+
lines.push(` review: ${r.consoleUrl}`);
|
|
209
|
+
if (r.mechanical.autoRejectable) {
|
|
210
|
+
lines.push(` -> auto-rejectable (${r.mechanical.reasonCode}); agent may run: mla kb review ${c.id} --reject --agent`);
|
|
211
|
+
}
|
|
212
|
+
lines.push("");
|
|
213
|
+
}
|
|
214
|
+
if (view.truncated) {
|
|
215
|
+
lines.push("The workspace queue exceeded the fetch cap; some candidates are not shown. Narrow with --doc <id>.");
|
|
216
|
+
lines.push("");
|
|
217
|
+
}
|
|
218
|
+
// A-0 (A4 surface 1): the CLI caller is UNKNOWN (a human and a coding agent run
|
|
219
|
+
// the identical command), so this block dual-addresses both in one message
|
|
220
|
+
// rather than guessing. Only rendered when the queue is non-empty (the count==0
|
|
221
|
+
// path returns the plain "no candidates" line above). The wording is the one
|
|
222
|
+
// agreed with An (plan §A4): the agent should offer to triage, but accepting an
|
|
223
|
+
// edge or applying a correction is a governed change under the user's authority,
|
|
224
|
+
// so the UX default is propose-first (NOT a hard server block; the server gate
|
|
225
|
+
// restricts only AGENT_PROXY, A-1).
|
|
226
|
+
lines.push("These candidates are pending review in this workspace.");
|
|
227
|
+
lines.push("- If you are the user: you can ask your coding agent to triage these for you.");
|
|
228
|
+
lines.push("- If you are the agent: you may triage them now, and you should offer to. Read");
|
|
229
|
+
lines.push(" both documents, recommend a verdict, auto-clear ONLY mechanically-invalid ones,");
|
|
230
|
+
lines.push(" and propose the correct type when one is mis-classified.");
|
|
231
|
+
lines.push("Accepting an edge or applying a correction is a governed change made under the");
|
|
232
|
+
lines.push("user's authority; by default propose it and let the user confirm.");
|
|
233
|
+
lines.push("");
|
|
234
|
+
lines.push(`Triage: mla kb review <id> --accept | --reject [--note "..."]`);
|
|
235
|
+
lines.push(`Auto-clear (mechanically-invalid only): mla kb review <id> --reject --agent`);
|
|
236
|
+
return lines.join("\n");
|
|
237
|
+
}
|
|
238
|
+
const PAGE_LIMIT = 200; // route hard cap (@Max(200))
|
|
239
|
+
const MAX_PAGES = 25; // backstop: 5000 candidates before we stop and flag truncation
|
|
240
|
+
// Follow the route's cursor to completion so the workspace count and the session
|
|
241
|
+
// filter both operate on the full set, not a capped first page. Stops at MAX_PAGES
|
|
242
|
+
// (truncated=true) as a runaway backstop. The cursor comes from our own control
|
|
243
|
+
// route, so a missing id/createdAt or an unparseable date is a server bug; fail
|
|
244
|
+
// LOUD rather than build a corrupt cursor and silently mis-paginate.
|
|
245
|
+
async function fetchAllPending(fetchPending, workspaceId, doc) {
|
|
246
|
+
const items = [];
|
|
247
|
+
let cursor = null;
|
|
248
|
+
for (let p = 0; p < MAX_PAGES; p++) {
|
|
249
|
+
const res = await fetchPending((0, relationship_candidate_query_1.buildPendingCandidateQuery)(workspaceId, doc, PAGE_LIMIT, cursor));
|
|
250
|
+
if (Array.isArray(res.items))
|
|
251
|
+
items.push(...res.items);
|
|
252
|
+
const nc = res.nextCursor;
|
|
253
|
+
if (!nc)
|
|
254
|
+
return { items, truncated: false };
|
|
255
|
+
if (typeof nc !== "object" || typeof nc.id !== "string" || nc.id === "") {
|
|
256
|
+
throw new Error("Malformed relationship-candidates cursor from control (missing id)");
|
|
257
|
+
}
|
|
258
|
+
const rawDate = nc.createdAt;
|
|
259
|
+
const createdAt = typeof rawDate === "string"
|
|
260
|
+
? rawDate
|
|
261
|
+
: rawDate instanceof Date
|
|
262
|
+
? rawDate.toISOString()
|
|
263
|
+
: null;
|
|
264
|
+
if (createdAt === null || Number.isNaN(Date.parse(createdAt))) {
|
|
265
|
+
throw new Error("Malformed relationship-candidates cursor from control (bad createdAt)");
|
|
266
|
+
}
|
|
267
|
+
cursor = { id: nc.id, createdAt };
|
|
268
|
+
}
|
|
269
|
+
return { items, truncated: true };
|
|
270
|
+
}
|
|
271
|
+
async function runKbPendingWith(argv,
|
|
272
|
+
// A-0c (A4 surface 2): onWorkspaceCount reports the WORKSPACE-WIDE pending count
|
|
273
|
+
// so the production entrypoint can drop it in the local cache the prompt-submit
|
|
274
|
+
// hook reads (Patch 8: no new hot-path network call; this count is free, we
|
|
275
|
+
// already fetched the queue). Reported ONLY for a non-doc scope (a `--doc` view is
|
|
276
|
+
// a subset and must never overwrite the cache) and ONLY when not truncated.
|
|
277
|
+
// Injected so unit tests can spy without touching the real home.
|
|
278
|
+
ctx, deps) {
|
|
279
|
+
let parsed;
|
|
280
|
+
try {
|
|
281
|
+
parsed = parseKbPendingArgs(argv);
|
|
282
|
+
}
|
|
283
|
+
catch (e) {
|
|
284
|
+
console.error(e.message);
|
|
285
|
+
return 2;
|
|
286
|
+
}
|
|
287
|
+
const env = ctx.env ?? process.env;
|
|
288
|
+
// Resolve "default": a current session if one is available, else the full queue.
|
|
289
|
+
let effective = parsed.scope;
|
|
290
|
+
if (effective.kind === "default") {
|
|
291
|
+
const sid = (env.CLAUDE_CODE_SESSION_ID || "").trim();
|
|
292
|
+
effective = sid ? { kind: "session", value: "current" } : { kind: "workspace" };
|
|
293
|
+
}
|
|
294
|
+
// Resolve a session scope BEFORE the network call so a bad session fails fast.
|
|
295
|
+
let scope = null;
|
|
296
|
+
if (effective.kind === "session") {
|
|
297
|
+
if (!deps.loadSessionScope) {
|
|
298
|
+
console.error("--session is not supported in this context.");
|
|
299
|
+
return 2;
|
|
300
|
+
}
|
|
301
|
+
try {
|
|
302
|
+
scope = deps.loadSessionScope(effective.value, {
|
|
303
|
+
env,
|
|
304
|
+
workspaceId: ctx.workspaceId,
|
|
305
|
+
nowMs: Date.now(),
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
catch (e) {
|
|
309
|
+
console.error(e.message);
|
|
310
|
+
return 2;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
const docArg = effective.kind === "doc" ? effective.doc : null;
|
|
314
|
+
let all;
|
|
315
|
+
try {
|
|
316
|
+
all = await fetchAllPending(deps.fetchPending, ctx.workspaceId, docArg);
|
|
317
|
+
}
|
|
318
|
+
catch (e) {
|
|
319
|
+
console.error(`Failed to list pending candidates: ${e.message}`);
|
|
320
|
+
return 1;
|
|
321
|
+
}
|
|
322
|
+
// Cache the workspace count from the COMPLETE set (every non-doc scope) so the
|
|
323
|
+
// prompt-submit governance nudge reflects the true total, not a session subset.
|
|
324
|
+
// SKIP the write when truncated: all.items.length is then a floor ("fetched up to
|
|
325
|
+
// the cap"), not the exact count, and writing it as exact would lie. The cache
|
|
326
|
+
// value does not carry truncation metadata today, so leaving the prior value
|
|
327
|
+
// (possibly stale) beats overwriting it with a wrong-but-confident number. A
|
|
328
|
+
// 5000-candidate queue never happens at pilot scale; this is a backstop. If
|
|
329
|
+
// truncation ever becomes real, extend the cache to carry a `+`/truncated flag.
|
|
330
|
+
if (effective.kind !== "doc" && !all.truncated)
|
|
331
|
+
ctx.onWorkspaceCount?.(all.items.length);
|
|
332
|
+
let items = all.items;
|
|
333
|
+
let scopeNote = null;
|
|
334
|
+
let scopeMeta;
|
|
335
|
+
if (scope) {
|
|
336
|
+
const fetched = items.length;
|
|
337
|
+
items = items.filter((c) => (0, session_scope_1.candidateInSession)(c, scope.keys));
|
|
338
|
+
const label = scope.source === "current-env"
|
|
339
|
+
? `your current session (${scope.sessionId})`
|
|
340
|
+
: `session ${scope.sessionId} (${scope.source})`;
|
|
341
|
+
scopeNote =
|
|
342
|
+
`Scoped to ${label}: ${items.length} of ${fetched} fetched candidate${fetched === 1 ? "" : "s"} ` +
|
|
343
|
+
`touch ${scope.keys.size} doc${scope.keys.size === 1 ? "" : "s"} this session produced. Use --all for the full workspace queue.`;
|
|
344
|
+
if (scope.keys.size === 0) {
|
|
345
|
+
scopeNote += " This session produced no indexed docs yet, so nothing is attributed to it.";
|
|
346
|
+
}
|
|
347
|
+
if (all.truncated) {
|
|
348
|
+
scopeNote += " WARNING: the workspace queue exceeded the fetch cap, so this session view may be incomplete.";
|
|
349
|
+
}
|
|
350
|
+
scopeMeta = {
|
|
351
|
+
kind: "session",
|
|
352
|
+
sessionId: scope.sessionId,
|
|
353
|
+
source: scope.source,
|
|
354
|
+
sessionDocCount: scope.keys.size,
|
|
355
|
+
fetchedCount: fetched,
|
|
356
|
+
displayedCount: items.length,
|
|
357
|
+
truncated: all.truncated,
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
else if (effective.kind === "doc") {
|
|
361
|
+
scopeMeta = {
|
|
362
|
+
kind: "doc",
|
|
363
|
+
doc: effective.doc,
|
|
364
|
+
fetchedCount: items.length,
|
|
365
|
+
displayedCount: items.length,
|
|
366
|
+
truncated: all.truncated,
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
else {
|
|
370
|
+
scopeMeta = {
|
|
371
|
+
kind: "workspace",
|
|
372
|
+
fetchedCount: items.length,
|
|
373
|
+
displayedCount: items.length,
|
|
374
|
+
truncated: all.truncated,
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
const view = buildPendingView(items, {
|
|
378
|
+
workspaceId: ctx.workspaceId,
|
|
379
|
+
consoleBase: ctx.consoleBase,
|
|
380
|
+
truncated: all.truncated,
|
|
381
|
+
scope: scopeMeta,
|
|
382
|
+
scopeNote,
|
|
383
|
+
});
|
|
384
|
+
console.log(parsed.json ? renderPendingJson(view) : renderPendingHuman(view));
|
|
385
|
+
return 0;
|
|
386
|
+
}
|
|
387
|
+
async function runKbPending(argv) {
|
|
388
|
+
let cfg;
|
|
389
|
+
try {
|
|
390
|
+
cfg = (0, config_1.loadWorkspaceConfig)();
|
|
391
|
+
}
|
|
392
|
+
catch (e) {
|
|
393
|
+
console.error(e.message);
|
|
394
|
+
return 2;
|
|
395
|
+
}
|
|
396
|
+
const consoleBase = (0, config_1.getConsoleUrl)(cfg);
|
|
397
|
+
const deps = {
|
|
398
|
+
fetchPending: (qs) => (0, http_1.get)(cfg, `/internal/v1/relationship-candidates?${qs}`, 12000),
|
|
399
|
+
loadSessionScope: (value, opts) => (0, session_scope_1.loadSessionScope)(value, opts),
|
|
400
|
+
};
|
|
401
|
+
return runKbPendingWith(argv, {
|
|
402
|
+
workspaceId: cfg.workspaceId,
|
|
403
|
+
consoleBase,
|
|
404
|
+
// A-0c: persist the workspace-wide count for the prompt-submit hook (surface 2).
|
|
405
|
+
onWorkspaceCount: (count) => (0, governance_cache_1.writePendingCountCache)(cfg.workspaceId, count),
|
|
406
|
+
}, deps);
|
|
407
|
+
}
|
|
408
|
+
// kb.ts dispatches the overloaded `review` verb (list mode) and the deprecated
|
|
409
|
+
// `pending` alias through this same listing runner.
|
|
410
|
+
exports.runKbReviewList = runKbPending;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// `mla kb personal list` / `mla kb personal show <id>` (Phase 3, Task 3.3).
|
|
3
|
+
//
|
|
4
|
+
// The owner-scoped view of a user's OWN Personal-KB documents.
|
|
5
|
+
//
|
|
6
|
+
// list -> GET /internal/v1/kb/documents?ownerUserId=<actor>&posture=SHADOW
|
|
7
|
+
// Lists ONLY the configured actor's ACTIVE personal docs. The
|
|
8
|
+
// owner is the configured actorUserId (readKbConfig), so a user
|
|
9
|
+
// can never list another owner's docs from their own CLI.
|
|
10
|
+
// show <id> -> reuse `mla kb show` (the existing single-doc detail path).
|
|
11
|
+
// Personal docs render through the same §4.2 detail view; there
|
|
12
|
+
// is no separate personal detail endpoint to maintain.
|
|
13
|
+
//
|
|
14
|
+
// Mirrors the kb_pending.ts deps-injection shape: a pure `runKbPersonalWith`
|
|
15
|
+
// core takes an injected fetcher (and a show-delegate) so the unit test can
|
|
16
|
+
// drive it without touching the network or the real config, while the public
|
|
17
|
+
// `runKbPersonal` wrapper wires the real intelGet fetcher and the real
|
|
18
|
+
// `runKbShow` delegate.
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.normalizePersonalShowId = normalizePersonalShowId;
|
|
21
|
+
exports.parseKbPersonalArgs = parseKbPersonalArgs;
|
|
22
|
+
exports.buildPersonalQuery = buildPersonalQuery;
|
|
23
|
+
exports.runKbPersonalWith = runKbPersonalWith;
|
|
24
|
+
exports.runKbPersonal = runKbPersonal;
|
|
25
|
+
const config_1 = require("../lib/config");
|
|
26
|
+
const http_1 = require("../lib/http");
|
|
27
|
+
const kb_show_1 = require("./kb_show");
|
|
28
|
+
const USAGE = "Usage: mla kb personal list [--json] | mla kb personal show <id>";
|
|
29
|
+
// The id `mla kb personal list` prints is a bare KbDocument cuid. `mla kb show`
|
|
30
|
+
// (the show delegate) only skips path-resolution when its input parses as
|
|
31
|
+
// `kbdoc:<id>`; a bare token is classified as a canonical PATH and 404s. So
|
|
32
|
+
// normalize a bare id into the kbdoc: form before delegating, while passing an
|
|
33
|
+
// already-prefixed artifact input (kbdoc:/kbdocrev:/note:) through untouched so
|
|
34
|
+
// a power user who types the canonical form is never double-prefixed.
|
|
35
|
+
const ARTIFACT_PREFIXES = ["kbdoc:", "kbdocrev:", "note:"];
|
|
36
|
+
function normalizePersonalShowId(raw) {
|
|
37
|
+
const t = raw.trim();
|
|
38
|
+
if (ARTIFACT_PREFIXES.some((p) => t.startsWith(p)))
|
|
39
|
+
return t;
|
|
40
|
+
return `kbdoc:${t}`;
|
|
41
|
+
}
|
|
42
|
+
function parseKbPersonalArgs(argv) {
|
|
43
|
+
const sub = argv[0];
|
|
44
|
+
if (sub !== "list" && sub !== "show") {
|
|
45
|
+
throw new Error(`mla kb personal takes 'list' or 'show'. ${USAGE}`);
|
|
46
|
+
}
|
|
47
|
+
let id = null;
|
|
48
|
+
let json = false;
|
|
49
|
+
for (let i = 1; i < argv.length; i++) {
|
|
50
|
+
const a = argv[i];
|
|
51
|
+
if (a === "--json") {
|
|
52
|
+
json = true;
|
|
53
|
+
}
|
|
54
|
+
else if (a.startsWith("-")) {
|
|
55
|
+
throw new Error(`Unknown flag: ${a}. ${USAGE}`);
|
|
56
|
+
}
|
|
57
|
+
else if (sub === "show" && id === null) {
|
|
58
|
+
id = a;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
throw new Error(`Unexpected argument: ${a}. ${USAGE}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (sub === "show" && id === null) {
|
|
65
|
+
throw new Error(`mla kb personal show requires a document id. ${USAGE}`);
|
|
66
|
+
}
|
|
67
|
+
return { sub, id, json };
|
|
68
|
+
}
|
|
69
|
+
// Build the owner-scoped list query. Always pins posture=SHADOW: personal docs
|
|
70
|
+
// are SHADOW-posture by construction (agent-distilled / private), so the list is
|
|
71
|
+
// the user's private shadow corpus, never the shared LIVE workspace docs. The
|
|
72
|
+
// owner is stamped explicitly so the server filters to this caller's rows only.
|
|
73
|
+
function buildPersonalQuery(workspaceId, ownerUserId) {
|
|
74
|
+
const qs = new URLSearchParams();
|
|
75
|
+
qs.set("workspaceId", workspaceId);
|
|
76
|
+
qs.set("ownerUserId", ownerUserId);
|
|
77
|
+
qs.set("posture", "SHADOW");
|
|
78
|
+
return qs.toString();
|
|
79
|
+
}
|
|
80
|
+
function renderPersonalHuman(ws, owner, docs) {
|
|
81
|
+
if (docs.length === 0) {
|
|
82
|
+
return `No personal KB documents for ${owner} (workspace ${ws}).`;
|
|
83
|
+
}
|
|
84
|
+
const lines = [];
|
|
85
|
+
const n = docs.length;
|
|
86
|
+
lines.push(`${n} personal KB document${n === 1 ? "" : "s"} for ${owner} (workspace ${ws}):`);
|
|
87
|
+
lines.push("");
|
|
88
|
+
for (const d of docs) {
|
|
89
|
+
const path = d.canonicalPath ?? "(no path)";
|
|
90
|
+
const posture = d.currentPosture ?? "?";
|
|
91
|
+
lines.push(` ${d.id}`);
|
|
92
|
+
lines.push(` ${path} [${posture}] updated ${d.updatedAt}`);
|
|
93
|
+
}
|
|
94
|
+
lines.push("");
|
|
95
|
+
lines.push("Inspect one: mla kb personal show <id>");
|
|
96
|
+
return lines.join("\n");
|
|
97
|
+
}
|
|
98
|
+
async function runKbPersonalWith(argv, ctx, deps) {
|
|
99
|
+
let parsed;
|
|
100
|
+
try {
|
|
101
|
+
parsed = parseKbPersonalArgs(argv);
|
|
102
|
+
}
|
|
103
|
+
catch (e) {
|
|
104
|
+
console.error(e.message);
|
|
105
|
+
return { documents: [], code: 2 };
|
|
106
|
+
}
|
|
107
|
+
if (parsed.sub === "show") {
|
|
108
|
+
// Reuse the existing single-doc detail path. The id is normalized to the
|
|
109
|
+
// kbdoc: form first (see normalizePersonalShowId): the bare cuid `list`
|
|
110
|
+
// prints would otherwise be treated as a path by `mla kb show` and 404.
|
|
111
|
+
const code = await deps.showDocument(normalizePersonalShowId(parsed.id));
|
|
112
|
+
return { documents: [], code };
|
|
113
|
+
}
|
|
114
|
+
// list
|
|
115
|
+
let resp;
|
|
116
|
+
try {
|
|
117
|
+
resp = await deps.fetchPersonal(buildPersonalQuery(ctx.workspaceId, ctx.ownerUserId));
|
|
118
|
+
}
|
|
119
|
+
catch (e) {
|
|
120
|
+
console.error(`Failed to list personal KB documents: ${e.message}`);
|
|
121
|
+
return { documents: [], code: 1 };
|
|
122
|
+
}
|
|
123
|
+
const documents = resp.documents ?? [];
|
|
124
|
+
if (parsed.json) {
|
|
125
|
+
console.log(JSON.stringify({ workspaceId: ctx.workspaceId, ownerUserId: ctx.ownerUserId, documents }, null, 2));
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
console.log(renderPersonalHuman(ctx.workspaceId, ctx.ownerUserId, documents));
|
|
129
|
+
}
|
|
130
|
+
return { documents, code: 0 };
|
|
131
|
+
}
|
|
132
|
+
async function runKbPersonal(argv) {
|
|
133
|
+
let cfg;
|
|
134
|
+
try {
|
|
135
|
+
cfg = (0, config_1.readKbConfig)();
|
|
136
|
+
}
|
|
137
|
+
catch (e) {
|
|
138
|
+
console.error(e.message);
|
|
139
|
+
return 2;
|
|
140
|
+
}
|
|
141
|
+
const deps = {
|
|
142
|
+
fetchPersonal: (qs) => (0, http_1.intelGet)(cfg, `/internal/v1/kb/documents?${qs}`, 12000),
|
|
143
|
+
// Delegate to `mla kb show <id>`: the single-doc detail path is reused
|
|
144
|
+
// wholesale, including its resolve/poll handling and exit codes.
|
|
145
|
+
showDocument: (id) => (0, kb_show_1.runKbShow)([id]),
|
|
146
|
+
};
|
|
147
|
+
const result = await runKbPersonalWith(argv, { workspaceId: cfg.workspaceId, ownerUserId: cfg.actorUserId }, deps);
|
|
148
|
+
return result.code;
|
|
149
|
+
}
|