@kaddo/cli 3.81.0 → 3.82.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
|
|
1365
|
-
return
|
|
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
|
|
6736
|
-
if (!exists(
|
|
6735
|
+
const path11 = join(dir, IGNORE_FILE);
|
|
6736
|
+
if (!exists(path11)) return [];
|
|
6737
6737
|
try {
|
|
6738
|
-
const raw = readFile(
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
6787
|
-
if (!exists(
|
|
6786
|
+
const path11 = join(dir, DESCRIPTOR_PATH);
|
|
6787
|
+
if (!exists(path11)) return [];
|
|
6788
6788
|
let parsed;
|
|
6789
6789
|
try {
|
|
6790
|
-
parsed = parseYaml5(readFile(
|
|
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(([
|
|
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
|
|
10106
|
-
if (!exists(
|
|
10105
|
+
const path11 = join(dir, DESCRIPTOR_PATH2);
|
|
10106
|
+
if (!exists(path11)) return { version: 1, modules: [] };
|
|
10107
10107
|
try {
|
|
10108
|
-
const parsed = parseYaml9(readFile(
|
|
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,
|
|
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:
|
|
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
|
|
10529
|
-
|
|
10530
|
-
writeFile(join(dir, EXTERNAL_PATH), serializeExternalRegistry(
|
|
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
|
|
10585
|
-
if (
|
|
10586
|
-
addNode({ id: layer2.id, type: layer2.type, label: layer2.label, path:
|
|
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
|
|
12123
|
-
const installed = isFile(join(dir,
|
|
12122
|
+
const path11 = agentInstallPath(file);
|
|
12123
|
+
const installed = isFile(join(dir, path11));
|
|
12124
12124
|
const group = agentGroupOf(file);
|
|
12125
|
-
return { agentPath:
|
|
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/
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
|
18628
|
-
import
|
|
18629
|
-
import
|
|
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 } =
|
|
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 =
|
|
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 =
|
|
19456
|
+
const readyDir = path9.dirname(filePath).replace(/[/\\]draft$/, path9.sep + "ready");
|
|
18692
19457
|
ensureDir(readyDir);
|
|
18693
|
-
const fileName =
|
|
18694
|
-
newPath =
|
|
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 } =
|
|
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
|
-
|
|
19527
|
+
fs6.unlinkSync(wi.filePath);
|
|
18763
19528
|
log2.success(`Updated status: ready`);
|
|
18764
19529
|
log2.success(`Moved file:`);
|
|
18765
|
-
log2.info(` ${
|
|
18766
|
-
log2.info(` \u2192 ${
|
|
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
|
|
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 =
|
|
19557
|
+
const __dirname = path10.dirname(fileURLToPath2(import.meta.url));
|
|
18793
19558
|
const candidates = [
|
|
18794
19559
|
// Bundled inside CLI dist (npm install)
|
|
18795
|
-
|
|
19560
|
+
path10.resolve(__dirname, "admin-dist"),
|
|
18796
19561
|
// Monorepo development
|
|
18797
|
-
|
|
18798
|
-
|
|
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 =
|
|
18841
|
-
const bundled =
|
|
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((
|
|
18921
|
-
runCapsuleAdd(
|
|
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);
|