loopctl-mcp-server 2.49.0 → 2.50.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 +2 -1
  2. package/index.js +51 -0
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -121,7 +121,7 @@ REST endpoint (`PATCH /api/v1/tenants/me/llm-config`), and the docs — so you (
121
121
  autonomous agent) can self-remediate without a human. Full agent-tenant lifecycle:
122
122
  [`docs/onboarding-agent-tenant.md`](../docs/onboarding-agent-tenant.md).
123
123
 
124
- ## Tools (95)
124
+ ## Tools (96)
125
125
 
126
126
  > Plus **per-tenant generated Context Retriever tools** (`cr_*`) appended
127
127
  > dynamically at runtime — see [Dynamic per-tenant Context Retriever
@@ -161,6 +161,7 @@ Epic 39 Repo Coordination Bus — a lightweight, tenant-isolated channel for age
161
161
  | `channel_claim` | Claim a handoff `ref` for EXACTLY ONE agent (Epic 40, US-40.B1) — coordinate an out-of-band unit of work (e.g. `handoff:repo#812`) among agents racing on the same repo. INSERT-to-claim: the first to claim `(tenant, project, ref)` wins; a concurrent LOSER gets a distinct 409 `already_claimed` (another agent already owns it — move on, do NOT retry the same ref). Project-scoped by membership. Optional `lease_seconds` (default 3600, max 86400). Required: `project_id`, `ref`. |
162
162
  | `channel_release` | Release YOUR OWN handoff claim so the `ref` reopens for another agent (deletes the claim). Owner-scoped: a claim you do not own / cross-tenant / nonexistent returns a byte-identical 404. Required: `project_id`, `ref`. |
163
163
  | `channel_done` | Mark YOUR OWN handoff claim done (sets `done_at`) — records you completed the claimed work; the row is retained ~7 days then swept. Owner-scoped like `channel_release`. Required: `project_id`, `ref`. |
164
+ | `channel_graduate` | Graduate a coordination post into the durable Knowledge wiki (Epic 40, US-40.E1). CONTENT-SELECTIVE — ONLY for a genuinely REUSABLE finding with no external tracker, worth another agent reading later; a transient directive should be LEFT TO EXPIRE on its 30-day TTL, never graduated. No automatic graduation. Reuses Knowledge's guardrails, never a bypass: the semantic novelty gate (a near-duplicate returns 200 `deduplicated`, nothing created) plus a secret scan (a denylisted credential returns 422). Records `source_type` `channel_graduation` + the post id, attributed to you; the source post is KEPT (its TTL reclaims it). Project-scoped by membership. Optional `tags`, `category` (default `finding`). Required: `post_id`, `title`. |
164
165
 
165
166
  ### Story Tools
166
167
 
package/index.js CHANGED
@@ -557,6 +557,26 @@ async function channelDelete({ post_id }) {
557
557
  return toContent(result);
558
558
  }
559
559
 
560
+ async function channelGraduate({ post_id, title, tags, category }) {
561
+ // Repo Coordination Bus (Epic 40, US-40.E1): graduate a coordination post into
562
+ // the durable Knowledge wiki on the AGENT key. CONTENT-SELECTIVE — for a
563
+ // genuinely REUSABLE finding with no external tracker, NOT routine handoffs (a
564
+ // transient directive should be left to expire on its 30-day TTL). Reuses
565
+ // Knowledge's semantic novelty gate (a near-duplicate returns 200 deduplicated,
566
+ // nothing created) plus an explicit secret scan (a denylisted credential returns
567
+ // 422) — never a bypass. Project-scoped by membership; tenant/agent server-stamped.
568
+ const payload = { title };
569
+ if (tags) payload.tags = tags;
570
+ if (category) payload.category = category;
571
+ const result = await apiCall(
572
+ "POST",
573
+ `/api/v1/channel/posts/${post_id}/graduate`,
574
+ payload,
575
+ process.env.LOOPCTL_AGENT_KEY,
576
+ );
577
+ return toContent(result);
578
+ }
579
+
560
580
  async function deleteProject({ project_id }) {
561
581
  const result = await apiCall(
562
582
  "DELETE",
@@ -2431,6 +2451,35 @@ const TOOLS = [
2431
2451
  required: ["post_id"],
2432
2452
  },
2433
2453
  },
2454
+ {
2455
+ name: "channel_graduate",
2456
+ description:
2457
+ "Graduate a repo coordination post into the durable Knowledge wiki (Epic 40 Repo Coordination Bus, US-40.E1), on the agent key. CONTENT-SELECTIVE — use this ONLY for a genuinely REUSABLE finding that has no external tracker and is worth another agent reading later (the durable home for a lesson learned). It is NOT the general handoff-durability answer: a transient directive (e.g. 'run this SQL now', 'rebasing branch X') should be LEFT TO EXPIRE on the post's 30-day TTL, never graduated. There is NO automatic graduation — this is always your explicit, deliberate decision. title is REQUIRED; the article body is carried from the post, project_id carries over, and tags are optional. Reuses Knowledge's EXISTING guardrails, never a bypass: the SEMANTIC NOVELTY gate (a near-duplicate returns 200 with deduplicated:true and creates nothing, pointing you at the canonical article) plus an explicit secret scan over the body (a denylisted credential shape returns 422 and nothing lands). The article records source_type 'channel_graduation' + the originating post id, attributed to you. The source post is KEPT (its TTL sweep reclaims it); redact it separately with channel_delete if it must go sooner. Project-scoped by membership; tenant/agent are server-stamped from your verified key. Rate-bounded so it cannot bulk-flood Knowledge from the channel.",
2458
+ inputSchema: {
2459
+ type: "object",
2460
+ properties: {
2461
+ post_id: {
2462
+ type: "string",
2463
+ description: "UUID of the channel post to graduate (must be in your tenant).",
2464
+ },
2465
+ title: {
2466
+ type: "string",
2467
+ description: "Title for the durable Knowledge article (required).",
2468
+ },
2469
+ tags: {
2470
+ type: "array",
2471
+ items: { type: "string" },
2472
+ description: "Optional topical tags for the article.",
2473
+ },
2474
+ category: {
2475
+ type: "string",
2476
+ description:
2477
+ "Optional article category (defaults to 'finding' — a reusable lesson).",
2478
+ },
2479
+ },
2480
+ required: ["post_id", "title"],
2481
+ },
2482
+ },
2434
2483
  {
2435
2484
  name: "channel_claim",
2436
2485
  description:
@@ -5303,6 +5352,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
5303
5352
 
5304
5353
  case "channel_delete":
5305
5354
  return await channelDelete(args);
5355
+ case "channel_graduate":
5356
+ return await channelGraduate(args);
5306
5357
 
5307
5358
  case "channel_claim":
5308
5359
  return await channelClaim(args);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loopctl-mcp-server",
3
- "version": "2.49.0",
3
+ "version": "2.50.0",
4
4
  "description": "MCP server for loopctl — structural trust for AI development loops",
5
5
  "type": "module",
6
6
  "main": "index.js",