loopctl-mcp-server 2.69.0 → 2.71.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 +6 -6
  2. package/index.js +58 -26
  3. 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 (non-destructive + audited; the in-place edit overwrites the prior body, so it is not reversible either); 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
 
@@ -299,12 +299,12 @@ it is enforced server-side and a no-op for a non-superadmin key — see below.)
299
299
  | `knowledge_bulk_publish` | **Requires `LOOPCTL_USER_KEY`.** Publish drafts, partial-success style: every valid draft publishes; others are reported per-id as `skipped` (already published — idempotent — or archived/superseded), `not_found`, or `errored`. No 100-id cap (auto-chunked); duplicates ignored; safe to retry. `meta.count` = published; `meta.counts`/`meta.results` give the breakdown. Required: `article_ids` (array). |
300
300
  | `knowledge_unpublish` | **Requires `LOOPCTL_USER_KEY`.** Revert a published article back to draft (hidden from search/context, not deleted). Required: `article_id`. |
301
301
  | `knowledge_bulk_unpublish` | **Requires `LOOPCTL_USER_KEY`.** Revert published articles to draft in bulk, partial-success style (mirror of `knowledge_bulk_publish`): per-id `unpublished`/`skipped` (already draft, or archived/superseded)/`not_found`/`errored`. No 100-id cap (auto-chunked, ≤5000); duplicates ignored; safe to retry. Not deleted (re-publish to restore; `knowledge_bulk_delete` to archive). `meta.count`/`meta.counts`/`meta.results` give the breakdown. Required: `article_ids` (array). |
302
- | `knowledge_archive` | Soft-delete an article (draft or published). Row retained for audit; hidden from all reads (reversible — re-publish/edit it back). Agent role — KB-content curation, visibility-scoped (another agent's private/owner memory 404s). Required: `article_id`. |
303
- | `knowledge_delete` | Alias for `knowledge_archive` — DELETE verb on the REST API archives under the hood (soft delete, reversible, audited). Agent role. (Irreversible HARD delete is `knowledge_bulk_delete hard:true`, which stays `LOOPCTL_USER_KEY`.) Required: `article_id`. |
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). |
302
+ | `knowledge_archive` | Soft-delete an article (draft or published). Row retained for audit; hidden from all reads. **NOT reversible by you** `:archived` is a TERMINAL status (no unarchive call, no outbound transition), so restoring one needs a user-role PATCH with an explicit status. Nothing is destroyed, but do not reach for this as an undoable action: for a retraction you can undo, use `knowledge_unpublish` and `knowledge_publish`. Agent role — KB-content curation, visibility-scoped (another agent's private/owner memory 404s). Required: `article_id`. |
303
+ | `knowledge_delete` | Alias for `knowledge_archive` — DELETE verb on the REST API archives under the hood (soft delete: row retained and audited, but NOT reversible by any call you can make, since `:archived` is terminal — use `knowledge_unpublish` when you need an undoable retraction). Agent role. (Irreversible HARD delete is `knowledge_bulk_delete hard:true`, which stays `LOOPCTL_USER_KEY`.) Required: `article_id`. |
304
+ | `knowledge_bulk_delete` | **Requires `LOOPCTL_USER_KEY`.** Bulk archive (default — non-destructive, but NOT reversible by any call: `:archived` is terminal and restoring needs a user-role PATCH) 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
+ | `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. **This tool** applies nothing and recomputes nothing it returns persisted rows. **The pass** it reports on does write: since #608 the nightly run UNPUBLISHES the losers of each `duplicate_capture` group that two consecutive reports both propose. That is its only write to `articles`, it is an unpublish and never an archive (archive is terminal for an article), and it still writes no links or conflict resolutions. 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), `generic_title` (a placeholder title that collides on active-title uniqueness and blocks hub creation). Two classes are **RETIRED** (#605) and no longer produced, though the `class` filter still accepts them so historical reports stay readable: `contradiction_candidate` (the nightly lint judges those pairs itself now) and `stale_entry` (age is not a defect signal for stale articles call `knowledge_lint`, which computes them with a caller-chosen `stale_days`). **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` are vestigial — nothing reads them to decide anything, there is no approve/reject surface and there will not be one (#605 supersedes #594); auto-apply is gated on reversibility and two-run agreement. They still reset to pending/null whenever the nightly pass re-derives a proposal, so refreshed machine output never inherits an earlier verdict. Requires orchestrator role. Optional: `day` (ISO8601, default most recent report), `class`, `limit` (default 50, max 500), `offset`. |
308
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`). |
309
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`. |
310
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
@@ -1786,8 +1786,9 @@ async function knowledgeUnpublish({ article_id }) {
1786
1786
  return toContent(result);
1787
1787
  }
1788
1788
 
1789
- // #331: single-article archive is agent-role KB curation (reversible soft delete,
1790
- // audited, visibility-scoped server-side).
1789
+ // #331: single-article archive is agent-role KB curation (non-destructive soft delete,
1790
+ // audited, visibility-scoped server-side). NOT reversible in code — #606/#605: `:archived`
1791
+ // is a terminal status. The row survives; nothing automated brings it back.
1791
1792
  async function knowledgeArchive({ article_id }) {
1792
1793
  const result = await apiCall(
1793
1794
  "POST",
@@ -4734,7 +4735,14 @@ const TOOLS = [
4734
4735
  tags: {
4735
4736
  type: "array",
4736
4737
  items: { type: "string" },
4737
- description: "Optional: list of tags.",
4738
+ description:
4739
+ "Optional: list of TOPICAL tags. RESERVED NAMESPACE: the 'idem-' prefix belongs " +
4740
+ "to per-source idempotency keys — a tag starting with it must be " +
4741
+ "idem-<family>-<digest> where <digest> is a 12- or 40-character lowercase hex " +
4742
+ "digest (e.g. idem-url-7ebe1ca33431), or the write is REJECTED with a 422. It is " +
4743
+ "never silently rewritten. Do not put a topic in that prefix. For idempotent " +
4744
+ "capture use the idempotency_key field, which is server-guaranteed unique per " +
4745
+ "tenant — a tag is not.",
4738
4746
  },
4739
4747
  project_id: {
4740
4748
  type: "string",
@@ -4810,7 +4818,9 @@ const TOOLS = [
4810
4818
  "project CLAUDE.mds and cross-links). Send only the fields you want to change; every " +
4811
4819
  "field is optional except article_id. `tags` REPLACES the whole array (send the full " +
4812
4820
  "desired set, not a delta). A changed body/tags re-triggers embedding + auto-linking. " +
4813
- "Agent role — this is KB-content curation (reversible + audited). Visibility-scoped: " +
4821
+ "Agent role — this is KB-content curation (non-destructive + audited: the edit is in " +
4822
+ "place and there is no version-restore endpoint, but the prior body is retained in the " +
4823
+ "article.updated audit entry). Visibility-scoped: " +
4814
4824
  "you can only edit an article you can see, so another agent's private/owner memory " +
4815
4825
  "returns 404. `tenant_id` is never accepted. Returns the full updated article. To " +
4816
4826
  "instead retire/replace an article, use knowledge_archive or knowledge_resolve_conflict.",
@@ -4836,7 +4846,11 @@ const TOOLS = [
4836
4846
  tags: {
4837
4847
  type: "array",
4838
4848
  items: { type: "string" },
4839
- description: "Optional: REPLACES the whole tags array (send the full desired set).",
4849
+ description:
4850
+ "Optional: REPLACES the whole tags array (send the full desired set). RESERVED " +
4851
+ "NAMESPACE: a tag starting with 'idem-' must be idem-<family>-<digest> " +
4852
+ "(<digest> = 12 or 40 lowercase hex chars, e.g. idem-url-7ebe1ca33431) or the " +
4853
+ "update is REJECTED with a 422 — it is never silently rewritten.",
4840
4854
  },
4841
4855
  metadata: {
4842
4856
  type: "object",
@@ -5190,11 +5204,15 @@ const TOOLS = [
5190
5204
  {
5191
5205
  name: "knowledge_archive",
5192
5206
  description:
5193
- "Archive an article (soft delete). The article is hidden from search, context, " +
5194
- "and the index but the row is retained for audit/history (reversible re-publish " +
5195
- "or edit it back). Works for drafts and published articles. Agent role KB-content " +
5196
- "curation. Visibility-scoped: you can only archive an article you can see, so " +
5197
- "another agent's private/owner memory returns 404.",
5207
+ "Archive an article (soft delete). The article is hidden from search, context, and " +
5208
+ "the index but the row is retained for audit/history. NOT reversible by you: " +
5209
+ "`:archived` is a TERMINAL article status there is no unarchive call and no " +
5210
+ "{archived -> anything} transition, so restoring one needs a user-role PATCH with an " +
5211
+ "explicit status. Nothing is destroyed, but do not reach for this as an undoable " +
5212
+ "action. If you want a RETRACTION you can undo, use knowledge_unpublish (published " +
5213
+ "-> draft) and knowledge_publish to put it back. Works for drafts and published " +
5214
+ "articles. Agent role — KB-content curation. Visibility-scoped: you can only archive " +
5215
+ "an article you can see, so another agent's private/owner memory returns 404.",
5198
5216
  inputSchema: {
5199
5217
  type: "object",
5200
5218
  properties: {
@@ -5210,10 +5228,13 @@ const TOOLS = [
5210
5228
  name: "knowledge_delete",
5211
5229
  description:
5212
5230
  "Delete an article. Under the hood this performs the same soft-delete (archive) " +
5213
- "as knowledge_archive — use whichever name is clearer at the call site. The row " +
5214
- "is retained for audit; there is no hard delete (that is knowledge_bulk_delete " +
5215
- "hard:true, which stays user-gated). Agent role KB-content curation, reversible + " +
5216
- "audited, visibility-scoped (another agent's private/owner memory 404s).",
5231
+ "as knowledge_archive — use whichever name is clearer at the call site, and note " +
5232
+ "that it inherits archive's terminality: the row is retained for audit, but " +
5233
+ "`:archived` has no outbound transition, so NOTHING you can call restores it. " +
5234
+ "For a retraction you can undo, use knowledge_unpublish instead. There is no hard " +
5235
+ "delete here (that is knowledge_bulk_delete hard:true, which stays user-gated). " +
5236
+ "Agent role — KB-content curation: non-destructive + audited, visibility-scoped " +
5237
+ "(another agent's private/owner memory 404s).",
5217
5238
  inputSchema: {
5218
5239
  type: "object",
5219
5240
  properties: {
@@ -5228,7 +5249,9 @@ const TOOLS = [
5228
5249
  {
5229
5250
  name: "knowledge_bulk_delete",
5230
5251
  description:
5231
- "Bulk archive (default, reversible) or IRREVERSIBLE hard-delete of articles by selector. " +
5252
+ "Bulk archive (default, non-destructive but NOT reversible by any call you can make " +
5253
+ "`:archived` is terminal; restoring needs a user-role PATCH) or IRREVERSIBLE hard-delete " +
5254
+ "of articles by selector. " +
5232
5255
  "REQUIRES LOOPCTL_USER_KEY (user role — orchestrator is NOT sufficient). Provide EXACTLY ONE " +
5233
5256
  "selector: article_ids (explicit list), source_type + source_id (every active article from " +
5234
5257
  "that source), or tag + confirm:true (every active article carrying the tag — high blast " +
@@ -5274,8 +5297,9 @@ const TOOLS = [
5274
5297
  hard: {
5275
5298
  type: "boolean",
5276
5299
  description:
5277
- "IRREVERSIBLE hard delete (vs default reversible archive). Run dry_run first to get a " +
5278
- "token, then pass hard:true + token.",
5300
+ "IRREVERSIBLE hard delete (vs the default soft archive, which is non-destructive but " +
5301
+ "terminal — `:archived` has no outbound transition, so restoring one needs a user-role " +
5302
+ "PATCH). Run dry_run first to get a token, then pass hard:true + token.",
5279
5303
  },
5280
5304
  token: {
5281
5305
  type: "string",
@@ -5466,16 +5490,21 @@ const TOOLS = [
5466
5490
  description:
5467
5491
  "Read the nightly consolidation (\"dream\") report: NUMBERED proposals for reconciling " +
5468
5492
  "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" +
5493
+ "evidence. THIS TOOL applies nothing and recomputes nothing it returns persisted rows. " +
5494
+ "The PASS it reports on does write: since #608 the nightly run UNPUBLISHES the losers of " +
5495
+ "each `duplicate_capture` group that two consecutive reports both propose. That is its " +
5496
+ "only write to articles, it is an unpublish and never an archive (archive is terminal for " +
5497
+ "an article), and it still writes no links or conflict resolutions. Requires orchestrator " +
5498
+ "role.\n\n" +
5471
5499
  "Classes: `duplicate_capture` (titles that collide once case/punctuation normalize away, " +
5472
5500
  "or idempotency keys that collide under the same normalization while differing verbatim — " +
5473
5501
  "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" +
5502
+ "and idempotency are separate paths); `generic_title` (a placeholder title that collides " +
5503
+ "on active-title uniqueness and blocks hub creation). Two classes are RETIRED (#605) and " +
5504
+ "no longer produced, though the `class` filter still accepts them so historical reports " +
5505
+ "stay readable: `contradiction_candidate` (the nightly lint judges those pairs itself now) " +
5506
+ "and `stale_entry` (age is not a defect signal — for stale articles call knowledge_lint, " +
5507
+ "which computes them with a caller-chosen `stale_days`).\n\n" +
5479
5508
  "Denominators: `corpus_size` counts PUBLISHED articles owned by the tenant at scan time, " +
5480
5509
  "not its total article count. `proposal_count` is the TRUE pre-cap count of PROPOSALS, not " +
5481
5510
  "of articles — one duplicate group of three articles is ONE proposal, and one article can " +
@@ -5483,8 +5512,11 @@ const TOOLS = [
5483
5512
  "report carries, lower than `proposal_count` exactly when a class hit `max_per_class` " +
5484
5513
  "(`truncated` flags which). `meta.total_count` counts persisted proposals matching the " +
5485
5514
  "`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.",
5515
+ "Review state (`review_status`/`reviewed_by`/`reviewed_at`) is VESTIGIAL: nothing reads it " +
5516
+ "to decide anything, there is no approve/reject surface and there will not be one (#605 " +
5517
+ "supersedes #594) — auto-apply is gated on reversibility and two-run agreement, not on an " +
5518
+ "approval. It still RESETS to pending/null whenever the nightly pass re-derives a proposal, " +
5519
+ "so refreshed machine output can never inherit an earlier verdict.",
5488
5520
  inputSchema: {
5489
5521
  type: "object",
5490
5522
  properties: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loopctl-mcp-server",
3
- "version": "2.69.0",
3
+ "version": "2.71.0",
4
4
  "description": "MCP server for loopctl \u2014 structural trust for AI development loops",
5
5
  "type": "module",
6
6
  "main": "index.js",