loopctl-mcp-server 2.25.0 → 2.27.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 (2) hide show
  1. package/index.js +143 -6
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -1027,9 +1027,9 @@ async function knowledgeBulkDelete({
1027
1027
 
1028
1028
  async function knowledgeDrafts({ limit, offset, project_id }) {
1029
1029
  const params = new URLSearchParams();
1030
- // Pass `limit` through verbatim (like knowledge_list/index/search) so the
1031
- // server honors it up to its max page size and returns 400 above it rather
1032
- // than silently clamping client-side, which would truncate draft enumeration.
1030
+ // Pass `limit` through verbatim (like knowledge_list/index/search) so the server
1031
+ // honors it up to its max page size, clamping above it server-side rather than
1032
+ // silently clamping client-side (which would truncate draft enumeration).
1033
1033
  if (limit != null) params.set("limit", String(limit));
1034
1034
  if (offset != null) params.set("offset", String(offset));
1035
1035
  if (project_id) params.set("project_id", project_id);
@@ -1038,6 +1038,41 @@ async function knowledgeDrafts({ limit, offset, project_id }) {
1038
1038
  return toContent(result);
1039
1039
  }
1040
1040
 
1041
+ async function knowledgeConflicts({ limit, offset }) {
1042
+ const params = new URLSearchParams();
1043
+ if (limit != null) params.set("limit", String(limit));
1044
+ if (offset != null) params.set("offset", String(offset));
1045
+ const qs = params.toString();
1046
+ const path = qs
1047
+ ? `/api/v1/knowledge/conflicts?${qs}`
1048
+ : "/api/v1/knowledge/conflicts";
1049
+ const result = await apiCall("GET", path, null, process.env.LOOPCTL_AGENT_KEY);
1050
+ return toContent(result);
1051
+ }
1052
+
1053
+ async function knowledgeResolveConflict({
1054
+ source_article_id,
1055
+ target_article_id,
1056
+ disposition,
1057
+ authoritative_article_id,
1058
+ classification,
1059
+ evidence,
1060
+ confidence,
1061
+ }) {
1062
+ const payload = { source_article_id, target_article_id, disposition };
1063
+ if (authoritative_article_id) payload.authoritative_article_id = authoritative_article_id;
1064
+ if (classification) payload.classification = classification;
1065
+ if (evidence) payload.evidence = evidence;
1066
+ if (confidence) payload.confidence = confidence;
1067
+ const result = await apiCall(
1068
+ "POST",
1069
+ "/api/v1/knowledge/conflicts/resolve",
1070
+ payload,
1071
+ process.env.LOOPCTL_AGENT_KEY,
1072
+ );
1073
+ return toContent(result);
1074
+ }
1075
+
1041
1076
  async function knowledgeLint({ project_id, stale_days, min_coverage, max_per_category }) {
1042
1077
  const params = new URLSearchParams();
1043
1078
  if (stale_days != null) params.set("stale_days", String(stale_days));
@@ -2852,8 +2887,8 @@ const TOOLS = [
2852
2887
  description:
2853
2888
  "List draft (unpublished) knowledge articles. Requires orchestrator role. " +
2854
2889
  "Returns paginated drafts with total_count in meta. Paginate via offset/limit " +
2855
- "(limit honored up to 1000; a limit above the max is rejected with 400, not " +
2856
- "silently clamped).",
2890
+ "(limit honored up to 1000; a limit above the max is clamped to the maximum, " +
2891
+ "never rejected, so pagination stays complete).",
2857
2892
  inputSchema: {
2858
2893
  type: "object",
2859
2894
  properties: {
@@ -2861,7 +2896,7 @@ const TOOLS = [
2861
2896
  type: "integer",
2862
2897
  description:
2863
2898
  "Max drafts per page (default 20, max 1000). A limit above the max is " +
2864
- "rejected with 400not silently clamped — so offset pagination stays complete.",
2899
+ "clamped to the maximum never rejected — so offset pagination stays complete.",
2865
2900
  default: 20,
2866
2901
  minimum: 1,
2867
2902
  maximum: 1000,
@@ -2880,6 +2915,102 @@ const TOOLS = [
2880
2915
  required: [],
2881
2916
  },
2882
2917
  },
2918
+ {
2919
+ name: "knowledge_conflicts",
2920
+ description:
2921
+ "List potential-conflict article pairs — published articles flagged 'too similar " +
2922
+ "to comfortably coexist' by the auto-linker / nightly lint sweep, highest-overlap " +
2923
+ "first. The KB only FLAGS the pair (via a mechanical similarity threshold); it does " +
2924
+ "NOT decide whether it's a redundancy to merge or a real contradiction — that's your " +
2925
+ "call, with the live context. Each entry has the two articles (id/title/status/" +
2926
+ "category) and their similarity. Read both, then merge (supersede one, knowledge_create " +
2927
+ "the merged article, or PATCH) or, if they genuinely disagree, reconcile. Paginated " +
2928
+ "with total_count in meta. Agent role.",
2929
+ inputSchema: {
2930
+ type: "object",
2931
+ properties: {
2932
+ limit: {
2933
+ type: "integer",
2934
+ description:
2935
+ "Max pairs per page (default 50, max 1000). A limit above the max is clamped " +
2936
+ "to the maximum — never rejected — so offset pagination stays complete.",
2937
+ default: 50,
2938
+ minimum: 1,
2939
+ maximum: 1000,
2940
+ },
2941
+ offset: {
2942
+ type: "integer",
2943
+ description: "Pagination offset. Default 0.",
2944
+ default: 0,
2945
+ minimum: 0,
2946
+ },
2947
+ },
2948
+ required: [],
2949
+ },
2950
+ },
2951
+ {
2952
+ name: "knowledge_resolve_conflict",
2953
+ description:
2954
+ "Record YOUR verdict on a potential-conflict pair (from knowledge_conflicts or an " +
2955
+ "article's potential_conflicts). You have the live context the KB lacks — it never " +
2956
+ "re-judges, it acts on what you record. Dispositions: 'dismiss' (a false positive — " +
2957
+ "the two don't actually conflict; drops out of the queue immediately); 'supersede' " +
2958
+ "(one article wins — pass authoritative_article_id, the winner; the nightly executor " +
2959
+ "creates a supersedes link and retires the loser, but ONLY at confidence:\"high\" — " +
2960
+ "reversible and audited); 'merge' (recorded for the later merge step). Non-destructive " +
2961
+ "at agent role — you record intent; the privileged nightly job executes it. " +
2962
+ "Last-write-wins per pair, so re-recording with fresher ground truth overrides. " +
2963
+ "Resolve only conflicts material to your current task; adjudicate against the actual " +
2964
+ "system, and if you can't tell which is right, leave it (or record low confidence) " +
2965
+ "rather than guessing.",
2966
+ inputSchema: {
2967
+ type: "object",
2968
+ properties: {
2969
+ source_article_id: {
2970
+ type: "string",
2971
+ description: "One article of the conflict pair (UUID). Order does not matter.",
2972
+ },
2973
+ target_article_id: {
2974
+ type: "string",
2975
+ description: "The other article of the conflict pair (UUID).",
2976
+ },
2977
+ disposition: {
2978
+ type: "string",
2979
+ enum: ["dismiss", "supersede", "merge"],
2980
+ description:
2981
+ "dismiss = false positive; supersede = one wins (set authoritative_article_id); " +
2982
+ "merge = combine (recorded for the later merge step).",
2983
+ },
2984
+ authoritative_article_id: {
2985
+ type: "string",
2986
+ description:
2987
+ "For supersede/merge: the WINNING article (must be one of the pair). The other " +
2988
+ "is the loser to retire/merge into the winner.",
2989
+ },
2990
+ classification: {
2991
+ type: "string",
2992
+ enum: ["redundant", "complementary", "contradictory"],
2993
+ description:
2994
+ "Your judgment of the relationship: redundant (same claim), complementary (same " +
2995
+ "topic, different facets — usually a dismiss), or contradictory (can't both be true).",
2996
+ },
2997
+ evidence: {
2998
+ type: "string",
2999
+ description:
3000
+ "Why you're sure — ideally a ground-truth reference (commit, file:line, URL, or the " +
3001
+ "observed behavior). Recorded for audit and for a human reviewing low-confidence calls.",
3002
+ },
3003
+ confidence: {
3004
+ type: "string",
3005
+ enum: ["high", "medium", "low"],
3006
+ description:
3007
+ "high, medium, or low. supersede auto-executes only at 'high'; lower confidence is " +
3008
+ "recorded but left for review. Default medium.",
3009
+ },
3010
+ },
3011
+ required: ["source_article_id", "target_article_id", "disposition"],
3012
+ },
3013
+ },
2883
3014
  {
2884
3015
  name: "knowledge_lint",
2885
3016
  description:
@@ -3500,6 +3631,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3500
3631
  case "knowledge_drafts":
3501
3632
  return await knowledgeDrafts(args);
3502
3633
 
3634
+ case "knowledge_conflicts":
3635
+ return await knowledgeConflicts(args);
3636
+
3637
+ case "knowledge_resolve_conflict":
3638
+ return await knowledgeResolveConflict(args);
3639
+
3503
3640
  case "knowledge_lint":
3504
3641
  return await knowledgeLint(args);
3505
3642
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loopctl-mcp-server",
3
- "version": "2.25.0",
3
+ "version": "2.27.0",
4
4
  "description": "MCP server for loopctl — structural trust for AI development loops",
5
5
  "type": "module",
6
6
  "main": "index.js",