@kaddo/cli 3.81.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.
@@ -5,7 +5,7 @@
5
5
  <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
7
  <title>admin</title>
8
- <script type="module" crossorigin src="/assets/index-xgxqXGxn.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-DIfpc20n.js"></script>
9
9
  <link rel="stylesheet" crossorigin href="/assets/index-DNkI-1xG.css">
10
10
  </head>
11
11
  <body>
@@ -70,6 +70,14 @@ import {
70
70
  buildRefinementHandoff as coreBuildRefinementHandoff,
71
71
  getSystemMapProjection as coreGetSystemMapProjection,
72
72
  buildTopologyEnrichmentHandoff as coreBuildTopologyHandoff,
73
+ listIntegrations as coreListIntegrations,
74
+ verifyIntegration as coreVerifyIntegration,
75
+ listExternalWorkItems as coreListExternalWorkItems,
76
+ getExternalWorkItem as coreGetExternalWorkItem,
77
+ previewImport as corePreviewImport,
78
+ importExternalWorkItem as coreImportExternalWorkItem,
79
+ IntegrationError,
80
+ IntegrationServiceError,
73
81
  WorkItemWriteError,
74
82
  exists,
75
83
  join,
@@ -335,6 +343,58 @@ var CoreError = class extends Error {
335
343
  }
336
344
  code;
337
345
  };
346
+ function mapIntegrationError(err) {
347
+ if (err instanceof IntegrationError) throw new CoreError(err.code, err.safeMessage);
348
+ if (err instanceof IntegrationServiceError) throw new CoreError(err.code, err.message);
349
+ throw err;
350
+ }
351
+ function assertExternalId(externalId) {
352
+ if (!externalId || externalId.includes("/") || externalId.includes("\\") || externalId.includes("..")) {
353
+ throw new CoreError("INVALID_EXTERNAL_ID", "Invalid external work item identifier.");
354
+ }
355
+ }
356
+ function getIntegrations(dir) {
357
+ return coreListIntegrations(dir);
358
+ }
359
+ async function getIntegrationStatus(dir, id) {
360
+ try {
361
+ return await coreVerifyIntegration(dir, id);
362
+ } catch (err) {
363
+ mapIntegrationError(err);
364
+ }
365
+ }
366
+ async function getExternalWorkItems(dir, id, opts) {
367
+ try {
368
+ return await coreListExternalWorkItems(dir, id, { cursor: opts.cursor, pageSize: opts.pageSize, filters: { status: opts.status, query: opts.query } });
369
+ } catch (err) {
370
+ mapIntegrationError(err);
371
+ }
372
+ }
373
+ async function getExternalWorkItemDetail(dir, id, externalId) {
374
+ assertExternalId(externalId);
375
+ try {
376
+ return await coreGetExternalWorkItem(dir, id, externalId);
377
+ } catch (err) {
378
+ mapIntegrationError(err);
379
+ }
380
+ }
381
+ async function previewIntegrationImport(dir, id, externalId, opts) {
382
+ assertExternalId(externalId);
383
+ try {
384
+ return await corePreviewImport(dir, id, externalId, { type: opts.type });
385
+ } catch (err) {
386
+ mapIntegrationError(err);
387
+ }
388
+ }
389
+ async function importIntegrationWorkItem(dir, id, externalId, opts) {
390
+ assertExternalId(externalId);
391
+ try {
392
+ return await coreImportExternalWorkItem(dir, id, externalId, { type: opts.type });
393
+ } catch (err) {
394
+ if (err instanceof WorkItemWriteError) throw new CoreError(err.code, err.message);
395
+ mapIntegrationError(err);
396
+ }
397
+ }
338
398
 
339
399
  // src/contracts/schemas.ts
340
400
  import { z } from "zod";
@@ -827,6 +887,44 @@ async function createAdminServer(opts) {
827
887
  app.get("/api/v1/admin/findings", coreRoute(getFindings));
828
888
  app.get("/api/v1/admin/system", coreRoute(getSystemMap));
829
889
  app.get("/api/v1/admin/system/topology-handoff", coreRoute(getTopologyHandoff));
890
+ const asyncCore = async (reply, fn) => {
891
+ try {
892
+ return await fn();
893
+ } catch (err) {
894
+ if (err instanceof CoreError) return reply.code(statusForCode(err.code)).send({ error: { code: err.code, message: err.message } });
895
+ throw err;
896
+ }
897
+ };
898
+ app.get("/api/v1/admin/integrations", coreRoute(getIntegrations));
899
+ app.get(
900
+ "/api/v1/admin/integrations/:id/status",
901
+ async (request, reply) => asyncCore(reply, () => getIntegrationStatus(projectDir, request.params.id))
902
+ );
903
+ app.get(
904
+ "/api/v1/admin/integrations/:id/work-items",
905
+ async (request, reply) => asyncCore(reply, () => getExternalWorkItems(projectDir, request.params.id, {
906
+ cursor: request.query.cursor,
907
+ pageSize: request.query.pageSize ? Number.parseInt(request.query.pageSize, 10) : void 0,
908
+ status: request.query.status,
909
+ query: request.query.query
910
+ }))
911
+ );
912
+ app.get(
913
+ "/api/v1/admin/integrations/:id/work-items/:externalId",
914
+ async (request, reply) => asyncCore(reply, () => getExternalWorkItemDetail(projectDir, request.params.id, request.params.externalId))
915
+ );
916
+ app.get(
917
+ "/api/v1/admin/integrations/:id/work-items/:externalId/import-preview",
918
+ async (request, reply) => asyncCore(reply, () => previewIntegrationImport(projectDir, request.params.id, request.params.externalId, { type: request.query.type }))
919
+ );
920
+ app.post(
921
+ "/api/v1/admin/integrations/:id/work-items/:externalId/import",
922
+ async (request, reply) => {
923
+ const type = request.body?.type;
924
+ if (!type) return reply.code(400).send({ error: { code: "INVALID_INPUT", message: "A Kaddo Work Item type is required to import." } });
925
+ return asyncCore(reply, () => importIntegrationWorkItem(projectDir, request.params.id, request.params.externalId, { type }));
926
+ }
927
+ );
830
928
  app.get("/api/v1/admin/knowledge/inventory", coreRoute(getKnowledgeInventory));
831
929
  app.get("/api/v1/admin/knowledge/artifact/:artifactId", async (request) => {
832
930
  try {
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),
@@ -8834,6 +8835,7 @@ function createWorkItem(dir, opts) {
8834
8835
  const id = nextWorkItemId(dir);
8835
8836
  const title = intent.split(/\r?\n/)[0].trim().slice(0, 120);
8836
8837
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
8838
+ const source = opts.source ? { ...opts.source, inferred: false } : { type: "manual", inferred: false };
8837
8839
  const data = {
8838
8840
  type,
8839
8841
  id,
@@ -8841,7 +8843,7 @@ function createWorkItem(dir, opts) {
8841
8843
  status: "draft",
8842
8844
  work_type: type,
8843
8845
  created_at: today,
8844
- source: { type: "manual", inferred: false },
8846
+ source,
8845
8847
  generated_by: "kaddo-admin",
8846
8848
  affected_modules: [],
8847
8849
  summary: intent
@@ -9366,7 +9368,462 @@ function buildRefinementHandoff(dir, workItemId) {
9366
9368
  text: lines.join("\n")
9367
9369
  };
9368
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
+ }
9369
9823
  export {
9824
+ INTEGRATIONS_FILE,
9825
+ IntegrationError,
9826
+ IntegrationServiceError,
9370
9827
  TOPOLOGY_FILE,
9371
9828
  TopologyWriteError,
9372
9829
  WorkItemNotFoundError,
@@ -9385,7 +9842,9 @@ export {
9385
9842
  discoverKnowledge,
9386
9843
  discoverWorkItems,
9387
9844
  exists,
9845
+ findLinkedWorkItem,
9388
9846
  findSystemPaths,
9847
+ getExternalWorkItem,
9389
9848
  getImpactCandidates,
9390
9849
  getSystemMapProjection,
9391
9850
  getSystemNeighbors,
@@ -9395,15 +9854,20 @@ export {
9395
9854
  getWorkItemForEdit,
9396
9855
  getWorkItems,
9397
9856
  getWorkItemsSummary,
9857
+ importExternalWorkItem,
9858
+ integrationRegistry,
9398
9859
  isActiveState,
9399
9860
  isModule,
9400
9861
  join,
9401
9862
  knowledgeLayers,
9402
9863
  lifecycleCounts,
9403
9864
  lifecycleStateOf,
9865
+ listExternalWorkItems,
9866
+ listIntegrations,
9404
9867
  loadConfig,
9405
9868
  loadMappedModules,
9406
9869
  loadSystemTopology,
9870
+ previewImport,
9407
9871
  readFile,
9408
9872
  searchSystemNodes,
9409
9873
  topologyRevision,
@@ -9411,5 +9875,6 @@ export {
9411
9875
  updateWorkItem,
9412
9876
  validateSystemTopology,
9413
9877
  validateTopologyProposal,
9414
- validateWorkItem
9878
+ validateWorkItem,
9879
+ verifyIntegration
9415
9880
  };