@kaddo/mcp 3.81.0 → 3.83.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/dist/index.js +663 -26
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -262,8 +262,8 @@ function sectionParagraph(md, title) {
262
262
  return "";
263
263
  }
264
264
  function listCapsules(root) {
265
- const registry = readYaml(root, ".kaddo/external.yml");
266
- const entries = Array.isArray(registry?.external) ? registry.external : [];
265
+ const registry2 = readYaml(root, ".kaddo/external.yml");
266
+ const entries = Array.isArray(registry2?.external) ? registry2.external : [];
267
267
  return entries.map((e) => {
268
268
  const md = e.path ? readText(root, e.path) : null;
269
269
  let system;
@@ -480,8 +480,8 @@ function loadConfig(dir) {
480
480
  const parsed = configSchema.safeParse(raw ?? {});
481
481
  if (!parsed.success) {
482
482
  const issues = parsed.error.issues.map((i) => {
483
- const path5 = i.path.join(".");
484
- return path5 ? ` - ${path5}: ${i.message}` : ` - ${i.message}`;
483
+ const path6 = i.path.join(".");
484
+ return path6 ? ` - ${path6}: ${i.message}` : ` - ${i.message}`;
485
485
  }).join("\n");
486
486
  throw new ConfigError(`Invalid .kaddo/config.yml:
487
487
  ${issues}`);
@@ -922,11 +922,11 @@ function moduleArtifactCoverage(dir, id) {
922
922
  };
923
923
  }
924
924
  function loadMappedModules(dir) {
925
- const path5 = join(dir, DESCRIPTOR_PATH);
926
- if (!exists(path5)) return [];
925
+ const path6 = join(dir, DESCRIPTOR_PATH);
926
+ if (!exists(path6)) return [];
927
927
  let parsed;
928
928
  try {
929
- parsed = parseYaml4(readFile(path5));
929
+ parsed = parseYaml4(readFile(path6));
930
930
  } catch {
931
931
  return [];
932
932
  }
@@ -2433,7 +2433,7 @@ function sectionParagraph2(md, title) {
2433
2433
  }
2434
2434
  return "";
2435
2435
  }
2436
- function parseCapsule(id, path5, md) {
2436
+ function parseCapsule(id, path6, md) {
2437
2437
  const { data } = matter4(md);
2438
2438
  const updatedAt = data.updated_at ? String(data.updated_at) : void 0;
2439
2439
  let ageDays = null;
@@ -2443,7 +2443,7 @@ function parseCapsule(id, path5, md) {
2443
2443
  }
2444
2444
  return {
2445
2445
  id,
2446
- path: path5,
2446
+ path: path6,
2447
2447
  system: data.system ? String(data.system) : id,
2448
2448
  owner: data.owner ? String(data.owner) : void 0,
2449
2449
  updatedAt,
@@ -2505,9 +2505,9 @@ function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
2505
2505
  ];
2506
2506
  const presentLayers = [];
2507
2507
  for (const layer2 of layerDocs) {
2508
- const path5 = layer2.files.map((f3) => `${KNOWLEDGE3}/${f3}`).find((rel) => exists(join(dir, rel)));
2509
- if (path5) {
2510
- addNode({ id: layer2.id, type: layer2.type, label: layer2.label, path: path5 });
2508
+ const path6 = layer2.files.map((f3) => `${KNOWLEDGE3}/${f3}`).find((rel) => exists(join(dir, rel)));
2509
+ if (path6) {
2510
+ addNode({ id: layer2.id, type: layer2.type, label: layer2.label, path: path6 });
2511
2511
  presentLayers.push(layer2.id);
2512
2512
  }
2513
2513
  }
@@ -4088,10 +4088,10 @@ function resolveNextStep(dir, now = /* @__PURE__ */ new Date()) {
4088
4088
  const q3 = (rel) => analyzeKnowledgeArtifact(dir, rel);
4089
4089
  const resolveAgent = (agent) => {
4090
4090
  const file = agent.endsWith(".md") ? agent : `${agent}.md`;
4091
- const path5 = agentInstallPath(file);
4092
- const installed = isFile(join(dir, path5));
4091
+ const path6 = agentInstallPath(file);
4092
+ const installed = isFile(join(dir, path6));
4093
4093
  const group = agentGroupOf(file);
4094
- return { agentPath: path5, agentInstalled: installed, installCommand: installed ? void 0 : `kaddo add agents --group ${group}` };
4094
+ return { agentPath: path6, agentInstalled: installed, installCommand: installed ? void 0 : `kaddo add agents --group ${group}` };
4095
4095
  };
4096
4096
  const moduleRepo = isModule(config);
4097
4097
  const coreRepo = isCore(config);
@@ -7643,6 +7643,7 @@ function parseWorkItemSource(frontmatter) {
7643
7643
  title: optStr(obj.title) ?? optStr(frontmatter.source_title),
7644
7644
  context: optStr(obj.context) ?? optStr(frontmatter.source_context),
7645
7645
  provider: optStr(obj.provider) ?? optStr(frontmatter.source_provider),
7646
+ integration: optStr(obj.integration) ?? optStr(frontmatter.source_integration),
7646
7647
  url: optStr(obj.url) ?? optStr(frontmatter.source_url),
7647
7648
  imported_at: optStr(obj.imported_at) ?? optStr(frontmatter.source_imported_at),
7648
7649
  synced_at: optStr(obj.synced_at) ?? optStr(frontmatter.source_synced_at),
@@ -9131,7 +9132,7 @@ function buildGuardHistory(dir) {
9131
9132
  const d3 = hotspotDir(t.code_path);
9132
9133
  hotspotMap.set(d3, (hotspotMap.get(d3) ?? 0) + 1);
9133
9134
  }
9134
- const hotspots = [...hotspotMap.entries()].map(([path5, warnings]) => ({ path: path5, warnings })).sort((a2, b2) => b2.warnings - a2.warnings);
9135
+ const hotspots = [...hotspotMap.entries()].map(([path6, warnings]) => ({ path: path6, warnings })).sort((a2, b2) => b2.warnings - a2.warnings);
9135
9136
  return {
9136
9137
  available: true,
9137
9138
  total_runs: sorted.length,
@@ -11515,16 +11516,16 @@ function findSystemPaths(dir, source, target, opts = {}) {
11515
11516
  out.get(r2.source).push(r2.target);
11516
11517
  }
11517
11518
  const paths = [];
11518
- const walk = (node, path5) => {
11519
+ const walk = (node, path6) => {
11519
11520
  if (paths.length >= 20) return;
11520
- if (node === target && path5.length > 1) {
11521
- paths.push([...path5]);
11521
+ if (node === target && path6.length > 1) {
11522
+ paths.push([...path6]);
11522
11523
  return;
11523
11524
  }
11524
- if (path5.length > maxDepth) return;
11525
+ if (path6.length > maxDepth) return;
11525
11526
  for (const nxt of out.get(node) ?? []) {
11526
- if (path5.includes(nxt)) continue;
11527
- walk(nxt, [...path5, nxt]);
11527
+ if (path6.includes(nxt)) continue;
11528
+ walk(nxt, [...path6, nxt]);
11528
11529
  }
11529
11530
  };
11530
11531
  walk(source, [source]);
@@ -11549,14 +11550,14 @@ function getImpactCandidates(dir, seedIds, opts = {}) {
11549
11550
  existing.reason.push(reason);
11550
11551
  continue;
11551
11552
  }
11552
- const path5 = findSystemPaths(dir, seed, n.id, { maxDepth: opts.maxDepth ?? DEFAULT_MAX_DEPTH })[0];
11553
+ const path6 = findSystemPaths(dir, seed, n.id, { maxDepth: opts.maxDepth ?? DEFAULT_MAX_DEPTH })[0];
11553
11554
  candidates.set(n.id, {
11554
11555
  nodeId: n.id,
11555
11556
  label: n.label,
11556
11557
  kind: n.type,
11557
11558
  ...n.moduleId ? { moduleId: n.moduleId } : {},
11558
11559
  reason: [reason],
11559
- ...path5 ? { graphPath: path5.map((id) => byId.get(id)?.label ?? id) } : {},
11560
+ ...path6 ? { graphPath: path6.map((id) => byId.get(id)?.label ?? id) } : {},
11560
11561
  ...n.implementationRefs?.length ? { implementationRefs: n.implementationRefs } : {},
11561
11562
  ...n.knowledgeRefs?.length ? { knowledgeRefs: n.knowledgeRefs } : {},
11562
11563
  status: "candidate",
@@ -11654,8 +11655,596 @@ function systemImpactCandidatesTool(root, args) {
11654
11655
  });
11655
11656
  }
11656
11657
 
11657
- // ../cli/src/core/context-pack.ts
11658
+ // ../cli/src/services/integrations.ts
11659
+ import matter13 from "gray-matter";
11660
+ import { parse as parseYaml12, stringify as stringifyYaml6 } from "yaml";
11661
+
11662
+ // ../cli/src/core/work-item-write.ts
11663
+ import fs5 from "fs";
11664
+ import path5 from "path";
11665
+ import crypto2 from "crypto";
11658
11666
  import matter12 from "gray-matter";
11667
+ var SECTION_ORDER = [
11668
+ "Actor",
11669
+ "Outcome",
11670
+ "Current behavior",
11671
+ "Target behavior",
11672
+ "Entry points",
11673
+ "End-to-end flow",
11674
+ "Scope unknowns",
11675
+ "Acceptance criteria"
11676
+ ];
11677
+ var KNOWN_HEADINGS = new Set(SECTION_ORDER.map((s) => s.toLowerCase()));
11678
+
11679
+ // ../integrations/src/errors.ts
11680
+ var RETRYABLE = /* @__PURE__ */ new Set([
11681
+ "INTEGRATION_RATE_LIMITED",
11682
+ "INTEGRATION_UNAVAILABLE",
11683
+ "INTEGRATION_TIMEOUT"
11684
+ ]);
11685
+ var IntegrationError = class extends Error {
11686
+ code;
11687
+ /** True for transient conditions (rate limit / unavailable / timeout). */
11688
+ retryable;
11689
+ /** A safe, provider-agnostic reason. Never the raw SDK message. */
11690
+ safeMessage;
11691
+ constructor(code, message) {
11692
+ super(message);
11693
+ this.name = "IntegrationError";
11694
+ this.code = code;
11695
+ this.retryable = RETRYABLE.has(code);
11696
+ this.safeMessage = message;
11697
+ }
11698
+ };
11699
+ function defaultMessageFor(code) {
11700
+ switch (code) {
11701
+ case "INTEGRATION_CONFIG_INVALID":
11702
+ return "The integration configuration is invalid.";
11703
+ case "INTEGRATION_UNAUTHORIZED":
11704
+ return "Authentication failed. Check the configured credentials.";
11705
+ case "INTEGRATION_FORBIDDEN":
11706
+ return "The configured credentials lack permission for this operation.";
11707
+ case "INTEGRATION_NOT_FOUND":
11708
+ return "The requested external resource was not found.";
11709
+ case "INTEGRATION_RATE_LIMITED":
11710
+ return "The external provider is rate limiting requests. Try again later.";
11711
+ case "INTEGRATION_UNAVAILABLE":
11712
+ return "The external provider is temporarily unavailable.";
11713
+ case "INTEGRATION_TIMEOUT":
11714
+ return "The external provider did not respond in time.";
11715
+ case "INTEGRATION_PROVIDER_ERROR":
11716
+ return "The external provider returned an error.";
11717
+ case "UNSUPPORTED_CAPABILITY":
11718
+ return "This adapter does not support the requested capability.";
11719
+ }
11720
+ }
11721
+ function integrationError(code, message) {
11722
+ return new IntegrationError(code, message ?? defaultMessageFor(code));
11723
+ }
11724
+
11725
+ // ../integrations/src/config.ts
11726
+ var ENV_SUFFIX = "_env";
11727
+ var SECRET_KEY = /(token|secret|password|key|pat|apikey|api_key)/i;
11728
+ function parseCredentials(raw, id, findings) {
11729
+ const creds = {};
11730
+ if (raw == null) return creds;
11731
+ if (typeof raw !== "object" || Array.isArray(raw)) {
11732
+ findings.push({ level: "blocking", id, message: `Integration "${id}": credentials must be a mapping of secret references.` });
11733
+ return creds;
11734
+ }
11735
+ for (const [key, value] of Object.entries(raw)) {
11736
+ if (key.endsWith(ENV_SUFFIX)) {
11737
+ const name = key.slice(0, -ENV_SUFFIX.length);
11738
+ if (typeof value === "string" && value.trim()) creds[name] = { env: value.trim() };
11739
+ else findings.push({ level: "blocking", id, message: `Integration "${id}": credential "${key}" must name an environment variable.` });
11740
+ continue;
11741
+ }
11742
+ if (SECRET_KEY.test(key) && typeof value === "string") {
11743
+ findings.push({ level: "blocking", id, message: `Integration "${id}": secret "${key}" must not be stored in config. Use "${key}${ENV_SUFFIX}: <ENV_VAR_NAME>".` });
11744
+ continue;
11745
+ }
11746
+ if (value && typeof value === "object" && typeof value.env === "string") {
11747
+ creds[key] = { env: String(value.env) };
11748
+ continue;
11749
+ }
11750
+ findings.push({ level: "warning", id, message: `Integration "${id}": ignoring unrecognized credential entry "${key}".` });
11751
+ }
11752
+ return creds;
11753
+ }
11754
+ function parseSecrets(raw, id, findings) {
11755
+ const secrets = {};
11756
+ if (raw == null) return secrets;
11757
+ if (typeof raw !== "object" || Array.isArray(raw)) {
11758
+ findings.push({ level: "blocking", id, message: `Integration "${id}": secrets must be a mapping of logical references.` });
11759
+ return secrets;
11760
+ }
11761
+ for (const [key, value] of Object.entries(raw)) {
11762
+ if (typeof value === "string" && value.trim()) {
11763
+ if (value.length > 100 || /^(ghp_|sk-|xox[bpsa]-|glpat-|ey[A-Za-z0-9])/i.test(value)) {
11764
+ findings.push({ level: "blocking", id, message: `Integration "${id}": secret "${key}" appears to contain an actual credential, not a reference name.` });
11765
+ continue;
11766
+ }
11767
+ secrets[key] = value.trim();
11768
+ } else {
11769
+ findings.push({ level: "warning", id, message: `Integration "${id}": ignoring non-string secret entry "${key}".` });
11770
+ }
11771
+ }
11772
+ return secrets;
11773
+ }
11774
+ function parseIntegrationsConfig(raw, opts) {
11775
+ const findings = [];
11776
+ const integrations = [];
11777
+ const list2 = raw && typeof raw === "object" && Array.isArray(raw.integrations) ? raw.integrations : Array.isArray(raw) ? raw : [];
11778
+ const seen = /* @__PURE__ */ new Set();
11779
+ for (const entry of list2) {
11780
+ if (!entry || typeof entry !== "object") {
11781
+ findings.push({ level: "blocking", message: "Each integration must be a mapping." });
11782
+ continue;
11783
+ }
11784
+ const o2 = entry;
11785
+ const id = typeof o2.id === "string" ? o2.id.trim() : "";
11786
+ const adapter = typeof o2.adapter === "string" ? o2.adapter.trim() : "";
11787
+ if (!id) {
11788
+ findings.push({ level: "blocking", message: 'An integration is missing a required "id".' });
11789
+ continue;
11790
+ }
11791
+ if (seen.has(id)) {
11792
+ findings.push({ level: "blocking", id, message: `Duplicate integration id "${id}".` });
11793
+ continue;
11794
+ }
11795
+ seen.add(id);
11796
+ if (!adapter) {
11797
+ findings.push({ level: "blocking", id, message: `Integration "${id}" is missing a required "adapter".` });
11798
+ continue;
11799
+ }
11800
+ if (!opts.adapterIds.has(adapter)) {
11801
+ findings.push({ level: "blocking", id, message: `Integration "${id}" references unknown adapter "${adapter}".` });
11802
+ }
11803
+ const enabled = o2.enabled === void 0 ? true : o2.enabled === true || o2.enabled === "true";
11804
+ const config = o2.config && typeof o2.config === "object" && !Array.isArray(o2.config) ? o2.config : {};
11805
+ const credentials = parseCredentials(o2.credentials, id, findings);
11806
+ const secrets = parseSecrets(o2.secrets, id, findings);
11807
+ const timeoutMs = typeof o2.timeout_ms === "number" ? o2.timeout_ms : typeof o2.timeoutMs === "number" ? o2.timeoutMs : void 0;
11808
+ integrations.push({ id, adapter, enabled, config, credentials, secrets, timeoutMs });
11809
+ }
11810
+ return { integrations, findings };
11811
+ }
11812
+ async function resolveAllCredentials(integration, resolver, env) {
11813
+ const credentials = {};
11814
+ const missing = [];
11815
+ for (const [name, ref] of Object.entries(integration.credentials)) {
11816
+ const value = env[ref.env];
11817
+ if (value && value.length > 0) credentials[name] = value;
11818
+ else missing.push(ref.env);
11819
+ }
11820
+ for (const [name, ref] of Object.entries(integration.secrets)) {
11821
+ if (credentials[name]) continue;
11822
+ const value = await resolver.resolve(ref);
11823
+ if (value !== void 0) credentials[name] = value;
11824
+ else missing.push(ref);
11825
+ }
11826
+ return { credentials, missing };
11827
+ }
11828
+
11829
+ // ../integrations/src/secrets.ts
11830
+ import { readFileSync as readFileSync2, writeFileSync, mkdirSync, existsSync as existsSync2, unlinkSync } from "fs";
11831
+ import { dirname as dirname2 } from "path";
11832
+ var SECRETS_FILENAME = ".secrets.json";
11833
+ function secretsPath(projectDir) {
11834
+ return `${projectDir}/.kaddo/${SECRETS_FILENAME}`;
11835
+ }
11836
+ function loadStore(filePath) {
11837
+ if (!existsSync2(filePath)) return {};
11838
+ try {
11839
+ const raw = readFileSync2(filePath, "utf-8");
11840
+ const parsed = JSON.parse(raw);
11841
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
11842
+ return {};
11843
+ } catch {
11844
+ return {};
11845
+ }
11846
+ }
11847
+ function saveStore(filePath, store) {
11848
+ mkdirSync(dirname2(filePath), { recursive: true });
11849
+ writeFileSync(filePath, JSON.stringify(store, null, 2) + "\n", "utf-8");
11850
+ }
11851
+ function createLocalSecretProvider(projectDir) {
11852
+ const fp = secretsPath(projectDir);
11853
+ return {
11854
+ async get(key) {
11855
+ const store = loadStore(fp);
11856
+ const val = store[key];
11857
+ return val !== void 0 && val !== "" ? val : void 0;
11858
+ },
11859
+ async set(key, value) {
11860
+ const store = loadStore(fp);
11861
+ store[key] = value;
11862
+ saveStore(fp, store);
11863
+ },
11864
+ async delete(key) {
11865
+ const store = loadStore(fp);
11866
+ delete store[key];
11867
+ saveStore(fp, store);
11868
+ },
11869
+ async exists(key) {
11870
+ const store = loadStore(fp);
11871
+ return key in store && store[key] !== void 0 && store[key] !== "";
11872
+ }
11873
+ };
11874
+ }
11875
+ function createEnvSecretProvider(env = process.env) {
11876
+ return {
11877
+ async get(key) {
11878
+ const val = env[key];
11879
+ return val !== void 0 && val !== "" ? val : void 0;
11880
+ },
11881
+ async set() {
11882
+ throw new Error("Environment secret provider is read-only.");
11883
+ },
11884
+ async delete() {
11885
+ throw new Error("Environment secret provider is read-only.");
11886
+ },
11887
+ async exists(key) {
11888
+ const val = env[key];
11889
+ return val !== void 0 && val !== "";
11890
+ }
11891
+ };
11892
+ }
11893
+ function createCompositeResolver(...providers) {
11894
+ return {
11895
+ async resolve(reference) {
11896
+ for (const provider of providers) {
11897
+ const val = await provider.get(reference);
11898
+ if (val !== void 0) return val;
11899
+ }
11900
+ return void 0;
11901
+ }
11902
+ };
11903
+ }
11904
+
11905
+ // ../integrations/src/registry.ts
11906
+ var DuplicateAdapterError = class extends Error {
11907
+ constructor(id) {
11908
+ super(`An integration adapter with id "${id}" is already registered.`);
11909
+ this.name = "DuplicateAdapterError";
11910
+ }
11911
+ };
11912
+ var IntegrationRegistry = class {
11913
+ adapters = /* @__PURE__ */ new Map();
11914
+ register(adapter) {
11915
+ if (this.adapters.has(adapter.id)) throw new DuplicateAdapterError(adapter.id);
11916
+ this.adapters.set(adapter.id, adapter);
11917
+ }
11918
+ get(id) {
11919
+ return this.adapters.get(id);
11920
+ }
11921
+ has(id) {
11922
+ return this.adapters.has(id);
11923
+ }
11924
+ list() {
11925
+ return [...this.adapters.values()].sort((a2, b2) => a2.id.localeCompare(b2.id));
11926
+ }
11927
+ ids() {
11928
+ return new Set(this.adapters.keys());
11929
+ }
11930
+ };
11931
+
11932
+ // ../integrations/src/mock-adapter.ts
11933
+ var MOCK_ADAPTER_ID = "mock";
11934
+ var DEFAULT_ITEMS = [
11935
+ {
11936
+ externalId: "EXT-001",
11937
+ provider: MOCK_ADAPTER_ID,
11938
+ title: "Open public registration to everyone",
11939
+ description: "Open self-service registration to the public once the private beta ends.",
11940
+ type: "Feature",
11941
+ status: "Open",
11942
+ url: "https://example.test/mock/EXT-001",
11943
+ labels: ["registration", "beta"],
11944
+ createdAt: "2026-01-05T10:00:00.000Z",
11945
+ updatedAt: "2026-02-01T09:30:00.000Z",
11946
+ rawMetadata: { board: "delivery" }
11947
+ },
11948
+ {
11949
+ externalId: "EXT-002",
11950
+ provider: MOCK_ADAPTER_ID,
11951
+ title: "Personalize onboarding steps",
11952
+ description: "The onboarding checklist should adapt to what the user has already completed.",
11953
+ type: "Task",
11954
+ status: "To Do",
11955
+ url: "https://example.test/mock/EXT-002",
11956
+ labels: ["onboarding"],
11957
+ createdAt: "2026-01-08T12:00:00.000Z",
11958
+ updatedAt: "2026-01-20T15:00:00.000Z"
11959
+ }
11960
+ ];
11961
+ function simulationOf(context, fallback) {
11962
+ const fromConfig = context.config?.simulate;
11963
+ return typeof fromConfig === "string" ? fromConfig : fallback;
11964
+ }
11965
+ function readFailure(sim) {
11966
+ switch (sim) {
11967
+ case "unauthorized":
11968
+ throw integrationError("INTEGRATION_UNAUTHORIZED");
11969
+ case "rate-limited":
11970
+ throw integrationError("INTEGRATION_RATE_LIMITED");
11971
+ case "unavailable":
11972
+ throw integrationError("INTEGRATION_UNAVAILABLE");
11973
+ case "timeout":
11974
+ throw integrationError("INTEGRATION_TIMEOUT");
11975
+ case "available":
11976
+ break;
11977
+ }
11978
+ }
11979
+ function createMockAdapter(opts = {}) {
11980
+ const items = opts.items ?? DEFAULT_ITEMS;
11981
+ const defaultSim = opts.simulate ?? "available";
11982
+ const defaultPageSize = opts.pageSize ?? 50;
11983
+ return {
11984
+ id: MOCK_ADAPTER_ID,
11985
+ metadata: {
11986
+ id: MOCK_ADAPTER_ID,
11987
+ displayName: "Mock Work Source",
11988
+ version: "1.0.0",
11989
+ description: "Deterministic offline reference adapter for validating the integration foundation.",
11990
+ configSchema: {
11991
+ simulate: {
11992
+ type: "select",
11993
+ required: false,
11994
+ label: "Simulation mode",
11995
+ description: "Controls what the mock adapter simulates during verify/read operations.",
11996
+ options: [
11997
+ { value: "available", label: "Available" },
11998
+ { value: "unauthorized", label: "Unauthorized" },
11999
+ { value: "rate-limited", label: "Rate Limited" },
12000
+ { value: "unavailable", label: "Unavailable" },
12001
+ { value: "timeout", label: "Timeout" }
12002
+ ],
12003
+ defaultValue: "available"
12004
+ }
12005
+ },
12006
+ secretSchema: {
12007
+ token: {
12008
+ type: "string",
12009
+ required: false,
12010
+ label: "API Token",
12011
+ description: "Optional token for testing secret handling (not used by the mock adapter)."
12012
+ }
12013
+ }
12014
+ },
12015
+ capabilities: {
12016
+ workItems: { list: true, read: true, import: true, write: false, statusSync: false, comments: false, webhooks: false }
12017
+ },
12018
+ async verifyConnection(context) {
12019
+ const sim = simulationOf(context, defaultSim);
12020
+ const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
12021
+ switch (sim) {
12022
+ case "available":
12023
+ return { status: "available", checkedAt };
12024
+ case "unauthorized":
12025
+ return { status: "unauthorized", message: "Check the configured credentials.", checkedAt };
12026
+ case "rate-limited":
12027
+ case "unavailable":
12028
+ case "timeout":
12029
+ return { status: "unavailable", message: "The mock provider is temporarily unavailable.", checkedAt };
12030
+ }
12031
+ },
12032
+ async listWorkItems(request) {
12033
+ readFailure(simulationOf(request.context, defaultSim));
12034
+ let pool = items;
12035
+ const f3 = request.filters;
12036
+ if (f3?.status) pool = pool.filter((i) => (i.status ?? "").toLowerCase() === f3.status.toLowerCase());
12037
+ if (f3?.query) pool = pool.filter((i) => `${i.title} ${i.description ?? ""}`.toLowerCase().includes(f3.query.toLowerCase()));
12038
+ if (f3?.updatedSince) pool = pool.filter((i) => (i.updatedAt ?? "") >= f3.updatedSince);
12039
+ const size = Math.max(1, request.pageSize ?? defaultPageSize);
12040
+ const start = request.cursor ? Math.max(0, Number.parseInt(request.cursor, 10) || 0) : 0;
12041
+ const slice = pool.slice(start, start + size);
12042
+ const end = start + slice.length;
12043
+ const hasMore = end < pool.length;
12044
+ return { items: slice, hasMore, ...hasMore ? { nextCursor: String(end) } : {} };
12045
+ },
12046
+ async getWorkItem(request) {
12047
+ readFailure(simulationOf(request.context, defaultSim));
12048
+ return items.find((i) => i.externalId === request.externalId) ?? null;
12049
+ }
12050
+ };
12051
+ }
12052
+
12053
+ // ../integrations/src/index.ts
12054
+ function createDefaultRegistry() {
12055
+ const registry2 = new IntegrationRegistry();
12056
+ registry2.register(createMockAdapter());
12057
+ return registry2;
12058
+ }
12059
+
12060
+ // ../cli/src/services/integrations.ts
12061
+ var INTEGRATIONS_FILE = ".kaddo/integrations.yml";
12062
+ var DEFAULT_TIMEOUT_MS = 1e4;
12063
+ var registry = createDefaultRegistry();
12064
+ var IntegrationServiceError = class extends Error {
12065
+ code;
12066
+ constructor(code, message) {
12067
+ super(message);
12068
+ this.name = "IntegrationServiceError";
12069
+ this.code = code;
12070
+ }
12071
+ };
12072
+ function loadRaw(dir) {
12073
+ const abs = join(dir, INTEGRATIONS_FILE);
12074
+ if (!exists(abs)) return { integrations: [] };
12075
+ try {
12076
+ return parseYaml12(readFile(abs)) ?? { integrations: [] };
12077
+ } catch {
12078
+ return { integrations: [] };
12079
+ }
12080
+ }
12081
+ function loadIntegrations(dir) {
12082
+ return parseIntegrationsConfig(loadRaw(dir), { adapterIds: registry.ids() });
12083
+ }
12084
+ function configStatus(integration, findings) {
12085
+ if (!integration.enabled) return "disabled";
12086
+ const blocking = findings.some((f3) => f3.id === integration.id && f3.level === "blocking");
12087
+ if (blocking || !registry.has(integration.adapter)) return "invalid-config";
12088
+ return "configured";
12089
+ }
12090
+ function secretResolver(dir, env) {
12091
+ return createCompositeResolver(createLocalSecretProvider(dir), createEnvSecretProvider(env));
12092
+ }
12093
+ function listIntegrations(dir) {
12094
+ const { integrations, findings } = loadIntegrations(dir);
12095
+ return integrations.map((integration) => {
12096
+ const adapter = registry.get(integration.adapter);
12097
+ return {
12098
+ id: integration.id,
12099
+ adapter: integration.adapter,
12100
+ enabled: integration.enabled,
12101
+ status: configStatus(integration, findings),
12102
+ displayName: adapter?.metadata.displayName ?? integration.adapter,
12103
+ capabilities: adapter?.capabilities ?? null,
12104
+ metadata: adapter?.metadata ?? null,
12105
+ credentialRefs: Object.values(integration.credentials).map((c) => c.env),
12106
+ secretRefs: Object.values(integration.secrets),
12107
+ secretStatus: {},
12108
+ findings: findings.filter((f3) => f3.id === integration.id)
12109
+ };
12110
+ });
12111
+ }
12112
+ function requireIntegration(dir, id) {
12113
+ const { integrations, findings } = loadIntegrations(dir);
12114
+ const integration = integrations.find((i) => i.id === id);
12115
+ if (!integration) throw new IntegrationServiceError("INTEGRATION_NOT_CONFIGURED", `No integration "${id}" is configured in this project.`);
12116
+ return { integration, findings, all: integrations };
12117
+ }
12118
+ function resolveAdapter(integration) {
12119
+ const adapter = registry.get(integration.adapter);
12120
+ if (!adapter) throw new IntegrationServiceError("ADAPTER_NOT_FOUND", `Unknown integration adapter "${integration.adapter}".`);
12121
+ return adapter;
12122
+ }
12123
+ async function buildContextWithSecrets(dir, integration, env) {
12124
+ const resolver = secretResolver(dir, env);
12125
+ const { credentials, missing } = await resolveAllCredentials(integration, resolver, env);
12126
+ return {
12127
+ context: { integrationId: integration.id, config: integration.config, credentials, timeoutMs: integration.timeoutMs ?? DEFAULT_TIMEOUT_MS },
12128
+ missing
12129
+ };
12130
+ }
12131
+ async function withTimeout(op, ms) {
12132
+ let timer;
12133
+ const timeout = new Promise((_3, reject) => {
12134
+ timer = setTimeout(() => reject(integrationError("INTEGRATION_TIMEOUT")), ms);
12135
+ });
12136
+ try {
12137
+ return await Promise.race([op, timeout]);
12138
+ } finally {
12139
+ if (timer) clearTimeout(timer);
12140
+ }
12141
+ }
12142
+ async function verifyIntegration(dir, id, env = process.env) {
12143
+ const { integration, findings } = requireIntegration(dir, id);
12144
+ const cfgStatus = configStatus(integration, findings);
12145
+ if (cfgStatus === "disabled") return { id, status: "disabled", connection: null, missingCredentials: [] };
12146
+ if (cfgStatus === "invalid-config") return { id, status: "invalid-config", connection: null, missingCredentials: [] };
12147
+ const adapter = resolveAdapter(integration);
12148
+ const { context, missing } = await buildContextWithSecrets(dir, integration, env);
12149
+ try {
12150
+ const connection = await withTimeout(adapter.verifyConnection(context), context.timeoutMs);
12151
+ return { id, status: statusOf(connection), connection, missingCredentials: missing, message: connection.message };
12152
+ } catch (err) {
12153
+ const e = err instanceof IntegrationError ? err : integrationError("INTEGRATION_PROVIDER_ERROR");
12154
+ return { id, status: e.code === "INTEGRATION_UNAUTHORIZED" ? "unauthorized" : "unavailable", connection: null, missingCredentials: missing, message: e.safeMessage };
12155
+ }
12156
+ }
12157
+ function statusOf(connection) {
12158
+ switch (connection.status) {
12159
+ case "available":
12160
+ return "available";
12161
+ case "unauthorized":
12162
+ return "unauthorized";
12163
+ case "unavailable":
12164
+ return "unavailable";
12165
+ case "invalid-config":
12166
+ return "invalid-config";
12167
+ }
12168
+ }
12169
+ async function listExternalWorkItems(dir, id, opts = {}, env = process.env) {
12170
+ const { integration } = requireIntegration(dir, id);
12171
+ const adapter = resolveAdapter(integration);
12172
+ if (!adapter.capabilities.workItems.list) throw integrationError("UNSUPPORTED_CAPABILITY", `Adapter "${adapter.id}" cannot list work items.`);
12173
+ const { context } = await buildContextWithSecrets(dir, integration, env);
12174
+ return withTimeout(adapter.listWorkItems({ context, cursor: opts.cursor, pageSize: opts.pageSize, filters: opts.filters }), context.timeoutMs);
12175
+ }
12176
+ async function getExternalWorkItem(dir, id, externalId, env = process.env) {
12177
+ const { integration } = requireIntegration(dir, id);
12178
+ const adapter = resolveAdapter(integration);
12179
+ if (!adapter.capabilities.workItems.read) throw integrationError("UNSUPPORTED_CAPABILITY", `Adapter "${adapter.id}" cannot read work items.`);
12180
+ const { context } = await buildContextWithSecrets(dir, integration, env);
12181
+ return withTimeout(adapter.getWorkItem({ context, externalId }), context.timeoutMs);
12182
+ }
12183
+
12184
+ // src/integrations.ts
12185
+ var ok4 = (data) => ({ ok: true, data });
12186
+ var fail3 = (message) => ({ ok: false, message });
12187
+ function safeError(err) {
12188
+ if (err instanceof IntegrationError) return fail3(`[${err.code}] ${err.safeMessage}`);
12189
+ return fail3("The integration operation failed.");
12190
+ }
12191
+ function integrationsListTool(root) {
12192
+ return ok4({
12193
+ integrations: listIntegrations(root).map((i) => ({
12194
+ id: i.id,
12195
+ adapter: i.adapter,
12196
+ enabled: i.enabled,
12197
+ status: i.status,
12198
+ displayName: i.displayName,
12199
+ capabilities: i.capabilities,
12200
+ // Only credential *reference* names — never values.
12201
+ credentialRefs: i.credentialRefs
12202
+ })),
12203
+ note: "Read-only. Reading external items never creates a Kaddo Work Item; import requires human confirmation."
12204
+ });
12205
+ }
12206
+ async function integrationsStatusTool(root, args) {
12207
+ try {
12208
+ const summaries = listIntegrations(root).filter((s) => !args.id || s.id === args.id);
12209
+ const results = await Promise.all(summaries.map(async (s) => {
12210
+ if (s.status === "disabled" || s.status === "invalid-config") return { id: s.id, status: s.status };
12211
+ const v = await verifyIntegration(root, s.id);
12212
+ return { id: v.id, status: v.status, message: v.message, missingCredentials: v.missingCredentials };
12213
+ }));
12214
+ return ok4({ statuses: results });
12215
+ } catch (err) {
12216
+ return safeError(err);
12217
+ }
12218
+ }
12219
+ async function integrationsWorkItemsTool(root, args) {
12220
+ try {
12221
+ const page = await listExternalWorkItems(root, args.id, {
12222
+ cursor: args.cursor,
12223
+ pageSize: args.pageSize,
12224
+ filters: { status: args.status, query: args.query }
12225
+ });
12226
+ return ok4({
12227
+ items: page.items,
12228
+ hasMore: page.hasMore,
12229
+ nextCursor: page.nextCursor ?? null,
12230
+ note: "Read-only external items. Import them explicitly (with human confirmation) to create a Kaddo Draft."
12231
+ });
12232
+ } catch (err) {
12233
+ return safeError(err);
12234
+ }
12235
+ }
12236
+ async function integrationsWorkItemTool(root, args) {
12237
+ try {
12238
+ const item = await getExternalWorkItem(root, args.id, args.externalId);
12239
+ if (!item) return ok4({ found: false });
12240
+ return ok4({ found: true, item });
12241
+ } catch (err) {
12242
+ return safeError(err);
12243
+ }
12244
+ }
12245
+
12246
+ // ../cli/src/core/context-pack.ts
12247
+ import matter14 from "gray-matter";
11659
12248
  var CONTEXT_PACK_VERSION = "1";
11660
12249
  var ARCH_DIR3 = "knowledge";
11661
12250
  function readScanJson(dir) {
@@ -11668,7 +12257,7 @@ function readScanJson(dir) {
11668
12257
  }
11669
12258
  }
11670
12259
  function firstParagraph4(markdown) {
11671
- const body = matter12(markdown).content.trim();
12260
+ const body = matter14(markdown).content.trim();
11672
12261
  const para = body.split("\n\n").map((p) => p.trim()).find((p) => p && !p.startsWith("#"));
11673
12262
  return para ?? "";
11674
12263
  }
@@ -12803,6 +13392,15 @@ function guarded(root, fn) {
12803
13392
  throw err;
12804
13393
  }
12805
13394
  }
13395
+ async function guardedAsync(root, fn) {
13396
+ try {
13397
+ assertKaddoProject(root);
13398
+ return await fn();
13399
+ } catch (err) {
13400
+ if (err instanceof KaddoMcpError) return { ok: false, message: err.message };
13401
+ throw err;
13402
+ }
13403
+ }
12806
13404
  function generated(root, label, fn) {
12807
13405
  try {
12808
13406
  assertKaddoProject(root);
@@ -13044,6 +13642,45 @@ function createServer(root) {
13044
13642
  },
13045
13643
  async (args) => toolText(guarded(root, () => systemImpactCandidatesTool(root, args)))
13046
13644
  );
13645
+ const INTEGRATION_READ_NOTE = "Read-only. Reading external items never creates a Kaddo Work Item; import is a separate, human-confirmed action.";
13646
+ server.registerTool(
13647
+ "kaddo_integrations_list",
13648
+ { title: "List integrations", description: `List configured external-work-system integrations and their capabilities. ${INTEGRATION_READ_NOTE}`, inputSchema: {} },
13649
+ async () => toolText(guarded(root, () => integrationsListTool(root)))
13650
+ );
13651
+ server.registerTool(
13652
+ "kaddo_integrations_status",
13653
+ {
13654
+ title: "Integration status",
13655
+ description: `Verify integrations and report connection status (available/unauthorized/unavailable/\u2026). Never returns secrets. ${INTEGRATION_READ_NOTE}`,
13656
+ inputSchema: { id: z3.string().optional() }
13657
+ },
13658
+ async (args) => toolText(await guardedAsync(root, () => integrationsStatusTool(root, args)))
13659
+ );
13660
+ server.registerTool(
13661
+ "kaddo_integrations_work_items",
13662
+ {
13663
+ title: "List external work items",
13664
+ description: `List external work items from an integration (paginated). ${INTEGRATION_READ_NOTE}`,
13665
+ inputSchema: {
13666
+ id: z3.string(),
13667
+ cursor: z3.string().optional(),
13668
+ pageSize: z3.number().int().positive().optional(),
13669
+ status: z3.string().optional(),
13670
+ query: z3.string().optional()
13671
+ }
13672
+ },
13673
+ async (args) => toolText(await guardedAsync(root, () => integrationsWorkItemsTool(root, args)))
13674
+ );
13675
+ server.registerTool(
13676
+ "kaddo_integrations_work_item",
13677
+ {
13678
+ title: "Read an external work item",
13679
+ description: `Read a single external work item by id. ${INTEGRATION_READ_NOTE}`,
13680
+ inputSchema: { id: z3.string(), externalId: z3.string() }
13681
+ },
13682
+ async (args) => toolText(await guardedAsync(root, () => integrationsWorkItemTool(root, args)))
13683
+ );
13047
13684
  let installedSkills = [];
13048
13685
  try {
13049
13686
  assertKaddoProject(root);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaddo/mcp",
3
- "version": "3.81.0",
3
+ "version": "3.83.0",
4
4
  "description": "MCP server for Kaddo project knowledge: read-only resources/tools/prompts plus safe derived generation under .kaddo/.",
5
5
  "license": "MIT",
6
6
  "repository": {