loopctl-mcp-server 2.68.0 → 2.70.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 +3 -2
- package/index.js +87 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -266,8 +266,8 @@ Epic 39 Repo Coordination Bus — a lightweight, tenant-isolated channel for age
|
|
|
266
266
|
| `knowledge_random_walk` | Random walk through the link graph from `start_id` (no cycles, up to `length` nodes), traversing only **agent's visible published articles**, surfacing unexpected connections. Agent callers see only their own and `shared` articles. Returns `{id, title, category}` in walk order. Required: `start_id`. Optional: `length` (default 4, max 25). |
|
|
267
267
|
| `knowledge_conflicts` | List potential-conflict article pairs — published articles flagged "too similar to comfortably coexist" by the auto-linker / nightly lint sweep, highest-overlap first. The KB only FLAGS the pair; it does NOT decide redundancy-vs-contradiction — that's your call with live context. Each entry has both articles (id/title/status/category) + similarity. Then merge (supersede one, `knowledge_create` the merged article, or PATCH) or reconcile if they genuinely disagree. Paginated with `total_count` in meta. Agent role. Optional: `limit` (default 50, max 1000, clamped), `offset`. |
|
|
268
268
|
| `knowledge_resolve_conflict` | Record YOUR verdict on a potential-conflict pair (from `knowledge_conflicts`). Dispositions: `dismiss` (false positive, drops from queue), `supersede` (one wins — pass `authoritative_article_id`; nightly executor links + retires loser, only at `confidence:"high"`, reversible/audited), `merge` (at high confidence an LLM synthesizes both into ONE new DRAFT, sources preserved, never auto-published). Non-destructive at agent role — you record intent, the privileged nightly job executes. Last-write-wins per pair. Required: `source_article_id`, `target_article_id`, `disposition`. Optional: `authoritative_article_id`, `classification`, `evidence`, `confidence`. |
|
|
269
|
-
| `knowledge_create` | Create a new knowledge article. File findings, document patterns, or record decisions. **Published immediately by default** (visible per `metadata.visibility` — default `owner` for agent authors, only visible to that agent; `shared` for visibility to all agents) — the response `note` says which outcome occurred. Pass `draft: true` to stage it for later review instead (publish afterwards with `knowledge_publish`). Pass `metadata: {visibility: "shared"}` to make the article visible to other agents; higher roles can set visibility and agent_id explicitly. Pass `idempotency_key` for idempotent capture (re-creating with the same key is a no-op returning the existing article — no partial duplicates). Optional: `category`, `tags`, `project_id`, `draft`, `idempotency_key`, `source_type`, `source_id`, `metadata`. |
|
|
270
|
-
| `knowledge_update` | Edit an EXISTING article IN PLACE, **preserving its ID** (IDs are load-bearing — cited in project CLAUDE.mds and cross-links). Fold in a new fact, tidy a hub, retag, or reclassify without churning a new row. Send only the fields to change; `tags` REPLACES the whole array. A changed body/tags re-triggers embedding + auto-linking. Agent role — KB-content curation (reversible + audited); visibility-scoped, so another agent's private/owner memory 404s. Required: `article_id`. Optional: `title`, `body`, `category`, `tags`, `metadata`. |
|
|
269
|
+
| `knowledge_create` | Create a new knowledge article. File findings, document patterns, or record decisions. **Published immediately by default** (visible per `metadata.visibility` — default `owner` for agent authors, only visible to that agent; `shared` for visibility to all agents) — the response `note` says which outcome occurred. Pass `draft: true` to stage it for later review instead (publish afterwards with `knowledge_publish`). Pass `metadata: {visibility: "shared"}` to make the article visible to other agents; higher roles can set visibility and agent_id explicitly. Pass `idempotency_key` for idempotent capture (re-creating with the same key is a no-op returning the existing article — no partial duplicates). RESERVED TAG NAMESPACE: a tag starting with `idem-` must be `idem-<family>-<digest>` (digest = 12 or 40 lowercase hex chars, e.g. `idem-url-7ebe1ca33431`) or the write is rejected 422 — never silently rewritten; put topics outside that prefix and use `idempotency_key` for idempotent capture. Optional: `category`, `tags`, `project_id`, `draft`, `idempotency_key`, `source_type`, `source_id`, `metadata`. |
|
|
270
|
+
| `knowledge_update` | Edit an EXISTING article IN PLACE, **preserving its ID** (IDs are load-bearing — cited in project CLAUDE.mds and cross-links). Fold in a new fact, tidy a hub, retag, or reclassify without churning a new row. Send only the fields to change; `tags` REPLACES the whole array. A changed body/tags re-triggers embedding + auto-linking. Agent role — KB-content curation (reversible + audited); visibility-scoped, so another agent's private/owner memory 404s. The reserved `idem-` tag namespace applies here too (see `knowledge_create`). Required: `article_id`. Optional: `title`, `body`, `category`, `tags`, `metadata`. |
|
|
271
271
|
| `knowledge_okf_export` | **Requires `LOOPCTL_USER_KEY`.** Export the wiki as a portable OKF (Open Knowledge Format) v0.1 bundle of markdown files. Writes to `out_dir`, or returns `{files, meta}` inline. |
|
|
272
272
|
| `knowledge_okf_import` | **Requires `LOOPCTL_USER_KEY`.** Import an OKF v0.1 bundle from a local directory. Creates or (with `merge`) updates articles; tolerates and preserves unknown frontmatter. |
|
|
273
273
|
|
|
@@ -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;
|
|
@@ -4720,7 +4734,14 @@ const TOOLS = [
|
|
|
4720
4734
|
tags: {
|
|
4721
4735
|
type: "array",
|
|
4722
4736
|
items: { type: "string" },
|
|
4723
|
-
description:
|
|
4737
|
+
description:
|
|
4738
|
+
"Optional: list of TOPICAL tags. RESERVED NAMESPACE: the 'idem-' prefix belongs " +
|
|
4739
|
+
"to per-source idempotency keys — a tag starting with it must be " +
|
|
4740
|
+
"idem-<family>-<digest> where <digest> is a 12- or 40-character lowercase hex " +
|
|
4741
|
+
"digest (e.g. idem-url-7ebe1ca33431), or the write is REJECTED with a 422. It is " +
|
|
4742
|
+
"never silently rewritten. Do not put a topic in that prefix. For idempotent " +
|
|
4743
|
+
"capture use the idempotency_key field, which is server-guaranteed unique per " +
|
|
4744
|
+
"tenant — a tag is not.",
|
|
4724
4745
|
},
|
|
4725
4746
|
project_id: {
|
|
4726
4747
|
type: "string",
|
|
@@ -4822,7 +4843,11 @@ const TOOLS = [
|
|
|
4822
4843
|
tags: {
|
|
4823
4844
|
type: "array",
|
|
4824
4845
|
items: { type: "string" },
|
|
4825
|
-
description:
|
|
4846
|
+
description:
|
|
4847
|
+
"Optional: REPLACES the whole tags array (send the full desired set). RESERVED " +
|
|
4848
|
+
"NAMESPACE: a tag starting with 'idem-' must be idem-<family>-<digest> " +
|
|
4849
|
+
"(<digest> = 12 or 40 lowercase hex chars, e.g. idem-url-7ebe1ca33431) or the " +
|
|
4850
|
+
"update is REJECTED with a 422 — it is never silently rewritten.",
|
|
4826
4851
|
},
|
|
4827
4852
|
metadata: {
|
|
4828
4853
|
type: "object",
|
|
@@ -5447,6 +5472,63 @@ const TOOLS = [
|
|
|
5447
5472
|
required: [],
|
|
5448
5473
|
},
|
|
5449
5474
|
},
|
|
5475
|
+
{
|
|
5476
|
+
name: "knowledge_consolidation",
|
|
5477
|
+
description:
|
|
5478
|
+
"Read the nightly consolidation (\"dream\") report: NUMBERED proposals for reconciling " +
|
|
5479
|
+
"the corpus, each naming the articles involved and quoting an excerpt from each as " +
|
|
5480
|
+
"evidence. REPORT ONLY — the pass writes no articles, links or conflict resolutions, " +
|
|
5481
|
+
"every proposal is `pending`, and this tool applies nothing. Requires orchestrator role.\n\n" +
|
|
5482
|
+
"Classes: `duplicate_capture` (titles that collide once case/punctuation normalize away, " +
|
|
5483
|
+
"or idempotency keys that collide under the same normalization while differing verbatim — " +
|
|
5484
|
+
"capture tag-format drift, which the novelty gate does not catch because novelty scoring " +
|
|
5485
|
+
"and idempotency are separate paths); `contradiction_candidate` (a SYSTEM-flagged " +
|
|
5486
|
+
"potential_conflict pair of PUBLISHED articles with no recorded verdict — record one via " +
|
|
5487
|
+
"knowledge_resolve_conflict, which accepts exactly these pairs; this report writes " +
|
|
5488
|
+
"none); `generic_title` (a placeholder title that collides on active-title uniqueness and " +
|
|
5489
|
+
"blocks hub creation); `stale_entry` (past the lint staleness threshold, never reconciled).\n\n" +
|
|
5490
|
+
"Denominators: `corpus_size` counts PUBLISHED articles owned by the tenant at scan time, " +
|
|
5491
|
+
"not its total article count. `proposal_count` is the TRUE pre-cap count of PROPOSALS, not " +
|
|
5492
|
+
"of articles — one duplicate group of three articles is ONE proposal, and one article can " +
|
|
5493
|
+
"appear in proposals of several classes. `persisted_count` is how many proposal ROWS the " +
|
|
5494
|
+
"report carries, lower than `proposal_count` exactly when a class hit `max_per_class` " +
|
|
5495
|
+
"(`truncated` flags which). `meta.total_count` counts persisted proposals matching the " +
|
|
5496
|
+
"`class` filter, so it is bounded by `persisted_count`, never by `proposal_count`.\n\n" +
|
|
5497
|
+
"Review state (`review_status`/`reviewed_by`/`reviewed_at`) RESETS to pending/null whenever " +
|
|
5498
|
+
"the nightly pass re-derives a proposal: refreshed machine output never inherits an approval.",
|
|
5499
|
+
inputSchema: {
|
|
5500
|
+
type: "object",
|
|
5501
|
+
properties: {
|
|
5502
|
+
day: {
|
|
5503
|
+
type: "string",
|
|
5504
|
+
description:
|
|
5505
|
+
"Optional ISO8601 date (YYYY-MM-DD, UTC) of the report to read. Defaults to the most recent report.",
|
|
5506
|
+
},
|
|
5507
|
+
class: {
|
|
5508
|
+
type: "string",
|
|
5509
|
+
enum: [
|
|
5510
|
+
"duplicate_capture",
|
|
5511
|
+
"contradiction_candidate",
|
|
5512
|
+
"generic_title",
|
|
5513
|
+
"stale_entry",
|
|
5514
|
+
],
|
|
5515
|
+
description: "Optional: return only proposals of this class.",
|
|
5516
|
+
},
|
|
5517
|
+
limit: {
|
|
5518
|
+
type: "integer",
|
|
5519
|
+
description: "Proposals per page. Default 50, max 500 (clamped, never rejected).",
|
|
5520
|
+
default: 50,
|
|
5521
|
+
minimum: 1,
|
|
5522
|
+
},
|
|
5523
|
+
offset: {
|
|
5524
|
+
type: "integer",
|
|
5525
|
+
description: "Proposals to skip. Default 0.",
|
|
5526
|
+
minimum: 0,
|
|
5527
|
+
},
|
|
5528
|
+
},
|
|
5529
|
+
required: [],
|
|
5530
|
+
},
|
|
5531
|
+
},
|
|
5450
5532
|
{
|
|
5451
5533
|
name: "knowledge_export",
|
|
5452
5534
|
description:
|
|
@@ -6899,6 +6981,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
6899
6981
|
case "knowledge_lint":
|
|
6900
6982
|
return await knowledgeLint(args);
|
|
6901
6983
|
|
|
6984
|
+
case "knowledge_consolidation":
|
|
6985
|
+
return await knowledgeConsolidation(args);
|
|
6986
|
+
|
|
6902
6987
|
case "knowledge_export":
|
|
6903
6988
|
return await knowledgeExport(args);
|
|
6904
6989
|
|