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