loopctl-mcp-server 2.68.0 → 2.69.0
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/README.md +1 -0
- package/index.js +74 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -304,6 +304,7 @@ it is enforced server-side and a no-op for a non-superadmin key — see below.)
|
|
|
304
304
|
| `knowledge_bulk_delete` | **Requires `LOOPCTL_USER_KEY`.** Bulk archive (default, reversible) or IRREVERSIBLE hard-delete by selector. Provide exactly one selector: `article_ids` (list), `source_type`+`source_id` (every active article from a source), or `tag`+`confirm:true` (every active article with the tag — high blast radius). Default = set-based soft archive (idempotent; `meta.count`=archived, `meta.counts`/`meta.results` give the breakdown; ≤5000). **Dry-run** (`dry_run:true`) mutates nothing, returns `meta.would_affect` (with `hard:true` also a single-use `meta.token`, or `meta.confirm_hash` for oversized selectors). **Hard delete** (irreversible): dry-run with `hard:true` for a token, then call again with `hard:true`+`token` to FK-correctly delete the frozen id-set (links first, access events cascade). |
|
|
305
305
|
| `knowledge_drafts` | List draft (unpublished) knowledge articles with pagination. Optional: `limit` (default 20, max 1000 — over-max → 400, no silent clamp), `offset` (default 0), `project_id`. Returns `meta.total_count`. |
|
|
306
306
|
| `knowledge_lint` | Run a lint check on the knowledge wiki to identify stale or low-coverage articles. Optional: `project_id`, `stale_days`, `min_coverage`, `max_per_category` (default 50, max 500). True totals returned in `summary.total_per_category`. |
|
|
307
|
+
| `knowledge_consolidation` | Read the nightly consolidation ("dream") report: NUMBERED proposals for reconciling the corpus, each naming the articles involved and quoting an excerpt from each as evidence. **REPORT ONLY** — the pass writes no articles, links or conflict resolutions; every proposal is `pending` and this tool applies nothing. Classes: `duplicate_capture` (titles that collide once case/punctuation normalize away, or idempotency keys that collide under the same normalization while differing verbatim — capture tag-format drift, which the novelty gate does not catch because novelty scoring and idempotency are separate paths), `contradiction_candidate` (a SYSTEM-flagged `potential_conflict` pair of PUBLISHED articles with no recorded verdict — record one with `knowledge_resolve_conflict`, which accepts exactly these pairs), `generic_title` (a placeholder title that collides on active-title uniqueness and blocks hub creation), `stale_entry`. **Denominators:** `corpus_size` = PUBLISHED articles owned by the tenant at scan time, not its total article count; `proposal_count` = the TRUE pre-cap count of PROPOSALS, not of articles (one duplicate group of three articles is ONE proposal, and one article can appear in proposals of several classes); `persisted_count` = proposal ROWS the report carries, lower than `proposal_count` exactly when a class hit `max_per_class` (`truncated` flags which); `meta.total_count` counts persisted proposals matching the `class` filter, so it is bounded by `persisted_count`, never `proposal_count`. `review_status`/`reviewed_by`/`reviewed_at` reset to pending/null whenever the nightly pass re-derives a proposal — refreshed machine output never inherits an approval. Requires orchestrator role. Optional: `day` (ISO8601, default most recent report), `class`, `limit` (default 50, max 500), `offset`. |
|
|
307
308
|
| `knowledge_export` | Export all knowledge articles as an OKF v0.1 bundle (gzipped tar archive, unbounded, bounded-memory streaming, fail-closed). Returns a curl command for direct download — **the download requires `LOOPCTL_USER_KEY`** (an orchestrator key would 403). Pass `format=json` for buffered in-memory JSON (convenience tool for file writers; capped at `export_max_buffered_export_articles` — returns 413 if over-cap). Optional: `project_id`, `format` (`tar.gz` default or `json`). |
|
|
308
309
|
| `knowledge_ingest` | Submit a URL or raw content for knowledge extraction. Enqueues an Oban job. Extracted articles are **drafts by default** (lower-trust LLM output); pass `publish: true` to publish on extraction. **BYO:** runs on the tenant's own Anthropic key — a keyless tenant gets a 422 whose result leads with an `ACTION REQUIRED` notice pointing at `set_llm_config` (see [First-time setup](#first-time-setup--provision-your-byo-llm-keys)). Required: `source_type`. One of: `url` or `content`. Optional: `project_id`, `publish`. |
|
|
309
310
|
| `knowledge_ingest_batch` | Submit up to 50 ingestion items in a single request. Each item has the same shape as `knowledge_ingest` (incl. `publish`). Returns per-item results. Required: `items`. Optional: batch-level `project_id` / `publish` defaults. |
|
package/index.js
CHANGED
|
@@ -1927,6 +1927,20 @@ async function knowledgeLint({ project_id, stale_days, min_coverage, max_per_cat
|
|
|
1927
1927
|
return toContent(result);
|
|
1928
1928
|
}
|
|
1929
1929
|
|
|
1930
|
+
async function knowledgeConsolidation({ day, class: klass, limit, offset } = {}) {
|
|
1931
|
+
const params = new URLSearchParams();
|
|
1932
|
+
if (day) params.set("day", String(day));
|
|
1933
|
+
if (klass) params.set("class", String(klass));
|
|
1934
|
+
if (limit != null) params.set("limit", String(limit));
|
|
1935
|
+
if (offset != null) params.set("offset", String(offset));
|
|
1936
|
+
const qs = params.toString();
|
|
1937
|
+
const path = qs
|
|
1938
|
+
? `/api/v1/knowledge/consolidation?${qs}`
|
|
1939
|
+
: "/api/v1/knowledge/consolidation";
|
|
1940
|
+
const result = await apiCall("GET", path, null, process.env.LOOPCTL_ORCH_KEY);
|
|
1941
|
+
return toContent(result);
|
|
1942
|
+
}
|
|
1943
|
+
|
|
1930
1944
|
async function knowledgeIngest({ url, content, source_type, project_id, publish }) {
|
|
1931
1945
|
const body = { source_type };
|
|
1932
1946
|
if (url) body.url = url;
|
|
@@ -5447,6 +5461,63 @@ const TOOLS = [
|
|
|
5447
5461
|
required: [],
|
|
5448
5462
|
},
|
|
5449
5463
|
},
|
|
5464
|
+
{
|
|
5465
|
+
name: "knowledge_consolidation",
|
|
5466
|
+
description:
|
|
5467
|
+
"Read the nightly consolidation (\"dream\") report: NUMBERED proposals for reconciling " +
|
|
5468
|
+
"the corpus, each naming the articles involved and quoting an excerpt from each as " +
|
|
5469
|
+
"evidence. REPORT ONLY — the pass writes no articles, links or conflict resolutions, " +
|
|
5470
|
+
"every proposal is `pending`, and this tool applies nothing. Requires orchestrator role.\n\n" +
|
|
5471
|
+
"Classes: `duplicate_capture` (titles that collide once case/punctuation normalize away, " +
|
|
5472
|
+
"or idempotency keys that collide under the same normalization while differing verbatim — " +
|
|
5473
|
+
"capture tag-format drift, which the novelty gate does not catch because novelty scoring " +
|
|
5474
|
+
"and idempotency are separate paths); `contradiction_candidate` (a SYSTEM-flagged " +
|
|
5475
|
+
"potential_conflict pair of PUBLISHED articles with no recorded verdict — record one via " +
|
|
5476
|
+
"knowledge_resolve_conflict, which accepts exactly these pairs; this report writes " +
|
|
5477
|
+
"none); `generic_title` (a placeholder title that collides on active-title uniqueness and " +
|
|
5478
|
+
"blocks hub creation); `stale_entry` (past the lint staleness threshold, never reconciled).\n\n" +
|
|
5479
|
+
"Denominators: `corpus_size` counts PUBLISHED articles owned by the tenant at scan time, " +
|
|
5480
|
+
"not its total article count. `proposal_count` is the TRUE pre-cap count of PROPOSALS, not " +
|
|
5481
|
+
"of articles — one duplicate group of three articles is ONE proposal, and one article can " +
|
|
5482
|
+
"appear in proposals of several classes. `persisted_count` is how many proposal ROWS the " +
|
|
5483
|
+
"report carries, lower than `proposal_count` exactly when a class hit `max_per_class` " +
|
|
5484
|
+
"(`truncated` flags which). `meta.total_count` counts persisted proposals matching the " +
|
|
5485
|
+
"`class` filter, so it is bounded by `persisted_count`, never by `proposal_count`.\n\n" +
|
|
5486
|
+
"Review state (`review_status`/`reviewed_by`/`reviewed_at`) RESETS to pending/null whenever " +
|
|
5487
|
+
"the nightly pass re-derives a proposal: refreshed machine output never inherits an approval.",
|
|
5488
|
+
inputSchema: {
|
|
5489
|
+
type: "object",
|
|
5490
|
+
properties: {
|
|
5491
|
+
day: {
|
|
5492
|
+
type: "string",
|
|
5493
|
+
description:
|
|
5494
|
+
"Optional ISO8601 date (YYYY-MM-DD, UTC) of the report to read. Defaults to the most recent report.",
|
|
5495
|
+
},
|
|
5496
|
+
class: {
|
|
5497
|
+
type: "string",
|
|
5498
|
+
enum: [
|
|
5499
|
+
"duplicate_capture",
|
|
5500
|
+
"contradiction_candidate",
|
|
5501
|
+
"generic_title",
|
|
5502
|
+
"stale_entry",
|
|
5503
|
+
],
|
|
5504
|
+
description: "Optional: return only proposals of this class.",
|
|
5505
|
+
},
|
|
5506
|
+
limit: {
|
|
5507
|
+
type: "integer",
|
|
5508
|
+
description: "Proposals per page. Default 50, max 500 (clamped, never rejected).",
|
|
5509
|
+
default: 50,
|
|
5510
|
+
minimum: 1,
|
|
5511
|
+
},
|
|
5512
|
+
offset: {
|
|
5513
|
+
type: "integer",
|
|
5514
|
+
description: "Proposals to skip. Default 0.",
|
|
5515
|
+
minimum: 0,
|
|
5516
|
+
},
|
|
5517
|
+
},
|
|
5518
|
+
required: [],
|
|
5519
|
+
},
|
|
5520
|
+
},
|
|
5450
5521
|
{
|
|
5451
5522
|
name: "knowledge_export",
|
|
5452
5523
|
description:
|
|
@@ -6899,6 +6970,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
6899
6970
|
case "knowledge_lint":
|
|
6900
6971
|
return await knowledgeLint(args);
|
|
6901
6972
|
|
|
6973
|
+
case "knowledge_consolidation":
|
|
6974
|
+
return await knowledgeConsolidation(args);
|
|
6975
|
+
|
|
6902
6976
|
case "knowledge_export":
|
|
6903
6977
|
return await knowledgeExport(args);
|
|
6904
6978
|
|