@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/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,772 @@ 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 } 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 parseIntegrationsConfig(raw, opts) {
17640
+ const findings = [];
17641
+ const integrations = [];
17642
+ const list2 = raw && typeof raw === "object" && Array.isArray(raw.integrations) ? raw.integrations : Array.isArray(raw) ? raw : [];
17643
+ const seen = /* @__PURE__ */ new Set();
17644
+ for (const entry of list2) {
17645
+ if (!entry || typeof entry !== "object") {
17646
+ findings.push({ level: "blocking", message: "Each integration must be a mapping." });
17647
+ continue;
17648
+ }
17649
+ const o = entry;
17650
+ const id = typeof o.id === "string" ? o.id.trim() : "";
17651
+ const adapter = typeof o.adapter === "string" ? o.adapter.trim() : "";
17652
+ if (!id) {
17653
+ findings.push({ level: "blocking", message: 'An integration is missing a required "id".' });
17654
+ continue;
17655
+ }
17656
+ if (seen.has(id)) {
17657
+ findings.push({ level: "blocking", id, message: `Duplicate integration id "${id}".` });
17658
+ continue;
17659
+ }
17660
+ seen.add(id);
17661
+ if (!adapter) {
17662
+ findings.push({ level: "blocking", id, message: `Integration "${id}" is missing a required "adapter".` });
17663
+ continue;
17664
+ }
17665
+ if (!opts.adapterIds.has(adapter)) {
17666
+ findings.push({ level: "blocking", id, message: `Integration "${id}" references unknown adapter "${adapter}".` });
17667
+ }
17668
+ const enabled = o.enabled === void 0 ? true : o.enabled === true || o.enabled === "true";
17669
+ const config = o.config && typeof o.config === "object" && !Array.isArray(o.config) ? o.config : {};
17670
+ const credentials = parseCredentials(o.credentials, id, findings);
17671
+ const timeoutMs = typeof o.timeout_ms === "number" ? o.timeout_ms : typeof o.timeoutMs === "number" ? o.timeoutMs : void 0;
17672
+ integrations.push({ id, adapter, enabled, config, credentials, timeoutMs });
17673
+ }
17674
+ return { integrations, findings };
17675
+ }
17676
+ function resolveCredentials(integration, env) {
17677
+ const credentials = {};
17678
+ const missing = [];
17679
+ for (const [name, ref] of Object.entries(integration.credentials)) {
17680
+ const value = env[ref.env];
17681
+ if (value && value.length > 0) credentials[name] = value;
17682
+ else missing.push(ref.env);
17683
+ }
17684
+ return { credentials, missing };
17685
+ }
17686
+
17687
+ // ../integrations/src/preview.ts
17688
+ function buildImportPreview(item, opts) {
17689
+ return {
17690
+ source: {
17691
+ provider: item.provider,
17692
+ integration: opts.integrationId,
17693
+ externalId: item.externalId,
17694
+ url: item.url,
17695
+ identityKey: externalIdentityKey(opts.integrationId, item.externalId),
17696
+ displayKey: externalDisplayKey(item.provider, item.externalId)
17697
+ },
17698
+ capturedIntent: item.title,
17699
+ description: item.description,
17700
+ externalType: item.type,
17701
+ externalStatus: item.status,
17702
+ kaddoStatus: "draft",
17703
+ kaddoType: opts.type ? opts.type : null,
17704
+ writes: false
17705
+ };
17706
+ }
17707
+
17708
+ // ../integrations/src/registry.ts
17709
+ var DuplicateAdapterError = class extends Error {
17710
+ constructor(id) {
17711
+ super(`An integration adapter with id "${id}" is already registered.`);
17712
+ this.name = "DuplicateAdapterError";
17713
+ }
17714
+ };
17715
+ var IntegrationRegistry = class {
17716
+ adapters = /* @__PURE__ */ new Map();
17717
+ register(adapter) {
17718
+ if (this.adapters.has(adapter.id)) throw new DuplicateAdapterError(adapter.id);
17719
+ this.adapters.set(adapter.id, adapter);
17720
+ }
17721
+ get(id) {
17722
+ return this.adapters.get(id);
17723
+ }
17724
+ has(id) {
17725
+ return this.adapters.has(id);
17726
+ }
17727
+ list() {
17728
+ return [...this.adapters.values()].sort((a, b) => a.id.localeCompare(b.id));
17729
+ }
17730
+ ids() {
17731
+ return new Set(this.adapters.keys());
17732
+ }
17733
+ };
17734
+
17735
+ // ../integrations/src/mock-adapter.ts
17736
+ var MOCK_ADAPTER_ID = "mock";
17737
+ var DEFAULT_ITEMS = [
17738
+ {
17739
+ externalId: "EXT-001",
17740
+ provider: MOCK_ADAPTER_ID,
17741
+ title: "Open public registration to everyone",
17742
+ description: "Open self-service registration to the public once the private beta ends.",
17743
+ type: "Feature",
17744
+ status: "Open",
17745
+ url: "https://example.test/mock/EXT-001",
17746
+ labels: ["registration", "beta"],
17747
+ createdAt: "2026-01-05T10:00:00.000Z",
17748
+ updatedAt: "2026-02-01T09:30:00.000Z",
17749
+ rawMetadata: { board: "delivery" }
17750
+ },
17751
+ {
17752
+ externalId: "EXT-002",
17753
+ provider: MOCK_ADAPTER_ID,
17754
+ title: "Personalize onboarding steps",
17755
+ description: "The onboarding checklist should adapt to what the user has already completed.",
17756
+ type: "Task",
17757
+ status: "To Do",
17758
+ url: "https://example.test/mock/EXT-002",
17759
+ labels: ["onboarding"],
17760
+ createdAt: "2026-01-08T12:00:00.000Z",
17761
+ updatedAt: "2026-01-20T15:00:00.000Z"
17762
+ }
17763
+ ];
17764
+ function simulationOf(context, fallback) {
17765
+ const fromConfig = context.config?.simulate;
17766
+ return typeof fromConfig === "string" ? fromConfig : fallback;
17767
+ }
17768
+ function readFailure(sim) {
17769
+ switch (sim) {
17770
+ case "unauthorized":
17771
+ throw integrationError("INTEGRATION_UNAUTHORIZED");
17772
+ case "rate-limited":
17773
+ throw integrationError("INTEGRATION_RATE_LIMITED");
17774
+ case "unavailable":
17775
+ throw integrationError("INTEGRATION_UNAVAILABLE");
17776
+ case "timeout":
17777
+ throw integrationError("INTEGRATION_TIMEOUT");
17778
+ case "available":
17779
+ break;
17780
+ }
17781
+ }
17782
+ function createMockAdapter(opts = {}) {
17783
+ const items = opts.items ?? DEFAULT_ITEMS;
17784
+ const defaultSim = opts.simulate ?? "available";
17785
+ const defaultPageSize = opts.pageSize ?? 50;
17786
+ return {
17787
+ id: MOCK_ADAPTER_ID,
17788
+ metadata: {
17789
+ id: MOCK_ADAPTER_ID,
17790
+ displayName: "Mock Work Source",
17791
+ version: "1.0.0",
17792
+ description: "Deterministic offline reference adapter for validating the integration foundation."
17793
+ },
17794
+ capabilities: {
17795
+ workItems: { list: true, read: true, import: true, write: false, statusSync: false, comments: false, webhooks: false }
17796
+ },
17797
+ async verifyConnection(context) {
17798
+ const sim = simulationOf(context, defaultSim);
17799
+ const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
17800
+ switch (sim) {
17801
+ case "available":
17802
+ return { status: "available", checkedAt };
17803
+ case "unauthorized":
17804
+ return { status: "unauthorized", message: "Check the configured credentials.", checkedAt };
17805
+ case "rate-limited":
17806
+ case "unavailable":
17807
+ case "timeout":
17808
+ return { status: "unavailable", message: "The mock provider is temporarily unavailable.", checkedAt };
17809
+ }
17810
+ },
17811
+ async listWorkItems(request) {
17812
+ readFailure(simulationOf(request.context, defaultSim));
17813
+ let pool = items;
17814
+ const f = request.filters;
17815
+ if (f?.status) pool = pool.filter((i) => (i.status ?? "").toLowerCase() === f.status.toLowerCase());
17816
+ if (f?.query) pool = pool.filter((i) => `${i.title} ${i.description ?? ""}`.toLowerCase().includes(f.query.toLowerCase()));
17817
+ if (f?.updatedSince) pool = pool.filter((i) => (i.updatedAt ?? "") >= f.updatedSince);
17818
+ const size = Math.max(1, request.pageSize ?? defaultPageSize);
17819
+ const start = request.cursor ? Math.max(0, Number.parseInt(request.cursor, 10) || 0) : 0;
17820
+ const slice = pool.slice(start, start + size);
17821
+ const end = start + slice.length;
17822
+ const hasMore = end < pool.length;
17823
+ return { items: slice, hasMore, ...hasMore ? { nextCursor: String(end) } : {} };
17824
+ },
17825
+ async getWorkItem(request) {
17826
+ readFailure(simulationOf(request.context, defaultSim));
17827
+ return items.find((i) => i.externalId === request.externalId) ?? null;
17828
+ }
17829
+ };
17830
+ }
17831
+
17832
+ // ../integrations/src/index.ts
17833
+ function createDefaultRegistry() {
17834
+ const registry2 = new IntegrationRegistry();
17835
+ registry2.register(createMockAdapter());
17836
+ return registry2;
17837
+ }
17838
+
17839
+ // src/services/integrations.ts
17840
+ var INTEGRATIONS_FILE = ".kaddo/integrations.yml";
17841
+ var DEFAULT_TIMEOUT_MS = 1e4;
17842
+ var registry = createDefaultRegistry();
17843
+ var IntegrationServiceError = class extends Error {
17844
+ code;
17845
+ constructor(code, message) {
17846
+ super(message);
17847
+ this.name = "IntegrationServiceError";
17848
+ this.code = code;
17849
+ }
17850
+ };
17851
+ function loadRaw(dir) {
17852
+ const abs = join(dir, INTEGRATIONS_FILE);
17853
+ if (!exists(abs)) return { integrations: [] };
17854
+ try {
17855
+ return parseYaml17(readFile(abs)) ?? { integrations: [] };
17856
+ } catch {
17857
+ return { integrations: [] };
17858
+ }
17859
+ }
17860
+ function loadIntegrations(dir) {
17861
+ return parseIntegrationsConfig(loadRaw(dir), { adapterIds: registry.ids() });
17862
+ }
17863
+ function configStatus(integration, findings) {
17864
+ if (!integration.enabled) return "disabled";
17865
+ const blocking = findings.some((f) => f.id === integration.id && f.level === "blocking");
17866
+ if (blocking || !registry.has(integration.adapter)) return "invalid-config";
17867
+ return "configured";
17868
+ }
17869
+ function listIntegrations(dir) {
17870
+ const { integrations, findings } = loadIntegrations(dir);
17871
+ return integrations.map((integration) => {
17872
+ const adapter = registry.get(integration.adapter);
17873
+ return {
17874
+ id: integration.id,
17875
+ adapter: integration.adapter,
17876
+ enabled: integration.enabled,
17877
+ status: configStatus(integration, findings),
17878
+ displayName: adapter?.metadata.displayName ?? integration.adapter,
17879
+ capabilities: adapter?.capabilities ?? null,
17880
+ metadata: adapter?.metadata ?? null,
17881
+ credentialRefs: Object.values(integration.credentials).map((c) => c.env),
17882
+ findings: findings.filter((f) => f.id === integration.id)
17883
+ };
17884
+ });
17885
+ }
17886
+ function requireIntegration(dir, id) {
17887
+ const { integrations, findings } = loadIntegrations(dir);
17888
+ const integration = integrations.find((i) => i.id === id);
17889
+ if (!integration) throw new IntegrationServiceError("INTEGRATION_NOT_CONFIGURED", `No integration "${id}" is configured in this project.`);
17890
+ return { integration, findings };
17891
+ }
17892
+ function resolveAdapter(integration) {
17893
+ const adapter = registry.get(integration.adapter);
17894
+ if (!adapter) throw new IntegrationServiceError("ADAPTER_NOT_FOUND", `Unknown integration adapter "${integration.adapter}".`);
17895
+ return adapter;
17896
+ }
17897
+ function buildContext(integration, env) {
17898
+ const { credentials, missing } = resolveCredentials(integration, env);
17899
+ return {
17900
+ context: { integrationId: integration.id, config: integration.config, credentials, timeoutMs: integration.timeoutMs ?? DEFAULT_TIMEOUT_MS },
17901
+ missing
17902
+ };
17903
+ }
17904
+ async function withTimeout(op, ms) {
17905
+ let timer;
17906
+ const timeout = new Promise((_, reject) => {
17907
+ timer = setTimeout(() => reject(integrationError("INTEGRATION_TIMEOUT")), ms);
17908
+ });
17909
+ try {
17910
+ return await Promise.race([op, timeout]);
17911
+ } finally {
17912
+ if (timer) clearTimeout(timer);
17913
+ }
17914
+ }
17915
+ async function verifyIntegration(dir, id, env = process.env) {
17916
+ const { integration, findings } = requireIntegration(dir, id);
17917
+ const cfgStatus = configStatus(integration, findings);
17918
+ if (cfgStatus === "disabled") return { id, status: "disabled", connection: null, missingCredentials: [] };
17919
+ if (cfgStatus === "invalid-config") return { id, status: "invalid-config", connection: null, missingCredentials: [] };
17920
+ const adapter = resolveAdapter(integration);
17921
+ const { context, missing } = buildContext(integration, env);
17922
+ try {
17923
+ const connection = await withTimeout(adapter.verifyConnection(context), context.timeoutMs);
17924
+ return { id, status: statusOf(connection), connection, missingCredentials: missing, message: connection.message };
17925
+ } catch (err) {
17926
+ const e = err instanceof IntegrationError ? err : integrationError("INTEGRATION_PROVIDER_ERROR");
17927
+ return { id, status: e.code === "INTEGRATION_UNAUTHORIZED" ? "unauthorized" : "unavailable", connection: null, missingCredentials: missing, message: e.safeMessage };
17928
+ }
17929
+ }
17930
+ function statusOf(connection) {
17931
+ switch (connection.status) {
17932
+ case "available":
17933
+ return "available";
17934
+ case "unauthorized":
17935
+ return "unauthorized";
17936
+ case "unavailable":
17937
+ return "unavailable";
17938
+ case "invalid-config":
17939
+ return "invalid-config";
17940
+ }
17941
+ }
17942
+ async function listExternalWorkItems(dir, id, opts = {}, env = process.env) {
17943
+ const { integration } = requireIntegration(dir, id);
17944
+ const adapter = resolveAdapter(integration);
17945
+ if (!adapter.capabilities.workItems.list) throw integrationError("UNSUPPORTED_CAPABILITY", `Adapter "${adapter.id}" cannot list work items.`);
17946
+ const { context } = buildContext(integration, env);
17947
+ return withTimeout(adapter.listWorkItems({ context, cursor: opts.cursor, pageSize: opts.pageSize, filters: opts.filters }), context.timeoutMs);
17948
+ }
17949
+ async function getExternalWorkItem(dir, id, externalId, env = process.env) {
17950
+ const { integration } = requireIntegration(dir, id);
17951
+ const adapter = resolveAdapter(integration);
17952
+ if (!adapter.capabilities.workItems.read) throw integrationError("UNSUPPORTED_CAPABILITY", `Adapter "${adapter.id}" cannot read work items.`);
17953
+ const { context } = buildContext(integration, env);
17954
+ return withTimeout(adapter.getWorkItem({ context, externalId }), context.timeoutMs);
17955
+ }
17956
+ function findLinkedWorkItem(dir, integrationId, externalId) {
17957
+ for (const art of discoverWorkItems(dir)) {
17958
+ let data;
17959
+ try {
17960
+ data = matter11(readFile(art.filePath)).data;
17961
+ } catch {
17962
+ continue;
17963
+ }
17964
+ const source = parseWorkItemSource(data);
17965
+ if (source.integration === integrationId && source.id === externalId) {
17966
+ return { workItemId: String(data.id ?? ""), title: String(data.title ?? "") };
17967
+ }
17968
+ }
17969
+ return null;
17970
+ }
17971
+ async function previewImport(dir, id, externalId, opts = {}, env = process.env) {
17972
+ const item = await getExternalWorkItem(dir, id, externalId, env);
17973
+ if (!item) throw integrationError("INTEGRATION_NOT_FOUND", `External work item "${externalId}" was not found.`);
17974
+ const preview = buildImportPreview(item, { integrationId: id, type: opts.type });
17975
+ return { preview, duplicate: findLinkedWorkItem(dir, id, externalId) };
17976
+ }
17977
+ async function importExternalWorkItem(dir, id, externalId, opts, env = process.env) {
17978
+ const item = await getExternalWorkItem(dir, id, externalId, env);
17979
+ if (!item) throw integrationError("INTEGRATION_NOT_FOUND", `External work item "${externalId}" was not found.`);
17980
+ const preview = buildImportPreview(item, { integrationId: id, type: opts.type });
17981
+ const existing = findLinkedWorkItem(dir, id, externalId);
17982
+ if (existing) return { workItemId: existing.workItemId, created: false, duplicateOf: existing.workItemId, preview };
17983
+ const intent = item.description ? `${item.title}
17984
+
17985
+ ${item.description}` : item.title;
17986
+ const created = createWorkItem(dir, {
17987
+ intent,
17988
+ type: opts.type,
17989
+ source: {
17990
+ type: "external",
17991
+ provider: item.provider,
17992
+ integration: id,
17993
+ id: externalId,
17994
+ url: item.url,
17995
+ imported_at: (/* @__PURE__ */ new Date()).toISOString()
17996
+ }
17997
+ });
17998
+ return { workItemId: created.id, created: true, preview, path: created.path };
17999
+ }
18000
+
18001
+ // src/commands/integrations.ts
18002
+ function requireProject2(dir) {
18003
+ if (!loadConfig(dir)) {
18004
+ log2.error("No Kaddo project was found in the current directory.");
18005
+ process.exit(1);
18006
+ }
18007
+ }
18008
+ function printJson(value) {
18009
+ console.log(JSON.stringify(value, null, 2));
18010
+ }
18011
+ function fail(err) {
18012
+ if (err instanceof IntegrationError) log2.error(`[${err.code}] ${err.safeMessage}`);
18013
+ else if (err instanceof IntegrationServiceError) log2.error(`[${err.code}] ${err.message}`);
18014
+ else log2.error("The integration operation failed.");
18015
+ process.exit(1);
18016
+ }
18017
+ function runIntegrationsList(dir, opts) {
18018
+ requireProject2(dir);
18019
+ const items = listIntegrations(dir);
18020
+ if (opts.json) return printJson(items.map((i) => ({ id: i.id, adapter: i.adapter, enabled: i.enabled, status: i.status, capabilities: i.capabilities })));
18021
+ intro2("Kaddo integrations");
18022
+ if (items.length === 0) {
18023
+ log2.info("No integrations are configured. Add them to .kaddo/integrations.yml.");
18024
+ return outro2("Done.");
18025
+ }
18026
+ for (const i of items) {
18027
+ log2.info(`${i.id} \xB7 adapter ${i.adapter} \xB7 ${i.status}${i.enabled ? "" : " (disabled)"}`);
18028
+ 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"}`);
18029
+ for (const f of i.findings) log2.warn(` [${f.level}] ${f.message}`);
18030
+ }
18031
+ outro2("Done.");
18032
+ }
18033
+ async function runIntegrationsStatus(dir, opts) {
18034
+ requireProject2(dir);
18035
+ const summaries = listIntegrations(dir);
18036
+ const results = await Promise.all(summaries.map(async (s) => {
18037
+ if (s.status === "disabled" || s.status === "invalid-config") return { id: s.id, status: s.status };
18038
+ try {
18039
+ const v = await verifyIntegration(dir, s.id);
18040
+ return { id: s.id, status: v.status, message: v.message, missingCredentials: v.missingCredentials };
18041
+ } catch (err) {
18042
+ return { id: s.id, status: "unavailable", message: err instanceof IntegrationError ? err.safeMessage : "Verification failed." };
18043
+ }
18044
+ }));
18045
+ if (opts.json) return printJson(results);
18046
+ intro2("Integration status");
18047
+ for (const r of results) {
18048
+ const line = `${r.id} \xB7 ${r.status}${r.message ? ` \u2014 ${r.message}` : ""}`;
18049
+ if (r.status === "available" || r.status === "configured") log2.info(line);
18050
+ else log2.warn(line);
18051
+ }
18052
+ outro2("Done.");
18053
+ }
18054
+ async function runIntegrationsVerify(dir, id, opts) {
18055
+ requireProject2(dir);
18056
+ try {
18057
+ const v = await verifyIntegration(dir, id);
18058
+ if (opts.json) return printJson({ id: v.id, status: v.status, message: v.message, missingCredentials: v.missingCredentials });
18059
+ intro2(`Verify ${id}`);
18060
+ const line = `Status: ${v.status}${v.message ? ` \u2014 ${v.message}` : ""}`;
18061
+ if (v.status === "available") log2.info(line);
18062
+ else log2.warn(line);
18063
+ if (v.missingCredentials.length) log2.warn(`Missing credentials: ${v.missingCredentials.join(", ")}`);
18064
+ outro2("Done.");
18065
+ } catch (err) {
18066
+ fail(err);
18067
+ }
18068
+ }
18069
+ async function runIntegrationsWorkItems(dir, id, opts) {
18070
+ requireProject2(dir);
18071
+ try {
18072
+ const page = await listExternalWorkItems(dir, id, {
18073
+ cursor: opts.cursor,
18074
+ pageSize: opts.pageSize ? Number.parseInt(opts.pageSize, 10) : void 0,
18075
+ filters: { status: opts.status, query: opts.query }
18076
+ });
18077
+ if (opts.json) return printJson(page);
18078
+ intro2(`External work items \xB7 ${id}`);
18079
+ for (const it of page.items) log2.info(`${it.externalId} \xB7 ${it.title}${it.status ? ` [${it.status}]` : ""}`);
18080
+ if (page.hasMore) log2.message(`More available \u2014 next cursor: ${page.nextCursor}`);
18081
+ outro2(`${page.items.length} item(s).`);
18082
+ } catch (err) {
18083
+ fail(err);
18084
+ }
18085
+ }
18086
+ async function runIntegrationsWorkItem(dir, id, externalId, opts) {
18087
+ requireProject2(dir);
18088
+ try {
18089
+ const item = await getExternalWorkItem(dir, id, externalId);
18090
+ if (!item) {
18091
+ log2.error(`External work item "${externalId}" was not found.`);
18092
+ process.exit(1);
18093
+ }
18094
+ if (opts.json) return printJson(item);
18095
+ intro2(`${item.provider} \xB7 ${item.externalId}`);
18096
+ log2.info(item.title);
18097
+ if (item.status) log2.message(`Status: ${item.status}`);
18098
+ if (item.type) log2.message(`Type: ${item.type}`);
18099
+ if (item.url) log2.message(`URL: ${item.url}`);
18100
+ if (item.description) log2.message(`
18101
+ ${item.description}`);
18102
+ outro2("Done.");
18103
+ } catch (err) {
18104
+ fail(err);
18105
+ }
18106
+ }
18107
+ async function runIntegrationsImport(dir, id, externalId, opts) {
18108
+ requireProject2(dir);
18109
+ try {
18110
+ const { preview, duplicate } = await previewImport(dir, id, externalId, { type: opts.type });
18111
+ intro2("Import external work item");
18112
+ if (duplicate) {
18113
+ log2.warn(`Already imported as ${duplicate.workItemId} \u2014 "${duplicate.title}".`);
18114
+ outro2("No duplicate was created.");
18115
+ return;
18116
+ }
18117
+ log2.info(`Source: ${preview.source.provider} \xB7 ${preview.source.externalId}`);
18118
+ log2.message(`Captured intent: ${preview.capturedIntent}`);
18119
+ log2.message("Will create: a Draft Work Item (needs refinement).");
18120
+ if (preview.externalType) log2.message(`External type: ${preview.externalType} (Kaddo type is chosen, never inferred)`);
18121
+ log2.message("No project files have been modified yet.");
18122
+ if (!opts.type) {
18123
+ log2.error("A Kaddo Work Item type is required. Re-run with --type <feature|fix|chore|\u2026>.");
18124
+ process.exit(1);
18125
+ }
18126
+ if (!opts.yes) {
18127
+ const ok = await confirm2({ message: `Import ${preview.source.displayKey} as a Draft ${opts.type} Work Item?` });
18128
+ if (ok !== true) {
18129
+ cancel2("Import cancelled. No files were changed.");
18130
+ process.exit(0);
18131
+ }
18132
+ }
18133
+ const result = await importExternalWorkItem(dir, id, externalId, { type: opts.type });
18134
+ if (!result.created) {
18135
+ log2.warn(`Already imported as ${result.duplicateOf}.`);
18136
+ outro2("No duplicate was created.");
18137
+ return;
18138
+ }
18139
+ log2.info(`Created ${result.workItemId} (Draft, needs refinement).`);
18140
+ outro2("Done. Refine it next with the Work Item refinement handoff.");
18141
+ } catch (err) {
18142
+ fail(err);
18143
+ }
18144
+ }
18145
+
18146
+ // src/core/impact-report.ts
18147
+ import matter12 from "gray-matter";
17383
18148
  function isBroadGlob(glob) {
17384
18149
  if (!glob.endsWith("/**")) return false;
17385
18150
  const prefix = glob.slice(0, -3);
@@ -17448,7 +18213,7 @@ function buildImpactReport(dir, opts = {}, now = /* @__PURE__ */ new Date()) {
17448
18213
  }
17449
18214
  }
17450
18215
  try {
17451
- const body = matter10(readFile(wi.filePath)).content;
18216
+ const body = matter12(readFile(wi.filePath)).content;
17452
18217
  if (hasSection(body, /acceptance|criterios de aceptaci/i)) withAcceptance++;
17453
18218
  else gaps.missing_acceptance_criteria.push(gapItem(wi, "Add an `## Acceptance Criteria` section."));
17454
18219
  if (hasSection(body, /definition of done|^#{1,6}\s*dod\b|definici[oó]n de (terminado|hecho)/i)) withDoD++;
@@ -17825,7 +18590,7 @@ function runReportImpact(opts = {}) {
17825
18590
  }
17826
18591
 
17827
18592
  // src/core/savings.ts
17828
- import { parse as parseYaml17 } from "yaml";
18593
+ import { parse as parseYaml18 } from "yaml";
17829
18594
  var DEFAULT_ASSUMPTIONS = {
17830
18595
  currency: "USD",
17831
18596
  hourly_cost: 40,
@@ -17843,7 +18608,7 @@ function loadAssumptions(dir) {
17843
18608
  const p2 = join(dir, SAVINGS_PATH);
17844
18609
  if (!exists(p2)) return { assumptions: { ...DEFAULT_ASSUMPTIONS }, source: "default" };
17845
18610
  try {
17846
- const raw = parseYaml17(readFile(p2));
18611
+ const raw = parseYaml18(readFile(p2));
17847
18612
  const a = raw?.assumptions ?? {};
17848
18613
  const t = raw?.team ?? {};
17849
18614
  const num = (v, d) => typeof v === "number" && Number.isFinite(v) ? v : d;
@@ -18525,7 +19290,7 @@ function runAdr(opts = {}) {
18525
19290
  }
18526
19291
 
18527
19292
  // src/commands/tech.ts
18528
- import fs4 from "fs";
19293
+ import fs5 from "fs";
18529
19294
  var DISCOVERY_FILES = ["architecture-notes.md", "decision-candidates.md"];
18530
19295
  var DISCOVERY_DIR = "knowledge/tech/discovery";
18531
19296
  function runTechOrganize(dir = cwd()) {
@@ -18542,7 +19307,7 @@ function runTechOrganize(dir = cwd()) {
18542
19307
  continue;
18543
19308
  }
18544
19309
  ensureDir(join(dir, DISCOVERY_DIR));
18545
- fs4.renameSync(from, to);
19310
+ fs5.renameSync(from, to);
18546
19311
  moved.push(`knowledge/tech/${file} \u2192 ${DISCOVERY_DIR}/${file}`);
18547
19312
  }
18548
19313
  if (moved.length > 0) {
@@ -18624,9 +19389,9 @@ function runAssetsUpdate(kind, opts = {}) {
18624
19389
  }
18625
19390
 
18626
19391
  // src/commands/ready.ts
18627
- import fs5 from "fs";
18628
- import path8 from "path";
18629
- import matter11 from "gray-matter";
19392
+ import fs6 from "fs";
19393
+ import path9 from "path";
19394
+ import matter13 from "gray-matter";
18630
19395
  function validateReadiness(content, frontmatter) {
18631
19396
  const warnings = [];
18632
19397
  const body = content.replace(/^---[\s\S]*?---/, "").trim();
@@ -18680,18 +19445,18 @@ function isResolvedSection(text3) {
18680
19445
  }
18681
19446
  function transitionToReady(filePath) {
18682
19447
  const raw = readFile(filePath);
18683
- const { data, content } = matter11(raw);
19448
+ const { data, content } = matter13(raw);
18684
19449
  const today2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
18685
19450
  data.status = "ready";
18686
19451
  data.ready_at = today2;
18687
- const newContent = matter11.stringify(content, data);
19452
+ const newContent = matter13.stringify(content, data);
18688
19453
  const posix = filePath.replace(/\\/g, "/");
18689
19454
  let newPath = filePath;
18690
19455
  if (posix.includes("/work-items/draft/")) {
18691
- const readyDir = path8.dirname(filePath).replace(/[/\\]draft$/, path8.sep + "ready");
19456
+ const readyDir = path9.dirname(filePath).replace(/[/\\]draft$/, path9.sep + "ready");
18692
19457
  ensureDir(readyDir);
18693
- const fileName = path8.basename(filePath);
18694
- newPath = path8.join(readyDir, fileName);
19458
+ const fileName = path9.basename(filePath);
19459
+ newPath = path9.join(readyDir, fileName);
18695
19460
  }
18696
19461
  return { newPath, newContent };
18697
19462
  }
@@ -18724,7 +19489,7 @@ async function runReady(id, opts = {}) {
18724
19489
  process.exit(1);
18725
19490
  }
18726
19491
  const raw = readFile(wi.filePath);
18727
- const { data } = matter11(raw);
19492
+ const { data } = matter13(raw);
18728
19493
  const source = parseWorkItemSource(data);
18729
19494
  const warnings = validateReadiness(raw, data);
18730
19495
  log2.info(`Work Item found:`);
@@ -18759,11 +19524,11 @@ async function runReady(id, opts = {}) {
18759
19524
  const { newPath, newContent } = transitionToReady(wi.filePath);
18760
19525
  if (newPath !== wi.filePath) {
18761
19526
  writeFile(newPath, newContent);
18762
- fs5.unlinkSync(wi.filePath);
19527
+ fs6.unlinkSync(wi.filePath);
18763
19528
  log2.success(`Updated status: ready`);
18764
19529
  log2.success(`Moved file:`);
18765
- log2.info(` ${path8.relative(dir, wi.filePath)}`);
18766
- log2.info(` \u2192 ${path8.relative(dir, newPath)}`);
19530
+ log2.info(` ${path9.relative(dir, wi.filePath)}`);
19531
+ log2.info(` \u2192 ${path9.relative(dir, newPath)}`);
18767
19532
  } else {
18768
19533
  writeFile(wi.filePath, newContent);
18769
19534
  log2.success(`Updated status: ready`);
@@ -18774,7 +19539,7 @@ async function runReady(id, opts = {}) {
18774
19539
  }
18775
19540
 
18776
19541
  // src/commands/admin.ts
18777
- import path9 from "path";
19542
+ import path10 from "path";
18778
19543
  import { fileURLToPath as fileURLToPath2 } from "url";
18779
19544
  import net from "net";
18780
19545
  function isPortAvailable(port, host) {
@@ -18789,13 +19554,13 @@ function isPortAvailable(port, host) {
18789
19554
  });
18790
19555
  }
18791
19556
  function resolveStaticDir() {
18792
- const __dirname = path9.dirname(fileURLToPath2(import.meta.url));
19557
+ const __dirname = path10.dirname(fileURLToPath2(import.meta.url));
18793
19558
  const candidates = [
18794
19559
  // Bundled inside CLI dist (npm install)
18795
- path9.resolve(__dirname, "admin-dist"),
19560
+ path10.resolve(__dirname, "admin-dist"),
18796
19561
  // Monorepo development
18797
- path9.resolve(__dirname, "..", "..", "admin", "dist"),
18798
- path9.resolve(__dirname, "..", "node_modules", "@kaddo", "admin", "dist")
19562
+ path10.resolve(__dirname, "..", "..", "admin", "dist"),
19563
+ path10.resolve(__dirname, "..", "node_modules", "@kaddo", "admin", "dist")
18799
19564
  ];
18800
19565
  for (const c of candidates) {
18801
19566
  if (exists(join(c, "index.html"))) return c;
@@ -18837,8 +19602,8 @@ async function runAdmin(opts = {}) {
18837
19602
  }
18838
19603
  let adminServer;
18839
19604
  try {
18840
- const __dirname = path9.dirname(fileURLToPath2(import.meta.url));
18841
- const bundled = path9.resolve(__dirname, "admin-server", "index.js");
19605
+ const __dirname = path10.dirname(fileURLToPath2(import.meta.url));
19606
+ const bundled = path10.resolve(__dirname, "admin-server", "index.js");
18842
19607
  if (exists(bundled)) {
18843
19608
  const { pathToFileURL } = await import("url");
18844
19609
  adminServer = await import(pathToFileURL(bundled).href);
@@ -18917,8 +19682,8 @@ var capsuleCmd = program.command("capsule").description("Export this project as
18917
19682
  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
19683
  runCapsuleExport(opts);
18919
19684
  });
18920
- capsuleCmd.command("add <path>").description("Register an external Knowledge Capsule as project context (.kaddo/external.yml)").action((path10) => {
18921
- runCapsuleAdd(path10);
19685
+ capsuleCmd.command("add <path>").description("Register an external Knowledge Capsule as project context (.kaddo/external.yml)").action((path11) => {
19686
+ runCapsuleAdd(path11);
18922
19687
  });
18923
19688
  var graphCmd = program.command("graph").description("Export the lightweight, file-based knowledge graph of the project");
18924
19689
  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 +19696,23 @@ topologyCmd.command("validate <file>").description("Validate a topology proposal
18931
19696
  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
19697
  runTopologyApply(file, opts);
18933
19698
  });
19699
+ var integrationsCmd = program.command("integrations").description("Connect Kaddo to external work systems (read-first; import requires human confirmation)");
19700
+ integrationsCmd.command("list").description("List configured integrations and their capabilities").option("--json", "Output JSON").action((opts) => runIntegrationsList(cwd(), opts));
19701
+ integrationsCmd.command("status").description("Verify each enabled integration and report its connection status").option("--json", "Output JSON").action(async (opts) => {
19702
+ await runIntegrationsStatus(cwd(), opts);
19703
+ });
19704
+ integrationsCmd.command("verify <id>").description("Verify one integration can actually connect (distinguishes configured from usable)").option("--json", "Output JSON").action(async (id, opts) => {
19705
+ await runIntegrationsVerify(cwd(), id, opts);
19706
+ });
19707
+ 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) => {
19708
+ await runIntegrationsWorkItems(cwd(), id, opts);
19709
+ });
19710
+ 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) => {
19711
+ await runIntegrationsWorkItem(cwd(), id, externalId, opts);
19712
+ });
19713
+ 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) => {
19714
+ await runIntegrationsImport(cwd(), id, externalId, opts);
19715
+ });
18934
19716
  var reportCmd = program.command("report").description("Generate Kaddo reports");
18935
19717
  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
19718
  runReportImpact(opts);