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.
Files changed (3) hide show
  1. package/README.md +1 -0
  2. package/index.js +74 -0
  3. 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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loopctl-mcp-server",
3
- "version": "2.68.0",
3
+ "version": "2.69.0",
4
4
  "description": "MCP server for loopctl \u2014 structural trust for AI development loops",
5
5
  "type": "module",
6
6
  "main": "index.js",