@kaddo/cli 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.
package/dist/index.js CHANGED
@@ -4,9 +4,6 @@
4
4
  import { createRequire } from "module";
5
5
  import { Command } from "commander";
6
6
 
7
- // src/commands/init.ts
8
- import path2 from "path";
9
-
10
7
  // src/utils/fs.ts
11
8
  import fs from "fs";
12
9
  import path from "path";
@@ -40,6 +37,9 @@ function join(...parts) {
40
37
  return path.join(...parts);
41
38
  }
42
39
 
40
+ // src/commands/init.ts
41
+ import path2 from "path";
42
+
43
43
  // src/utils/ui.ts
44
44
  import * as p from "@clack/prompts";
45
45
  var intro2 = (title) => p.intro(title);
@@ -1361,8 +1361,8 @@ function loadConfig(dir) {
1361
1361
  const parsed = configSchema.safeParse(raw ?? {});
1362
1362
  if (!parsed.success) {
1363
1363
  const issues = parsed.error.issues.map((i) => {
1364
- const path10 = i.path.join(".");
1365
- return path10 ? ` - ${path10}: ${i.message}` : ` - ${i.message}`;
1364
+ const path11 = i.path.join(".");
1365
+ return path11 ? ` - ${path11}: ${i.message}` : ` - ${i.message}`;
1366
1366
  }).join("\n");
1367
1367
  throw new ConfigError(`Invalid .kaddo/config.yml:
1368
1368
  ${issues}`);
@@ -6732,10 +6732,10 @@ function analyzeGuard(touchedFiles, artifacts, silentWithoutOwnership) {
6732
6732
  import { parse as parseYaml4, stringify as stringifyYaml2 } from "yaml";
6733
6733
  var IGNORE_FILE = ".kaddo/ignores.yml";
6734
6734
  function loadIgnores(dir) {
6735
- const path10 = join(dir, IGNORE_FILE);
6736
- if (!exists(path10)) return [];
6735
+ const path11 = join(dir, IGNORE_FILE);
6736
+ if (!exists(path11)) return [];
6737
6737
  try {
6738
- const raw = readFile(path10);
6738
+ const raw = readFile(path11);
6739
6739
  const parsed = parseYaml4(raw);
6740
6740
  return Array.isArray(parsed) ? parsed : [];
6741
6741
  } catch {
@@ -6743,7 +6743,7 @@ function loadIgnores(dir) {
6743
6743
  }
6744
6744
  }
6745
6745
  function saveIgnore(dir, entry) {
6746
- const path10 = join(dir, IGNORE_FILE);
6746
+ const path11 = join(dir, IGNORE_FILE);
6747
6747
  const existing = loadIgnores(dir);
6748
6748
  const idx = existing.findIndex((e) => e.artifact_id === entry.artifact_id);
6749
6749
  if (idx >= 0) {
@@ -6751,17 +6751,17 @@ function saveIgnore(dir, entry) {
6751
6751
  } else {
6752
6752
  existing.push(entry);
6753
6753
  }
6754
- writeFile(path10, stringifyYaml2(existing));
6754
+ writeFile(path11, stringifyYaml2(existing));
6755
6755
  }
6756
6756
  function isIgnored(ignores, artifactId) {
6757
6757
  return ignores.find((e) => e.artifact_id === artifactId);
6758
6758
  }
6759
6759
  function removeIgnore(dir, artifactId) {
6760
- const path10 = join(dir, IGNORE_FILE);
6760
+ const path11 = join(dir, IGNORE_FILE);
6761
6761
  const existing = loadIgnores(dir);
6762
6762
  const filtered = existing.filter((e) => e.artifact_id !== artifactId);
6763
6763
  if (filtered.length === existing.length) return false;
6764
- writeFile(path10, stringifyYaml2(filtered));
6764
+ writeFile(path11, stringifyYaml2(filtered));
6765
6765
  return true;
6766
6766
  }
6767
6767
 
@@ -6783,11 +6783,11 @@ function moduleArtifactCoverage(dir, id) {
6783
6783
  };
6784
6784
  }
6785
6785
  function loadMappedModules(dir) {
6786
- const path10 = join(dir, DESCRIPTOR_PATH);
6787
- if (!exists(path10)) return [];
6786
+ const path11 = join(dir, DESCRIPTOR_PATH);
6787
+ if (!exists(path11)) return [];
6788
6788
  let parsed;
6789
6789
  try {
6790
- parsed = parseYaml5(readFile(path10));
6790
+ parsed = parseYaml5(readFile(path11));
6791
6791
  } catch {
6792
6792
  return [];
6793
6793
  }
@@ -7531,7 +7531,7 @@ function buildGuardHistory(dir) {
7531
7531
  const d = hotspotDir(t.code_path);
7532
7532
  hotspotMap.set(d, (hotspotMap.get(d) ?? 0) + 1);
7533
7533
  }
7534
- const hotspots = [...hotspotMap.entries()].map(([path10, warnings]) => ({ path: path10, warnings })).sort((a, b) => b.warnings - a.warnings);
7534
+ const hotspots = [...hotspotMap.entries()].map(([path11, warnings]) => ({ path: path11, warnings })).sort((a, b) => b.warnings - a.warnings);
7535
7535
  return {
7536
7536
  available: true,
7537
7537
  total_runs: sorted.length,
@@ -10102,10 +10102,10 @@ function slugify2(name) {
10102
10102
  return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
10103
10103
  }
10104
10104
  function readModulesDescriptor(dir) {
10105
- const path10 = join(dir, DESCRIPTOR_PATH2);
10106
- if (!exists(path10)) return { version: 1, modules: [] };
10105
+ const path11 = join(dir, DESCRIPTOR_PATH2);
10106
+ if (!exists(path11)) return { version: 1, modules: [] };
10107
10107
  try {
10108
- const parsed = parseYaml9(readFile(path10));
10108
+ const parsed = parseYaml9(readFile(path11));
10109
10109
  return { version: parsed.version ?? 1, modules: parsed.modules ?? [] };
10110
10110
  } catch {
10111
10111
  return { version: 1, modules: [] };
@@ -10485,7 +10485,7 @@ function sectionParagraph(md, title) {
10485
10485
  }
10486
10486
  return "";
10487
10487
  }
10488
- function parseCapsule(id, path10, md) {
10488
+ function parseCapsule(id, path11, md) {
10489
10489
  const { data } = matter3(md);
10490
10490
  const updatedAt = data.updated_at ? String(data.updated_at) : void 0;
10491
10491
  let ageDays = null;
@@ -10495,7 +10495,7 @@ function parseCapsule(id, path10, md) {
10495
10495
  }
10496
10496
  return {
10497
10497
  id,
10498
- path: path10,
10498
+ path: path11,
10499
10499
  system: data.system ? String(data.system) : id,
10500
10500
  owner: data.owner ? String(data.owner) : void 0,
10501
10501
  updatedAt,
@@ -10525,9 +10525,9 @@ function addExternalCapsule(dir, sourceFile) {
10525
10525
  owner: data.owner ? String(data.owner) : void 0,
10526
10526
  lastImportedAt: (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
10527
10527
  };
10528
- const registry = loadExternalRegistry(dir).filter((e) => e.id !== id);
10529
- registry.push(entry);
10530
- writeFile(join(dir, EXTERNAL_PATH), serializeExternalRegistry(registry));
10528
+ const registry2 = loadExternalRegistry(dir).filter((e) => e.id !== id);
10529
+ registry2.push(entry);
10530
+ writeFile(join(dir, EXTERNAL_PATH), serializeExternalRegistry(registry2));
10531
10531
  return { id, destRel, entry, isCapsuleType };
10532
10532
  }
10533
10533
  function loadExternalCapsules(dir) {
@@ -10581,9 +10581,9 @@ function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
10581
10581
  ];
10582
10582
  const presentLayers = [];
10583
10583
  for (const layer2 of layerDocs) {
10584
- const path10 = layer2.files.map((f) => `${KNOWLEDGE3}/${f}`).find((rel) => exists(join(dir, rel)));
10585
- if (path10) {
10586
- addNode({ id: layer2.id, type: layer2.type, label: layer2.label, path: path10 });
10584
+ const path11 = layer2.files.map((f) => `${KNOWLEDGE3}/${f}`).find((rel) => exists(join(dir, rel)));
10585
+ if (path11) {
10586
+ addNode({ id: layer2.id, type: layer2.type, label: layer2.label, path: path11 });
10587
10587
  presentLayers.push(layer2.id);
10588
10588
  }
10589
10589
  }
@@ -12119,10 +12119,10 @@ function resolveNextStep(dir, now = /* @__PURE__ */ new Date()) {
12119
12119
  const q = (rel) => analyzeKnowledgeArtifact(dir, rel);
12120
12120
  const resolveAgent = (agent) => {
12121
12121
  const file = agent.endsWith(".md") ? agent : `${agent}.md`;
12122
- const path10 = agentInstallPath(file);
12123
- const installed = isFile(join(dir, path10));
12122
+ const path11 = agentInstallPath(file);
12123
+ const installed = isFile(join(dir, path11));
12124
12124
  const group = agentGroupOf(file);
12125
- return { agentPath: path10, agentInstalled: installed, installCommand: installed ? void 0 : `kaddo add agents --group ${group}` };
12125
+ return { agentPath: path11, agentInstalled: installed, installCommand: installed ? void 0 : `kaddo add agents --group ${group}` };
12126
12126
  };
12127
12127
  const moduleRepo = isModule(config);
12128
12128
  const coreRepo = isCore(config);
@@ -12727,6 +12727,7 @@ function parseWorkItemSource(frontmatter) {
12727
12727
  title: optStr(obj.title) ?? optStr(frontmatter.source_title),
12728
12728
  context: optStr(obj.context) ?? optStr(frontmatter.source_context),
12729
12729
  provider: optStr(obj.provider) ?? optStr(frontmatter.source_provider),
12730
+ integration: optStr(obj.integration) ?? optStr(frontmatter.source_integration),
12730
12731
  url: optStr(obj.url) ?? optStr(frontmatter.source_url),
12731
12732
  imported_at: optStr(obj.imported_at) ?? optStr(frontmatter.source_imported_at),
12732
12733
  synced_at: optStr(obj.synced_at) ?? optStr(frontmatter.source_synced_at),
@@ -17378,8 +17379,905 @@ async function runTopologyApply(file, opts = {}) {
17378
17379
  }
17379
17380
  }
17380
17381
 
17381
- // src/core/impact-report.ts
17382
+ // src/services/integrations.ts
17383
+ import matter11 from "gray-matter";
17384
+ import { parse as parseYaml17, stringify as stringifyYaml8 } from "yaml";
17385
+
17386
+ // src/core/work-item-write.ts
17387
+ import fs4 from "fs";
17388
+ import path8 from "path";
17389
+ import crypto2 from "crypto";
17382
17390
  import matter10 from "gray-matter";
17391
+ var WORK_ITEMS_DIR3 = "knowledge/delivery/work-items";
17392
+ var WorkItemWriteError = class extends Error {
17393
+ constructor(code, message) {
17394
+ super(message);
17395
+ this.code = code;
17396
+ this.name = "WorkItemWriteError";
17397
+ }
17398
+ code;
17399
+ };
17400
+ function revisionOf(raw) {
17401
+ return crypto2.createHash("sha256").update(raw, "utf-8").digest("hex");
17402
+ }
17403
+ function slugify5(s) {
17404
+ return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 50);
17405
+ }
17406
+ function nextWorkItemId2(dir) {
17407
+ const wiDir = join(dir, WORK_ITEMS_DIR3);
17408
+ let max = 0;
17409
+ const walk = (d) => {
17410
+ if (!exists(d)) return;
17411
+ for (const entry of fs4.readdirSync(d)) {
17412
+ const full = join(d, entry);
17413
+ if (isFile(full)) {
17414
+ const m = entry.match(/WI-(\d+)/);
17415
+ if (m) max = Math.max(max, parseInt(m[1], 10));
17416
+ } else if (!entry.startsWith(".")) {
17417
+ walk(full);
17418
+ }
17419
+ }
17420
+ };
17421
+ walk(wiDir);
17422
+ return `WI-${String(max + 1).padStart(3, "0")}`;
17423
+ }
17424
+ function atomicWrite2(filePath, content) {
17425
+ fs4.mkdirSync(path8.dirname(filePath), { recursive: true });
17426
+ const tmp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
17427
+ fs4.writeFileSync(tmp, content, "utf-8");
17428
+ try {
17429
+ fs4.renameSync(tmp, filePath);
17430
+ } catch (err) {
17431
+ try {
17432
+ fs4.rmSync(tmp, { force: true });
17433
+ } catch {
17434
+ }
17435
+ throw err;
17436
+ }
17437
+ }
17438
+ var SECTION_ORDER = [
17439
+ "Actor",
17440
+ "Outcome",
17441
+ "Current behavior",
17442
+ "Target behavior",
17443
+ "Entry points",
17444
+ "End-to-end flow",
17445
+ "Scope unknowns",
17446
+ "Acceptance criteria"
17447
+ ];
17448
+ var KNOWN_HEADINGS = new Set(SECTION_ORDER.map((s) => s.toLowerCase()));
17449
+ function renderList(items) {
17450
+ return items.map((i) => `- ${i.trim()}`).filter((l) => l.trim() !== "-").join("\n");
17451
+ }
17452
+ function renderCriteria(items) {
17453
+ return items.filter((c) => c.text.trim()).map((c) => c.checked === true ? `- [x] ${c.text.trim()}` : c.checked === false ? `- [ ] ${c.text.trim()}` : `- ${c.text.trim()}`).join("\n");
17454
+ }
17455
+ function desiredSections(input) {
17456
+ const d = /* @__PURE__ */ new Map();
17457
+ const put = (heading, value) => d.set(heading.toLowerCase(), value.trim() ? value.trim() : null);
17458
+ put("Actor", input.actor ?? "");
17459
+ put("Outcome", input.outcome ?? "");
17460
+ put("Current behavior", input.currentBehavior ?? "");
17461
+ put("Target behavior", input.targetBehavior ?? "");
17462
+ put("Entry points", input.entryPoints ?? "");
17463
+ put("End-to-end flow", input.endToEndFlow ?? "");
17464
+ d.set("scope unknowns", input.scopeUnknowns.some((u) => u.trim()) ? renderList(input.scopeUnknowns) : null);
17465
+ d.set("acceptance criteria", input.acceptanceCriteria.some((c) => c.text.trim()) ? renderCriteria(input.acceptanceCriteria) : null);
17466
+ return d;
17467
+ }
17468
+ function freshBody(input) {
17469
+ const preamble = `# ${input.title}
17470
+
17471
+ > Type: ${input.type}`;
17472
+ const out = [];
17473
+ const desired = desiredSections(input);
17474
+ for (const heading of SECTION_ORDER) {
17475
+ const body = desired.get(heading.toLowerCase());
17476
+ if (body != null) out.push(`## ${heading}
17477
+
17478
+ ${body}`);
17479
+ }
17480
+ return `${preamble}
17481
+
17482
+ ${out.join("\n\n")}
17483
+ `.replace(/\n{3,}/g, "\n\n");
17484
+ }
17485
+ function serialize(data, body) {
17486
+ return matter10.stringify(`
17487
+ ${body.trim()}
17488
+ `, data);
17489
+ }
17490
+ var CAPTURE_SECTIONS = [
17491
+ { field: "problem", heading: "Problem" },
17492
+ { field: "expected_result", heading: "Expected result" },
17493
+ { field: "impact", heading: "Impact" },
17494
+ { field: "acceptance_criteria", heading: "Acceptance criteria", list: true },
17495
+ { field: "design", heading: "Design" },
17496
+ { field: "risks", heading: "Risks" }
17497
+ ];
17498
+ function captureBody(title, type, answers) {
17499
+ const out = [`# ${title}`, "", `> Type: ${type}`];
17500
+ for (const { field, heading, list: list2 } of CAPTURE_SECTIONS) {
17501
+ const value = answers[field]?.trim();
17502
+ if (!value) continue;
17503
+ if (list2) {
17504
+ const items = value.split(/\r?\n/).map((l) => l.trim()).filter(Boolean).map((l) => /^[-*+]\s/.test(l) ? l : `- ${l}`);
17505
+ out.push("", `## ${heading}`, "", items.join("\n"));
17506
+ } else {
17507
+ out.push("", `## ${heading}`, "", value);
17508
+ }
17509
+ }
17510
+ return out.join("\n") + "\n";
17511
+ }
17512
+ function createWorkItem(dir, opts) {
17513
+ const intent = opts.intent.trim();
17514
+ if (!intent) throw new WorkItemWriteError("INVALID_INPUT", "An intent or summary is required.");
17515
+ const type = normalizeType(opts.type.trim()) ?? "";
17516
+ if (!type) throw new WorkItemWriteError("INVALID_INPUT", `Unknown Work Item type "${opts.type}".`);
17517
+ const id = nextWorkItemId2(dir);
17518
+ const title = intent.split(/\r?\n/)[0].trim().slice(0, 120);
17519
+ const today2 = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
17520
+ const source = opts.source ? { ...opts.source, inferred: false } : { type: "manual", inferred: false };
17521
+ const data = {
17522
+ type,
17523
+ id,
17524
+ title,
17525
+ status: "draft",
17526
+ work_type: type,
17527
+ created_at: today2,
17528
+ source,
17529
+ generated_by: "kaddo-admin",
17530
+ affected_modules: [],
17531
+ summary: intent
17532
+ };
17533
+ const answers = opts.answers ?? {};
17534
+ const hasAnswers = Object.values(answers).some((v) => v?.trim());
17535
+ const body = hasAnswers ? captureBody(title, type, answers) : freshBody({
17536
+ title,
17537
+ type,
17538
+ summary: intent,
17539
+ scopeUnknowns: [],
17540
+ affectedModules: [],
17541
+ moduleCoverage: [],
17542
+ impactAnalysis: [],
17543
+ acceptanceCriteria: [],
17544
+ decisions: [],
17545
+ relatedKnowledge: [],
17546
+ scopeConfidence: null
17547
+ });
17548
+ const relPath = `${WORK_ITEMS_DIR3}/draft/${id}-${slugify5(title)}.md`;
17549
+ const filePath = join(dir, relPath);
17550
+ if (exists(filePath)) throw new WorkItemWriteError("INVALID_INPUT", `Work Item file already exists: ${relPath}`);
17551
+ const raw = serialize(data, body);
17552
+ atomicWrite2(filePath, raw);
17553
+ return { id, path: relPath, revision: revisionOf(raw) };
17554
+ }
17555
+
17556
+ // ../integrations/src/errors.ts
17557
+ var RETRYABLE = /* @__PURE__ */ new Set([
17558
+ "INTEGRATION_RATE_LIMITED",
17559
+ "INTEGRATION_UNAVAILABLE",
17560
+ "INTEGRATION_TIMEOUT"
17561
+ ]);
17562
+ var IntegrationError = class extends Error {
17563
+ code;
17564
+ /** True for transient conditions (rate limit / unavailable / timeout). */
17565
+ retryable;
17566
+ /** A safe, provider-agnostic reason. Never the raw SDK message. */
17567
+ safeMessage;
17568
+ constructor(code, message) {
17569
+ super(message);
17570
+ this.name = "IntegrationError";
17571
+ this.code = code;
17572
+ this.retryable = RETRYABLE.has(code);
17573
+ this.safeMessage = message;
17574
+ }
17575
+ };
17576
+ function defaultMessageFor(code) {
17577
+ switch (code) {
17578
+ case "INTEGRATION_CONFIG_INVALID":
17579
+ return "The integration configuration is invalid.";
17580
+ case "INTEGRATION_UNAUTHORIZED":
17581
+ return "Authentication failed. Check the configured credentials.";
17582
+ case "INTEGRATION_FORBIDDEN":
17583
+ return "The configured credentials lack permission for this operation.";
17584
+ case "INTEGRATION_NOT_FOUND":
17585
+ return "The requested external resource was not found.";
17586
+ case "INTEGRATION_RATE_LIMITED":
17587
+ return "The external provider is rate limiting requests. Try again later.";
17588
+ case "INTEGRATION_UNAVAILABLE":
17589
+ return "The external provider is temporarily unavailable.";
17590
+ case "INTEGRATION_TIMEOUT":
17591
+ return "The external provider did not respond in time.";
17592
+ case "INTEGRATION_PROVIDER_ERROR":
17593
+ return "The external provider returned an error.";
17594
+ case "UNSUPPORTED_CAPABILITY":
17595
+ return "This adapter does not support the requested capability.";
17596
+ }
17597
+ }
17598
+ function integrationError(code, message) {
17599
+ return new IntegrationError(code, message ?? defaultMessageFor(code));
17600
+ }
17601
+
17602
+ // ../integrations/src/identity.ts
17603
+ function externalIdentityKey(integrationId, externalId) {
17604
+ return `${integrationId}#${externalId}`;
17605
+ }
17606
+ function externalDisplayKey(provider, externalId) {
17607
+ return `${provider}#${externalId}`;
17608
+ }
17609
+
17610
+ // ../integrations/src/config.ts
17611
+ var ENV_SUFFIX = "_env";
17612
+ var SECRET_KEY = /(token|secret|password|key|pat|apikey|api_key)/i;
17613
+ function parseCredentials(raw, id, findings) {
17614
+ const creds = {};
17615
+ if (raw == null) return creds;
17616
+ if (typeof raw !== "object" || Array.isArray(raw)) {
17617
+ findings.push({ level: "blocking", id, message: `Integration "${id}": credentials must be a mapping of secret references.` });
17618
+ return creds;
17619
+ }
17620
+ for (const [key, value] of Object.entries(raw)) {
17621
+ if (key.endsWith(ENV_SUFFIX)) {
17622
+ const name = key.slice(0, -ENV_SUFFIX.length);
17623
+ if (typeof value === "string" && value.trim()) creds[name] = { env: value.trim() };
17624
+ else findings.push({ level: "blocking", id, message: `Integration "${id}": credential "${key}" must name an environment variable.` });
17625
+ continue;
17626
+ }
17627
+ if (SECRET_KEY.test(key) && typeof value === "string") {
17628
+ findings.push({ level: "blocking", id, message: `Integration "${id}": secret "${key}" must not be stored in config. Use "${key}${ENV_SUFFIX}: <ENV_VAR_NAME>".` });
17629
+ continue;
17630
+ }
17631
+ if (value && typeof value === "object" && typeof value.env === "string") {
17632
+ creds[key] = { env: String(value.env) };
17633
+ continue;
17634
+ }
17635
+ findings.push({ level: "warning", id, message: `Integration "${id}": ignoring unrecognized credential entry "${key}".` });
17636
+ }
17637
+ return creds;
17638
+ }
17639
+ function parseSecrets(raw, id, findings) {
17640
+ const secrets = {};
17641
+ if (raw == null) return secrets;
17642
+ if (typeof raw !== "object" || Array.isArray(raw)) {
17643
+ findings.push({ level: "blocking", id, message: `Integration "${id}": secrets must be a mapping of logical references.` });
17644
+ return secrets;
17645
+ }
17646
+ for (const [key, value] of Object.entries(raw)) {
17647
+ if (typeof value === "string" && value.trim()) {
17648
+ if (value.length > 100 || /^(ghp_|sk-|xox[bpsa]-|glpat-|ey[A-Za-z0-9])/i.test(value)) {
17649
+ findings.push({ level: "blocking", id, message: `Integration "${id}": secret "${key}" appears to contain an actual credential, not a reference name.` });
17650
+ continue;
17651
+ }
17652
+ secrets[key] = value.trim();
17653
+ } else {
17654
+ findings.push({ level: "warning", id, message: `Integration "${id}": ignoring non-string secret entry "${key}".` });
17655
+ }
17656
+ }
17657
+ return secrets;
17658
+ }
17659
+ function parseIntegrationsConfig(raw, opts) {
17660
+ const findings = [];
17661
+ const integrations = [];
17662
+ const list2 = raw && typeof raw === "object" && Array.isArray(raw.integrations) ? raw.integrations : Array.isArray(raw) ? raw : [];
17663
+ const seen = /* @__PURE__ */ new Set();
17664
+ for (const entry of list2) {
17665
+ if (!entry || typeof entry !== "object") {
17666
+ findings.push({ level: "blocking", message: "Each integration must be a mapping." });
17667
+ continue;
17668
+ }
17669
+ const o = entry;
17670
+ const id = typeof o.id === "string" ? o.id.trim() : "";
17671
+ const adapter = typeof o.adapter === "string" ? o.adapter.trim() : "";
17672
+ if (!id) {
17673
+ findings.push({ level: "blocking", message: 'An integration is missing a required "id".' });
17674
+ continue;
17675
+ }
17676
+ if (seen.has(id)) {
17677
+ findings.push({ level: "blocking", id, message: `Duplicate integration id "${id}".` });
17678
+ continue;
17679
+ }
17680
+ seen.add(id);
17681
+ if (!adapter) {
17682
+ findings.push({ level: "blocking", id, message: `Integration "${id}" is missing a required "adapter".` });
17683
+ continue;
17684
+ }
17685
+ if (!opts.adapterIds.has(adapter)) {
17686
+ findings.push({ level: "blocking", id, message: `Integration "${id}" references unknown adapter "${adapter}".` });
17687
+ }
17688
+ const enabled = o.enabled === void 0 ? true : o.enabled === true || o.enabled === "true";
17689
+ const config = o.config && typeof o.config === "object" && !Array.isArray(o.config) ? o.config : {};
17690
+ const credentials = parseCredentials(o.credentials, id, findings);
17691
+ const secrets = parseSecrets(o.secrets, id, findings);
17692
+ const timeoutMs = typeof o.timeout_ms === "number" ? o.timeout_ms : typeof o.timeoutMs === "number" ? o.timeoutMs : void 0;
17693
+ integrations.push({ id, adapter, enabled, config, credentials, secrets, timeoutMs });
17694
+ }
17695
+ return { integrations, findings };
17696
+ }
17697
+ async function resolveAllCredentials(integration, resolver, env) {
17698
+ const credentials = {};
17699
+ const missing = [];
17700
+ for (const [name, ref] of Object.entries(integration.credentials)) {
17701
+ const value = env[ref.env];
17702
+ if (value && value.length > 0) credentials[name] = value;
17703
+ else missing.push(ref.env);
17704
+ }
17705
+ for (const [name, ref] of Object.entries(integration.secrets)) {
17706
+ if (credentials[name]) continue;
17707
+ const value = await resolver.resolve(ref);
17708
+ if (value !== void 0) credentials[name] = value;
17709
+ else missing.push(ref);
17710
+ }
17711
+ return { credentials, missing };
17712
+ }
17713
+
17714
+ // ../integrations/src/preview.ts
17715
+ function buildImportPreview(item, opts) {
17716
+ return {
17717
+ source: {
17718
+ provider: item.provider,
17719
+ integration: opts.integrationId,
17720
+ externalId: item.externalId,
17721
+ url: item.url,
17722
+ identityKey: externalIdentityKey(opts.integrationId, item.externalId),
17723
+ displayKey: externalDisplayKey(item.provider, item.externalId)
17724
+ },
17725
+ capturedIntent: item.title,
17726
+ description: item.description,
17727
+ externalType: item.type,
17728
+ externalStatus: item.status,
17729
+ kaddoStatus: "draft",
17730
+ kaddoType: opts.type ? opts.type : null,
17731
+ writes: false
17732
+ };
17733
+ }
17734
+
17735
+ // ../integrations/src/secrets.ts
17736
+ import { readFileSync as readFileSync2, writeFileSync, mkdirSync, existsSync as existsSync2, unlinkSync } from "fs";
17737
+ import { dirname as dirname2 } from "path";
17738
+ var SECRETS_FILENAME = ".secrets.json";
17739
+ function secretsPath(projectDir) {
17740
+ return `${projectDir}/.kaddo/${SECRETS_FILENAME}`;
17741
+ }
17742
+ function loadStore(filePath) {
17743
+ if (!existsSync2(filePath)) return {};
17744
+ try {
17745
+ const raw = readFileSync2(filePath, "utf-8");
17746
+ const parsed = JSON.parse(raw);
17747
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
17748
+ return {};
17749
+ } catch {
17750
+ return {};
17751
+ }
17752
+ }
17753
+ function saveStore(filePath, store) {
17754
+ mkdirSync(dirname2(filePath), { recursive: true });
17755
+ writeFileSync(filePath, JSON.stringify(store, null, 2) + "\n", "utf-8");
17756
+ }
17757
+ function createLocalSecretProvider(projectDir) {
17758
+ const fp = secretsPath(projectDir);
17759
+ return {
17760
+ async get(key) {
17761
+ const store = loadStore(fp);
17762
+ const val = store[key];
17763
+ return val !== void 0 && val !== "" ? val : void 0;
17764
+ },
17765
+ async set(key, value) {
17766
+ const store = loadStore(fp);
17767
+ store[key] = value;
17768
+ saveStore(fp, store);
17769
+ },
17770
+ async delete(key) {
17771
+ const store = loadStore(fp);
17772
+ delete store[key];
17773
+ saveStore(fp, store);
17774
+ },
17775
+ async exists(key) {
17776
+ const store = loadStore(fp);
17777
+ return key in store && store[key] !== void 0 && store[key] !== "";
17778
+ }
17779
+ };
17780
+ }
17781
+ function createEnvSecretProvider(env = process.env) {
17782
+ return {
17783
+ async get(key) {
17784
+ const val = env[key];
17785
+ return val !== void 0 && val !== "" ? val : void 0;
17786
+ },
17787
+ async set() {
17788
+ throw new Error("Environment secret provider is read-only.");
17789
+ },
17790
+ async delete() {
17791
+ throw new Error("Environment secret provider is read-only.");
17792
+ },
17793
+ async exists(key) {
17794
+ const val = env[key];
17795
+ return val !== void 0 && val !== "";
17796
+ }
17797
+ };
17798
+ }
17799
+ function createCompositeResolver(...providers) {
17800
+ return {
17801
+ async resolve(reference) {
17802
+ for (const provider of providers) {
17803
+ const val = await provider.get(reference);
17804
+ if (val !== void 0) return val;
17805
+ }
17806
+ return void 0;
17807
+ }
17808
+ };
17809
+ }
17810
+
17811
+ // ../integrations/src/registry.ts
17812
+ var DuplicateAdapterError = class extends Error {
17813
+ constructor(id) {
17814
+ super(`An integration adapter with id "${id}" is already registered.`);
17815
+ this.name = "DuplicateAdapterError";
17816
+ }
17817
+ };
17818
+ var IntegrationRegistry = class {
17819
+ adapters = /* @__PURE__ */ new Map();
17820
+ register(adapter) {
17821
+ if (this.adapters.has(adapter.id)) throw new DuplicateAdapterError(adapter.id);
17822
+ this.adapters.set(adapter.id, adapter);
17823
+ }
17824
+ get(id) {
17825
+ return this.adapters.get(id);
17826
+ }
17827
+ has(id) {
17828
+ return this.adapters.has(id);
17829
+ }
17830
+ list() {
17831
+ return [...this.adapters.values()].sort((a, b) => a.id.localeCompare(b.id));
17832
+ }
17833
+ ids() {
17834
+ return new Set(this.adapters.keys());
17835
+ }
17836
+ };
17837
+
17838
+ // ../integrations/src/mock-adapter.ts
17839
+ var MOCK_ADAPTER_ID = "mock";
17840
+ var DEFAULT_ITEMS = [
17841
+ {
17842
+ externalId: "EXT-001",
17843
+ provider: MOCK_ADAPTER_ID,
17844
+ title: "Open public registration to everyone",
17845
+ description: "Open self-service registration to the public once the private beta ends.",
17846
+ type: "Feature",
17847
+ status: "Open",
17848
+ url: "https://example.test/mock/EXT-001",
17849
+ labels: ["registration", "beta"],
17850
+ createdAt: "2026-01-05T10:00:00.000Z",
17851
+ updatedAt: "2026-02-01T09:30:00.000Z",
17852
+ rawMetadata: { board: "delivery" }
17853
+ },
17854
+ {
17855
+ externalId: "EXT-002",
17856
+ provider: MOCK_ADAPTER_ID,
17857
+ title: "Personalize onboarding steps",
17858
+ description: "The onboarding checklist should adapt to what the user has already completed.",
17859
+ type: "Task",
17860
+ status: "To Do",
17861
+ url: "https://example.test/mock/EXT-002",
17862
+ labels: ["onboarding"],
17863
+ createdAt: "2026-01-08T12:00:00.000Z",
17864
+ updatedAt: "2026-01-20T15:00:00.000Z"
17865
+ }
17866
+ ];
17867
+ function simulationOf(context, fallback) {
17868
+ const fromConfig = context.config?.simulate;
17869
+ return typeof fromConfig === "string" ? fromConfig : fallback;
17870
+ }
17871
+ function readFailure(sim) {
17872
+ switch (sim) {
17873
+ case "unauthorized":
17874
+ throw integrationError("INTEGRATION_UNAUTHORIZED");
17875
+ case "rate-limited":
17876
+ throw integrationError("INTEGRATION_RATE_LIMITED");
17877
+ case "unavailable":
17878
+ throw integrationError("INTEGRATION_UNAVAILABLE");
17879
+ case "timeout":
17880
+ throw integrationError("INTEGRATION_TIMEOUT");
17881
+ case "available":
17882
+ break;
17883
+ }
17884
+ }
17885
+ function createMockAdapter(opts = {}) {
17886
+ const items = opts.items ?? DEFAULT_ITEMS;
17887
+ const defaultSim = opts.simulate ?? "available";
17888
+ const defaultPageSize = opts.pageSize ?? 50;
17889
+ return {
17890
+ id: MOCK_ADAPTER_ID,
17891
+ metadata: {
17892
+ id: MOCK_ADAPTER_ID,
17893
+ displayName: "Mock Work Source",
17894
+ version: "1.0.0",
17895
+ description: "Deterministic offline reference adapter for validating the integration foundation.",
17896
+ configSchema: {
17897
+ simulate: {
17898
+ type: "select",
17899
+ required: false,
17900
+ label: "Simulation mode",
17901
+ description: "Controls what the mock adapter simulates during verify/read operations.",
17902
+ options: [
17903
+ { value: "available", label: "Available" },
17904
+ { value: "unauthorized", label: "Unauthorized" },
17905
+ { value: "rate-limited", label: "Rate Limited" },
17906
+ { value: "unavailable", label: "Unavailable" },
17907
+ { value: "timeout", label: "Timeout" }
17908
+ ],
17909
+ defaultValue: "available"
17910
+ }
17911
+ },
17912
+ secretSchema: {
17913
+ token: {
17914
+ type: "string",
17915
+ required: false,
17916
+ label: "API Token",
17917
+ description: "Optional token for testing secret handling (not used by the mock adapter)."
17918
+ }
17919
+ }
17920
+ },
17921
+ capabilities: {
17922
+ workItems: { list: true, read: true, import: true, write: false, statusSync: false, comments: false, webhooks: false }
17923
+ },
17924
+ async verifyConnection(context) {
17925
+ const sim = simulationOf(context, defaultSim);
17926
+ const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
17927
+ switch (sim) {
17928
+ case "available":
17929
+ return { status: "available", checkedAt };
17930
+ case "unauthorized":
17931
+ return { status: "unauthorized", message: "Check the configured credentials.", checkedAt };
17932
+ case "rate-limited":
17933
+ case "unavailable":
17934
+ case "timeout":
17935
+ return { status: "unavailable", message: "The mock provider is temporarily unavailable.", checkedAt };
17936
+ }
17937
+ },
17938
+ async listWorkItems(request) {
17939
+ readFailure(simulationOf(request.context, defaultSim));
17940
+ let pool = items;
17941
+ const f = request.filters;
17942
+ if (f?.status) pool = pool.filter((i) => (i.status ?? "").toLowerCase() === f.status.toLowerCase());
17943
+ if (f?.query) pool = pool.filter((i) => `${i.title} ${i.description ?? ""}`.toLowerCase().includes(f.query.toLowerCase()));
17944
+ if (f?.updatedSince) pool = pool.filter((i) => (i.updatedAt ?? "") >= f.updatedSince);
17945
+ const size = Math.max(1, request.pageSize ?? defaultPageSize);
17946
+ const start = request.cursor ? Math.max(0, Number.parseInt(request.cursor, 10) || 0) : 0;
17947
+ const slice = pool.slice(start, start + size);
17948
+ const end = start + slice.length;
17949
+ const hasMore = end < pool.length;
17950
+ return { items: slice, hasMore, ...hasMore ? { nextCursor: String(end) } : {} };
17951
+ },
17952
+ async getWorkItem(request) {
17953
+ readFailure(simulationOf(request.context, defaultSim));
17954
+ return items.find((i) => i.externalId === request.externalId) ?? null;
17955
+ }
17956
+ };
17957
+ }
17958
+
17959
+ // ../integrations/src/index.ts
17960
+ function createDefaultRegistry() {
17961
+ const registry2 = new IntegrationRegistry();
17962
+ registry2.register(createMockAdapter());
17963
+ return registry2;
17964
+ }
17965
+
17966
+ // src/services/integrations.ts
17967
+ var INTEGRATIONS_FILE = ".kaddo/integrations.yml";
17968
+ var DEFAULT_TIMEOUT_MS = 1e4;
17969
+ var registry = createDefaultRegistry();
17970
+ var IntegrationServiceError = class extends Error {
17971
+ code;
17972
+ constructor(code, message) {
17973
+ super(message);
17974
+ this.name = "IntegrationServiceError";
17975
+ this.code = code;
17976
+ }
17977
+ };
17978
+ function loadRaw(dir) {
17979
+ const abs = join(dir, INTEGRATIONS_FILE);
17980
+ if (!exists(abs)) return { integrations: [] };
17981
+ try {
17982
+ return parseYaml17(readFile(abs)) ?? { integrations: [] };
17983
+ } catch {
17984
+ return { integrations: [] };
17985
+ }
17986
+ }
17987
+ function loadIntegrations(dir) {
17988
+ return parseIntegrationsConfig(loadRaw(dir), { adapterIds: registry.ids() });
17989
+ }
17990
+ function configStatus(integration, findings) {
17991
+ if (!integration.enabled) return "disabled";
17992
+ const blocking = findings.some((f) => f.id === integration.id && f.level === "blocking");
17993
+ if (blocking || !registry.has(integration.adapter)) return "invalid-config";
17994
+ return "configured";
17995
+ }
17996
+ function secretResolver(dir, env) {
17997
+ return createCompositeResolver(createLocalSecretProvider(dir), createEnvSecretProvider(env));
17998
+ }
17999
+ function listIntegrations(dir) {
18000
+ const { integrations, findings } = loadIntegrations(dir);
18001
+ return integrations.map((integration) => {
18002
+ const adapter = registry.get(integration.adapter);
18003
+ return {
18004
+ id: integration.id,
18005
+ adapter: integration.adapter,
18006
+ enabled: integration.enabled,
18007
+ status: configStatus(integration, findings),
18008
+ displayName: adapter?.metadata.displayName ?? integration.adapter,
18009
+ capabilities: adapter?.capabilities ?? null,
18010
+ metadata: adapter?.metadata ?? null,
18011
+ credentialRefs: Object.values(integration.credentials).map((c) => c.env),
18012
+ secretRefs: Object.values(integration.secrets),
18013
+ secretStatus: {},
18014
+ findings: findings.filter((f) => f.id === integration.id)
18015
+ };
18016
+ });
18017
+ }
18018
+ function requireIntegration(dir, id) {
18019
+ const { integrations, findings } = loadIntegrations(dir);
18020
+ const integration = integrations.find((i) => i.id === id);
18021
+ if (!integration) throw new IntegrationServiceError("INTEGRATION_NOT_CONFIGURED", `No integration "${id}" is configured in this project.`);
18022
+ return { integration, findings, all: integrations };
18023
+ }
18024
+ function resolveAdapter(integration) {
18025
+ const adapter = registry.get(integration.adapter);
18026
+ if (!adapter) throw new IntegrationServiceError("ADAPTER_NOT_FOUND", `Unknown integration adapter "${integration.adapter}".`);
18027
+ return adapter;
18028
+ }
18029
+ async function buildContextWithSecrets(dir, integration, env) {
18030
+ const resolver = secretResolver(dir, env);
18031
+ const { credentials, missing } = await resolveAllCredentials(integration, resolver, env);
18032
+ return {
18033
+ context: { integrationId: integration.id, config: integration.config, credentials, timeoutMs: integration.timeoutMs ?? DEFAULT_TIMEOUT_MS },
18034
+ missing
18035
+ };
18036
+ }
18037
+ async function withTimeout(op, ms) {
18038
+ let timer;
18039
+ const timeout = new Promise((_, reject) => {
18040
+ timer = setTimeout(() => reject(integrationError("INTEGRATION_TIMEOUT")), ms);
18041
+ });
18042
+ try {
18043
+ return await Promise.race([op, timeout]);
18044
+ } finally {
18045
+ if (timer) clearTimeout(timer);
18046
+ }
18047
+ }
18048
+ async function verifyIntegration(dir, id, env = process.env) {
18049
+ const { integration, findings } = requireIntegration(dir, id);
18050
+ const cfgStatus = configStatus(integration, findings);
18051
+ if (cfgStatus === "disabled") return { id, status: "disabled", connection: null, missingCredentials: [] };
18052
+ if (cfgStatus === "invalid-config") return { id, status: "invalid-config", connection: null, missingCredentials: [] };
18053
+ const adapter = resolveAdapter(integration);
18054
+ const { context, missing } = await buildContextWithSecrets(dir, integration, env);
18055
+ try {
18056
+ const connection = await withTimeout(adapter.verifyConnection(context), context.timeoutMs);
18057
+ return { id, status: statusOf(connection), connection, missingCredentials: missing, message: connection.message };
18058
+ } catch (err) {
18059
+ const e = err instanceof IntegrationError ? err : integrationError("INTEGRATION_PROVIDER_ERROR");
18060
+ return { id, status: e.code === "INTEGRATION_UNAUTHORIZED" ? "unauthorized" : "unavailable", connection: null, missingCredentials: missing, message: e.safeMessage };
18061
+ }
18062
+ }
18063
+ function statusOf(connection) {
18064
+ switch (connection.status) {
18065
+ case "available":
18066
+ return "available";
18067
+ case "unauthorized":
18068
+ return "unauthorized";
18069
+ case "unavailable":
18070
+ return "unavailable";
18071
+ case "invalid-config":
18072
+ return "invalid-config";
18073
+ }
18074
+ }
18075
+ async function listExternalWorkItems(dir, id, opts = {}, env = process.env) {
18076
+ const { integration } = requireIntegration(dir, id);
18077
+ const adapter = resolveAdapter(integration);
18078
+ if (!adapter.capabilities.workItems.list) throw integrationError("UNSUPPORTED_CAPABILITY", `Adapter "${adapter.id}" cannot list work items.`);
18079
+ const { context } = await buildContextWithSecrets(dir, integration, env);
18080
+ return withTimeout(adapter.listWorkItems({ context, cursor: opts.cursor, pageSize: opts.pageSize, filters: opts.filters }), context.timeoutMs);
18081
+ }
18082
+ async function getExternalWorkItem(dir, id, externalId, env = process.env) {
18083
+ const { integration } = requireIntegration(dir, id);
18084
+ const adapter = resolveAdapter(integration);
18085
+ if (!adapter.capabilities.workItems.read) throw integrationError("UNSUPPORTED_CAPABILITY", `Adapter "${adapter.id}" cannot read work items.`);
18086
+ const { context } = await buildContextWithSecrets(dir, integration, env);
18087
+ return withTimeout(adapter.getWorkItem({ context, externalId }), context.timeoutMs);
18088
+ }
18089
+ function findLinkedWorkItem(dir, integrationId, externalId) {
18090
+ for (const art of discoverWorkItems(dir)) {
18091
+ let data;
18092
+ try {
18093
+ data = matter11(readFile(art.filePath)).data;
18094
+ } catch {
18095
+ continue;
18096
+ }
18097
+ const source = parseWorkItemSource(data);
18098
+ if (source.integration === integrationId && source.id === externalId) {
18099
+ return { workItemId: String(data.id ?? ""), title: String(data.title ?? "") };
18100
+ }
18101
+ }
18102
+ return null;
18103
+ }
18104
+ async function previewImport(dir, id, externalId, opts = {}, env = process.env) {
18105
+ const item = await getExternalWorkItem(dir, id, externalId, env);
18106
+ if (!item) throw integrationError("INTEGRATION_NOT_FOUND", `External work item "${externalId}" was not found.`);
18107
+ const preview = buildImportPreview(item, { integrationId: id, type: opts.type });
18108
+ return { preview, duplicate: findLinkedWorkItem(dir, id, externalId) };
18109
+ }
18110
+ async function importExternalWorkItem(dir, id, externalId, opts, env = process.env) {
18111
+ const item = await getExternalWorkItem(dir, id, externalId, env);
18112
+ if (!item) throw integrationError("INTEGRATION_NOT_FOUND", `External work item "${externalId}" was not found.`);
18113
+ const preview = buildImportPreview(item, { integrationId: id, type: opts.type });
18114
+ const existing = findLinkedWorkItem(dir, id, externalId);
18115
+ if (existing) return { workItemId: existing.workItemId, created: false, duplicateOf: existing.workItemId, preview };
18116
+ const intent = item.description ? `${item.title}
18117
+
18118
+ ${item.description}` : item.title;
18119
+ const created = createWorkItem(dir, {
18120
+ intent,
18121
+ type: opts.type,
18122
+ source: {
18123
+ type: "external",
18124
+ provider: item.provider,
18125
+ integration: id,
18126
+ id: externalId,
18127
+ url: item.url,
18128
+ imported_at: (/* @__PURE__ */ new Date()).toISOString()
18129
+ }
18130
+ });
18131
+ return { workItemId: created.id, created: true, preview, path: created.path };
18132
+ }
18133
+
18134
+ // src/commands/integrations.ts
18135
+ function requireProject2(dir) {
18136
+ if (!loadConfig(dir)) {
18137
+ log2.error("No Kaddo project was found in the current directory.");
18138
+ process.exit(1);
18139
+ }
18140
+ }
18141
+ function printJson(value) {
18142
+ console.log(JSON.stringify(value, null, 2));
18143
+ }
18144
+ function fail(err) {
18145
+ if (err instanceof IntegrationError) log2.error(`[${err.code}] ${err.safeMessage}`);
18146
+ else if (err instanceof IntegrationServiceError) log2.error(`[${err.code}] ${err.message}`);
18147
+ else log2.error("The integration operation failed.");
18148
+ process.exit(1);
18149
+ }
18150
+ function runIntegrationsList(dir, opts) {
18151
+ requireProject2(dir);
18152
+ const items = listIntegrations(dir);
18153
+ if (opts.json) return printJson(items.map((i) => ({ id: i.id, adapter: i.adapter, enabled: i.enabled, status: i.status, capabilities: i.capabilities })));
18154
+ intro2("Kaddo integrations");
18155
+ if (items.length === 0) {
18156
+ log2.info("No integrations are configured. Add them to .kaddo/integrations.yml.");
18157
+ return outro2("Done.");
18158
+ }
18159
+ for (const i of items) {
18160
+ log2.info(`${i.id} \xB7 adapter ${i.adapter} \xB7 ${i.status}${i.enabled ? "" : " (disabled)"}`);
18161
+ if (i.capabilities) log2.message(` read: ${i.capabilities.workItems.read ? "\u2713" : "\u2717"} list: ${i.capabilities.workItems.list ? "\u2713" : "\u2717"} import: ${i.capabilities.workItems.import ? "\u2713" : "\u2717"}`);
18162
+ for (const f of i.findings) log2.warn(` [${f.level}] ${f.message}`);
18163
+ }
18164
+ outro2("Done.");
18165
+ }
18166
+ async function runIntegrationsStatus(dir, opts) {
18167
+ requireProject2(dir);
18168
+ const summaries = listIntegrations(dir);
18169
+ const results = await Promise.all(summaries.map(async (s) => {
18170
+ if (s.status === "disabled" || s.status === "invalid-config") return { id: s.id, status: s.status };
18171
+ try {
18172
+ const v = await verifyIntegration(dir, s.id);
18173
+ return { id: s.id, status: v.status, message: v.message, missingCredentials: v.missingCredentials };
18174
+ } catch (err) {
18175
+ return { id: s.id, status: "unavailable", message: err instanceof IntegrationError ? err.safeMessage : "Verification failed." };
18176
+ }
18177
+ }));
18178
+ if (opts.json) return printJson(results);
18179
+ intro2("Integration status");
18180
+ for (const r of results) {
18181
+ const line = `${r.id} \xB7 ${r.status}${r.message ? ` \u2014 ${r.message}` : ""}`;
18182
+ if (r.status === "available" || r.status === "configured") log2.info(line);
18183
+ else log2.warn(line);
18184
+ }
18185
+ outro2("Done.");
18186
+ }
18187
+ async function runIntegrationsVerify(dir, id, opts) {
18188
+ requireProject2(dir);
18189
+ try {
18190
+ const v = await verifyIntegration(dir, id);
18191
+ if (opts.json) return printJson({ id: v.id, status: v.status, message: v.message, missingCredentials: v.missingCredentials });
18192
+ intro2(`Verify ${id}`);
18193
+ const line = `Status: ${v.status}${v.message ? ` \u2014 ${v.message}` : ""}`;
18194
+ if (v.status === "available") log2.info(line);
18195
+ else log2.warn(line);
18196
+ if (v.missingCredentials.length) log2.warn(`Missing credentials: ${v.missingCredentials.join(", ")}`);
18197
+ outro2("Done.");
18198
+ } catch (err) {
18199
+ fail(err);
18200
+ }
18201
+ }
18202
+ async function runIntegrationsWorkItems(dir, id, opts) {
18203
+ requireProject2(dir);
18204
+ try {
18205
+ const page = await listExternalWorkItems(dir, id, {
18206
+ cursor: opts.cursor,
18207
+ pageSize: opts.pageSize ? Number.parseInt(opts.pageSize, 10) : void 0,
18208
+ filters: { status: opts.status, query: opts.query }
18209
+ });
18210
+ if (opts.json) return printJson(page);
18211
+ intro2(`External work items \xB7 ${id}`);
18212
+ for (const it of page.items) log2.info(`${it.externalId} \xB7 ${it.title}${it.status ? ` [${it.status}]` : ""}`);
18213
+ if (page.hasMore) log2.message(`More available \u2014 next cursor: ${page.nextCursor}`);
18214
+ outro2(`${page.items.length} item(s).`);
18215
+ } catch (err) {
18216
+ fail(err);
18217
+ }
18218
+ }
18219
+ async function runIntegrationsWorkItem(dir, id, externalId, opts) {
18220
+ requireProject2(dir);
18221
+ try {
18222
+ const item = await getExternalWorkItem(dir, id, externalId);
18223
+ if (!item) {
18224
+ log2.error(`External work item "${externalId}" was not found.`);
18225
+ process.exit(1);
18226
+ }
18227
+ if (opts.json) return printJson(item);
18228
+ intro2(`${item.provider} \xB7 ${item.externalId}`);
18229
+ log2.info(item.title);
18230
+ if (item.status) log2.message(`Status: ${item.status}`);
18231
+ if (item.type) log2.message(`Type: ${item.type}`);
18232
+ if (item.url) log2.message(`URL: ${item.url}`);
18233
+ if (item.description) log2.message(`
18234
+ ${item.description}`);
18235
+ outro2("Done.");
18236
+ } catch (err) {
18237
+ fail(err);
18238
+ }
18239
+ }
18240
+ async function runIntegrationsImport(dir, id, externalId, opts) {
18241
+ requireProject2(dir);
18242
+ try {
18243
+ const { preview, duplicate } = await previewImport(dir, id, externalId, { type: opts.type });
18244
+ intro2("Import external work item");
18245
+ if (duplicate) {
18246
+ log2.warn(`Already imported as ${duplicate.workItemId} \u2014 "${duplicate.title}".`);
18247
+ outro2("No duplicate was created.");
18248
+ return;
18249
+ }
18250
+ log2.info(`Source: ${preview.source.provider} \xB7 ${preview.source.externalId}`);
18251
+ log2.message(`Captured intent: ${preview.capturedIntent}`);
18252
+ log2.message("Will create: a Draft Work Item (needs refinement).");
18253
+ if (preview.externalType) log2.message(`External type: ${preview.externalType} (Kaddo type is chosen, never inferred)`);
18254
+ log2.message("No project files have been modified yet.");
18255
+ if (!opts.type) {
18256
+ log2.error("A Kaddo Work Item type is required. Re-run with --type <feature|fix|chore|\u2026>.");
18257
+ process.exit(1);
18258
+ }
18259
+ if (!opts.yes) {
18260
+ const ok = await confirm2({ message: `Import ${preview.source.displayKey} as a Draft ${opts.type} Work Item?` });
18261
+ if (ok !== true) {
18262
+ cancel2("Import cancelled. No files were changed.");
18263
+ process.exit(0);
18264
+ }
18265
+ }
18266
+ const result = await importExternalWorkItem(dir, id, externalId, { type: opts.type });
18267
+ if (!result.created) {
18268
+ log2.warn(`Already imported as ${result.duplicateOf}.`);
18269
+ outro2("No duplicate was created.");
18270
+ return;
18271
+ }
18272
+ log2.info(`Created ${result.workItemId} (Draft, needs refinement).`);
18273
+ outro2("Done. Refine it next with the Work Item refinement handoff.");
18274
+ } catch (err) {
18275
+ fail(err);
18276
+ }
18277
+ }
18278
+
18279
+ // src/core/impact-report.ts
18280
+ import matter12 from "gray-matter";
17383
18281
  function isBroadGlob(glob) {
17384
18282
  if (!glob.endsWith("/**")) return false;
17385
18283
  const prefix = glob.slice(0, -3);
@@ -17448,7 +18346,7 @@ function buildImpactReport(dir, opts = {}, now = /* @__PURE__ */ new Date()) {
17448
18346
  }
17449
18347
  }
17450
18348
  try {
17451
- const body = matter10(readFile(wi.filePath)).content;
18349
+ const body = matter12(readFile(wi.filePath)).content;
17452
18350
  if (hasSection(body, /acceptance|criterios de aceptaci/i)) withAcceptance++;
17453
18351
  else gaps.missing_acceptance_criteria.push(gapItem(wi, "Add an `## Acceptance Criteria` section."));
17454
18352
  if (hasSection(body, /definition of done|^#{1,6}\s*dod\b|definici[oó]n de (terminado|hecho)/i)) withDoD++;
@@ -17825,7 +18723,7 @@ function runReportImpact(opts = {}) {
17825
18723
  }
17826
18724
 
17827
18725
  // src/core/savings.ts
17828
- import { parse as parseYaml17 } from "yaml";
18726
+ import { parse as parseYaml18 } from "yaml";
17829
18727
  var DEFAULT_ASSUMPTIONS = {
17830
18728
  currency: "USD",
17831
18729
  hourly_cost: 40,
@@ -17843,7 +18741,7 @@ function loadAssumptions(dir) {
17843
18741
  const p2 = join(dir, SAVINGS_PATH);
17844
18742
  if (!exists(p2)) return { assumptions: { ...DEFAULT_ASSUMPTIONS }, source: "default" };
17845
18743
  try {
17846
- const raw = parseYaml17(readFile(p2));
18744
+ const raw = parseYaml18(readFile(p2));
17847
18745
  const a = raw?.assumptions ?? {};
17848
18746
  const t = raw?.team ?? {};
17849
18747
  const num = (v, d) => typeof v === "number" && Number.isFinite(v) ? v : d;
@@ -18525,7 +19423,7 @@ function runAdr(opts = {}) {
18525
19423
  }
18526
19424
 
18527
19425
  // src/commands/tech.ts
18528
- import fs4 from "fs";
19426
+ import fs5 from "fs";
18529
19427
  var DISCOVERY_FILES = ["architecture-notes.md", "decision-candidates.md"];
18530
19428
  var DISCOVERY_DIR = "knowledge/tech/discovery";
18531
19429
  function runTechOrganize(dir = cwd()) {
@@ -18542,7 +19440,7 @@ function runTechOrganize(dir = cwd()) {
18542
19440
  continue;
18543
19441
  }
18544
19442
  ensureDir(join(dir, DISCOVERY_DIR));
18545
- fs4.renameSync(from, to);
19443
+ fs5.renameSync(from, to);
18546
19444
  moved.push(`knowledge/tech/${file} \u2192 ${DISCOVERY_DIR}/${file}`);
18547
19445
  }
18548
19446
  if (moved.length > 0) {
@@ -18624,9 +19522,9 @@ function runAssetsUpdate(kind, opts = {}) {
18624
19522
  }
18625
19523
 
18626
19524
  // src/commands/ready.ts
18627
- import fs5 from "fs";
18628
- import path8 from "path";
18629
- import matter11 from "gray-matter";
19525
+ import fs6 from "fs";
19526
+ import path9 from "path";
19527
+ import matter13 from "gray-matter";
18630
19528
  function validateReadiness(content, frontmatter) {
18631
19529
  const warnings = [];
18632
19530
  const body = content.replace(/^---[\s\S]*?---/, "").trim();
@@ -18680,18 +19578,18 @@ function isResolvedSection(text3) {
18680
19578
  }
18681
19579
  function transitionToReady(filePath) {
18682
19580
  const raw = readFile(filePath);
18683
- const { data, content } = matter11(raw);
19581
+ const { data, content } = matter13(raw);
18684
19582
  const today2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
18685
19583
  data.status = "ready";
18686
19584
  data.ready_at = today2;
18687
- const newContent = matter11.stringify(content, data);
19585
+ const newContent = matter13.stringify(content, data);
18688
19586
  const posix = filePath.replace(/\\/g, "/");
18689
19587
  let newPath = filePath;
18690
19588
  if (posix.includes("/work-items/draft/")) {
18691
- const readyDir = path8.dirname(filePath).replace(/[/\\]draft$/, path8.sep + "ready");
19589
+ const readyDir = path9.dirname(filePath).replace(/[/\\]draft$/, path9.sep + "ready");
18692
19590
  ensureDir(readyDir);
18693
- const fileName = path8.basename(filePath);
18694
- newPath = path8.join(readyDir, fileName);
19591
+ const fileName = path9.basename(filePath);
19592
+ newPath = path9.join(readyDir, fileName);
18695
19593
  }
18696
19594
  return { newPath, newContent };
18697
19595
  }
@@ -18724,7 +19622,7 @@ async function runReady(id, opts = {}) {
18724
19622
  process.exit(1);
18725
19623
  }
18726
19624
  const raw = readFile(wi.filePath);
18727
- const { data } = matter11(raw);
19625
+ const { data } = matter13(raw);
18728
19626
  const source = parseWorkItemSource(data);
18729
19627
  const warnings = validateReadiness(raw, data);
18730
19628
  log2.info(`Work Item found:`);
@@ -18759,11 +19657,11 @@ async function runReady(id, opts = {}) {
18759
19657
  const { newPath, newContent } = transitionToReady(wi.filePath);
18760
19658
  if (newPath !== wi.filePath) {
18761
19659
  writeFile(newPath, newContent);
18762
- fs5.unlinkSync(wi.filePath);
19660
+ fs6.unlinkSync(wi.filePath);
18763
19661
  log2.success(`Updated status: ready`);
18764
19662
  log2.success(`Moved file:`);
18765
- log2.info(` ${path8.relative(dir, wi.filePath)}`);
18766
- log2.info(` \u2192 ${path8.relative(dir, newPath)}`);
19663
+ log2.info(` ${path9.relative(dir, wi.filePath)}`);
19664
+ log2.info(` \u2192 ${path9.relative(dir, newPath)}`);
18767
19665
  } else {
18768
19666
  writeFile(wi.filePath, newContent);
18769
19667
  log2.success(`Updated status: ready`);
@@ -18774,7 +19672,7 @@ async function runReady(id, opts = {}) {
18774
19672
  }
18775
19673
 
18776
19674
  // src/commands/admin.ts
18777
- import path9 from "path";
19675
+ import path10 from "path";
18778
19676
  import { fileURLToPath as fileURLToPath2 } from "url";
18779
19677
  import net from "net";
18780
19678
  function isPortAvailable(port, host) {
@@ -18789,13 +19687,13 @@ function isPortAvailable(port, host) {
18789
19687
  });
18790
19688
  }
18791
19689
  function resolveStaticDir() {
18792
- const __dirname = path9.dirname(fileURLToPath2(import.meta.url));
19690
+ const __dirname = path10.dirname(fileURLToPath2(import.meta.url));
18793
19691
  const candidates = [
18794
19692
  // Bundled inside CLI dist (npm install)
18795
- path9.resolve(__dirname, "admin-dist"),
19693
+ path10.resolve(__dirname, "admin-dist"),
18796
19694
  // Monorepo development
18797
- path9.resolve(__dirname, "..", "..", "admin", "dist"),
18798
- path9.resolve(__dirname, "..", "node_modules", "@kaddo", "admin", "dist")
19695
+ path10.resolve(__dirname, "..", "..", "admin", "dist"),
19696
+ path10.resolve(__dirname, "..", "node_modules", "@kaddo", "admin", "dist")
18799
19697
  ];
18800
19698
  for (const c of candidates) {
18801
19699
  if (exists(join(c, "index.html"))) return c;
@@ -18837,8 +19735,8 @@ async function runAdmin(opts = {}) {
18837
19735
  }
18838
19736
  let adminServer;
18839
19737
  try {
18840
- const __dirname = path9.dirname(fileURLToPath2(import.meta.url));
18841
- const bundled = path9.resolve(__dirname, "admin-server", "index.js");
19738
+ const __dirname = path10.dirname(fileURLToPath2(import.meta.url));
19739
+ const bundled = path10.resolve(__dirname, "admin-server", "index.js");
18842
19740
  if (exists(bundled)) {
18843
19741
  const { pathToFileURL } = await import("url");
18844
19742
  adminServer = await import(pathToFileURL(bundled).href);
@@ -18917,8 +19815,8 @@ var capsuleCmd = program.command("capsule").description("Export this project as
18917
19815
  capsuleCmd.command("export").description("Write a Knowledge Capsule about this project to .kaddo/exports/").option("--scope <scope>", 'Export scope: "system" includes all mapped modules').option("--module <id>", "Export a capsule for a specific mapped module").action((opts) => {
18918
19816
  runCapsuleExport(opts);
18919
19817
  });
18920
- capsuleCmd.command("add <path>").description("Register an external Knowledge Capsule as project context (.kaddo/external.yml)").action((path10) => {
18921
- runCapsuleAdd(path10);
19818
+ capsuleCmd.command("add <path>").description("Register an external Knowledge Capsule as project context (.kaddo/external.yml)").action((path11) => {
19819
+ runCapsuleAdd(path11);
18922
19820
  });
18923
19821
  var graphCmd = program.command("graph").description("Export the lightweight, file-based knowledge graph of the project");
18924
19822
  graphCmd.command("export").description("Write the knowledge graph to .kaddo/graph.json and .kaddo/graph.mmd").option("--scope <scope>", "Graph scope: active (default) or all").option("--format <format>", "Output format: json, mermaid (default: both)").action((opts) => {
@@ -18931,6 +19829,23 @@ topologyCmd.command("validate <file>").description("Validate a topology proposal
18931
19829
  topologyCmd.command("apply <file>").description("Apply a validated topology proposal to the canonical artifact after human confirmation").option("-y, --yes", "Skip the confirmation prompt (for already-approved automation)").action((file, opts) => {
18932
19830
  runTopologyApply(file, opts);
18933
19831
  });
19832
+ var integrationsCmd = program.command("integrations").description("Connect Kaddo to external work systems (read-first; import requires human confirmation)");
19833
+ integrationsCmd.command("list").description("List configured integrations and their capabilities").option("--json", "Output JSON").action((opts) => runIntegrationsList(cwd(), opts));
19834
+ integrationsCmd.command("status").description("Verify each enabled integration and report its connection status").option("--json", "Output JSON").action(async (opts) => {
19835
+ await runIntegrationsStatus(cwd(), opts);
19836
+ });
19837
+ integrationsCmd.command("verify <id>").description("Verify one integration can actually connect (distinguishes configured from usable)").option("--json", "Output JSON").action(async (id, opts) => {
19838
+ await runIntegrationsVerify(cwd(), id, opts);
19839
+ });
19840
+ integrationsCmd.command("work-items <id>").description("List external work items from an integration (paginated, read-only)").option("--json", "Output JSON").option("--cursor <cursor>", "Pagination cursor from a previous page").option("--page-size <n>", "Items per page").option("--status <status>", "Filter by external status").option("--query <text>", "Free-text filter").action(async (id, opts) => {
19841
+ await runIntegrationsWorkItems(cwd(), id, opts);
19842
+ });
19843
+ integrationsCmd.command("work-item <id> <external-id>").description("Read a single external work item (read-only)").option("--json", "Output JSON").action(async (id, externalId, opts) => {
19844
+ await runIntegrationsWorkItem(cwd(), id, externalId, opts);
19845
+ });
19846
+ integrationsCmd.command("import <id> <external-id>").description("Preview and, after confirmation, import an external work item as a Draft Work Item").option("--type <type>", "Kaddo Work Item type (required to create; never inferred from the external type)").option("-y, --yes", "Skip the confirmation prompt (for already-approved automation)").action(async (id, externalId, opts) => {
19847
+ await runIntegrationsImport(cwd(), id, externalId, opts);
19848
+ });
18934
19849
  var reportCmd = program.command("report").description("Generate Kaddo reports");
18935
19850
  reportCmd.command("impact").description("Knowledge Impact Report: knowledge health, coverage, traceability, readiness (deterministic, no LLM)").option("--json", "Output JSON instead of Markdown").option("--scope <scope>", "Scope: all (default \u2014 accumulated impact) or active").option("--output <path>", "Write the report to a file (e.g. .kaddo/reports/impact-report.md)").action((opts) => {
18936
19851
  runReportImpact(opts);