@kaddo/mcp 3.115.0 → 3.116.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.
Files changed (2) hide show
  1. package/dist/index.js +465 -123
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -371,6 +371,9 @@ function exists(p) {
371
371
  function readFile(p) {
372
372
  return fs2.readFileSync(p, "utf-8");
373
373
  }
374
+ function ensureDir(dirPath) {
375
+ fs2.mkdirSync(dirPath, { recursive: true });
376
+ }
374
377
  function readDir(dirPath) {
375
378
  if (!exists(dirPath)) return [];
376
379
  return fs2.readdirSync(dirPath);
@@ -485,8 +488,8 @@ function loadConfig(dir) {
485
488
  const parsed = configSchema.safeParse(raw ?? {});
486
489
  if (!parsed.success) {
487
490
  const issues = parsed.error.issues.map((i) => {
488
- const path7 = i.path.join(".");
489
- return path7 ? ` - ${path7}: ${i.message}` : ` - ${i.message}`;
491
+ const path9 = i.path.join(".");
492
+ return path9 ? ` - ${path9}: ${i.message}` : ` - ${i.message}`;
490
493
  }).join("\n");
491
494
  throw new ConfigError(`Invalid .kaddo/config.yml:
492
495
  ${issues}`);
@@ -927,11 +930,11 @@ function moduleArtifactCoverage(dir, id) {
927
930
  };
928
931
  }
929
932
  function loadMappedModules(dir) {
930
- const path7 = join(dir, DESCRIPTOR_PATH);
931
- if (!exists(path7)) return [];
933
+ const path9 = join(dir, DESCRIPTOR_PATH);
934
+ if (!exists(path9)) return [];
932
935
  let parsed;
933
936
  try {
934
- parsed = parseYaml4(readFile(path7));
937
+ parsed = parseYaml4(readFile(path9));
935
938
  } catch {
936
939
  return [];
937
940
  }
@@ -2438,7 +2441,7 @@ function sectionParagraph2(md, title) {
2438
2441
  }
2439
2442
  return "";
2440
2443
  }
2441
- function parseCapsule(id, path7, md) {
2444
+ function parseCapsule(id, path9, md) {
2442
2445
  const { data } = matter4(md);
2443
2446
  const updatedAt = data.updated_at ? String(data.updated_at) : void 0;
2444
2447
  let ageDays = null;
@@ -2448,7 +2451,7 @@ function parseCapsule(id, path7, md) {
2448
2451
  }
2449
2452
  return {
2450
2453
  id,
2451
- path: path7,
2454
+ path: path9,
2452
2455
  system: data.system ? String(data.system) : id,
2453
2456
  owner: data.owner ? String(data.owner) : void 0,
2454
2457
  updatedAt,
@@ -2593,6 +2596,18 @@ function discoverInitiatives(dir) {
2593
2596
  // ../cli/src/core/resources.ts
2594
2597
  import matter6 from "gray-matter";
2595
2598
  var RESOURCE_ARTIFACT_TYPE = "project-resource";
2599
+ var RESOURCE_TYPES = [
2600
+ "database",
2601
+ "cloud",
2602
+ "api",
2603
+ "queue",
2604
+ "storage",
2605
+ "repository",
2606
+ "platform",
2607
+ "service",
2608
+ "other"
2609
+ ];
2610
+ var INTERFACE_TYPES = ["cli", "mcp", "api", "sql", "sdk", "iac", "console", "other"];
2596
2611
  function str(v) {
2597
2612
  return typeof v === "string" && v.trim() ? v.trim() : null;
2598
2613
  }
@@ -2636,6 +2651,18 @@ function parseAuth(v) {
2636
2651
  const refs = strArray(o2.refs).filter((r2) => !r2.includes("=") && /^[A-Za-z0-9_.-]+$/.test(r2));
2637
2652
  return { mode: str(o2.mode), refs };
2638
2653
  }
2654
+ function parseScope(v) {
2655
+ if (!v || typeof v !== "object" || Array.isArray(v)) return null;
2656
+ const o2 = v;
2657
+ const type = str(o2.type);
2658
+ if (!type) return null;
2659
+ return { type, module: str(o2.module) };
2660
+ }
2661
+ function purposeFromBody(body) {
2662
+ const m2 = body.match(/^#{1,2}\s+Purpose\s*\n+([\s\S]*?)(?=\n#{1,2}\s|\s*$)/m);
2663
+ const text3 = m2 ? m2[1].trim() : "";
2664
+ return text3 || null;
2665
+ }
2639
2666
  function toDetail(filePath, relPath, raw) {
2640
2667
  const parsed = matter6(raw);
2641
2668
  const fm = parsed.data ?? {};
@@ -2647,8 +2674,10 @@ function toDetail(filePath, relPath, raw) {
2647
2674
  resourceType: str(fm.resource_type),
2648
2675
  provider: str(fm.provider),
2649
2676
  environments: strArray(fm.environments),
2677
+ scope: parseScope(fm.scope),
2678
+ modules: strArray(fm.modules),
2650
2679
  path: relPath,
2651
- purpose: str(fm.purpose),
2680
+ purpose: str(fm.purpose) ?? purposeFromBody(body),
2652
2681
  interfaces: parseInterfaces(fm.access_interfaces),
2653
2682
  boundaries: parseBoundaries(fm.access_boundaries),
2654
2683
  ...(() => {
@@ -2667,6 +2696,8 @@ function getResources(dir) {
2667
2696
  resourceType: str(fm.resource_type),
2668
2697
  provider: str(fm.provider),
2669
2698
  environments: strArray(fm.environments),
2699
+ scope: parseScope(fm.scope),
2700
+ modules: strArray(fm.modules),
2670
2701
  path: a2.relPath
2671
2702
  };
2672
2703
  }).sort((x2, y) => x2.id.localeCompare(y.id));
@@ -2737,9 +2768,9 @@ function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
2737
2768
  ];
2738
2769
  const presentLayers = [];
2739
2770
  for (const layer2 of layerDocs) {
2740
- const path7 = layer2.files.map((f3) => `${KNOWLEDGE3}/${f3}`).find((rel) => exists(join(dir, rel)));
2741
- if (path7) {
2742
- addNode({ id: layer2.id, type: layer2.type, label: layer2.label, path: path7 });
2771
+ const path9 = layer2.files.map((f3) => `${KNOWLEDGE3}/${f3}`).find((rel) => exists(join(dir, rel)));
2772
+ if (path9) {
2773
+ addNode({ id: layer2.id, type: layer2.type, label: layer2.label, path: path9 });
2743
2774
  presentLayers.push(layer2.id);
2744
2775
  }
2745
2776
  }
@@ -2782,12 +2813,20 @@ function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
2782
2813
  const resourceTitleById = /* @__PURE__ */ new Map();
2783
2814
  for (const r2 of getResources(dir)) {
2784
2815
  resourceTitleById.set(r2.id, r2.title);
2816
+ const resId = `resource:${r2.id}`;
2785
2817
  addNode({
2786
- id: `resource:${r2.id}`,
2818
+ id: resId,
2787
2819
  type: "project-resource",
2788
2820
  label: r2.title ? `${r2.id} ${r2.title}`.trim() : r2.id,
2789
2821
  path: r2.path
2790
2822
  });
2823
+ const depModules = new Set(r2.modules);
2824
+ if (r2.scope?.type === "module" && r2.scope.module) depModules.add(r2.scope.module);
2825
+ for (const mod of depModules) {
2826
+ const modId = `module:${mod}`;
2827
+ addNode({ id: modId, type: "module", label: mod });
2828
+ addEdge(modId, resId, "depends_on");
2829
+ }
2791
2830
  }
2792
2831
  for (const wi of selectedWIs) {
2793
2832
  const id = wi.id || wi.title;
@@ -4363,10 +4402,10 @@ function resolveNextStep(dir, now = /* @__PURE__ */ new Date()) {
4363
4402
  const q3 = (rel) => analyzeKnowledgeArtifact(dir, rel);
4364
4403
  const resolveAgent = (agent) => {
4365
4404
  const file = agent.endsWith(".md") ? agent : `${agent}.md`;
4366
- const path7 = agentInstallPath(file);
4367
- const installed = isFile(join(dir, path7));
4405
+ const path9 = agentInstallPath(file);
4406
+ const installed = isFile(join(dir, path9));
4368
4407
  const group = agentGroupOf(file);
4369
- return { agentPath: path7, agentInstalled: installed, installCommand: installed ? void 0 : `kaddo add agents --group ${group}` };
4408
+ return { agentPath: path9, agentInstalled: installed, installCommand: installed ? void 0 : `kaddo add agents --group ${group}` };
4370
4409
  };
4371
4410
  const moduleRepo = isModule(config);
4372
4411
  const coreRepo = isCore(config);
@@ -6121,6 +6160,16 @@ supporting notes as \`knowledge/tech/discovery/architecture-notes.md\` and decis
6121
6160
  under \`knowledge/tech/decisions/\`. Kaddo still reads the legacy root locations for backward
6122
6161
  compatibility, but new output should use \`discovery/\`.
6123
6162
 
6163
+ ## Project Resource candidates
6164
+
6165
+ When the knowledge describes an external system the project depends on (a database, cloud account,
6166
+ API, queue, storage, service\u2026), you may propose it as a **candidate** Project Resource \u2014 never create
6167
+ it silently. Discovery \u2260 creation: present candidates for human review and materialize only through
6168
+ \`kaddo_create_resource\` / \`kaddo resources create\` with explicit confirmation. Ground every
6169
+ candidate in the knowledge (no guessing from ambiguous names). Model the stable system, not its access
6170
+ interface (CLI/MCP/SQL/API are interfaces); keep \`resources\` separate from \`affected_modules\`; and
6171
+ never include secret values \u2014 only reference names.
6172
+
6124
6173
  ## Quality Checklist
6125
6174
 
6126
6175
  - Every component is backed by evidence from the context pack.
@@ -9701,7 +9750,7 @@ function buildGuardHistory(dir) {
9701
9750
  const d3 = hotspotDir(t.code_path);
9702
9751
  hotspotMap.set(d3, (hotspotMap.get(d3) ?? 0) + 1);
9703
9752
  }
9704
- const hotspots = [...hotspotMap.entries()].map(([path7, warnings]) => ({ path: path7, warnings })).sort((a2, b2) => b2.warnings - a2.warnings);
9753
+ const hotspots = [...hotspotMap.entries()].map(([path9, warnings]) => ({ path: path9, warnings })).sort((a2, b2) => b2.warnings - a2.warnings);
9705
9754
  return {
9706
9755
  available: true,
9707
9756
  total_runs: sorted.length,
@@ -11060,7 +11109,7 @@ function getPrompt(root, name) {
11060
11109
  }
11061
11110
 
11062
11111
  // src/tools.ts
11063
- import matter13 from "gray-matter";
11112
+ import matter15 from "gray-matter";
11064
11113
 
11065
11114
  // ../cli/dist/core.js
11066
11115
  import fs3 from "fs";
@@ -11084,18 +11133,19 @@ import { dirname as dirname2, join as join22, parse as parse2 } from "path";
11084
11133
  import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
11085
11134
  import matter72 from "gray-matter";
11086
11135
  import matter82 from "gray-matter";
11087
- import { parse as parseYaml82, stringify as stringifyYaml42 } from "yaml";
11088
- import fs32 from "fs";
11089
- import path32 from "path";
11090
- import crypto2 from "crypto";
11091
11136
  import matter92 from "gray-matter";
11092
- import crypto3 from "crypto";
11137
+ import { parse as parseYaml82, stringify as stringifyYaml42 } from "yaml";
11093
11138
  import fs4 from "fs";
11139
+ import path4 from "path";
11140
+ import crypto2 from "crypto";
11094
11141
  import matter102 from "gray-matter";
11142
+ import crypto3 from "crypto";
11143
+ import fs5 from "fs";
11095
11144
  import matter112 from "gray-matter";
11145
+ import matter122 from "gray-matter";
11096
11146
  import { parse as parseYaml92, stringify as stringifyYaml5 } from "yaml";
11097
11147
  import { parse as parseYaml102, stringify as stringifyYaml6 } from "yaml";
11098
- import matter122 from "gray-matter";
11148
+ import matter13 from "gray-matter";
11099
11149
  function exists2(p) {
11100
11150
  return fs3.existsSync(p);
11101
11151
  }
@@ -11206,8 +11256,8 @@ function loadConfig2(dir) {
11206
11256
  const parsed = configSchema2.safeParse(raw ?? {});
11207
11257
  if (!parsed.success) {
11208
11258
  const issues = parsed.error.issues.map((i) => {
11209
- const path42 = i.path.join(".");
11210
- return path42 ? ` - ${path42}: ${i.message}` : ` - ${i.message}`;
11259
+ const path52 = i.path.join(".");
11260
+ return path52 ? ` - ${path52}: ${i.message}` : ` - ${i.message}`;
11211
11261
  }).join("\n");
11212
11262
  throw new ConfigError2(`Invalid .kaddo/config.yml:
11213
11263
  ${issues}`);
@@ -11411,11 +11461,11 @@ function moduleArtifactCoverage2(dir, id) {
11411
11461
  };
11412
11462
  }
11413
11463
  function loadMappedModules2(dir) {
11414
- const path42 = join3(dir, DESCRIPTOR_PATH2);
11415
- if (!exists2(path42)) return [];
11464
+ const path52 = join3(dir, DESCRIPTOR_PATH2);
11465
+ if (!exists2(path52)) return [];
11416
11466
  let parsed;
11417
11467
  try {
11418
- parsed = parseYaml32(readFile2(path42));
11468
+ parsed = parseYaml32(readFile2(path52));
11419
11469
  } catch {
11420
11470
  return [];
11421
11471
  }
@@ -12861,6 +12911,18 @@ function parseAuth2(v) {
12861
12911
  const refs = strArray3(o2.refs).filter((r2) => !r2.includes("=") && /^[A-Za-z0-9_.-]+$/.test(r2));
12862
12912
  return { mode: str2(o2.mode), refs };
12863
12913
  }
12914
+ function parseScope2(v) {
12915
+ if (!v || typeof v !== "object" || Array.isArray(v)) return null;
12916
+ const o2 = v;
12917
+ const type = str2(o2.type);
12918
+ if (!type) return null;
12919
+ return { type, module: str2(o2.module) };
12920
+ }
12921
+ function purposeFromBody2(body) {
12922
+ const m2 = body.match(/^#{1,2}\s+Purpose\s*\n+([\s\S]*?)(?=\n#{1,2}\s|\s*$)/m);
12923
+ const text22 = m2 ? m2[1].trim() : "";
12924
+ return text22 || null;
12925
+ }
12864
12926
  function toDetail2(filePath, relPath, raw) {
12865
12927
  const parsed = matter42(raw);
12866
12928
  const fm = parsed.data ?? {};
@@ -12872,8 +12934,10 @@ function toDetail2(filePath, relPath, raw) {
12872
12934
  resourceType: str2(fm.resource_type),
12873
12935
  provider: str2(fm.provider),
12874
12936
  environments: strArray3(fm.environments),
12937
+ scope: parseScope2(fm.scope),
12938
+ modules: strArray3(fm.modules),
12875
12939
  path: relPath,
12876
- purpose: str2(fm.purpose),
12940
+ purpose: str2(fm.purpose) ?? purposeFromBody2(body),
12877
12941
  interfaces: parseInterfaces2(fm.access_interfaces),
12878
12942
  boundaries: parseBoundaries2(fm.access_boundaries),
12879
12943
  ...(() => {
@@ -12892,6 +12956,8 @@ function getResources2(dir) {
12892
12956
  resourceType: str2(fm.resource_type),
12893
12957
  provider: str2(fm.provider),
12894
12958
  environments: strArray3(fm.environments),
12959
+ scope: parseScope2(fm.scope),
12960
+ modules: strArray3(fm.modules),
12895
12961
  path: a2.relPath
12896
12962
  };
12897
12963
  }).sort((x2, y) => x2.id.localeCompare(y.id));
@@ -12960,9 +13026,9 @@ function buildGraph2(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
12960
13026
  ];
12961
13027
  const presentLayers = [];
12962
13028
  for (const layer2 of layerDocs) {
12963
- const path42 = layer2.files.map((f3) => `${KNOWLEDGE22}/${f3}`).find((rel) => exists2(join3(dir, rel)));
12964
- if (path42) {
12965
- addNode({ id: layer2.id, type: layer2.type, label: layer2.label, path: path42 });
13029
+ const path52 = layer2.files.map((f3) => `${KNOWLEDGE22}/${f3}`).find((rel) => exists2(join3(dir, rel)));
13030
+ if (path52) {
13031
+ addNode({ id: layer2.id, type: layer2.type, label: layer2.label, path: path52 });
12966
13032
  presentLayers.push(layer2.id);
12967
13033
  }
12968
13034
  }
@@ -13005,12 +13071,20 @@ function buildGraph2(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
13005
13071
  const resourceTitleById = /* @__PURE__ */ new Map();
13006
13072
  for (const r2 of getResources2(dir)) {
13007
13073
  resourceTitleById.set(r2.id, r2.title);
13074
+ const resId = `resource:${r2.id}`;
13008
13075
  addNode({
13009
- id: `resource:${r2.id}`,
13076
+ id: resId,
13010
13077
  type: "project-resource",
13011
13078
  label: r2.title ? `${r2.id} ${r2.title}`.trim() : r2.id,
13012
13079
  path: r2.path
13013
13080
  });
13081
+ const depModules = new Set(r2.modules);
13082
+ if (r2.scope?.type === "module" && r2.scope.module) depModules.add(r2.scope.module);
13083
+ for (const mod of depModules) {
13084
+ const modId = `module:${mod}`;
13085
+ addNode({ id: modId, type: "module", label: mod });
13086
+ addEdge(modId, resId, "depends_on");
13087
+ }
13014
13088
  }
13015
13089
  for (const wi of selectedWIs) {
13016
13090
  const id = wi.id || wi.title;
@@ -14698,6 +14772,16 @@ supporting notes as \`knowledge/tech/discovery/architecture-notes.md\` and decis
14698
14772
  under \`knowledge/tech/decisions/\`. Kaddo still reads the legacy root locations for backward
14699
14773
  compatibility, but new output should use \`discovery/\`.
14700
14774
 
14775
+ ## Project Resource candidates
14776
+
14777
+ When the knowledge describes an external system the project depends on (a database, cloud account,
14778
+ API, queue, storage, service\u2026), you may propose it as a **candidate** Project Resource \u2014 never create
14779
+ it silently. Discovery \u2260 creation: present candidates for human review and materialize only through
14780
+ \`kaddo_create_resource\` / \`kaddo resources create\` with explicit confirmation. Ground every
14781
+ candidate in the knowledge (no guessing from ambiguous names). Model the stable system, not its access
14782
+ interface (CLI/MCP/SQL/API are interfaces); keep \`resources\` separate from \`affected_modules\`; and
14783
+ never include secret values \u2014 only reference names.
14784
+
14701
14785
  ## Quality Checklist
14702
14786
 
14703
14787
  - Every component is backed by evidence from the context pack.
@@ -17251,8 +17335,8 @@ function parseGraphReason(v) {
17251
17335
  if (!v || typeof v !== "object") return null;
17252
17336
  const o2 = v;
17253
17337
  const relationship = typeof o2.relationship === "string" && o2.relationship.trim() ? o2.relationship.trim() : null;
17254
- const path42 = Array.isArray(o2.path) ? o2.path.map(String).filter(Boolean) : [];
17255
- return relationship || path42.length ? { relationship, path: path42 } : null;
17338
+ const path52 = Array.isArray(o2.path) ? o2.path.map(String).filter(Boolean) : [];
17339
+ return relationship || path52.length ? { relationship, path: path52 } : null;
17256
17340
  }
17257
17341
  function parseExplain(o2) {
17258
17342
  const refs = Array.isArray(o2.evidence) ? o2.evidence : Array.isArray(o2.evidence_refs) ? o2.evidence_refs : [];
@@ -17307,7 +17391,7 @@ function parseOriginalSnapshot(fm) {
17307
17391
  function readBody(filePath) {
17308
17392
  try {
17309
17393
  const raw = readFile2(filePath);
17310
- return matter82(raw).content;
17394
+ return matter92(raw).content;
17311
17395
  } catch {
17312
17396
  return "";
17313
17397
  }
@@ -17509,7 +17593,7 @@ function nextWorkItemId(dir) {
17509
17593
  let max = 0;
17510
17594
  const walk = (d3) => {
17511
17595
  if (!exists2(d3)) return;
17512
- for (const entry of fs32.readdirSync(d3)) {
17596
+ for (const entry of fs4.readdirSync(d3)) {
17513
17597
  const full = join3(d3, entry);
17514
17598
  if (isFile2(full)) {
17515
17599
  const m2 = entry.match(/WI-(\d+)/);
@@ -17522,15 +17606,15 @@ function nextWorkItemId(dir) {
17522
17606
  walk(wiDir);
17523
17607
  return `WI-${String(max + 1).padStart(3, "0")}`;
17524
17608
  }
17525
- function atomicWrite2(filePath, content) {
17526
- fs32.mkdirSync(path32.dirname(filePath), { recursive: true });
17609
+ function atomicWrite3(filePath, content) {
17610
+ fs4.mkdirSync(path4.dirname(filePath), { recursive: true });
17527
17611
  const tmp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
17528
- fs32.writeFileSync(tmp, content, "utf-8");
17612
+ fs4.writeFileSync(tmp, content, "utf-8");
17529
17613
  try {
17530
- fs32.renameSync(tmp, filePath);
17614
+ fs4.renameSync(tmp, filePath);
17531
17615
  } catch (err) {
17532
17616
  try {
17533
- fs32.rmSync(tmp, { force: true });
17617
+ fs4.rmSync(tmp, { force: true });
17534
17618
  } catch {
17535
17619
  }
17536
17620
  throw err;
@@ -17591,8 +17675,8 @@ function ensureH1(body, title) {
17591
17675
 
17592
17676
  ${trimmed}`;
17593
17677
  }
17594
- function serialize(data, body) {
17595
- return matter92.stringify(`
17678
+ function serialize2(data, body) {
17679
+ return matter102.stringify(`
17596
17680
  ${body.trim()}
17597
17681
  `, data);
17598
17682
  }
@@ -17660,8 +17744,8 @@ function createWorkItem(dir, opts) {
17660
17744
  const relPath = `${WORK_ITEMS_DIR}/draft/${id}-${slugify22(title)}.md`;
17661
17745
  const filePath = join3(dir, relPath);
17662
17746
  if (exists2(filePath)) throw new WorkItemWriteError("INVALID_INPUT", `Work Item file already exists: ${relPath}`);
17663
- const raw = serialize(data, body);
17664
- atomicWrite2(filePath, raw);
17747
+ const raw = serialize2(data, body);
17748
+ atomicWrite3(filePath, raw);
17665
17749
  return { id, path: relPath, revision: revisionOf(raw) };
17666
17750
  }
17667
17751
  function dimensionOf(type) {
@@ -18383,7 +18467,7 @@ function detectFormat(raw) {
18383
18467
  if (!trimmed) throw new ImportError("EMPTY_INPUT", "Import content is empty.");
18384
18468
  if (trimmed.startsWith("---")) {
18385
18469
  try {
18386
- const { data } = matter102(trimmed);
18470
+ const { data } = matter112(trimmed);
18387
18471
  if (data && typeof data === "object" && Object.keys(data).length > 0) {
18388
18472
  const d3 = data;
18389
18473
  if (d3.type === "work-item" || d3.id && typeof d3.id === "string" && /^WI-\d+/.test(d3.id)) {
@@ -18405,7 +18489,7 @@ function parseContent(raw) {
18405
18489
  let bodyText = trimmed;
18406
18490
  if (format === "kaddo-frontmatter" || format === "markdown-frontmatter") {
18407
18491
  try {
18408
- const parsed = matter102(trimmed);
18492
+ const parsed = matter112(trimmed);
18409
18493
  frontmatterFields = parsed.data ?? {};
18410
18494
  bodyText = parsed.content.trim();
18411
18495
  } catch {
@@ -18502,7 +18586,7 @@ function findDuplicateByHash(dir, hash) {
18502
18586
  const raw = readFile2(item.filePath);
18503
18587
  if (!raw) continue;
18504
18588
  try {
18505
- const { data } = matter102(raw);
18589
+ const { data } = matter112(raw);
18506
18590
  const source = data?.source;
18507
18591
  if (source && typeof source === "object" && !Array.isArray(source)) {
18508
18592
  const s = source;
@@ -18569,7 +18653,7 @@ function importWorkItem(dir, opts) {
18569
18653
  };
18570
18654
  }
18571
18655
  if (opts.onConflict === "replace") {
18572
- fs4.rmSync(existing.filePath, { force: true });
18656
+ fs5.rmSync(existing.filePath, { force: true });
18573
18657
  replacedWorkItem = existing.workItemId;
18574
18658
  }
18575
18659
  }
@@ -19011,8 +19095,8 @@ async function handleJiraResponse(res) {
19011
19095
  }
19012
19096
  return await res.json();
19013
19097
  }
19014
- async function jiraFetch(baseUrl, path42, auth, timeoutMs, params) {
19015
- const url = new URL(path42, baseUrl + "/");
19098
+ async function jiraFetch(baseUrl, path52, auth, timeoutMs, params) {
19099
+ const url = new URL(path52, baseUrl + "/");
19016
19100
  if (params) for (const [k2, v] of Object.entries(params)) url.searchParams.set(k2, v);
19017
19101
  const controller = new AbortController();
19018
19102
  const timer = setTimeout(() => controller.abort(), timeoutMs);
@@ -19030,8 +19114,8 @@ async function jiraFetch(baseUrl, path42, auth, timeoutMs, params) {
19030
19114
  clearTimeout(timer);
19031
19115
  }
19032
19116
  }
19033
- async function jiraPost(baseUrl, path42, auth, timeoutMs, body) {
19034
- const url = new URL(path42, baseUrl + "/");
19117
+ async function jiraPost(baseUrl, path52, auth, timeoutMs, body) {
19118
+ const url = new URL(path52, baseUrl + "/");
19035
19119
  const controller = new AbortController();
19036
19120
  const timer = setTimeout(() => controller.abort(), timeoutMs);
19037
19121
  try {
@@ -19397,7 +19481,7 @@ function buildInitiativeContent(id, input) {
19397
19481
  if (input.priority) data.priority = input.priority;
19398
19482
  if (input.source) data.source = input.source;
19399
19483
  if (input.sourceId) data.source_id = input.sourceId;
19400
- return matter122.stringify("\n" + (input.body ?? defaultBody(input.title.trim())), data);
19484
+ return matter13.stringify("\n" + (input.body ?? defaultBody(input.title.trim())), data);
19401
19485
  }
19402
19486
  function createInitiative(dir, input) {
19403
19487
  if (!input.title?.trim()) throw new InitiativeWriteError("Initiative title is required.");
@@ -19421,10 +19505,10 @@ function transitionInitiative(dir, id, to) {
19421
19505
  throw new InitiativeWriteError(`Invalid transition ${ini.status} \u2192 ${to}.`);
19422
19506
  }
19423
19507
  const raw = readFile2(ini.filePath);
19424
- const { data, content } = matter122(raw, {});
19508
+ const { data, content } = matter13(raw, {});
19425
19509
  data.status = to;
19426
19510
  if (to === "completed") data.completed_at = today2();
19427
- const newContent = matter122.stringify(content, data);
19511
+ const newContent = matter13.stringify(content, data);
19428
19512
  writeFile2(ini.filePath, newContent);
19429
19513
  const updated = parseInitiative2(dir, ini.filePath, newContent);
19430
19514
  if (!updated) throw new InitiativeWriteError("Failed to parse the Initiative after transition.");
@@ -19434,7 +19518,7 @@ function addCandidate(dir, id, candidate) {
19434
19518
  const ini = getInitiative(dir, id);
19435
19519
  if (!ini) throw new InitiativeWriteError(`Initiative ${id} not found.`);
19436
19520
  const raw = readFile2(ini.filePath);
19437
- const { data, content } = matter122(raw, {});
19521
+ const { data, content } = matter13(raw, {});
19438
19522
  const existing = Array.isArray(data.candidates) ? data.candidates : [];
19439
19523
  const fallbackId = `WI-CANDIDATE-${String(existing.length + 1).padStart(3, "0")}`;
19440
19524
  const normalized = normalizeCandidate(candidate, fallbackId);
@@ -19442,7 +19526,7 @@ function addCandidate(dir, id, candidate) {
19442
19526
  throw new InitiativeWriteError(`Candidate ${normalized.id} already exists in ${id}.`);
19443
19527
  }
19444
19528
  data.candidates = [...existing, candidateToData(normalized)];
19445
- const newContent = matter122.stringify(content, data);
19529
+ const newContent = matter13.stringify(content, data);
19446
19530
  writeFile2(ini.filePath, newContent);
19447
19531
  const updated = parseInitiative2(dir, ini.filePath, newContent);
19448
19532
  if (!updated) throw new InitiativeWriteError("Failed to parse the Initiative after adding a candidate.");
@@ -19452,7 +19536,7 @@ function updateInitiative(dir, id, patch) {
19452
19536
  const ini = getInitiative(dir, id);
19453
19537
  if (!ini) throw new InitiativeWriteError(`Initiative ${id} not found.`);
19454
19538
  const raw = readFile2(ini.filePath);
19455
- const { data, content } = matter122(raw, {});
19539
+ const { data, content } = matter13(raw, {});
19456
19540
  if (patch.title !== void 0) {
19457
19541
  if (!patch.title.trim()) throw new InitiativeWriteError("Initiative title cannot be empty.");
19458
19542
  data.title = patch.title.trim();
@@ -19462,7 +19546,7 @@ function updateInitiative(dir, id, patch) {
19462
19546
  if (patch.knowledgeLevel !== void 0) data.knowledge_level = patch.knowledgeLevel;
19463
19547
  if (patch.domains !== void 0) data.domains = patch.domains;
19464
19548
  if (patch.relatedCapabilities !== void 0) data.related_capabilities = patch.relatedCapabilities;
19465
- const newContent = matter122.stringify(content, data);
19549
+ const newContent = matter13.stringify(content, data);
19466
19550
  writeFile2(ini.filePath, newContent);
19467
19551
  const updated = parseInitiative2(dir, ini.filePath, newContent);
19468
19552
  if (!updated) throw new InitiativeWriteError("Failed to parse the Initiative after update.");
@@ -19480,10 +19564,10 @@ function addExternalLink(dir, id, link) {
19480
19564
  throw new InitiativeWriteError(`External link ${link.integration}:${link.externalId} already exists on ${id}.`);
19481
19565
  }
19482
19566
  const raw = readFile2(ini.filePath);
19483
- const { data, content } = matter122(raw, {});
19567
+ const { data, content } = matter13(raw, {});
19484
19568
  const existing = Array.isArray(data.external_links) ? data.external_links : [];
19485
19569
  data.external_links = [...existing, externalLinkToData(link)];
19486
- const newContent = matter122.stringify(content, data);
19570
+ const newContent = matter13.stringify(content, data);
19487
19571
  writeFile2(ini.filePath, newContent);
19488
19572
  const updated = parseInitiative2(dir, ini.filePath, newContent);
19489
19573
  if (!updated) throw new InitiativeWriteError("Failed to parse the Initiative after adding an external link.");
@@ -19552,7 +19636,7 @@ function buildWorkItemContent(wiId, iniId, candidate) {
19552
19636
  "_What did we learn? Update after completion._",
19553
19637
  ""
19554
19638
  ].join("\n");
19555
- return matter122.stringify("\n" + body, data);
19639
+ return matter13.stringify("\n" + body, data);
19556
19640
  }
19557
19641
  function materializeCandidate(dir, id, candidateId) {
19558
19642
  const ini = getInitiative(dir, id);
@@ -19569,13 +19653,13 @@ function materializeCandidate(dir, id, candidateId) {
19569
19653
  ensureDir2(join3(dir, DRAFT_DIR));
19570
19654
  writeFile2(join3(dir, DRAFT_DIR, fileName), buildWorkItemContent(wiId, ini.id, candidate));
19571
19655
  const raw = readFile2(ini.filePath);
19572
- const { data, content } = matter122(raw, {});
19656
+ const { data, content } = matter13(raw, {});
19573
19657
  const cands = Array.isArray(data.candidates) ? data.candidates : [];
19574
19658
  for (const c of cands) {
19575
19659
  if (c && typeof c === "object" && String(c.id) === candidateId) c.materialized_as = wiId;
19576
19660
  }
19577
19661
  data.candidates = cands;
19578
- const newContent = matter122.stringify(content, data);
19662
+ const newContent = matter13.stringify(content, data);
19579
19663
  writeFile2(ini.filePath, newContent);
19580
19664
  const updated = parseInitiative2(dir, ini.filePath, newContent);
19581
19665
  if (!updated) throw new InitiativeWriteError("Failed to parse the Initiative after materialization.");
@@ -19686,6 +19770,164 @@ function evaluateInitiativeCompletion(dir, ini) {
19686
19770
  };
19687
19771
  }
19688
19772
 
19773
+ // ../cli/src/core/resource-write.ts
19774
+ import fs6 from "fs";
19775
+ import path5 from "path";
19776
+ import matter14 from "gray-matter";
19777
+ var RESOURCES_DIR = "knowledge/tech/resources";
19778
+ var ResourceWriteError = class extends Error {
19779
+ constructor(code, message) {
19780
+ super(message);
19781
+ this.code = code;
19782
+ this.name = "ResourceWriteError";
19783
+ }
19784
+ code;
19785
+ };
19786
+ function slug4(s) {
19787
+ return s.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60);
19788
+ }
19789
+ function atomicWrite(filePath, content) {
19790
+ const tmp = `${filePath}.tmp-${process.pid}-${Date.now()}`;
19791
+ fs6.writeFileSync(tmp, content, "utf-8");
19792
+ fs6.renameSync(tmp, filePath);
19793
+ }
19794
+ function sanitizeRefs(refs) {
19795
+ if (!Array.isArray(refs)) return [];
19796
+ return refs.map((r2) => String(r2).trim()).filter((r2) => r2 && !r2.includes("=") && /^[A-Za-z0-9_.-]+$/.test(r2));
19797
+ }
19798
+ function allResourceArtifacts(dir) {
19799
+ return discoverKnowledge(dir).filter((a2) => a2.type === RESOURCE_ARTIFACT_TYPE);
19800
+ }
19801
+ function nextResourceId(dir, title, explicit) {
19802
+ const existing = new Set(allResourceArtifacts(dir).map((a2) => (a2.id || "").toUpperCase()));
19803
+ if (explicit && explicit.trim()) return explicit.trim();
19804
+ const base = `RES-${slug4(title) || "resource"}`;
19805
+ if (!existing.has(base.toUpperCase())) return base;
19806
+ for (let i = 2; i < 1e3; i++) {
19807
+ const candidate = `${base}-${i}`;
19808
+ if (!existing.has(candidate.toUpperCase())) return candidate;
19809
+ }
19810
+ throw new ResourceWriteError("ID_EXHAUSTED", `Could not derive a unique id for "${title}".`);
19811
+ }
19812
+ function validateResource(dir, input) {
19813
+ const findings = [];
19814
+ if (input.resourceType && !RESOURCE_TYPES.includes(input.resourceType)) {
19815
+ findings.push({ level: "warning", message: `Unknown resource_type "${input.resourceType}" (not in the known set).` });
19816
+ }
19817
+ for (const i of input.accessInterfaces ?? []) {
19818
+ if (i.type && !INTERFACE_TYPES.includes(i.type)) {
19819
+ findings.push({ level: "warning", message: `Unknown access interface type "${i.type}".` });
19820
+ }
19821
+ }
19822
+ const mapped = loadMappedModules(dir).map((m2) => m2.id);
19823
+ if (mapped.length > 0) {
19824
+ const refs = /* @__PURE__ */ new Set();
19825
+ if (input.scope?.type === "module" && input.scope.module) refs.add(input.scope.module);
19826
+ for (const m2 of input.modules ?? []) refs.add(m2);
19827
+ for (const m2 of refs) {
19828
+ if (!mapped.includes(m2)) findings.push({ level: "warning", message: `Module "${m2}" is not in .kaddo/modules.yml.` });
19829
+ }
19830
+ }
19831
+ if (input.scope && input.scope.type === "module" && !input.scope.module) {
19832
+ findings.push({ level: "error", message: 'scope.type "module" requires a module id.' });
19833
+ }
19834
+ return findings;
19835
+ }
19836
+ function buildFrontmatter(id, input, base = {}) {
19837
+ const data = { ...base };
19838
+ data.type = RESOURCE_ARTIFACT_TYPE;
19839
+ data.id = id;
19840
+ if (input.title != null) data.title = input.title;
19841
+ if (input.resourceType != null) data.resource_type = input.resourceType;
19842
+ if (input.provider != null) data.provider = input.provider;
19843
+ if (input.environments != null) data.environments = input.environments;
19844
+ if (input.scope != null) data.scope = input.scope.module ? { type: input.scope.type, module: input.scope.module } : { type: input.scope.type };
19845
+ if (input.modules != null) data.modules = input.modules;
19846
+ if (input.accessInterfaces != null) data.access_interfaces = input.accessInterfaces;
19847
+ if (input.accessBoundaries != null) data.access_boundaries = input.accessBoundaries;
19848
+ if (input.authentication != null) {
19849
+ const mode = input.authentication.mode;
19850
+ const refs = sanitizeRefs(input.authentication.refs);
19851
+ data.authentication = { ...mode ? { mode } : {}, ...refs.length ? { refs } : {} };
19852
+ }
19853
+ return data;
19854
+ }
19855
+ function serialize(data, body) {
19856
+ return matter14.stringify(`
19857
+ ${body.trim()}
19858
+ `, data);
19859
+ }
19860
+ function createResource(dir, input) {
19861
+ const title = (input.title ?? "").trim();
19862
+ if (!title) throw new ResourceWriteError("INVALID_INPUT", "A resource title is required.");
19863
+ const resourceType = (input.resourceType ?? "").trim();
19864
+ if (!resourceType) throw new ResourceWriteError("INVALID_INPUT", "A resource_type is required.");
19865
+ const findings = validateResource(dir, input);
19866
+ if (findings.some((f3) => f3.level === "error")) {
19867
+ throw new ResourceWriteError("VALIDATION_FAILED", findings.filter((f3) => f3.level === "error").map((f3) => f3.message).join("; "));
19868
+ }
19869
+ const existing = allResourceArtifacts(dir);
19870
+ if (input.id && existing.some((a2) => (a2.id || "").toUpperCase() === input.id.toUpperCase())) {
19871
+ throw new ResourceWriteError("DUPLICATE", `A resource with id "${input.id}" already exists.`);
19872
+ }
19873
+ if (existing.some((a2) => (a2.title || "").toLowerCase() === title.toLowerCase())) {
19874
+ throw new ResourceWriteError("DUPLICATE", `A resource titled "${title}" already exists.`);
19875
+ }
19876
+ const id = nextResourceId(dir, title, input.id);
19877
+ const data = buildFrontmatter(id, input);
19878
+ const body = `# Purpose
19879
+
19880
+ ${(input.purpose ?? "").trim() || "TODO: describe what this resource is for."}`;
19881
+ const relPath = `${RESOURCES_DIR}/${slug4(title) || slug4(id)}.md`;
19882
+ const filePath = join(dir, relPath);
19883
+ if (exists(filePath)) throw new ResourceWriteError("DUPLICATE", `Resource file already exists: ${relPath}`);
19884
+ ensureDir(path5.dirname(filePath));
19885
+ atomicWrite(filePath, serialize(data, body));
19886
+ return { id, path: relPath, findings };
19887
+ }
19888
+ function updateResource(dir, id, input) {
19889
+ const match = allResourceArtifacts(dir).find((a2) => a2.id === id || a2.title === id);
19890
+ if (!match) throw new ResourceWriteError("NOT_FOUND", `Project Resource "${id}" not found.`);
19891
+ const findings = validateResource(dir, input);
19892
+ if (findings.some((f3) => f3.level === "error")) {
19893
+ throw new ResourceWriteError("VALIDATION_FAILED", findings.filter((f3) => f3.level === "error").map((f3) => f3.message).join("; "));
19894
+ }
19895
+ const raw = readFile(match.filePath);
19896
+ const parsed = matter14(raw);
19897
+ const base = parsed.data ?? {};
19898
+ const data = buildFrontmatter(match.id || id, input, base);
19899
+ let body = parsed.content.trim();
19900
+ if (input.purpose != null) {
19901
+ const purpose = `# Purpose
19902
+
19903
+ ${input.purpose.trim()}`;
19904
+ body = /^#\s+Purpose/m.test(body) ? body.replace(/#\s+Purpose[\s\S]*?(?=\n#\s|\n*$)/, purpose + "\n") : `${purpose}
19905
+
19906
+ ${body}`.trim();
19907
+ }
19908
+ atomicWrite(match.filePath, serialize(data, body));
19909
+ return { id: match.id || id, path: match.relPath, findings };
19910
+ }
19911
+ function getResourceReferences(dir, id) {
19912
+ const refs = [];
19913
+ for (const wi of discoverWorkItems(dir)) {
19914
+ for (const rel of parseWorkItemResources(wi.rawFrontmatter)) {
19915
+ if (rel.id === id) refs.push({ kind: "work-item", id: wi.id || wi.title, role: rel.role, path: wi.relPath });
19916
+ }
19917
+ }
19918
+ return refs;
19919
+ }
19920
+ function deleteResource(dir, id, opts = {}) {
19921
+ const match = allResourceArtifacts(dir).find((a2) => a2.id === id || a2.title === id);
19922
+ if (!match) throw new ResourceWriteError("NOT_FOUND", `Project Resource "${id}" not found.`);
19923
+ const references = getResourceReferences(dir, match.id || id);
19924
+ if (!opts.confirm) {
19925
+ return { deleted: false, preview: { id: match.id || id, references } };
19926
+ }
19927
+ fs6.rmSync(match.filePath, { force: true });
19928
+ return { deleted: true, path: match.relPath };
19929
+ }
19930
+
19689
19931
  // src/tools.ts
19690
19932
  var ok = (data) => ({ ok: true, data });
19691
19933
  var fail = (message) => ({ ok: false, message });
@@ -19781,12 +20023,12 @@ function markWorkItemReady(root, id, confirm2) {
19781
20023
  }
19782
20024
  const raw = readText(root, item.path);
19783
20025
  if (!raw) return fail(`Could not read Work Item file: ${item.path}`);
19784
- const parsed = matter13(raw);
20026
+ const parsed = matter15(raw);
19785
20027
  const data = { ...parsed.data };
19786
20028
  const today3 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
19787
20029
  data.status = "ready";
19788
20030
  data.ready_at = today3;
19789
- const newContent = matter13.stringify(parsed.content, data);
20031
+ const newContent = matter15.stringify(parsed.content, data);
19790
20032
  const posixPath = item.path.replace(/\\/g, "/");
19791
20033
  let newRelPath = item.path;
19792
20034
  if (posixPath.includes("/work-items/draft/")) {
@@ -19957,11 +20199,64 @@ function getResourceTool(root, id) {
19957
20199
  if (!resource) return fail(`Project Resource "${id}" not found.`);
19958
20200
  return ok(resource);
19959
20201
  }
20202
+ function toResourceInput(args) {
20203
+ const input = {};
20204
+ if (args.title != null) input.title = args.title;
20205
+ if (args.resource_type != null) input.resourceType = args.resource_type;
20206
+ if (args.provider != null) input.provider = args.provider;
20207
+ if (args.environments != null) input.environments = args.environments;
20208
+ if (args.scope != null || args.module != null) {
20209
+ const type = args.scope || (args.module ? "module" : "system");
20210
+ input.scope = type === "module" ? { type: "module", module: args.module ?? null } : { type: "system" };
20211
+ }
20212
+ if (args.modules != null) input.modules = args.modules;
20213
+ if (args.purpose != null) input.purpose = args.purpose;
20214
+ if (args.access_interfaces != null) input.accessInterfaces = args.access_interfaces;
20215
+ if (args.authentication != null) input.authentication = args.authentication;
20216
+ return input;
20217
+ }
20218
+ function createResourceTool(root, args, confirm2) {
20219
+ const input = toResourceInput(args);
20220
+ const findings = validateResource(root, input);
20221
+ if (!confirm2) {
20222
+ return ok({ status: "needs_confirmation", action: "create", proposal: input, findings, message: "Re-call with confirm=true to create this resource." });
20223
+ }
20224
+ try {
20225
+ return ok({ status: "created", ...createResource(root, input) });
20226
+ } catch (e) {
20227
+ return fail(e instanceof ResourceWriteError ? e.message : String(e));
20228
+ }
20229
+ }
20230
+ function updateResourceTool(root, id, args, confirm2) {
20231
+ if (!getResource(root, id)) return fail(`Project Resource "${id}" not found.`);
20232
+ const input = toResourceInput(args);
20233
+ const findings = validateResource(root, input);
20234
+ if (!confirm2) {
20235
+ return ok({ status: "needs_confirmation", action: "update", id, changes: input, findings, message: "Re-call with confirm=true to apply this update." });
20236
+ }
20237
+ try {
20238
+ return ok({ status: "updated", ...updateResource(root, id, input) });
20239
+ } catch (e) {
20240
+ return fail(e instanceof ResourceWriteError ? e.message : String(e));
20241
+ }
20242
+ }
20243
+ function deleteResourceTool(root, id, confirm2) {
20244
+ if (!getResource(root, id)) return fail(`Project Resource "${id}" not found.`);
20245
+ if (!confirm2) {
20246
+ const references = getResourceReferences(root, id);
20247
+ return ok({ status: "needs_confirmation", action: "delete", id, references, message: "Re-call with confirm=true to delete. References in other artifacts are NOT removed." });
20248
+ }
20249
+ try {
20250
+ return ok({ status: "deleted", ...deleteResource(root, id, { confirm: true }) });
20251
+ } catch (e) {
20252
+ return fail(e instanceof ResourceWriteError ? e.message : String(e));
20253
+ }
20254
+ }
19960
20255
 
19961
20256
  // src/multirepo.ts
19962
- import fs5 from "fs";
19963
- import path4 from "path";
19964
- import matter14 from "gray-matter";
20257
+ import fs7 from "fs";
20258
+ import path6 from "path";
20259
+ import matter16 from "gray-matter";
19965
20260
  import { parse as parseYaml11, stringify as stringifyYaml7 } from "yaml";
19966
20261
  var ok2 = (data) => ({ ok: true, data });
19967
20262
  var fail2 = (message) => ({ ok: false, message });
@@ -19981,24 +20276,24 @@ function isCore2(config) {
19981
20276
  return projectRole2(config) === "core";
19982
20277
  }
19983
20278
  function readModuleFile(root, repoPath, relFile) {
19984
- const moduleDir = path4.resolve(root, repoPath);
19985
- const abs = path4.resolve(moduleDir, relFile);
20279
+ const moduleDir = path6.resolve(root, repoPath);
20280
+ const abs = path6.resolve(moduleDir, relFile);
19986
20281
  if (!abs.startsWith(moduleDir)) return null;
19987
- if (!fs5.existsSync(abs) || !fs5.statSync(abs).isFile()) return null;
19988
- return fs5.readFileSync(abs, "utf-8");
20282
+ if (!fs7.existsSync(abs) || !fs7.statSync(abs).isFile()) return null;
20283
+ return fs7.readFileSync(abs, "utf-8");
19989
20284
  }
19990
20285
  function detectModuleStatus2(root, repoPath) {
19991
- const moduleDir = path4.resolve(root, repoPath);
19992
- if (!fs5.existsSync(moduleDir)) {
20286
+ const moduleDir = path6.resolve(root, repoPath);
20287
+ if (!fs7.existsSync(moduleDir)) {
19993
20288
  return { status: "missing", configured: false, moduleContext: false, currentState: false, codebase: false, warning: `Directory \`${repoPath}\` does not exist.` };
19994
20289
  }
19995
- const configPath = path4.join(moduleDir, ".kaddo", "config.yml");
19996
- if (!fs5.existsSync(configPath)) {
20290
+ const configPath = path6.join(moduleDir, ".kaddo", "config.yml");
20291
+ if (!fs7.existsSync(configPath)) {
19997
20292
  return { status: "not_configured", configured: false, moduleContext: false, currentState: false, codebase: false, warning: `Run \`kaddo init\` inside \`${repoPath}\` and select multirepo \u2192 module.` };
19998
20293
  }
19999
20294
  let parsed = null;
20000
20295
  try {
20001
- parsed = parseYaml11(fs5.readFileSync(configPath, "utf-8"));
20296
+ parsed = parseYaml11(fs7.readFileSync(configPath, "utf-8"));
20002
20297
  } catch {
20003
20298
  }
20004
20299
  const role = parsed?.project?.role ?? parsed?.multirepo?.role;
@@ -20009,14 +20304,14 @@ function detectModuleStatus2(root, repoPath) {
20009
20304
  status: "configured",
20010
20305
  configured: true,
20011
20306
  role: "module",
20012
- moduleContext: fs5.existsSync(path4.join(moduleDir, "knowledge", "tech", "module", "module-context.md")) || fs5.existsSync(path4.join(moduleDir, "knowledge", "module", "module-context.md")),
20013
- currentState: fs5.existsSync(path4.join(moduleDir, "knowledge", "tech", "current-state.md")),
20014
- codebase: fs5.existsSync(path4.join(moduleDir, "knowledge", "tech", "codebase.md"))
20307
+ moduleContext: fs7.existsSync(path6.join(moduleDir, "knowledge", "tech", "module", "module-context.md")) || fs7.existsSync(path6.join(moduleDir, "knowledge", "module", "module-context.md")),
20308
+ currentState: fs7.existsSync(path6.join(moduleDir, "knowledge", "tech", "current-state.md")),
20309
+ codebase: fs7.existsSync(path6.join(moduleDir, "knowledge", "tech", "codebase.md"))
20015
20310
  };
20016
20311
  }
20017
20312
  function resolveModuleContextPath(root, repoPath) {
20018
- const moduleDir = path4.resolve(root, repoPath);
20019
- if (fs5.existsSync(path4.join(moduleDir, "knowledge", "tech", "module", "module-context.md"))) {
20313
+ const moduleDir = path6.resolve(root, repoPath);
20314
+ if (fs7.existsSync(path6.join(moduleDir, "knowledge", "tech", "module", "module-context.md"))) {
20020
20315
  return "knowledge/tech/module/module-context.md";
20021
20316
  }
20022
20317
  return "knowledge/module/module-context.md";
@@ -20113,7 +20408,7 @@ function validateWorkItemModulesTool(root, args) {
20113
20408
  if (!wi) return fail2(`Work Item "${args.workItemId}" not found.`);
20114
20409
  const raw = readText(root, wi.path);
20115
20410
  if (!raw) return fail2(`Could not read Work Item file.`);
20116
- const parsed = matter14(raw);
20411
+ const parsed = matter16(raw);
20117
20412
  const data = parsed.data;
20118
20413
  const affectedModules = Array.isArray(data.affected_modules) ? data.affected_modules.map(String) : [];
20119
20414
  const codeEntries = Array.isArray(data.code) ? data.code.filter((c) => typeof c === "object" && c !== null).map((c) => ({ repo: typeof c.repo === "string" ? c.repo : void 0, paths: Array.isArray(c.paths) ? c.paths.map(String) : [] })) : [];
@@ -20166,7 +20461,7 @@ function getWorkItemContextTool(root, args) {
20166
20461
  if (!wi) return fail2(`Work Item "${args.workItemId}" not found.`);
20167
20462
  const raw = readText(root, wi.path);
20168
20463
  if (!raw) return fail2(`Could not read Work Item file.`);
20169
- const parsed = matter14(raw);
20464
+ const parsed = matter16(raw);
20170
20465
  const data = parsed.data;
20171
20466
  const affectedModules = Array.isArray(data.affected_modules) ? data.affected_modules.map(String) : [];
20172
20467
  const codeEntries = Array.isArray(data.code) ? data.code.filter((c) => typeof c === "object" && c !== null).map((c) => ({ repo: typeof c.repo === "string" ? c.repo : void 0, paths: Array.isArray(c.paths) ? c.paths.map(String) : [] })) : [];
@@ -20220,15 +20515,15 @@ function extractSection(content, heading) {
20220
20515
  return out;
20221
20516
  }
20222
20517
  function buildBranchStrategy(wi, affectedModules, codeEntries, descriptor) {
20223
- const slug5 = slugify4(wi.title);
20518
+ const slug6 = slugify4(wi.title);
20224
20519
  const wiLower = wi.id.toLowerCase();
20225
- const coreBranch = `feature/${wiLower}-${slug5}`;
20520
+ const coreBranch = `feature/${wiLower}-${slug6}`;
20226
20521
  const moduleBranches = {};
20227
20522
  const commitMessages = {};
20228
20523
  for (const modId of affectedModules) {
20229
20524
  const mod = (descriptor.modules ?? []).find((m2) => m2.id === modId);
20230
20525
  const repo = mod ? moduleRepoPath(mod) || modId : modId;
20231
- moduleBranches[repo] = `feature/${wiLower}-${modId}-${slug5.slice(0, 20)}`;
20526
+ moduleBranches[repo] = `feature/${wiLower}-${modId}-${slug6.slice(0, 20)}`;
20232
20527
  commitMessages[repo] = `fix(${wiLower}): update ${modId}`;
20233
20528
  }
20234
20529
  commitMessages["core"] = `docs(${wiLower}): capture cross-repo implementation summary`;
@@ -20252,7 +20547,7 @@ function suggestBranchStrategyTool(root, args) {
20252
20547
  if (!wi) return fail2(`Work Item "${args.workItemId}" not found.`);
20253
20548
  const raw = readText(root, wi.path);
20254
20549
  if (!raw) return fail2(`Could not read Work Item file.`);
20255
- const parsed = matter14(raw);
20550
+ const parsed = matter16(raw);
20256
20551
  const data = parsed.data;
20257
20552
  const affectedModules = Array.isArray(data.affected_modules) ? data.affected_modules.map(String) : [];
20258
20553
  const codeEntries = Array.isArray(data.code) ? data.code.filter((c) => typeof c === "object" && c !== null).map((c) => ({ repo: typeof c.repo === "string" ? c.repo : void 0, paths: Array.isArray(c.paths) ? c.paths.map(String) : [] })) : [];
@@ -20401,31 +20696,31 @@ function modulesDiscoverTool(root, args) {
20401
20696
  const sysName = config.system?.name ?? config.project?.name ?? "";
20402
20697
  const workspaceRoots2 = config.multirepo?.workspace_roots ?? [".."];
20403
20698
  const IGNORED = /* @__PURE__ */ new Set([".git", "node_modules", "dist", "build", "coverage", "vendor", ".next", "out", "tmp", "temp", ".kaddo"]);
20404
- const coreAbs = path4.resolve(root);
20699
+ const coreAbs = path6.resolve(root);
20405
20700
  const discovered = [];
20406
20701
  for (const wr of workspaceRoots2) {
20407
- const rootAbs = path4.resolve(root, wr);
20408
- if (!fs5.existsSync(rootAbs) || !fs5.statSync(rootAbs).isDirectory()) continue;
20702
+ const rootAbs = path6.resolve(root, wr);
20703
+ if (!fs7.existsSync(rootAbs) || !fs7.statSync(rootAbs).isDirectory()) continue;
20409
20704
  let entries;
20410
20705
  try {
20411
- entries = fs5.readdirSync(rootAbs);
20706
+ entries = fs7.readdirSync(rootAbs);
20412
20707
  } catch {
20413
20708
  continue;
20414
20709
  }
20415
20710
  for (const entry of entries) {
20416
20711
  if (IGNORED.has(entry)) continue;
20417
- const candidateAbs = path4.resolve(rootAbs, entry);
20418
- if (!fs5.statSync(candidateAbs).isDirectory()) continue;
20712
+ const candidateAbs = path6.resolve(rootAbs, entry);
20713
+ if (!fs7.statSync(candidateAbs).isDirectory()) continue;
20419
20714
  if (candidateAbs === coreAbs) continue;
20420
- const relPath = path4.relative(root, candidateAbs).replace(/\\/g, "/");
20421
- const cfgPath = path4.join(candidateAbs, ".kaddo", "config.yml");
20422
- if (!fs5.existsSync(cfgPath)) {
20715
+ const relPath = path6.relative(root, candidateAbs).replace(/\\/g, "/");
20716
+ const cfgPath = path6.join(candidateAbs, ".kaddo", "config.yml");
20717
+ if (!fs7.existsSync(cfgPath)) {
20423
20718
  discovered.push({ id: entry, path: relPath, status: "not_configured", eligibleForMapping: false });
20424
20719
  continue;
20425
20720
  }
20426
20721
  let parsed = null;
20427
20722
  try {
20428
- parsed = parseYaml11(fs5.readFileSync(cfgPath, "utf-8"));
20723
+ parsed = parseYaml11(fs7.readFileSync(cfgPath, "utf-8"));
20429
20724
  } catch {
20430
20725
  }
20431
20726
  const role = parsed?.project?.role ?? parsed?.multirepo?.role;
@@ -20499,8 +20794,8 @@ ${list3(data.owners)}
20499
20794
 
20500
20795
  // ../cli/src/core/system-topology.ts
20501
20796
  import { parse as parseYaml12, stringify as stringifyYaml8 } from "yaml";
20502
- import fs6 from "fs";
20503
- import path5 from "path";
20797
+ import fs8 from "fs";
20798
+ import path7 from "path";
20504
20799
  import crypto from "crypto";
20505
20800
  var TOPOLOGY_FILE2 = "knowledge/tech/system-topology.yml";
20506
20801
  var SYSTEM_ENTITY_KINDS2 = /* @__PURE__ */ new Set([
@@ -20872,16 +21167,16 @@ function findSystemPaths(dir, source, target, opts = {}) {
20872
21167
  out.get(r2.source).push(r2.target);
20873
21168
  }
20874
21169
  const paths = [];
20875
- const walk = (node, path7) => {
21170
+ const walk = (node, path9) => {
20876
21171
  if (paths.length >= 20) return;
20877
- if (node === target && path7.length > 1) {
20878
- paths.push([...path7]);
21172
+ if (node === target && path9.length > 1) {
21173
+ paths.push([...path9]);
20879
21174
  return;
20880
21175
  }
20881
- if (path7.length > maxDepth) return;
21176
+ if (path9.length > maxDepth) return;
20882
21177
  for (const nxt of out.get(node) ?? []) {
20883
- if (path7.includes(nxt)) continue;
20884
- walk(nxt, [...path7, nxt]);
21178
+ if (path9.includes(nxt)) continue;
21179
+ walk(nxt, [...path9, nxt]);
20885
21180
  }
20886
21181
  };
20887
21182
  walk(source, [source]);
@@ -20906,14 +21201,14 @@ function getImpactCandidates(dir, seedIds, opts = {}) {
20906
21201
  existing.reason.push(reason);
20907
21202
  continue;
20908
21203
  }
20909
- const path7 = findSystemPaths(dir, seed, n.id, { maxDepth: opts.maxDepth ?? DEFAULT_MAX_DEPTH })[0];
21204
+ const path9 = findSystemPaths(dir, seed, n.id, { maxDepth: opts.maxDepth ?? DEFAULT_MAX_DEPTH })[0];
20910
21205
  candidates.set(n.id, {
20911
21206
  nodeId: n.id,
20912
21207
  label: n.label,
20913
21208
  kind: n.type,
20914
21209
  ...n.moduleId ? { moduleId: n.moduleId } : {},
20915
21210
  reason: [reason],
20916
- ...path7 ? { graphPath: path7.map((id) => byId.get(id)?.label ?? id) } : {},
21211
+ ...path9 ? { graphPath: path9.map((id) => byId.get(id)?.label ?? id) } : {},
20917
21212
  ...n.implementationRefs?.length ? { implementationRefs: n.implementationRefs } : {},
20918
21213
  ...n.knowledgeRefs?.length ? { knowledgeRefs: n.knowledgeRefs } : {},
20919
21214
  status: "candidate",
@@ -21012,14 +21307,14 @@ function systemImpactCandidatesTool(root, args) {
21012
21307
  }
21013
21308
 
21014
21309
  // ../cli/src/services/integrations.ts
21015
- import matter16 from "gray-matter";
21310
+ import matter18 from "gray-matter";
21016
21311
  import { parse as parseYaml13, stringify as stringifyYaml9 } from "yaml";
21017
21312
 
21018
21313
  // ../cli/src/core/work-item-write.ts
21019
- import fs7 from "fs";
21020
- import path6 from "path";
21314
+ import fs9 from "fs";
21315
+ import path8 from "path";
21021
21316
  import crypto4 from "crypto";
21022
- import matter15 from "gray-matter";
21317
+ import matter17 from "gray-matter";
21023
21318
  var SECTION_ORDER2 = [
21024
21319
  "Actor",
21025
21320
  "Outcome",
@@ -21687,8 +21982,8 @@ async function handleJiraResponse2(res) {
21687
21982
  }
21688
21983
  return await res.json();
21689
21984
  }
21690
- async function jiraFetch2(baseUrl, path7, auth, timeoutMs, params) {
21691
- const url = new URL(path7, baseUrl + "/");
21985
+ async function jiraFetch2(baseUrl, path9, auth, timeoutMs, params) {
21986
+ const url = new URL(path9, baseUrl + "/");
21692
21987
  if (params) for (const [k2, v] of Object.entries(params)) url.searchParams.set(k2, v);
21693
21988
  const controller = new AbortController();
21694
21989
  const timer = setTimeout(() => controller.abort(), timeoutMs);
@@ -21706,8 +22001,8 @@ async function jiraFetch2(baseUrl, path7, auth, timeoutMs, params) {
21706
22001
  clearTimeout(timer);
21707
22002
  }
21708
22003
  }
21709
- async function jiraPost2(baseUrl, path7, auth, timeoutMs, body) {
21710
- const url = new URL(path7, baseUrl + "/");
22004
+ async function jiraPost2(baseUrl, path9, auth, timeoutMs, body) {
22005
+ const url = new URL(path9, baseUrl + "/");
21711
22006
  const controller = new AbortController();
21712
22007
  const timer = setTimeout(() => controller.abort(), timeoutMs);
21713
22008
  try {
@@ -22067,7 +22362,7 @@ async function integrationsWorkItemTool(root, args) {
22067
22362
  }
22068
22363
 
22069
22364
  // ../cli/src/core/context-pack.ts
22070
- import matter17 from "gray-matter";
22365
+ import matter19 from "gray-matter";
22071
22366
  var CONTEXT_PACK_VERSION = "1";
22072
22367
  var ARCH_DIR4 = "knowledge";
22073
22368
  function readScanJson(dir) {
@@ -22080,7 +22375,7 @@ function readScanJson(dir) {
22080
22375
  }
22081
22376
  }
22082
22377
  function firstParagraph4(markdown) {
22083
- const body = matter17(markdown).content.trim();
22378
+ const body = matter19(markdown).content.trim();
22084
22379
  const para = body.split("\n\n").map((p) => p.trim()).find((p) => p && !p.startsWith("#"));
22085
22380
  return para ?? "";
22086
22381
  }
@@ -23047,7 +23342,7 @@ function requireKnowledge(root) {
23047
23342
  throw new KaddoMcpError("Knowledge repository not found. Run `kaddo bootstrap` first.");
23048
23343
  }
23049
23344
  }
23050
- function slug4(s) {
23345
+ function slug5(s) {
23051
23346
  return s.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "project";
23052
23347
  }
23053
23348
  function generateContext(root) {
@@ -23180,7 +23475,7 @@ function generateCapsuleDraft(root) {
23180
23475
  const config = requireConfig(root);
23181
23476
  requireKnowledge(root);
23182
23477
  const capsule = buildCapsule(root, config);
23183
- const name = slug4(config.project.name);
23478
+ const name = slug5(config.project.name);
23184
23479
  const md = `.kaddo/exports/${name}.capsule.md`;
23185
23480
  const json = `.kaddo/exports/${name}.capsule.json`;
23186
23481
  writeDerived(root, md, renderCapsuleMarkdown(capsule));
@@ -23478,6 +23773,53 @@ function createServer(root) {
23478
23773
  { title: "Get Project Resource", description: "Get one Project Resource by ID: purpose, environments, access interfaces, boundaries and auth references (never values).", inputSchema: { id: z4.string() } },
23479
23774
  async (args) => toolText(guarded(root, () => getResourceTool(root, args.id)))
23480
23775
  );
23776
+ const resourceFields = {
23777
+ title: z4.string().optional(),
23778
+ resource_type: z4.string().optional(),
23779
+ provider: z4.string().optional(),
23780
+ environments: z4.array(z4.string()).optional(),
23781
+ scope: z4.string().optional(),
23782
+ module: z4.string().optional(),
23783
+ modules: z4.array(z4.string()).optional(),
23784
+ purpose: z4.string().optional(),
23785
+ access_interfaces: z4.array(z4.object({
23786
+ type: z4.string(),
23787
+ tool: z4.string().optional(),
23788
+ provider: z4.string().optional(),
23789
+ purpose: z4.string().optional(),
23790
+ operations: z4.array(z4.string()).optional(),
23791
+ environments: z4.array(z4.string()).optional(),
23792
+ constraints: z4.string().optional()
23793
+ })).optional(),
23794
+ authentication: z4.object({ mode: z4.string().optional(), refs: z4.array(z4.string()).optional() }).optional()
23795
+ };
23796
+ server.registerTool(
23797
+ "kaddo_create_resource",
23798
+ {
23799
+ title: "Create Project Resource",
23800
+ description: "Propose creating a Project Resource. Without confirm, returns a preview + validation findings (writes nothing). With confirm=true, Core persists it as Markdown. Never include credential values \u2014 only reference names.",
23801
+ inputSchema: { ...resourceFields, confirm: z4.boolean().optional() }
23802
+ },
23803
+ async (args) => toolText(guarded(root, () => createResourceTool(root, args, args.confirm)))
23804
+ );
23805
+ server.registerTool(
23806
+ "kaddo_update_resource",
23807
+ {
23808
+ title: "Update Project Resource",
23809
+ description: "Propose updating a Project Resource (no-lossy). Without confirm, returns a preview; with confirm=true, Core applies it. Never include credential values.",
23810
+ inputSchema: { id: z4.string(), ...resourceFields, confirm: z4.boolean().optional() }
23811
+ },
23812
+ async (args) => toolText(guarded(root, () => updateResourceTool(root, args.id, args, args.confirm)))
23813
+ );
23814
+ server.registerTool(
23815
+ "kaddo_delete_resource",
23816
+ {
23817
+ title: "Delete Project Resource",
23818
+ description: "Delete a Project Resource. Without confirm, returns what references it (writes nothing); with confirm=true, Core removes only the resource \u2014 references in other artifacts are left intact.",
23819
+ inputSchema: { id: z4.string(), confirm: z4.boolean().optional() }
23820
+ },
23821
+ async (args) => toolText(guarded(root, () => deleteResourceTool(root, args.id, args.confirm)))
23822
+ );
23481
23823
  server.registerTool(
23482
23824
  "kaddo_list_capsules",
23483
23825
  { title: "List Knowledge Capsules", description: "List registered external Knowledge Capsules.", inputSchema: {} },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaddo/mcp",
3
- "version": "3.115.0",
3
+ "version": "3.116.0",
4
4
  "description": "MCP server for Kaddo project knowledge: read-only resources/tools/prompts plus safe derived generation under .kaddo/.",
5
5
  "license": "MIT",
6
6
  "repository": {