@kaddo/cli 3.80.0 → 3.82.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.
package/dist/core.js CHANGED
@@ -6583,6 +6583,7 @@ function parseWorkItemSource(frontmatter) {
6583
6583
  title: optStr(obj.title) ?? optStr(frontmatter.source_title),
6584
6584
  context: optStr(obj.context) ?? optStr(frontmatter.source_context),
6585
6585
  provider: optStr(obj.provider) ?? optStr(frontmatter.source_provider),
6586
+ integration: optStr(obj.integration) ?? optStr(frontmatter.source_integration),
6586
6587
  url: optStr(obj.url) ?? optStr(frontmatter.source_url),
6587
6588
  imported_at: optStr(obj.imported_at) ?? optStr(frontmatter.source_imported_at),
6588
6589
  synced_at: optStr(obj.synced_at) ?? optStr(frontmatter.source_synced_at),
@@ -8122,6 +8123,25 @@ function getWorkItem(dir, workItemId) {
8122
8123
  };
8123
8124
  return { ...detail, refinement: computeRefinementStatus(detail) };
8124
8125
  }
8126
+ function parseGraphReason(v) {
8127
+ if (!v || typeof v !== "object") return null;
8128
+ const o = v;
8129
+ const relationship = typeof o.relationship === "string" && o.relationship.trim() ? o.relationship.trim() : null;
8130
+ const path4 = Array.isArray(o.path) ? o.path.map(String).filter(Boolean) : [];
8131
+ return relationship || path4.length ? { relationship, path: path4 } : null;
8132
+ }
8133
+ function parseExplain(o) {
8134
+ const refs = Array.isArray(o.evidence) ? o.evidence : Array.isArray(o.evidence_refs) ? o.evidence_refs : [];
8135
+ return {
8136
+ reason: typeof o.reason === "string" && o.reason.trim() ? o.reason.trim() : null,
8137
+ graphReason: parseGraphReason(o.graph_reason),
8138
+ evidenceRefs: refs.map(String).filter(Boolean),
8139
+ evidenceSummary: typeof o.evidence_summary === "string" && o.evidence_summary.trim() ? o.evidence_summary.trim() : null
8140
+ };
8141
+ }
8142
+ function emptyExplain() {
8143
+ return { reason: null, graphReason: null, evidenceRefs: [], evidenceSummary: null };
8144
+ }
8125
8145
  function parseSystemImpact(dir, fm) {
8126
8146
  const topology = loadSystemTopology(dir);
8127
8147
  const byId = new Map(topology.entities.map((e) => [e.id, e]));
@@ -8129,10 +8149,19 @@ function parseSystemImpact(dir, fm) {
8129
8149
  const e = byId.get(id);
8130
8150
  return { id, nodeId: `sys:${id}`, label: e?.label ?? id, kind: e?.kind ?? "unknown", moduleId: e?.moduleId ?? null };
8131
8151
  };
8132
- const affected = Array.isArray(fm.affected_system_entities) ? fm.affected_system_entities.map(String).filter(Boolean).map(resolve) : [];
8133
- const reviewed = Array.isArray(fm.reviewed_system_entities) ? fm.reviewed_system_entities.filter((r) => Boolean(r) && typeof r === "object").map((r) => ({ ...resolve(String(r.id ?? "")), status: String(r.status ?? "unknown"), reason: r.reason ? String(r.reason) : null })).filter((r) => r.id) : [];
8152
+ const affected = Array.isArray(fm.affected_system_entities) ? fm.affected_system_entities.map((raw) => {
8153
+ if (typeof raw === "string") return raw ? { ...resolve(raw), ...emptyExplain() } : null;
8154
+ if (raw && typeof raw === "object") {
8155
+ const o = raw;
8156
+ const id = String(o.id ?? "");
8157
+ return id ? { ...resolve(id), ...parseExplain(o) } : null;
8158
+ }
8159
+ return null;
8160
+ }).filter((e) => e != null) : [];
8161
+ const reviewed = Array.isArray(fm.reviewed_system_entities) ? fm.reviewed_system_entities.filter((r) => Boolean(r) && typeof r === "object").map((r) => ({ ...resolve(String(r.id ?? "")), ...parseExplain(r), status: String(r.status ?? "unknown") })).filter((r) => r.id) : [];
8134
8162
  const graphRevision = typeof fm.graph_revision === "string" && fm.graph_revision.trim() ? fm.graph_revision.trim() : null;
8135
- return { affectedSystemEntities: affected, reviewedSystemEntities: reviewed, graphRevision };
8163
+ const graphCoverage = topology.entities.length === 0 ? "unavailable" : topology.relationships.length > 0 ? "available" : "partial";
8164
+ return { affectedSystemEntities: affected, reviewedSystemEntities: reviewed, graphRevision, graphCoverage };
8136
8165
  }
8137
8166
  function readBody(filePath) {
8138
8167
  try {
@@ -8806,6 +8835,7 @@ function createWorkItem(dir, opts) {
8806
8835
  const id = nextWorkItemId(dir);
8807
8836
  const title = intent.split(/\r?\n/)[0].trim().slice(0, 120);
8808
8837
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
8838
+ const source = opts.source ? { ...opts.source, inferred: false } : { type: "manual", inferred: false };
8809
8839
  const data = {
8810
8840
  type,
8811
8841
  id,
@@ -8813,7 +8843,7 @@ function createWorkItem(dir, opts) {
8813
8843
  status: "draft",
8814
8844
  work_type: type,
8815
8845
  created_at: today,
8816
- source: { type: "manual", inferred: false },
8846
+ source,
8817
8847
  generated_by: "kaddo-admin",
8818
8848
  affected_modules: [],
8819
8849
  summary: intent
@@ -8890,7 +8920,8 @@ function validateWorkItem(dir, id) {
8890
8920
  const topology = loadSystemTopology(dir);
8891
8921
  const entityById = new Map(topology.entities.map((e) => [e.id, e]));
8892
8922
  const fm = data;
8893
- const affectedEntities = Array.isArray(fm.affected_system_entities) ? fm.affected_system_entities.map(String) : [];
8923
+ const idOf = (raw2) => typeof raw2 === "string" ? raw2 : raw2 && typeof raw2 === "object" ? String(raw2.id ?? "") : "";
8924
+ const affectedEntities = Array.isArray(fm.affected_system_entities) ? fm.affected_system_entities.map(idOf).filter(Boolean) : [];
8894
8925
  for (const eid of affectedEntities) {
8895
8926
  const e = entityById.get(eid);
8896
8927
  if (!e) {
@@ -9276,11 +9307,12 @@ function buildRefinementHandoff(dir, workItemId) {
9276
9307
  const lines = [
9277
9308
  `Refine Work Item ${wi.id} \u2014 "${wi.title}" \u2014 in project "${projectName}" using Kaddo.`,
9278
9309
  "",
9279
- "Use a Kaddo-enabled agent with access to this repository. Drive the refinement with the",
9280
- `canonical ${RECOMMENDED_AGENT} and the ${RECOMMENDED_SKILL} skill (via Kaddo MCP or skills).`,
9310
+ `Use the canonical ${RECOMMENDED_AGENT} and the ${RECOMMENDED_SKILL} skill (via Kaddo MCP or skills),`,
9311
+ "with access to this repository.",
9281
9312
  "",
9282
- "Inspect the actual implementation before defining scope \u2014 do not guess affected modules from",
9283
- "the Work Item title. Read the current behavior in the code first, then classify."
9313
+ "Inspect the actual implementation and the relevant mapped modules before defining the final scope \u2014",
9314
+ "do not guess affected modules from the Work Item title. Read the current behavior in the code first,",
9315
+ "then classify."
9284
9316
  ];
9285
9317
  if (multirepo) {
9286
9318
  lines.push(
@@ -9293,15 +9325,23 @@ function buildRefinementHandoff(dir, workItemId) {
9293
9325
  if (topology !== "unavailable") {
9294
9326
  lines.push(
9295
9327
  "",
9296
- `The semantic system Graph is ${topology}. Identify the relevant system entry points, then use`,
9297
- "the Kaddo Graph (search / neighbors / paths) to find connected components, dependencies, APIs,",
9298
- "datastores and external systems. Treat Graph-derived entities as IMPACT CANDIDATES, not",
9299
- "confirmed scope: inspect each candidate in the repository and classify it as affected,",
9300
- "reviewed-not-affected or unknown, preserving the reason/evidence. A missing Graph edge does not",
9301
- "mean no impact \u2014 especially when coverage is partial."
9328
+ `The semantic system Graph is ${topology}. When semantic system topology is available:`,
9329
+ "1. identify the relevant system entry points;",
9330
+ "2. query the Kaddo Graph (search / neighbors / paths) for connected entities;",
9331
+ "3. treat Graph results as IMPACT CANDIDATES, not confirmed scope;",
9332
+ "4. inspect the actual implementation in the repository for each relevant candidate;",
9333
+ "5. classify each candidate as affected, reviewed-not-affected or unknown;",
9334
+ "6. preserve the evidence and reasons behind each classification.",
9335
+ "",
9336
+ "A missing Graph relationship does not mean no impact, especially when Graph coverage is partial \u2014",
9337
+ "keep inspecting the repository beyond the Graph candidates when the task requires it."
9302
9338
  );
9303
9339
  } else {
9304
- lines.push("", "The semantic system Graph is not available yet; refine using the repository and Knowledge.");
9340
+ lines.push(
9341
+ "",
9342
+ "The semantic system Graph is unavailable. Continue repository-driven refinement normally, using the",
9343
+ "repository, Knowledge and mapped modules \u2014 the Graph is enrichment, not a prerequisite."
9344
+ );
9305
9345
  }
9306
9346
  lines.push(
9307
9347
  "",
@@ -9328,7 +9368,462 @@ function buildRefinementHandoff(dir, workItemId) {
9328
9368
  text: lines.join("\n")
9329
9369
  };
9330
9370
  }
9371
+
9372
+ // src/services/integrations.ts
9373
+ import matter8 from "gray-matter";
9374
+ import { parse as parseYaml9 } from "yaml";
9375
+
9376
+ // ../integrations/src/errors.ts
9377
+ var RETRYABLE = /* @__PURE__ */ new Set([
9378
+ "INTEGRATION_RATE_LIMITED",
9379
+ "INTEGRATION_UNAVAILABLE",
9380
+ "INTEGRATION_TIMEOUT"
9381
+ ]);
9382
+ var IntegrationError = class extends Error {
9383
+ code;
9384
+ /** True for transient conditions (rate limit / unavailable / timeout). */
9385
+ retryable;
9386
+ /** A safe, provider-agnostic reason. Never the raw SDK message. */
9387
+ safeMessage;
9388
+ constructor(code, message) {
9389
+ super(message);
9390
+ this.name = "IntegrationError";
9391
+ this.code = code;
9392
+ this.retryable = RETRYABLE.has(code);
9393
+ this.safeMessage = message;
9394
+ }
9395
+ };
9396
+ function defaultMessageFor(code) {
9397
+ switch (code) {
9398
+ case "INTEGRATION_CONFIG_INVALID":
9399
+ return "The integration configuration is invalid.";
9400
+ case "INTEGRATION_UNAUTHORIZED":
9401
+ return "Authentication failed. Check the configured credentials.";
9402
+ case "INTEGRATION_FORBIDDEN":
9403
+ return "The configured credentials lack permission for this operation.";
9404
+ case "INTEGRATION_NOT_FOUND":
9405
+ return "The requested external resource was not found.";
9406
+ case "INTEGRATION_RATE_LIMITED":
9407
+ return "The external provider is rate limiting requests. Try again later.";
9408
+ case "INTEGRATION_UNAVAILABLE":
9409
+ return "The external provider is temporarily unavailable.";
9410
+ case "INTEGRATION_TIMEOUT":
9411
+ return "The external provider did not respond in time.";
9412
+ case "INTEGRATION_PROVIDER_ERROR":
9413
+ return "The external provider returned an error.";
9414
+ case "UNSUPPORTED_CAPABILITY":
9415
+ return "This adapter does not support the requested capability.";
9416
+ }
9417
+ }
9418
+ function integrationError(code, message) {
9419
+ return new IntegrationError(code, message ?? defaultMessageFor(code));
9420
+ }
9421
+
9422
+ // ../integrations/src/identity.ts
9423
+ function externalIdentityKey(integrationId, externalId) {
9424
+ return `${integrationId}#${externalId}`;
9425
+ }
9426
+ function externalDisplayKey(provider, externalId) {
9427
+ return `${provider}#${externalId}`;
9428
+ }
9429
+
9430
+ // ../integrations/src/config.ts
9431
+ var ENV_SUFFIX = "_env";
9432
+ var SECRET_KEY = /(token|secret|password|key|pat|apikey|api_key)/i;
9433
+ function parseCredentials(raw, id, findings) {
9434
+ const creds = {};
9435
+ if (raw == null) return creds;
9436
+ if (typeof raw !== "object" || Array.isArray(raw)) {
9437
+ findings.push({ level: "blocking", id, message: `Integration "${id}": credentials must be a mapping of secret references.` });
9438
+ return creds;
9439
+ }
9440
+ for (const [key, value] of Object.entries(raw)) {
9441
+ if (key.endsWith(ENV_SUFFIX)) {
9442
+ const name = key.slice(0, -ENV_SUFFIX.length);
9443
+ if (typeof value === "string" && value.trim()) creds[name] = { env: value.trim() };
9444
+ else findings.push({ level: "blocking", id, message: `Integration "${id}": credential "${key}" must name an environment variable.` });
9445
+ continue;
9446
+ }
9447
+ if (SECRET_KEY.test(key) && typeof value === "string") {
9448
+ findings.push({ level: "blocking", id, message: `Integration "${id}": secret "${key}" must not be stored in config. Use "${key}${ENV_SUFFIX}: <ENV_VAR_NAME>".` });
9449
+ continue;
9450
+ }
9451
+ if (value && typeof value === "object" && typeof value.env === "string") {
9452
+ creds[key] = { env: String(value.env) };
9453
+ continue;
9454
+ }
9455
+ findings.push({ level: "warning", id, message: `Integration "${id}": ignoring unrecognized credential entry "${key}".` });
9456
+ }
9457
+ return creds;
9458
+ }
9459
+ function parseIntegrationsConfig(raw, opts) {
9460
+ const findings = [];
9461
+ const integrations = [];
9462
+ const list2 = raw && typeof raw === "object" && Array.isArray(raw.integrations) ? raw.integrations : Array.isArray(raw) ? raw : [];
9463
+ const seen = /* @__PURE__ */ new Set();
9464
+ for (const entry of list2) {
9465
+ if (!entry || typeof entry !== "object") {
9466
+ findings.push({ level: "blocking", message: "Each integration must be a mapping." });
9467
+ continue;
9468
+ }
9469
+ const o = entry;
9470
+ const id = typeof o.id === "string" ? o.id.trim() : "";
9471
+ const adapter = typeof o.adapter === "string" ? o.adapter.trim() : "";
9472
+ if (!id) {
9473
+ findings.push({ level: "blocking", message: 'An integration is missing a required "id".' });
9474
+ continue;
9475
+ }
9476
+ if (seen.has(id)) {
9477
+ findings.push({ level: "blocking", id, message: `Duplicate integration id "${id}".` });
9478
+ continue;
9479
+ }
9480
+ seen.add(id);
9481
+ if (!adapter) {
9482
+ findings.push({ level: "blocking", id, message: `Integration "${id}" is missing a required "adapter".` });
9483
+ continue;
9484
+ }
9485
+ if (!opts.adapterIds.has(adapter)) {
9486
+ findings.push({ level: "blocking", id, message: `Integration "${id}" references unknown adapter "${adapter}".` });
9487
+ }
9488
+ const enabled = o.enabled === void 0 ? true : o.enabled === true || o.enabled === "true";
9489
+ const config = o.config && typeof o.config === "object" && !Array.isArray(o.config) ? o.config : {};
9490
+ const credentials = parseCredentials(o.credentials, id, findings);
9491
+ const timeoutMs = typeof o.timeout_ms === "number" ? o.timeout_ms : typeof o.timeoutMs === "number" ? o.timeoutMs : void 0;
9492
+ integrations.push({ id, adapter, enabled, config, credentials, timeoutMs });
9493
+ }
9494
+ return { integrations, findings };
9495
+ }
9496
+ function resolveCredentials(integration, env) {
9497
+ const credentials = {};
9498
+ const missing = [];
9499
+ for (const [name, ref] of Object.entries(integration.credentials)) {
9500
+ const value = env[ref.env];
9501
+ if (value && value.length > 0) credentials[name] = value;
9502
+ else missing.push(ref.env);
9503
+ }
9504
+ return { credentials, missing };
9505
+ }
9506
+
9507
+ // ../integrations/src/preview.ts
9508
+ function buildImportPreview(item, opts) {
9509
+ return {
9510
+ source: {
9511
+ provider: item.provider,
9512
+ integration: opts.integrationId,
9513
+ externalId: item.externalId,
9514
+ url: item.url,
9515
+ identityKey: externalIdentityKey(opts.integrationId, item.externalId),
9516
+ displayKey: externalDisplayKey(item.provider, item.externalId)
9517
+ },
9518
+ capturedIntent: item.title,
9519
+ description: item.description,
9520
+ externalType: item.type,
9521
+ externalStatus: item.status,
9522
+ kaddoStatus: "draft",
9523
+ kaddoType: opts.type ? opts.type : null,
9524
+ writes: false
9525
+ };
9526
+ }
9527
+
9528
+ // ../integrations/src/registry.ts
9529
+ var DuplicateAdapterError = class extends Error {
9530
+ constructor(id) {
9531
+ super(`An integration adapter with id "${id}" is already registered.`);
9532
+ this.name = "DuplicateAdapterError";
9533
+ }
9534
+ };
9535
+ var IntegrationRegistry = class {
9536
+ adapters = /* @__PURE__ */ new Map();
9537
+ register(adapter) {
9538
+ if (this.adapters.has(adapter.id)) throw new DuplicateAdapterError(adapter.id);
9539
+ this.adapters.set(adapter.id, adapter);
9540
+ }
9541
+ get(id) {
9542
+ return this.adapters.get(id);
9543
+ }
9544
+ has(id) {
9545
+ return this.adapters.has(id);
9546
+ }
9547
+ list() {
9548
+ return [...this.adapters.values()].sort((a, b) => a.id.localeCompare(b.id));
9549
+ }
9550
+ ids() {
9551
+ return new Set(this.adapters.keys());
9552
+ }
9553
+ };
9554
+
9555
+ // ../integrations/src/mock-adapter.ts
9556
+ var MOCK_ADAPTER_ID = "mock";
9557
+ var DEFAULT_ITEMS = [
9558
+ {
9559
+ externalId: "EXT-001",
9560
+ provider: MOCK_ADAPTER_ID,
9561
+ title: "Open public registration to everyone",
9562
+ description: "Open self-service registration to the public once the private beta ends.",
9563
+ type: "Feature",
9564
+ status: "Open",
9565
+ url: "https://example.test/mock/EXT-001",
9566
+ labels: ["registration", "beta"],
9567
+ createdAt: "2026-01-05T10:00:00.000Z",
9568
+ updatedAt: "2026-02-01T09:30:00.000Z",
9569
+ rawMetadata: { board: "delivery" }
9570
+ },
9571
+ {
9572
+ externalId: "EXT-002",
9573
+ provider: MOCK_ADAPTER_ID,
9574
+ title: "Personalize onboarding steps",
9575
+ description: "The onboarding checklist should adapt to what the user has already completed.",
9576
+ type: "Task",
9577
+ status: "To Do",
9578
+ url: "https://example.test/mock/EXT-002",
9579
+ labels: ["onboarding"],
9580
+ createdAt: "2026-01-08T12:00:00.000Z",
9581
+ updatedAt: "2026-01-20T15:00:00.000Z"
9582
+ }
9583
+ ];
9584
+ function simulationOf(context, fallback) {
9585
+ const fromConfig = context.config?.simulate;
9586
+ return typeof fromConfig === "string" ? fromConfig : fallback;
9587
+ }
9588
+ function readFailure(sim) {
9589
+ switch (sim) {
9590
+ case "unauthorized":
9591
+ throw integrationError("INTEGRATION_UNAUTHORIZED");
9592
+ case "rate-limited":
9593
+ throw integrationError("INTEGRATION_RATE_LIMITED");
9594
+ case "unavailable":
9595
+ throw integrationError("INTEGRATION_UNAVAILABLE");
9596
+ case "timeout":
9597
+ throw integrationError("INTEGRATION_TIMEOUT");
9598
+ case "available":
9599
+ break;
9600
+ }
9601
+ }
9602
+ function createMockAdapter(opts = {}) {
9603
+ const items = opts.items ?? DEFAULT_ITEMS;
9604
+ const defaultSim = opts.simulate ?? "available";
9605
+ const defaultPageSize = opts.pageSize ?? 50;
9606
+ return {
9607
+ id: MOCK_ADAPTER_ID,
9608
+ metadata: {
9609
+ id: MOCK_ADAPTER_ID,
9610
+ displayName: "Mock Work Source",
9611
+ version: "1.0.0",
9612
+ description: "Deterministic offline reference adapter for validating the integration foundation."
9613
+ },
9614
+ capabilities: {
9615
+ workItems: { list: true, read: true, import: true, write: false, statusSync: false, comments: false, webhooks: false }
9616
+ },
9617
+ async verifyConnection(context) {
9618
+ const sim = simulationOf(context, defaultSim);
9619
+ const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
9620
+ switch (sim) {
9621
+ case "available":
9622
+ return { status: "available", checkedAt };
9623
+ case "unauthorized":
9624
+ return { status: "unauthorized", message: "Check the configured credentials.", checkedAt };
9625
+ case "rate-limited":
9626
+ case "unavailable":
9627
+ case "timeout":
9628
+ return { status: "unavailable", message: "The mock provider is temporarily unavailable.", checkedAt };
9629
+ }
9630
+ },
9631
+ async listWorkItems(request) {
9632
+ readFailure(simulationOf(request.context, defaultSim));
9633
+ let pool = items;
9634
+ const f = request.filters;
9635
+ if (f?.status) pool = pool.filter((i) => (i.status ?? "").toLowerCase() === f.status.toLowerCase());
9636
+ if (f?.query) pool = pool.filter((i) => `${i.title} ${i.description ?? ""}`.toLowerCase().includes(f.query.toLowerCase()));
9637
+ if (f?.updatedSince) pool = pool.filter((i) => (i.updatedAt ?? "") >= f.updatedSince);
9638
+ const size = Math.max(1, request.pageSize ?? defaultPageSize);
9639
+ const start = request.cursor ? Math.max(0, Number.parseInt(request.cursor, 10) || 0) : 0;
9640
+ const slice = pool.slice(start, start + size);
9641
+ const end = start + slice.length;
9642
+ const hasMore = end < pool.length;
9643
+ return { items: slice, hasMore, ...hasMore ? { nextCursor: String(end) } : {} };
9644
+ },
9645
+ async getWorkItem(request) {
9646
+ readFailure(simulationOf(request.context, defaultSim));
9647
+ return items.find((i) => i.externalId === request.externalId) ?? null;
9648
+ }
9649
+ };
9650
+ }
9651
+
9652
+ // ../integrations/src/index.ts
9653
+ function createDefaultRegistry() {
9654
+ const registry2 = new IntegrationRegistry();
9655
+ registry2.register(createMockAdapter());
9656
+ return registry2;
9657
+ }
9658
+
9659
+ // src/services/integrations.ts
9660
+ var INTEGRATIONS_FILE = ".kaddo/integrations.yml";
9661
+ var DEFAULT_TIMEOUT_MS = 1e4;
9662
+ var registry = createDefaultRegistry();
9663
+ function integrationRegistry() {
9664
+ return registry;
9665
+ }
9666
+ var IntegrationServiceError = class extends Error {
9667
+ code;
9668
+ constructor(code, message) {
9669
+ super(message);
9670
+ this.name = "IntegrationServiceError";
9671
+ this.code = code;
9672
+ }
9673
+ };
9674
+ function loadRaw(dir) {
9675
+ const abs = join(dir, INTEGRATIONS_FILE);
9676
+ if (!exists(abs)) return { integrations: [] };
9677
+ try {
9678
+ return parseYaml9(readFile(abs)) ?? { integrations: [] };
9679
+ } catch {
9680
+ return { integrations: [] };
9681
+ }
9682
+ }
9683
+ function loadIntegrations(dir) {
9684
+ return parseIntegrationsConfig(loadRaw(dir), { adapterIds: registry.ids() });
9685
+ }
9686
+ function configStatus(integration, findings) {
9687
+ if (!integration.enabled) return "disabled";
9688
+ const blocking = findings.some((f) => f.id === integration.id && f.level === "blocking");
9689
+ if (blocking || !registry.has(integration.adapter)) return "invalid-config";
9690
+ return "configured";
9691
+ }
9692
+ function listIntegrations(dir) {
9693
+ const { integrations, findings } = loadIntegrations(dir);
9694
+ return integrations.map((integration) => {
9695
+ const adapter = registry.get(integration.adapter);
9696
+ return {
9697
+ id: integration.id,
9698
+ adapter: integration.adapter,
9699
+ enabled: integration.enabled,
9700
+ status: configStatus(integration, findings),
9701
+ displayName: adapter?.metadata.displayName ?? integration.adapter,
9702
+ capabilities: adapter?.capabilities ?? null,
9703
+ metadata: adapter?.metadata ?? null,
9704
+ credentialRefs: Object.values(integration.credentials).map((c) => c.env),
9705
+ findings: findings.filter((f) => f.id === integration.id)
9706
+ };
9707
+ });
9708
+ }
9709
+ function requireIntegration(dir, id) {
9710
+ const { integrations, findings } = loadIntegrations(dir);
9711
+ const integration = integrations.find((i) => i.id === id);
9712
+ if (!integration) throw new IntegrationServiceError("INTEGRATION_NOT_CONFIGURED", `No integration "${id}" is configured in this project.`);
9713
+ return { integration, findings };
9714
+ }
9715
+ function resolveAdapter(integration) {
9716
+ const adapter = registry.get(integration.adapter);
9717
+ if (!adapter) throw new IntegrationServiceError("ADAPTER_NOT_FOUND", `Unknown integration adapter "${integration.adapter}".`);
9718
+ return adapter;
9719
+ }
9720
+ function buildContext(integration, env) {
9721
+ const { credentials, missing } = resolveCredentials(integration, env);
9722
+ return {
9723
+ context: { integrationId: integration.id, config: integration.config, credentials, timeoutMs: integration.timeoutMs ?? DEFAULT_TIMEOUT_MS },
9724
+ missing
9725
+ };
9726
+ }
9727
+ async function withTimeout(op, ms) {
9728
+ let timer;
9729
+ const timeout = new Promise((_, reject) => {
9730
+ timer = setTimeout(() => reject(integrationError("INTEGRATION_TIMEOUT")), ms);
9731
+ });
9732
+ try {
9733
+ return await Promise.race([op, timeout]);
9734
+ } finally {
9735
+ if (timer) clearTimeout(timer);
9736
+ }
9737
+ }
9738
+ async function verifyIntegration(dir, id, env = process.env) {
9739
+ const { integration, findings } = requireIntegration(dir, id);
9740
+ const cfgStatus = configStatus(integration, findings);
9741
+ if (cfgStatus === "disabled") return { id, status: "disabled", connection: null, missingCredentials: [] };
9742
+ if (cfgStatus === "invalid-config") return { id, status: "invalid-config", connection: null, missingCredentials: [] };
9743
+ const adapter = resolveAdapter(integration);
9744
+ const { context, missing } = buildContext(integration, env);
9745
+ try {
9746
+ const connection = await withTimeout(adapter.verifyConnection(context), context.timeoutMs);
9747
+ return { id, status: statusOf(connection), connection, missingCredentials: missing, message: connection.message };
9748
+ } catch (err) {
9749
+ const e = err instanceof IntegrationError ? err : integrationError("INTEGRATION_PROVIDER_ERROR");
9750
+ return { id, status: e.code === "INTEGRATION_UNAUTHORIZED" ? "unauthorized" : "unavailable", connection: null, missingCredentials: missing, message: e.safeMessage };
9751
+ }
9752
+ }
9753
+ function statusOf(connection) {
9754
+ switch (connection.status) {
9755
+ case "available":
9756
+ return "available";
9757
+ case "unauthorized":
9758
+ return "unauthorized";
9759
+ case "unavailable":
9760
+ return "unavailable";
9761
+ case "invalid-config":
9762
+ return "invalid-config";
9763
+ }
9764
+ }
9765
+ async function listExternalWorkItems(dir, id, opts = {}, env = process.env) {
9766
+ const { integration } = requireIntegration(dir, id);
9767
+ const adapter = resolveAdapter(integration);
9768
+ if (!adapter.capabilities.workItems.list) throw integrationError("UNSUPPORTED_CAPABILITY", `Adapter "${adapter.id}" cannot list work items.`);
9769
+ const { context } = buildContext(integration, env);
9770
+ return withTimeout(adapter.listWorkItems({ context, cursor: opts.cursor, pageSize: opts.pageSize, filters: opts.filters }), context.timeoutMs);
9771
+ }
9772
+ async function getExternalWorkItem(dir, id, externalId, env = process.env) {
9773
+ const { integration } = requireIntegration(dir, id);
9774
+ const adapter = resolveAdapter(integration);
9775
+ if (!adapter.capabilities.workItems.read) throw integrationError("UNSUPPORTED_CAPABILITY", `Adapter "${adapter.id}" cannot read work items.`);
9776
+ const { context } = buildContext(integration, env);
9777
+ return withTimeout(adapter.getWorkItem({ context, externalId }), context.timeoutMs);
9778
+ }
9779
+ function findLinkedWorkItem(dir, integrationId, externalId) {
9780
+ for (const art of discoverWorkItems(dir)) {
9781
+ let data;
9782
+ try {
9783
+ data = matter8(readFile(art.filePath)).data;
9784
+ } catch {
9785
+ continue;
9786
+ }
9787
+ const source = parseWorkItemSource(data);
9788
+ if (source.integration === integrationId && source.id === externalId) {
9789
+ return { workItemId: String(data.id ?? ""), title: String(data.title ?? "") };
9790
+ }
9791
+ }
9792
+ return null;
9793
+ }
9794
+ async function previewImport(dir, id, externalId, opts = {}, env = process.env) {
9795
+ const item = await getExternalWorkItem(dir, id, externalId, env);
9796
+ if (!item) throw integrationError("INTEGRATION_NOT_FOUND", `External work item "${externalId}" was not found.`);
9797
+ const preview = buildImportPreview(item, { integrationId: id, type: opts.type });
9798
+ return { preview, duplicate: findLinkedWorkItem(dir, id, externalId) };
9799
+ }
9800
+ async function importExternalWorkItem(dir, id, externalId, opts, env = process.env) {
9801
+ const item = await getExternalWorkItem(dir, id, externalId, env);
9802
+ if (!item) throw integrationError("INTEGRATION_NOT_FOUND", `External work item "${externalId}" was not found.`);
9803
+ const preview = buildImportPreview(item, { integrationId: id, type: opts.type });
9804
+ const existing = findLinkedWorkItem(dir, id, externalId);
9805
+ if (existing) return { workItemId: existing.workItemId, created: false, duplicateOf: existing.workItemId, preview };
9806
+ const intent = item.description ? `${item.title}
9807
+
9808
+ ${item.description}` : item.title;
9809
+ const created = createWorkItem(dir, {
9810
+ intent,
9811
+ type: opts.type,
9812
+ source: {
9813
+ type: "external",
9814
+ provider: item.provider,
9815
+ integration: id,
9816
+ id: externalId,
9817
+ url: item.url,
9818
+ imported_at: (/* @__PURE__ */ new Date()).toISOString()
9819
+ }
9820
+ });
9821
+ return { workItemId: created.id, created: true, preview, path: created.path };
9822
+ }
9331
9823
  export {
9824
+ INTEGRATIONS_FILE,
9825
+ IntegrationError,
9826
+ IntegrationServiceError,
9332
9827
  TOPOLOGY_FILE,
9333
9828
  TopologyWriteError,
9334
9829
  WorkItemNotFoundError,
@@ -9347,7 +9842,9 @@ export {
9347
9842
  discoverKnowledge,
9348
9843
  discoverWorkItems,
9349
9844
  exists,
9845
+ findLinkedWorkItem,
9350
9846
  findSystemPaths,
9847
+ getExternalWorkItem,
9351
9848
  getImpactCandidates,
9352
9849
  getSystemMapProjection,
9353
9850
  getSystemNeighbors,
@@ -9357,15 +9854,20 @@ export {
9357
9854
  getWorkItemForEdit,
9358
9855
  getWorkItems,
9359
9856
  getWorkItemsSummary,
9857
+ importExternalWorkItem,
9858
+ integrationRegistry,
9360
9859
  isActiveState,
9361
9860
  isModule,
9362
9861
  join,
9363
9862
  knowledgeLayers,
9364
9863
  lifecycleCounts,
9365
9864
  lifecycleStateOf,
9865
+ listExternalWorkItems,
9866
+ listIntegrations,
9366
9867
  loadConfig,
9367
9868
  loadMappedModules,
9368
9869
  loadSystemTopology,
9870
+ previewImport,
9369
9871
  readFile,
9370
9872
  searchSystemNodes,
9371
9873
  topologyRevision,
@@ -9373,5 +9875,6 @@ export {
9373
9875
  updateWorkItem,
9374
9876
  validateSystemTopology,
9375
9877
  validateTopologyProposal,
9376
- validateWorkItem
9878
+ validateWorkItem,
9879
+ verifyIntegration
9377
9880
  };