@kaddo/mcp 3.115.0 → 3.117.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 +674 -156
  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);
@@ -390,13 +393,15 @@ import matter10 from "gray-matter";
390
393
 
391
394
  // ../cli/src/core/config.ts
392
395
  import { z } from "zod";
393
- import { parse as parseYaml2 } from "yaml";
396
+ import { parse as parseYaml2, stringify as stringifyYaml } from "yaml";
394
397
  var PROJECT_STATES = ["new", "pre-ai", "legacy", "ai-assisted"];
398
+ var PROJECT_MODES = ["standard", "poc"];
395
399
  var TEAM_SIZES = ["indie", "small", "medium", "enterprise"];
396
400
  var REPOSITORY_STRUCTURES = ["monorepo", "multirepo"];
397
401
  var PROJECT_LANGUAGES = ["en", "es"];
398
402
  var DEFAULTS = {
399
403
  state: "pre-ai",
404
+ mode: "standard",
400
405
  structure: "monorepo",
401
406
  teamSize: "indie",
402
407
  language: "en"
@@ -408,6 +413,9 @@ function projectLanguage(config) {
408
413
  const lang = config.project.language;
409
414
  return lang === "es" ? "es" : "en";
410
415
  }
416
+ function projectMode(config) {
417
+ return config.project.mode === "poc" ? "poc" : "standard";
418
+ }
411
419
  var ConfigError = class extends Error {
412
420
  constructor(message) {
413
421
  super(message);
@@ -419,6 +427,11 @@ var projectStateSchema = z.enum(["new", "pre-ai", "legacy", "ai-assisted"], {
419
427
  message: `project.state must be one of: ${PROJECT_STATES.join(", ")}`
420
428
  })
421
429
  });
430
+ var projectModeSchema = z.enum(["standard", "poc"], {
431
+ errorMap: () => ({
432
+ message: `project.mode must be one of: ${PROJECT_MODES.join(", ")}`
433
+ })
434
+ });
422
435
  var teamSizeSchema = z.enum(["indie", "small", "medium", "enterprise"], {
423
436
  errorMap: () => ({
424
437
  message: `team.size must be one of: ${TEAM_SIZES.join(", ")}`
@@ -440,6 +453,7 @@ var configSchema = z.object({
440
453
  project: z.object({
441
454
  name: z.string().default("project"),
442
455
  state: projectStateSchema.default(DEFAULTS.state),
456
+ mode: projectModeSchema.default(DEFAULTS.mode),
443
457
  structure: structureSchema.default(DEFAULTS.structure),
444
458
  language: languageSchema.default(DEFAULTS.language),
445
459
  role: multirepoRoleSchema,
@@ -485,8 +499,8 @@ function loadConfig(dir) {
485
499
  const parsed = configSchema.safeParse(raw ?? {});
486
500
  if (!parsed.success) {
487
501
  const issues = parsed.error.issues.map((i) => {
488
- const path7 = i.path.join(".");
489
- return path7 ? ` - ${path7}: ${i.message}` : ` - ${i.message}`;
502
+ const path9 = i.path.join(".");
503
+ return path9 ? ` - ${path9}: ${i.message}` : ` - ${i.message}`;
490
504
  }).join("\n");
491
505
  throw new ConfigError(`Invalid .kaddo/config.yml:
492
506
  ${issues}`);
@@ -888,7 +902,7 @@ function assessPhase(input) {
888
902
 
889
903
  // ../cli/src/core/capsule.ts
890
904
  import matter4 from "gray-matter";
891
- import { parse as parseYaml7, stringify as stringifyYaml3 } from "yaml";
905
+ import { parse as parseYaml7, stringify as stringifyYaml4 } from "yaml";
892
906
 
893
907
  // ../cli/src/services/owners.ts
894
908
  import { parse as parseYaml3 } from "yaml";
@@ -927,11 +941,11 @@ function moduleArtifactCoverage(dir, id) {
927
941
  };
928
942
  }
929
943
  function loadMappedModules(dir) {
930
- const path7 = join(dir, DESCRIPTOR_PATH);
931
- if (!exists(path7)) return [];
944
+ const path9 = join(dir, DESCRIPTOR_PATH);
945
+ if (!exists(path9)) return [];
932
946
  let parsed;
933
947
  try {
934
- parsed = parseYaml4(readFile(path7));
948
+ parsed = parseYaml4(readFile(path9));
935
949
  } catch {
936
950
  return [];
937
951
  }
@@ -1142,7 +1156,7 @@ var Q2 = o("\u25B2", "!");
1142
1156
  var ee = o("\u25A0", "x");
1143
1157
 
1144
1158
  // ../cli/src/commands/modules-map.ts
1145
- import { parse as parseYaml6, stringify as stringifyYaml2 } from "yaml";
1159
+ import { parse as parseYaml6, stringify as stringifyYaml3 } from "yaml";
1146
1160
 
1147
1161
  // ../cli/src/templates/registry.ts
1148
1162
  var QUALITY = "## Quality checklist";
@@ -2306,7 +2320,7 @@ ${QUALITY}
2306
2320
  `;
2307
2321
 
2308
2322
  // ../cli/src/commands/modules-discover.ts
2309
- import { parse as parseYaml5, stringify as stringifyYaml } from "yaml";
2323
+ import { parse as parseYaml5, stringify as stringifyYaml2 } from "yaml";
2310
2324
 
2311
2325
  // ../cli/src/core/capsule.ts
2312
2326
  var KNOWLEDGE2 = "knowledge";
@@ -2438,7 +2452,7 @@ function sectionParagraph2(md, title) {
2438
2452
  }
2439
2453
  return "";
2440
2454
  }
2441
- function parseCapsule(id, path7, md) {
2455
+ function parseCapsule(id, path9, md) {
2442
2456
  const { data } = matter4(md);
2443
2457
  const updatedAt = data.updated_at ? String(data.updated_at) : void 0;
2444
2458
  let ageDays = null;
@@ -2448,7 +2462,7 @@ function parseCapsule(id, path7, md) {
2448
2462
  }
2449
2463
  return {
2450
2464
  id,
2451
- path: path7,
2465
+ path: path9,
2452
2466
  system: data.system ? String(data.system) : id,
2453
2467
  owner: data.owner ? String(data.owner) : void 0,
2454
2468
  updatedAt,
@@ -2593,6 +2607,18 @@ function discoverInitiatives(dir) {
2593
2607
  // ../cli/src/core/resources.ts
2594
2608
  import matter6 from "gray-matter";
2595
2609
  var RESOURCE_ARTIFACT_TYPE = "project-resource";
2610
+ var RESOURCE_TYPES = [
2611
+ "database",
2612
+ "cloud",
2613
+ "api",
2614
+ "queue",
2615
+ "storage",
2616
+ "repository",
2617
+ "platform",
2618
+ "service",
2619
+ "other"
2620
+ ];
2621
+ var INTERFACE_TYPES = ["cli", "mcp", "api", "sql", "sdk", "iac", "console", "other"];
2596
2622
  function str(v) {
2597
2623
  return typeof v === "string" && v.trim() ? v.trim() : null;
2598
2624
  }
@@ -2636,6 +2662,18 @@ function parseAuth(v) {
2636
2662
  const refs = strArray(o2.refs).filter((r2) => !r2.includes("=") && /^[A-Za-z0-9_.-]+$/.test(r2));
2637
2663
  return { mode: str(o2.mode), refs };
2638
2664
  }
2665
+ function parseScope(v) {
2666
+ if (!v || typeof v !== "object" || Array.isArray(v)) return null;
2667
+ const o2 = v;
2668
+ const type = str(o2.type);
2669
+ if (!type) return null;
2670
+ return { type, module: str(o2.module) };
2671
+ }
2672
+ function purposeFromBody(body) {
2673
+ const m2 = body.match(/^#{1,2}\s+Purpose\s*\n+([\s\S]*?)(?=\n#{1,2}\s|\s*$)/m);
2674
+ const text3 = m2 ? m2[1].trim() : "";
2675
+ return text3 || null;
2676
+ }
2639
2677
  function toDetail(filePath, relPath, raw) {
2640
2678
  const parsed = matter6(raw);
2641
2679
  const fm = parsed.data ?? {};
@@ -2647,8 +2685,10 @@ function toDetail(filePath, relPath, raw) {
2647
2685
  resourceType: str(fm.resource_type),
2648
2686
  provider: str(fm.provider),
2649
2687
  environments: strArray(fm.environments),
2688
+ scope: parseScope(fm.scope),
2689
+ modules: strArray(fm.modules),
2650
2690
  path: relPath,
2651
- purpose: str(fm.purpose),
2691
+ purpose: str(fm.purpose) ?? purposeFromBody(body),
2652
2692
  interfaces: parseInterfaces(fm.access_interfaces),
2653
2693
  boundaries: parseBoundaries(fm.access_boundaries),
2654
2694
  ...(() => {
@@ -2667,6 +2707,8 @@ function getResources(dir) {
2667
2707
  resourceType: str(fm.resource_type),
2668
2708
  provider: str(fm.provider),
2669
2709
  environments: strArray(fm.environments),
2710
+ scope: parseScope(fm.scope),
2711
+ modules: strArray(fm.modules),
2670
2712
  path: a2.relPath
2671
2713
  };
2672
2714
  }).sort((x2, y) => x2.id.localeCompare(y.id));
@@ -2737,9 +2779,9 @@ function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
2737
2779
  ];
2738
2780
  const presentLayers = [];
2739
2781
  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 });
2782
+ const path9 = layer2.files.map((f3) => `${KNOWLEDGE3}/${f3}`).find((rel) => exists(join(dir, rel)));
2783
+ if (path9) {
2784
+ addNode({ id: layer2.id, type: layer2.type, label: layer2.label, path: path9 });
2743
2785
  presentLayers.push(layer2.id);
2744
2786
  }
2745
2787
  }
@@ -2782,12 +2824,20 @@ function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
2782
2824
  const resourceTitleById = /* @__PURE__ */ new Map();
2783
2825
  for (const r2 of getResources(dir)) {
2784
2826
  resourceTitleById.set(r2.id, r2.title);
2827
+ const resId = `resource:${r2.id}`;
2785
2828
  addNode({
2786
- id: `resource:${r2.id}`,
2829
+ id: resId,
2787
2830
  type: "project-resource",
2788
2831
  label: r2.title ? `${r2.id} ${r2.title}`.trim() : r2.id,
2789
2832
  path: r2.path
2790
2833
  });
2834
+ const depModules = new Set(r2.modules);
2835
+ if (r2.scope?.type === "module" && r2.scope.module) depModules.add(r2.scope.module);
2836
+ for (const mod of depModules) {
2837
+ const modId = `module:${mod}`;
2838
+ addNode({ id: modId, type: "module", label: mod });
2839
+ addEdge(modId, resId, "depends_on");
2840
+ }
2791
2841
  }
2792
2842
  for (const wi of selectedWIs) {
2793
2843
  const id = wi.id || wi.title;
@@ -4273,6 +4323,43 @@ function buildTechDecisions(dir) {
4273
4323
  };
4274
4324
  }
4275
4325
 
4326
+ // ../cli/src/core/poc.ts
4327
+ var POC_ARTIFACT_PATH = "knowledge/delivery/poc.md";
4328
+ var PLACEHOLDER = /^[_<].*[_>]$|^(describe|list|document|what|how)\b/i;
4329
+ function section2(markdown, heading) {
4330
+ const match = markdown.match(new RegExp(`^##\\s+${heading}\\s*$([\\s\\S]*?)(?=^##\\s+|(?![\\s\\S]))`, "im"));
4331
+ return match?.[1] ?? "";
4332
+ }
4333
+ function meaningful(text3) {
4334
+ return text3.split(/\r?\n/).some((line) => {
4335
+ const value = line.trim().replace(/^[-*]\s+(?:\[[^\]]+\]\s*)?/, "");
4336
+ return Boolean(value) && !PLACEHOLDER.test(value) && !/^status:\s*(pending|tbd)$/i.test(value);
4337
+ });
4338
+ }
4339
+ function readPocSummary(dir) {
4340
+ const path9 = join(dir, POC_ARTIFACT_PATH);
4341
+ if (!exists(path9)) {
4342
+ return { exists: false, hypothesisDefined: false, successCriteriaCount: 0, constraintsDefined: false, nonGoalsDefined: false, conclusion: "pending" };
4343
+ }
4344
+ let markdown = "";
4345
+ try {
4346
+ markdown = readFile(path9);
4347
+ } catch {
4348
+ return { exists: false, hypothesisDefined: false, successCriteriaCount: 0, constraintsDefined: false, nonGoalsDefined: false, conclusion: "pending" };
4349
+ }
4350
+ const criteria = section2(markdown, "Success Criteria").split(/\r?\n/).filter((line) => /^\s*[-*]\s+(?:\[[ xX]\]\s+)?/.test(line)).filter((line) => meaningful(line)).length;
4351
+ const conclusionBody = section2(markdown, "Conclusion");
4352
+ const conclusionMatch = conclusionBody.match(/\b(?:status|outcome)\s*:\s*(validated|rejected|inconclusive)\b/i);
4353
+ return {
4354
+ exists: true,
4355
+ hypothesisDefined: meaningful(section2(markdown, "Hypothesis")),
4356
+ successCriteriaCount: criteria,
4357
+ constraintsDefined: meaningful(section2(markdown, "Constraints")),
4358
+ nonGoalsDefined: meaningful(section2(markdown, "Non-goals")),
4359
+ conclusion: conclusionMatch ? conclusionMatch[1].toLowerCase() : "pending"
4360
+ };
4361
+ }
4362
+
4276
4363
  // ../cli/src/core/next-step.ts
4277
4364
  function buildDeliveryState(dir) {
4278
4365
  const wis = discoverWorkItems(dir);
@@ -4360,13 +4447,47 @@ function resolveNextStep(dir, now = /* @__PURE__ */ new Date()) {
4360
4447
  return { id: "init", phase: "Setup", label: "Run `kaddo init` to initialize Kaddo.", command: "kaddo init", reason: "Kaddo is not initialized in this project." };
4361
4448
  }
4362
4449
  const state = config.project.state;
4450
+ if (projectMode(config) === "poc") {
4451
+ const poc = readPocSummary(dir);
4452
+ if (!poc.exists) {
4453
+ return { id: "bootstrap", phase: "Setup", label: "Run `kaddo bootstrap` to create the POC knowledge baseline.", command: "kaddo bootstrap", reason: `Missing ${POC_ARTIFACT_PATH}.` };
4454
+ }
4455
+ if (!poc.hypothesisDefined) {
4456
+ return { id: "poc-hypothesis", phase: "POC Definition", label: "Define the POC hypothesis.", target: POC_ARTIFACT_PATH, reason: "The POC needs a testable hypothesis before work begins." };
4457
+ }
4458
+ if (poc.successCriteriaCount === 0) {
4459
+ return { id: "poc-success-criteria", phase: "POC Definition", label: "Define observable POC success criteria.", target: POC_ARTIFACT_PATH, reason: "No success criteria are defined for the hypothesis." };
4460
+ }
4461
+ const qCurrentState2 = analyzeKnowledgeArtifact(dir, CS);
4462
+ const qCodebase2 = analyzeKnowledgeArtifact(dir, CB);
4463
+ if (qCurrentState2 !== "useful" || qCodebase2 !== "useful") {
4464
+ return { id: "poc-technical-context", phase: "POC Discovery", label: "Document the minimal technical context for the experiment.", target: qCurrentState2 !== "useful" ? CS : CB, agent: "architecture-agent", reason: "The experiment surface and constraints are not yet sufficiently documented." };
4465
+ }
4466
+ if (!exists(join(dir, "knowledge", "resources"))) {
4467
+ return { id: "poc-resources", phase: "POC Discovery", label: "Identify the project resources relevant to the experiment.", command: "kaddo resources list", reason: "No project resource catalog is available for the experiment context." };
4468
+ }
4469
+ const delivery = buildDeliveryState(dir);
4470
+ if (delivery.total_work_items === 0) {
4471
+ return { id: "poc-create-experiment", phase: "Experiment", label: "Create the first experiment Work Item (prefer `spike`).", command: "kaddo create spike", reason: "The POC has no Work Item to produce evidence." };
4472
+ }
4473
+ if (delivery.in_progress_work_items > 0) {
4474
+ return { id: "guard", phase: "Experiment", label: "Run `kaddo guard` and capture evidence from the experiment.", command: "kaddo guard", reason: `${delivery.in_progress_work_items} experiment Work Item(s) are in progress.` };
4475
+ }
4476
+ if (delivery.ready_work_items > 0 || delivery.draft_work_items > 0 || delivery.blocked_work_items > 0) {
4477
+ return { id: delivery.ready_work_items > 0 ? "implement-work-item" : "refine-work-item", phase: "Experiment", label: delivery.ready_work_items > 0 ? "Implement the ready experiment Work Item and collect evidence." : "Refine the experiment Work Item before running it.", agent: "work-item-agent", reason: "The POC evidence Work Item is not complete yet." };
4478
+ }
4479
+ if (poc.conclusion === "pending") {
4480
+ return { id: "poc-evaluate", phase: "Evaluation", label: "Evaluate the experiment evidence and record the POC conclusion.", target: POC_ARTIFACT_PATH, reason: "Experiment Work Items are complete but the POC conclusion is pending." };
4481
+ }
4482
+ return { id: "poc-complete", phase: "Conclusion", label: `POC concluded: ${poc.conclusion}. Decide whether to graduate it to standard delivery.`, target: POC_ARTIFACT_PATH, reason: "The POC conclusion is recorded from experiment evidence." };
4483
+ }
4363
4484
  const q3 = (rel) => analyzeKnowledgeArtifact(dir, rel);
4364
4485
  const resolveAgent = (agent) => {
4365
4486
  const file = agent.endsWith(".md") ? agent : `${agent}.md`;
4366
- const path7 = agentInstallPath(file);
4367
- const installed = isFile(join(dir, path7));
4487
+ const path9 = agentInstallPath(file);
4488
+ const installed = isFile(join(dir, path9));
4368
4489
  const group = agentGroupOf(file);
4369
- return { agentPath: path7, agentInstalled: installed, installCommand: installed ? void 0 : `kaddo add agents --group ${group}` };
4490
+ return { agentPath: path9, agentInstalled: installed, installCommand: installed ? void 0 : `kaddo add agents --group ${group}` };
4370
4491
  };
4371
4492
  const moduleRepo = isModule(config);
4372
4493
  const coreRepo = isCore(config);
@@ -6121,6 +6242,16 @@ supporting notes as \`knowledge/tech/discovery/architecture-notes.md\` and decis
6121
6242
  under \`knowledge/tech/decisions/\`. Kaddo still reads the legacy root locations for backward
6122
6243
  compatibility, but new output should use \`discovery/\`.
6123
6244
 
6245
+ ## Project Resource candidates
6246
+
6247
+ When the knowledge describes an external system the project depends on (a database, cloud account,
6248
+ API, queue, storage, service\u2026), you may propose it as a **candidate** Project Resource \u2014 never create
6249
+ it silently. Discovery \u2260 creation: present candidates for human review and materialize only through
6250
+ \`kaddo_create_resource\` / \`kaddo resources create\` with explicit confirmation. Ground every
6251
+ candidate in the knowledge (no guessing from ambiguous names). Model the stable system, not its access
6252
+ interface (CLI/MCP/SQL/API are interfaces); keep \`resources\` separate from \`affected_modules\`; and
6253
+ never include secret values \u2014 only reference names.
6254
+
6124
6255
  ## Quality Checklist
6125
6256
 
6126
6257
  - Every component is backed by evidence from the context pack.
@@ -8601,6 +8732,47 @@ var LEGACY_STEPS = [
8601
8732
  runGuard,
8602
8733
  captureLearning
8603
8734
  ];
8735
+ var definePocHypothesis = {
8736
+ id: "poc-hypothesis",
8737
+ label: "Define hypothesis",
8738
+ evaluate: (ctx) => {
8739
+ const poc = readPocSummary(ctx.dir);
8740
+ return { status: poc.hypothesisDefined ? "done" : "current", evidence: poc.hypothesisDefined ? [POC_ARTIFACT_PATH] : void 0, reason: poc.hypothesisDefined ? void 0 : "A POC starts with one testable hypothesis." };
8741
+ }
8742
+ };
8743
+ var definePocCriteria = {
8744
+ id: "poc-success-criteria",
8745
+ label: "Define success criteria",
8746
+ evaluate: (ctx) => {
8747
+ const poc = readPocSummary(ctx.dir);
8748
+ return { status: poc.successCriteriaCount > 0 ? "done" : "pending", evidence: poc.successCriteriaCount > 0 ? [POC_ARTIFACT_PATH] : void 0, reason: poc.successCriteriaCount > 0 ? void 0 : "Define observable evidence for the hypothesis." };
8749
+ }
8750
+ };
8751
+ var describePocTech = { ...describeArchitecture, id: "poc-technical-context", label: "Document minimal technical context" };
8752
+ var identifyPocResources = {
8753
+ id: "poc-resources",
8754
+ label: "Identify relevant resources",
8755
+ evaluate: (ctx) => exists(join(ctx.dir, "knowledge/resources")) ? { status: "done", evidence: ["knowledge/resources/"] } : { status: "pending", command: "kaddo resources list" }
8756
+ };
8757
+ var createPocExperiment = {
8758
+ id: "poc-create-experiment",
8759
+ label: "Create experiment Work Item",
8760
+ evaluate: (ctx) => ctx.totalWorkItems > 0 ? { status: "done", evidence: ["knowledge/delivery/work-items/"] } : { status: "pending", command: "kaddo create spike", reason: "Prefer a spike for a focused experiment." }
8761
+ };
8762
+ var capturePocEvidence = {
8763
+ id: "poc-evidence",
8764
+ label: "Capture experiment evidence",
8765
+ evaluate: (ctx) => ctx.completedWorkItems > 0 ? { status: "done", evidence: ["knowledge/delivery/work-items/"] } : { status: "pending", reason: "Complete an experiment Work Item and link its evidence in the POC artifact." }
8766
+ };
8767
+ var concludePoc = {
8768
+ id: "poc-evaluate",
8769
+ label: "Evaluate and conclude",
8770
+ evaluate: (ctx) => {
8771
+ const conclusion = readPocSummary(ctx.dir).conclusion;
8772
+ return conclusion === "pending" ? { status: "pending", reason: "Record validated, rejected, or inconclusive from the evidence." } : { status: "done", evidence: [POC_ARTIFACT_PATH], reason: `Conclusion: ${conclusion}.` };
8773
+ }
8774
+ };
8775
+ var POC_STEPS = [enableKaddo, bootstrapBaseline, definePocHypothesis, definePocCriteria, describePocTech, identifyPocResources, createPocExperiment, capturePocEvidence, concludePoc];
8604
8776
  function stepsForState(state) {
8605
8777
  switch (state) {
8606
8778
  case "new":
@@ -8697,14 +8869,21 @@ function mapNextStepId(id) {
8697
8869
  "init-module-context": "enable-kaddo",
8698
8870
  "module-ready": "ready-for-core-orchestration",
8699
8871
  "validate-work-item-modules": "validate-module-knowledge",
8700
- "modules-discover": "enable-kaddo"
8872
+ "modules-discover": "enable-kaddo",
8873
+ "poc-hypothesis": "poc-hypothesis",
8874
+ "poc-success-criteria": "poc-success-criteria",
8875
+ "poc-technical-context": "poc-technical-context",
8876
+ "poc-resources": "poc-resources",
8877
+ "poc-create-experiment": "poc-create-experiment",
8878
+ "poc-evaluate": "poc-evaluate",
8879
+ "poc-complete": "poc-evaluate"
8701
8880
  };
8702
8881
  return MAP[id] ?? id;
8703
8882
  }
8704
8883
  function buildProjectRoute(dir) {
8705
8884
  const config = loadConfig(dir);
8706
8885
  const state = config?.project.state ?? "pre-ai";
8707
- const defs = config && isModule(config) ? MODULE_STEPS : stepsForState(state);
8886
+ const defs = config && projectMode(config) === "poc" ? POC_STEPS : config && isModule(config) ? MODULE_STEPS : stepsForState(state);
8708
8887
  const ctx = buildRouteContext(dir);
8709
8888
  const steps = defs.map((d3) => {
8710
8889
  const result = d3.evaluate(ctx);
@@ -9701,7 +9880,7 @@ function buildGuardHistory(dir) {
9701
9880
  const d3 = hotspotDir(t.code_path);
9702
9881
  hotspotMap.set(d3, (hotspotMap.get(d3) ?? 0) + 1);
9703
9882
  }
9704
- const hotspots = [...hotspotMap.entries()].map(([path7, warnings]) => ({ path: path7, warnings })).sort((a2, b2) => b2.warnings - a2.warnings);
9883
+ const hotspots = [...hotspotMap.entries()].map(([path9, warnings]) => ({ path: path9, warnings })).sort((a2, b2) => b2.warnings - a2.warnings);
9705
9884
  return {
9706
9885
  available: true,
9707
9886
  total_runs: sorted.length,
@@ -10777,6 +10956,18 @@ var RESOURCES = [
10777
10956
  "Roadmap not found. Create knowledge/delivery/roadmap.md (e.g. via the roadmap-agent) first."
10778
10957
  )
10779
10958
  },
10959
+ {
10960
+ uri: "kaddo://poc",
10961
+ name: "Kaddo proof of concept",
10962
+ description: "Canonical POC hypothesis, success criteria, evidence, and conclusion.",
10963
+ mimeType: "text/markdown",
10964
+ read: (root) => fileOrHint(
10965
+ root,
10966
+ "kaddo://poc",
10967
+ "knowledge/delivery/poc.md",
10968
+ "POC artifact not found. Set `project.mode: poc` and run `kaddo bootstrap` first."
10969
+ )
10970
+ },
10780
10971
  {
10781
10972
  uri: "kaddo://capsules",
10782
10973
  name: "Kaddo knowledge capsules",
@@ -11060,20 +11251,20 @@ function getPrompt(root, name) {
11060
11251
  }
11061
11252
 
11062
11253
  // src/tools.ts
11063
- import matter13 from "gray-matter";
11254
+ import matter15 from "gray-matter";
11064
11255
 
11065
11256
  // ../cli/dist/core.js
11066
11257
  import fs3 from "fs";
11067
11258
  import path3 from "path";
11068
11259
  import { z as z3 } from "zod";
11069
- import { parse as parseYaml10 } from "yaml";
11260
+ import { parse as parseYaml10, stringify as stringifyYaml5 } from "yaml";
11070
11261
  import matter12 from "gray-matter";
11071
11262
  import matter22 from "gray-matter";
11072
- import { parse as parseYaml62, stringify as stringifyYaml32 } from "yaml";
11263
+ import { parse as parseYaml62, stringify as stringifyYaml42 } from "yaml";
11073
11264
  import { parse as parseYaml22 } from "yaml";
11074
11265
  import { parse as parseYaml32 } from "yaml";
11075
- import { parse as parseYaml52, stringify as stringifyYaml22 } from "yaml";
11076
- import { parse as parseYaml42, stringify as stringifyYaml4 } from "yaml";
11266
+ import { parse as parseYaml52, stringify as stringifyYaml32 } from "yaml";
11267
+ import { parse as parseYaml42, stringify as stringifyYaml22 } from "yaml";
11077
11268
  import matter32 from "gray-matter";
11078
11269
  import matter42 from "gray-matter";
11079
11270
  import matter52 from "gray-matter";
@@ -11084,18 +11275,19 @@ import { dirname as dirname2, join as join22, parse as parse2 } from "path";
11084
11275
  import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
11085
11276
  import matter72 from "gray-matter";
11086
11277
  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
11278
  import matter92 from "gray-matter";
11092
- import crypto3 from "crypto";
11279
+ import { parse as parseYaml82, stringify as stringifyYaml52 } from "yaml";
11093
11280
  import fs4 from "fs";
11281
+ import path4 from "path";
11282
+ import crypto2 from "crypto";
11094
11283
  import matter102 from "gray-matter";
11284
+ import crypto3 from "crypto";
11285
+ import fs5 from "fs";
11095
11286
  import matter112 from "gray-matter";
11096
- import { parse as parseYaml92, stringify as stringifyYaml5 } from "yaml";
11097
- import { parse as parseYaml102, stringify as stringifyYaml6 } from "yaml";
11098
11287
  import matter122 from "gray-matter";
11288
+ import { parse as parseYaml92, stringify as stringifyYaml6 } from "yaml";
11289
+ import { parse as parseYaml102, stringify as stringifyYaml7 } from "yaml";
11290
+ import matter13 from "gray-matter";
11099
11291
  function exists2(p) {
11100
11292
  return fs3.existsSync(p);
11101
11293
  }
@@ -11120,11 +11312,13 @@ function join3(...parts) {
11120
11312
  return path3.join(...parts);
11121
11313
  }
11122
11314
  var PROJECT_STATES2 = ["new", "pre-ai", "legacy", "ai-assisted"];
11315
+ var PROJECT_MODES2 = ["standard", "poc"];
11123
11316
  var TEAM_SIZES2 = ["indie", "small", "medium", "enterprise"];
11124
11317
  var REPOSITORY_STRUCTURES2 = ["monorepo", "multirepo"];
11125
11318
  var PROJECT_LANGUAGES2 = ["en", "es"];
11126
11319
  var DEFAULTS2 = {
11127
11320
  state: "pre-ai",
11321
+ mode: "standard",
11128
11322
  structure: "monorepo",
11129
11323
  teamSize: "indie",
11130
11324
  language: "en"
@@ -11140,6 +11334,11 @@ var projectStateSchema2 = z3.enum(["new", "pre-ai", "legacy", "ai-assisted"], {
11140
11334
  message: `project.state must be one of: ${PROJECT_STATES2.join(", ")}`
11141
11335
  })
11142
11336
  });
11337
+ var projectModeSchema2 = z3.enum(["standard", "poc"], {
11338
+ errorMap: () => ({
11339
+ message: `project.mode must be one of: ${PROJECT_MODES2.join(", ")}`
11340
+ })
11341
+ });
11143
11342
  var teamSizeSchema2 = z3.enum(["indie", "small", "medium", "enterprise"], {
11144
11343
  errorMap: () => ({
11145
11344
  message: `team.size must be one of: ${TEAM_SIZES2.join(", ")}`
@@ -11161,6 +11360,7 @@ var configSchema2 = z3.object({
11161
11360
  project: z3.object({
11162
11361
  name: z3.string().default("project"),
11163
11362
  state: projectStateSchema2.default(DEFAULTS2.state),
11363
+ mode: projectModeSchema2.default(DEFAULTS2.mode),
11164
11364
  structure: structureSchema2.default(DEFAULTS2.structure),
11165
11365
  language: languageSchema2.default(DEFAULTS2.language),
11166
11366
  role: multirepoRoleSchema2,
@@ -11206,8 +11406,8 @@ function loadConfig2(dir) {
11206
11406
  const parsed = configSchema2.safeParse(raw ?? {});
11207
11407
  if (!parsed.success) {
11208
11408
  const issues = parsed.error.issues.map((i) => {
11209
- const path42 = i.path.join(".");
11210
- return path42 ? ` - ${path42}: ${i.message}` : ` - ${i.message}`;
11409
+ const path52 = i.path.join(".");
11410
+ return path52 ? ` - ${path52}: ${i.message}` : ` - ${i.message}`;
11211
11411
  }).join("\n");
11212
11412
  throw new ConfigError2(`Invalid .kaddo/config.yml:
11213
11413
  ${issues}`);
@@ -11411,11 +11611,11 @@ function moduleArtifactCoverage2(dir, id) {
11411
11611
  };
11412
11612
  }
11413
11613
  function loadMappedModules2(dir) {
11414
- const path42 = join3(dir, DESCRIPTOR_PATH2);
11415
- if (!exists2(path42)) return [];
11614
+ const path52 = join3(dir, DESCRIPTOR_PATH2);
11615
+ if (!exists2(path52)) return [];
11416
11616
  let parsed;
11417
11617
  try {
11418
- parsed = parseYaml32(readFile2(path42));
11618
+ parsed = parseYaml32(readFile2(path52));
11419
11619
  } catch {
11420
11620
  return [];
11421
11621
  }
@@ -12861,6 +13061,18 @@ function parseAuth2(v) {
12861
13061
  const refs = strArray3(o2.refs).filter((r2) => !r2.includes("=") && /^[A-Za-z0-9_.-]+$/.test(r2));
12862
13062
  return { mode: str2(o2.mode), refs };
12863
13063
  }
13064
+ function parseScope2(v) {
13065
+ if (!v || typeof v !== "object" || Array.isArray(v)) return null;
13066
+ const o2 = v;
13067
+ const type = str2(o2.type);
13068
+ if (!type) return null;
13069
+ return { type, module: str2(o2.module) };
13070
+ }
13071
+ function purposeFromBody2(body) {
13072
+ const m2 = body.match(/^#{1,2}\s+Purpose\s*\n+([\s\S]*?)(?=\n#{1,2}\s|\s*$)/m);
13073
+ const text22 = m2 ? m2[1].trim() : "";
13074
+ return text22 || null;
13075
+ }
12864
13076
  function toDetail2(filePath, relPath, raw) {
12865
13077
  const parsed = matter42(raw);
12866
13078
  const fm = parsed.data ?? {};
@@ -12872,8 +13084,10 @@ function toDetail2(filePath, relPath, raw) {
12872
13084
  resourceType: str2(fm.resource_type),
12873
13085
  provider: str2(fm.provider),
12874
13086
  environments: strArray3(fm.environments),
13087
+ scope: parseScope2(fm.scope),
13088
+ modules: strArray3(fm.modules),
12875
13089
  path: relPath,
12876
- purpose: str2(fm.purpose),
13090
+ purpose: str2(fm.purpose) ?? purposeFromBody2(body),
12877
13091
  interfaces: parseInterfaces2(fm.access_interfaces),
12878
13092
  boundaries: parseBoundaries2(fm.access_boundaries),
12879
13093
  ...(() => {
@@ -12892,6 +13106,8 @@ function getResources2(dir) {
12892
13106
  resourceType: str2(fm.resource_type),
12893
13107
  provider: str2(fm.provider),
12894
13108
  environments: strArray3(fm.environments),
13109
+ scope: parseScope2(fm.scope),
13110
+ modules: strArray3(fm.modules),
12895
13111
  path: a2.relPath
12896
13112
  };
12897
13113
  }).sort((x2, y) => x2.id.localeCompare(y.id));
@@ -12960,9 +13176,9 @@ function buildGraph2(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
12960
13176
  ];
12961
13177
  const presentLayers = [];
12962
13178
  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 });
13179
+ const path52 = layer2.files.map((f3) => `${KNOWLEDGE22}/${f3}`).find((rel) => exists2(join3(dir, rel)));
13180
+ if (path52) {
13181
+ addNode({ id: layer2.id, type: layer2.type, label: layer2.label, path: path52 });
12966
13182
  presentLayers.push(layer2.id);
12967
13183
  }
12968
13184
  }
@@ -13005,12 +13221,20 @@ function buildGraph2(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
13005
13221
  const resourceTitleById = /* @__PURE__ */ new Map();
13006
13222
  for (const r2 of getResources2(dir)) {
13007
13223
  resourceTitleById.set(r2.id, r2.title);
13224
+ const resId = `resource:${r2.id}`;
13008
13225
  addNode({
13009
- id: `resource:${r2.id}`,
13226
+ id: resId,
13010
13227
  type: "project-resource",
13011
13228
  label: r2.title ? `${r2.id} ${r2.title}`.trim() : r2.id,
13012
13229
  path: r2.path
13013
13230
  });
13231
+ const depModules = new Set(r2.modules);
13232
+ if (r2.scope?.type === "module" && r2.scope.module) depModules.add(r2.scope.module);
13233
+ for (const mod of depModules) {
13234
+ const modId = `module:${mod}`;
13235
+ addNode({ id: modId, type: "module", label: mod });
13236
+ addEdge(modId, resId, "depends_on");
13237
+ }
13014
13238
  }
13015
13239
  for (const wi of selectedWIs) {
13016
13240
  const id = wi.id || wi.title;
@@ -14698,6 +14922,16 @@ supporting notes as \`knowledge/tech/discovery/architecture-notes.md\` and decis
14698
14922
  under \`knowledge/tech/decisions/\`. Kaddo still reads the legacy root locations for backward
14699
14923
  compatibility, but new output should use \`discovery/\`.
14700
14924
 
14925
+ ## Project Resource candidates
14926
+
14927
+ When the knowledge describes an external system the project depends on (a database, cloud account,
14928
+ API, queue, storage, service\u2026), you may propose it as a **candidate** Project Resource \u2014 never create
14929
+ it silently. Discovery \u2260 creation: present candidates for human review and materialize only through
14930
+ \`kaddo_create_resource\` / \`kaddo resources create\` with explicit confirmation. Ground every
14931
+ candidate in the knowledge (no guessing from ambiguous names). Model the stable system, not its access
14932
+ interface (CLI/MCP/SQL/API are interfaces); keep \`resources\` separate from \`affected_modules\`; and
14933
+ never include secret values \u2014 only reference names.
14934
+
14701
14935
  ## Quality Checklist
14702
14936
 
14703
14937
  - Every component is backed by evidence from the context pack.
@@ -16904,6 +17138,7 @@ var LEGACY_STEPS2 = [
16904
17138
  runGuard2,
16905
17139
  captureLearning2
16906
17140
  ];
17141
+ var describePocTech2 = { ...describeArchitecture2, id: "poc-technical-context", label: "Document minimal technical context" };
16907
17142
  var USER_FACING_KEYWORDS_EN = [
16908
17143
  "user",
16909
17144
  "visitor",
@@ -17251,8 +17486,8 @@ function parseGraphReason(v) {
17251
17486
  if (!v || typeof v !== "object") return null;
17252
17487
  const o2 = v;
17253
17488
  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;
17489
+ const path52 = Array.isArray(o2.path) ? o2.path.map(String).filter(Boolean) : [];
17490
+ return relationship || path52.length ? { relationship, path: path52 } : null;
17256
17491
  }
17257
17492
  function parseExplain(o2) {
17258
17493
  const refs = Array.isArray(o2.evidence) ? o2.evidence : Array.isArray(o2.evidence_refs) ? o2.evidence_refs : [];
@@ -17307,7 +17542,7 @@ function parseOriginalSnapshot(fm) {
17307
17542
  function readBody(filePath) {
17308
17543
  try {
17309
17544
  const raw = readFile2(filePath);
17310
- return matter82(raw).content;
17545
+ return matter92(raw).content;
17311
17546
  } catch {
17312
17547
  return "";
17313
17548
  }
@@ -17509,7 +17744,7 @@ function nextWorkItemId(dir) {
17509
17744
  let max = 0;
17510
17745
  const walk = (d3) => {
17511
17746
  if (!exists2(d3)) return;
17512
- for (const entry of fs32.readdirSync(d3)) {
17747
+ for (const entry of fs4.readdirSync(d3)) {
17513
17748
  const full = join3(d3, entry);
17514
17749
  if (isFile2(full)) {
17515
17750
  const m2 = entry.match(/WI-(\d+)/);
@@ -17522,15 +17757,15 @@ function nextWorkItemId(dir) {
17522
17757
  walk(wiDir);
17523
17758
  return `WI-${String(max + 1).padStart(3, "0")}`;
17524
17759
  }
17525
- function atomicWrite2(filePath, content) {
17526
- fs32.mkdirSync(path32.dirname(filePath), { recursive: true });
17760
+ function atomicWrite3(filePath, content) {
17761
+ fs4.mkdirSync(path4.dirname(filePath), { recursive: true });
17527
17762
  const tmp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
17528
- fs32.writeFileSync(tmp, content, "utf-8");
17763
+ fs4.writeFileSync(tmp, content, "utf-8");
17529
17764
  try {
17530
- fs32.renameSync(tmp, filePath);
17765
+ fs4.renameSync(tmp, filePath);
17531
17766
  } catch (err) {
17532
17767
  try {
17533
- fs32.rmSync(tmp, { force: true });
17768
+ fs4.rmSync(tmp, { force: true });
17534
17769
  } catch {
17535
17770
  }
17536
17771
  throw err;
@@ -17591,8 +17826,8 @@ function ensureH1(body, title) {
17591
17826
 
17592
17827
  ${trimmed}`;
17593
17828
  }
17594
- function serialize(data, body) {
17595
- return matter92.stringify(`
17829
+ function serialize2(data, body) {
17830
+ return matter102.stringify(`
17596
17831
  ${body.trim()}
17597
17832
  `, data);
17598
17833
  }
@@ -17660,8 +17895,8 @@ function createWorkItem(dir, opts) {
17660
17895
  const relPath = `${WORK_ITEMS_DIR}/draft/${id}-${slugify22(title)}.md`;
17661
17896
  const filePath = join3(dir, relPath);
17662
17897
  if (exists2(filePath)) throw new WorkItemWriteError("INVALID_INPUT", `Work Item file already exists: ${relPath}`);
17663
- const raw = serialize(data, body);
17664
- atomicWrite2(filePath, raw);
17898
+ const raw = serialize2(data, body);
17899
+ atomicWrite3(filePath, raw);
17665
17900
  return { id, path: relPath, revision: revisionOf(raw) };
17666
17901
  }
17667
17902
  function dimensionOf(type) {
@@ -18383,7 +18618,7 @@ function detectFormat(raw) {
18383
18618
  if (!trimmed) throw new ImportError("EMPTY_INPUT", "Import content is empty.");
18384
18619
  if (trimmed.startsWith("---")) {
18385
18620
  try {
18386
- const { data } = matter102(trimmed);
18621
+ const { data } = matter112(trimmed);
18387
18622
  if (data && typeof data === "object" && Object.keys(data).length > 0) {
18388
18623
  const d3 = data;
18389
18624
  if (d3.type === "work-item" || d3.id && typeof d3.id === "string" && /^WI-\d+/.test(d3.id)) {
@@ -18405,7 +18640,7 @@ function parseContent(raw) {
18405
18640
  let bodyText = trimmed;
18406
18641
  if (format === "kaddo-frontmatter" || format === "markdown-frontmatter") {
18407
18642
  try {
18408
- const parsed = matter102(trimmed);
18643
+ const parsed = matter112(trimmed);
18409
18644
  frontmatterFields = parsed.data ?? {};
18410
18645
  bodyText = parsed.content.trim();
18411
18646
  } catch {
@@ -18502,7 +18737,7 @@ function findDuplicateByHash(dir, hash) {
18502
18737
  const raw = readFile2(item.filePath);
18503
18738
  if (!raw) continue;
18504
18739
  try {
18505
- const { data } = matter102(raw);
18740
+ const { data } = matter112(raw);
18506
18741
  const source = data?.source;
18507
18742
  if (source && typeof source === "object" && !Array.isArray(source)) {
18508
18743
  const s = source;
@@ -18569,7 +18804,7 @@ function importWorkItem(dir, opts) {
18569
18804
  };
18570
18805
  }
18571
18806
  if (opts.onConflict === "replace") {
18572
- fs4.rmSync(existing.filePath, { force: true });
18807
+ fs5.rmSync(existing.filePath, { force: true });
18573
18808
  replacedWorkItem = existing.workItemId;
18574
18809
  }
18575
18810
  }
@@ -19011,8 +19246,8 @@ async function handleJiraResponse(res) {
19011
19246
  }
19012
19247
  return await res.json();
19013
19248
  }
19014
- async function jiraFetch(baseUrl, path42, auth, timeoutMs, params) {
19015
- const url = new URL(path42, baseUrl + "/");
19249
+ async function jiraFetch(baseUrl, path52, auth, timeoutMs, params) {
19250
+ const url = new URL(path52, baseUrl + "/");
19016
19251
  if (params) for (const [k2, v] of Object.entries(params)) url.searchParams.set(k2, v);
19017
19252
  const controller = new AbortController();
19018
19253
  const timer = setTimeout(() => controller.abort(), timeoutMs);
@@ -19030,8 +19265,8 @@ async function jiraFetch(baseUrl, path42, auth, timeoutMs, params) {
19030
19265
  clearTimeout(timer);
19031
19266
  }
19032
19267
  }
19033
- async function jiraPost(baseUrl, path42, auth, timeoutMs, body) {
19034
- const url = new URL(path42, baseUrl + "/");
19268
+ async function jiraPost(baseUrl, path52, auth, timeoutMs, body) {
19269
+ const url = new URL(path52, baseUrl + "/");
19035
19270
  const controller = new AbortController();
19036
19271
  const timer = setTimeout(() => controller.abort(), timeoutMs);
19037
19272
  try {
@@ -19222,7 +19457,7 @@ function persistConsent(dir, consent) {
19222
19457
  const prev = doc.telemetry ?? {};
19223
19458
  delete prev.enabled;
19224
19459
  doc.telemetry = { ...prev, consent, consentVersion: CURRENT_CONSENT_VERSION };
19225
- writeFile2(configPath, stringifyYaml6(doc));
19460
+ writeFile2(configPath, stringifyYaml7(doc));
19226
19461
  }
19227
19462
  function isDeferralActive(dir) {
19228
19463
  const p = join3(dir, ".kaddo", "telemetry-deferred.json");
@@ -19397,7 +19632,7 @@ function buildInitiativeContent(id, input) {
19397
19632
  if (input.priority) data.priority = input.priority;
19398
19633
  if (input.source) data.source = input.source;
19399
19634
  if (input.sourceId) data.source_id = input.sourceId;
19400
- return matter122.stringify("\n" + (input.body ?? defaultBody(input.title.trim())), data);
19635
+ return matter13.stringify("\n" + (input.body ?? defaultBody(input.title.trim())), data);
19401
19636
  }
19402
19637
  function createInitiative(dir, input) {
19403
19638
  if (!input.title?.trim()) throw new InitiativeWriteError("Initiative title is required.");
@@ -19421,10 +19656,10 @@ function transitionInitiative(dir, id, to) {
19421
19656
  throw new InitiativeWriteError(`Invalid transition ${ini.status} \u2192 ${to}.`);
19422
19657
  }
19423
19658
  const raw = readFile2(ini.filePath);
19424
- const { data, content } = matter122(raw, {});
19659
+ const { data, content } = matter13(raw, {});
19425
19660
  data.status = to;
19426
19661
  if (to === "completed") data.completed_at = today2();
19427
- const newContent = matter122.stringify(content, data);
19662
+ const newContent = matter13.stringify(content, data);
19428
19663
  writeFile2(ini.filePath, newContent);
19429
19664
  const updated = parseInitiative2(dir, ini.filePath, newContent);
19430
19665
  if (!updated) throw new InitiativeWriteError("Failed to parse the Initiative after transition.");
@@ -19434,7 +19669,7 @@ function addCandidate(dir, id, candidate) {
19434
19669
  const ini = getInitiative(dir, id);
19435
19670
  if (!ini) throw new InitiativeWriteError(`Initiative ${id} not found.`);
19436
19671
  const raw = readFile2(ini.filePath);
19437
- const { data, content } = matter122(raw, {});
19672
+ const { data, content } = matter13(raw, {});
19438
19673
  const existing = Array.isArray(data.candidates) ? data.candidates : [];
19439
19674
  const fallbackId = `WI-CANDIDATE-${String(existing.length + 1).padStart(3, "0")}`;
19440
19675
  const normalized = normalizeCandidate(candidate, fallbackId);
@@ -19442,7 +19677,7 @@ function addCandidate(dir, id, candidate) {
19442
19677
  throw new InitiativeWriteError(`Candidate ${normalized.id} already exists in ${id}.`);
19443
19678
  }
19444
19679
  data.candidates = [...existing, candidateToData(normalized)];
19445
- const newContent = matter122.stringify(content, data);
19680
+ const newContent = matter13.stringify(content, data);
19446
19681
  writeFile2(ini.filePath, newContent);
19447
19682
  const updated = parseInitiative2(dir, ini.filePath, newContent);
19448
19683
  if (!updated) throw new InitiativeWriteError("Failed to parse the Initiative after adding a candidate.");
@@ -19452,7 +19687,7 @@ function updateInitiative(dir, id, patch) {
19452
19687
  const ini = getInitiative(dir, id);
19453
19688
  if (!ini) throw new InitiativeWriteError(`Initiative ${id} not found.`);
19454
19689
  const raw = readFile2(ini.filePath);
19455
- const { data, content } = matter122(raw, {});
19690
+ const { data, content } = matter13(raw, {});
19456
19691
  if (patch.title !== void 0) {
19457
19692
  if (!patch.title.trim()) throw new InitiativeWriteError("Initiative title cannot be empty.");
19458
19693
  data.title = patch.title.trim();
@@ -19462,7 +19697,7 @@ function updateInitiative(dir, id, patch) {
19462
19697
  if (patch.knowledgeLevel !== void 0) data.knowledge_level = patch.knowledgeLevel;
19463
19698
  if (patch.domains !== void 0) data.domains = patch.domains;
19464
19699
  if (patch.relatedCapabilities !== void 0) data.related_capabilities = patch.relatedCapabilities;
19465
- const newContent = matter122.stringify(content, data);
19700
+ const newContent = matter13.stringify(content, data);
19466
19701
  writeFile2(ini.filePath, newContent);
19467
19702
  const updated = parseInitiative2(dir, ini.filePath, newContent);
19468
19703
  if (!updated) throw new InitiativeWriteError("Failed to parse the Initiative after update.");
@@ -19480,10 +19715,10 @@ function addExternalLink(dir, id, link) {
19480
19715
  throw new InitiativeWriteError(`External link ${link.integration}:${link.externalId} already exists on ${id}.`);
19481
19716
  }
19482
19717
  const raw = readFile2(ini.filePath);
19483
- const { data, content } = matter122(raw, {});
19718
+ const { data, content } = matter13(raw, {});
19484
19719
  const existing = Array.isArray(data.external_links) ? data.external_links : [];
19485
19720
  data.external_links = [...existing, externalLinkToData(link)];
19486
- const newContent = matter122.stringify(content, data);
19721
+ const newContent = matter13.stringify(content, data);
19487
19722
  writeFile2(ini.filePath, newContent);
19488
19723
  const updated = parseInitiative2(dir, ini.filePath, newContent);
19489
19724
  if (!updated) throw new InitiativeWriteError("Failed to parse the Initiative after adding an external link.");
@@ -19552,7 +19787,7 @@ function buildWorkItemContent(wiId, iniId, candidate) {
19552
19787
  "_What did we learn? Update after completion._",
19553
19788
  ""
19554
19789
  ].join("\n");
19555
- return matter122.stringify("\n" + body, data);
19790
+ return matter13.stringify("\n" + body, data);
19556
19791
  }
19557
19792
  function materializeCandidate(dir, id, candidateId) {
19558
19793
  const ini = getInitiative(dir, id);
@@ -19569,13 +19804,13 @@ function materializeCandidate(dir, id, candidateId) {
19569
19804
  ensureDir2(join3(dir, DRAFT_DIR));
19570
19805
  writeFile2(join3(dir, DRAFT_DIR, fileName), buildWorkItemContent(wiId, ini.id, candidate));
19571
19806
  const raw = readFile2(ini.filePath);
19572
- const { data, content } = matter122(raw, {});
19807
+ const { data, content } = matter13(raw, {});
19573
19808
  const cands = Array.isArray(data.candidates) ? data.candidates : [];
19574
19809
  for (const c of cands) {
19575
19810
  if (c && typeof c === "object" && String(c.id) === candidateId) c.materialized_as = wiId;
19576
19811
  }
19577
19812
  data.candidates = cands;
19578
- const newContent = matter122.stringify(content, data);
19813
+ const newContent = matter13.stringify(content, data);
19579
19814
  writeFile2(ini.filePath, newContent);
19580
19815
  const updated = parseInitiative2(dir, ini.filePath, newContent);
19581
19816
  if (!updated) throw new InitiativeWriteError("Failed to parse the Initiative after materialization.");
@@ -19686,6 +19921,164 @@ function evaluateInitiativeCompletion(dir, ini) {
19686
19921
  };
19687
19922
  }
19688
19923
 
19924
+ // ../cli/src/core/resource-write.ts
19925
+ import fs6 from "fs";
19926
+ import path5 from "path";
19927
+ import matter14 from "gray-matter";
19928
+ var RESOURCES_DIR = "knowledge/tech/resources";
19929
+ var ResourceWriteError = class extends Error {
19930
+ constructor(code, message) {
19931
+ super(message);
19932
+ this.code = code;
19933
+ this.name = "ResourceWriteError";
19934
+ }
19935
+ code;
19936
+ };
19937
+ function slug4(s) {
19938
+ return s.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60);
19939
+ }
19940
+ function atomicWrite(filePath, content) {
19941
+ const tmp = `${filePath}.tmp-${process.pid}-${Date.now()}`;
19942
+ fs6.writeFileSync(tmp, content, "utf-8");
19943
+ fs6.renameSync(tmp, filePath);
19944
+ }
19945
+ function sanitizeRefs(refs) {
19946
+ if (!Array.isArray(refs)) return [];
19947
+ return refs.map((r2) => String(r2).trim()).filter((r2) => r2 && !r2.includes("=") && /^[A-Za-z0-9_.-]+$/.test(r2));
19948
+ }
19949
+ function allResourceArtifacts(dir) {
19950
+ return discoverKnowledge(dir).filter((a2) => a2.type === RESOURCE_ARTIFACT_TYPE);
19951
+ }
19952
+ function nextResourceId(dir, title, explicit) {
19953
+ const existing = new Set(allResourceArtifacts(dir).map((a2) => (a2.id || "").toUpperCase()));
19954
+ if (explicit && explicit.trim()) return explicit.trim();
19955
+ const base = `RES-${slug4(title) || "resource"}`;
19956
+ if (!existing.has(base.toUpperCase())) return base;
19957
+ for (let i = 2; i < 1e3; i++) {
19958
+ const candidate = `${base}-${i}`;
19959
+ if (!existing.has(candidate.toUpperCase())) return candidate;
19960
+ }
19961
+ throw new ResourceWriteError("ID_EXHAUSTED", `Could not derive a unique id for "${title}".`);
19962
+ }
19963
+ function validateResource(dir, input) {
19964
+ const findings = [];
19965
+ if (input.resourceType && !RESOURCE_TYPES.includes(input.resourceType)) {
19966
+ findings.push({ level: "warning", message: `Unknown resource_type "${input.resourceType}" (not in the known set).` });
19967
+ }
19968
+ for (const i of input.accessInterfaces ?? []) {
19969
+ if (i.type && !INTERFACE_TYPES.includes(i.type)) {
19970
+ findings.push({ level: "warning", message: `Unknown access interface type "${i.type}".` });
19971
+ }
19972
+ }
19973
+ const mapped = loadMappedModules(dir).map((m2) => m2.id);
19974
+ if (mapped.length > 0) {
19975
+ const refs = /* @__PURE__ */ new Set();
19976
+ if (input.scope?.type === "module" && input.scope.module) refs.add(input.scope.module);
19977
+ for (const m2 of input.modules ?? []) refs.add(m2);
19978
+ for (const m2 of refs) {
19979
+ if (!mapped.includes(m2)) findings.push({ level: "warning", message: `Module "${m2}" is not in .kaddo/modules.yml.` });
19980
+ }
19981
+ }
19982
+ if (input.scope && input.scope.type === "module" && !input.scope.module) {
19983
+ findings.push({ level: "error", message: 'scope.type "module" requires a module id.' });
19984
+ }
19985
+ return findings;
19986
+ }
19987
+ function buildFrontmatter(id, input, base = {}) {
19988
+ const data = { ...base };
19989
+ data.type = RESOURCE_ARTIFACT_TYPE;
19990
+ data.id = id;
19991
+ if (input.title != null) data.title = input.title;
19992
+ if (input.resourceType != null) data.resource_type = input.resourceType;
19993
+ if (input.provider != null) data.provider = input.provider;
19994
+ if (input.environments != null) data.environments = input.environments;
19995
+ if (input.scope != null) data.scope = input.scope.module ? { type: input.scope.type, module: input.scope.module } : { type: input.scope.type };
19996
+ if (input.modules != null) data.modules = input.modules;
19997
+ if (input.accessInterfaces != null) data.access_interfaces = input.accessInterfaces;
19998
+ if (input.accessBoundaries != null) data.access_boundaries = input.accessBoundaries;
19999
+ if (input.authentication != null) {
20000
+ const mode = input.authentication.mode;
20001
+ const refs = sanitizeRefs(input.authentication.refs);
20002
+ data.authentication = { ...mode ? { mode } : {}, ...refs.length ? { refs } : {} };
20003
+ }
20004
+ return data;
20005
+ }
20006
+ function serialize(data, body) {
20007
+ return matter14.stringify(`
20008
+ ${body.trim()}
20009
+ `, data);
20010
+ }
20011
+ function createResource(dir, input) {
20012
+ const title = (input.title ?? "").trim();
20013
+ if (!title) throw new ResourceWriteError("INVALID_INPUT", "A resource title is required.");
20014
+ const resourceType = (input.resourceType ?? "").trim();
20015
+ if (!resourceType) throw new ResourceWriteError("INVALID_INPUT", "A resource_type is required.");
20016
+ const findings = validateResource(dir, input);
20017
+ if (findings.some((f3) => f3.level === "error")) {
20018
+ throw new ResourceWriteError("VALIDATION_FAILED", findings.filter((f3) => f3.level === "error").map((f3) => f3.message).join("; "));
20019
+ }
20020
+ const existing = allResourceArtifacts(dir);
20021
+ if (input.id && existing.some((a2) => (a2.id || "").toUpperCase() === input.id.toUpperCase())) {
20022
+ throw new ResourceWriteError("DUPLICATE", `A resource with id "${input.id}" already exists.`);
20023
+ }
20024
+ if (existing.some((a2) => (a2.title || "").toLowerCase() === title.toLowerCase())) {
20025
+ throw new ResourceWriteError("DUPLICATE", `A resource titled "${title}" already exists.`);
20026
+ }
20027
+ const id = nextResourceId(dir, title, input.id);
20028
+ const data = buildFrontmatter(id, input);
20029
+ const body = `# Purpose
20030
+
20031
+ ${(input.purpose ?? "").trim() || "TODO: describe what this resource is for."}`;
20032
+ const relPath = `${RESOURCES_DIR}/${slug4(title) || slug4(id)}.md`;
20033
+ const filePath = join(dir, relPath);
20034
+ if (exists(filePath)) throw new ResourceWriteError("DUPLICATE", `Resource file already exists: ${relPath}`);
20035
+ ensureDir(path5.dirname(filePath));
20036
+ atomicWrite(filePath, serialize(data, body));
20037
+ return { id, path: relPath, findings };
20038
+ }
20039
+ function updateResource(dir, id, input) {
20040
+ const match = allResourceArtifacts(dir).find((a2) => a2.id === id || a2.title === id);
20041
+ if (!match) throw new ResourceWriteError("NOT_FOUND", `Project Resource "${id}" not found.`);
20042
+ const findings = validateResource(dir, input);
20043
+ if (findings.some((f3) => f3.level === "error")) {
20044
+ throw new ResourceWriteError("VALIDATION_FAILED", findings.filter((f3) => f3.level === "error").map((f3) => f3.message).join("; "));
20045
+ }
20046
+ const raw = readFile(match.filePath);
20047
+ const parsed = matter14(raw);
20048
+ const base = parsed.data ?? {};
20049
+ const data = buildFrontmatter(match.id || id, input, base);
20050
+ let body = parsed.content.trim();
20051
+ if (input.purpose != null) {
20052
+ const purpose = `# Purpose
20053
+
20054
+ ${input.purpose.trim()}`;
20055
+ body = /^#\s+Purpose/m.test(body) ? body.replace(/#\s+Purpose[\s\S]*?(?=\n#\s|\n*$)/, purpose + "\n") : `${purpose}
20056
+
20057
+ ${body}`.trim();
20058
+ }
20059
+ atomicWrite(match.filePath, serialize(data, body));
20060
+ return { id: match.id || id, path: match.relPath, findings };
20061
+ }
20062
+ function getResourceReferences(dir, id) {
20063
+ const refs = [];
20064
+ for (const wi of discoverWorkItems(dir)) {
20065
+ for (const rel of parseWorkItemResources(wi.rawFrontmatter)) {
20066
+ if (rel.id === id) refs.push({ kind: "work-item", id: wi.id || wi.title, role: rel.role, path: wi.relPath });
20067
+ }
20068
+ }
20069
+ return refs;
20070
+ }
20071
+ function deleteResource(dir, id, opts = {}) {
20072
+ const match = allResourceArtifacts(dir).find((a2) => a2.id === id || a2.title === id);
20073
+ if (!match) throw new ResourceWriteError("NOT_FOUND", `Project Resource "${id}" not found.`);
20074
+ const references = getResourceReferences(dir, match.id || id);
20075
+ if (!opts.confirm) {
20076
+ return { deleted: false, preview: { id: match.id || id, references } };
20077
+ }
20078
+ fs6.rmSync(match.filePath, { force: true });
20079
+ return { deleted: true, path: match.relPath };
20080
+ }
20081
+
19689
20082
  // src/tools.ts
19690
20083
  var ok = (data) => ({ ok: true, data });
19691
20084
  var fail = (message) => ({ ok: false, message });
@@ -19781,12 +20174,12 @@ function markWorkItemReady(root, id, confirm2) {
19781
20174
  }
19782
20175
  const raw = readText(root, item.path);
19783
20176
  if (!raw) return fail(`Could not read Work Item file: ${item.path}`);
19784
- const parsed = matter13(raw);
20177
+ const parsed = matter15(raw);
19785
20178
  const data = { ...parsed.data };
19786
20179
  const today3 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
19787
20180
  data.status = "ready";
19788
20181
  data.ready_at = today3;
19789
- const newContent = matter13.stringify(parsed.content, data);
20182
+ const newContent = matter15.stringify(parsed.content, data);
19790
20183
  const posixPath = item.path.replace(/\\/g, "/");
19791
20184
  let newRelPath = item.path;
19792
20185
  if (posixPath.includes("/work-items/draft/")) {
@@ -19957,12 +20350,65 @@ function getResourceTool(root, id) {
19957
20350
  if (!resource) return fail(`Project Resource "${id}" not found.`);
19958
20351
  return ok(resource);
19959
20352
  }
20353
+ function toResourceInput(args) {
20354
+ const input = {};
20355
+ if (args.title != null) input.title = args.title;
20356
+ if (args.resource_type != null) input.resourceType = args.resource_type;
20357
+ if (args.provider != null) input.provider = args.provider;
20358
+ if (args.environments != null) input.environments = args.environments;
20359
+ if (args.scope != null || args.module != null) {
20360
+ const type = args.scope || (args.module ? "module" : "system");
20361
+ input.scope = type === "module" ? { type: "module", module: args.module ?? null } : { type: "system" };
20362
+ }
20363
+ if (args.modules != null) input.modules = args.modules;
20364
+ if (args.purpose != null) input.purpose = args.purpose;
20365
+ if (args.access_interfaces != null) input.accessInterfaces = args.access_interfaces;
20366
+ if (args.authentication != null) input.authentication = args.authentication;
20367
+ return input;
20368
+ }
20369
+ function createResourceTool(root, args, confirm2) {
20370
+ const input = toResourceInput(args);
20371
+ const findings = validateResource(root, input);
20372
+ if (!confirm2) {
20373
+ return ok({ status: "needs_confirmation", action: "create", proposal: input, findings, message: "Re-call with confirm=true to create this resource." });
20374
+ }
20375
+ try {
20376
+ return ok({ status: "created", ...createResource(root, input) });
20377
+ } catch (e) {
20378
+ return fail(e instanceof ResourceWriteError ? e.message : String(e));
20379
+ }
20380
+ }
20381
+ function updateResourceTool(root, id, args, confirm2) {
20382
+ if (!getResource(root, id)) return fail(`Project Resource "${id}" not found.`);
20383
+ const input = toResourceInput(args);
20384
+ const findings = validateResource(root, input);
20385
+ if (!confirm2) {
20386
+ return ok({ status: "needs_confirmation", action: "update", id, changes: input, findings, message: "Re-call with confirm=true to apply this update." });
20387
+ }
20388
+ try {
20389
+ return ok({ status: "updated", ...updateResource(root, id, input) });
20390
+ } catch (e) {
20391
+ return fail(e instanceof ResourceWriteError ? e.message : String(e));
20392
+ }
20393
+ }
20394
+ function deleteResourceTool(root, id, confirm2) {
20395
+ if (!getResource(root, id)) return fail(`Project Resource "${id}" not found.`);
20396
+ if (!confirm2) {
20397
+ const references = getResourceReferences(root, id);
20398
+ return ok({ status: "needs_confirmation", action: "delete", id, references, message: "Re-call with confirm=true to delete. References in other artifacts are NOT removed." });
20399
+ }
20400
+ try {
20401
+ return ok({ status: "deleted", ...deleteResource(root, id, { confirm: true }) });
20402
+ } catch (e) {
20403
+ return fail(e instanceof ResourceWriteError ? e.message : String(e));
20404
+ }
20405
+ }
19960
20406
 
19961
20407
  // src/multirepo.ts
19962
- import fs5 from "fs";
19963
- import path4 from "path";
19964
- import matter14 from "gray-matter";
19965
- import { parse as parseYaml11, stringify as stringifyYaml7 } from "yaml";
20408
+ import fs7 from "fs";
20409
+ import path6 from "path";
20410
+ import matter16 from "gray-matter";
20411
+ import { parse as parseYaml11, stringify as stringifyYaml8 } from "yaml";
19966
20412
  var ok2 = (data) => ({ ok: true, data });
19967
20413
  var fail2 = (message) => ({ ok: false, message });
19968
20414
  function moduleRepoPath(m2) {
@@ -19981,24 +20427,24 @@ function isCore2(config) {
19981
20427
  return projectRole2(config) === "core";
19982
20428
  }
19983
20429
  function readModuleFile(root, repoPath, relFile) {
19984
- const moduleDir = path4.resolve(root, repoPath);
19985
- const abs = path4.resolve(moduleDir, relFile);
20430
+ const moduleDir = path6.resolve(root, repoPath);
20431
+ const abs = path6.resolve(moduleDir, relFile);
19986
20432
  if (!abs.startsWith(moduleDir)) return null;
19987
- if (!fs5.existsSync(abs) || !fs5.statSync(abs).isFile()) return null;
19988
- return fs5.readFileSync(abs, "utf-8");
20433
+ if (!fs7.existsSync(abs) || !fs7.statSync(abs).isFile()) return null;
20434
+ return fs7.readFileSync(abs, "utf-8");
19989
20435
  }
19990
20436
  function detectModuleStatus2(root, repoPath) {
19991
- const moduleDir = path4.resolve(root, repoPath);
19992
- if (!fs5.existsSync(moduleDir)) {
20437
+ const moduleDir = path6.resolve(root, repoPath);
20438
+ if (!fs7.existsSync(moduleDir)) {
19993
20439
  return { status: "missing", configured: false, moduleContext: false, currentState: false, codebase: false, warning: `Directory \`${repoPath}\` does not exist.` };
19994
20440
  }
19995
- const configPath = path4.join(moduleDir, ".kaddo", "config.yml");
19996
- if (!fs5.existsSync(configPath)) {
20441
+ const configPath = path6.join(moduleDir, ".kaddo", "config.yml");
20442
+ if (!fs7.existsSync(configPath)) {
19997
20443
  return { status: "not_configured", configured: false, moduleContext: false, currentState: false, codebase: false, warning: `Run \`kaddo init\` inside \`${repoPath}\` and select multirepo \u2192 module.` };
19998
20444
  }
19999
20445
  let parsed = null;
20000
20446
  try {
20001
- parsed = parseYaml11(fs5.readFileSync(configPath, "utf-8"));
20447
+ parsed = parseYaml11(fs7.readFileSync(configPath, "utf-8"));
20002
20448
  } catch {
20003
20449
  }
20004
20450
  const role = parsed?.project?.role ?? parsed?.multirepo?.role;
@@ -20009,14 +20455,14 @@ function detectModuleStatus2(root, repoPath) {
20009
20455
  status: "configured",
20010
20456
  configured: true,
20011
20457
  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"))
20458
+ moduleContext: fs7.existsSync(path6.join(moduleDir, "knowledge", "tech", "module", "module-context.md")) || fs7.existsSync(path6.join(moduleDir, "knowledge", "module", "module-context.md")),
20459
+ currentState: fs7.existsSync(path6.join(moduleDir, "knowledge", "tech", "current-state.md")),
20460
+ codebase: fs7.existsSync(path6.join(moduleDir, "knowledge", "tech", "codebase.md"))
20015
20461
  };
20016
20462
  }
20017
20463
  function resolveModuleContextPath(root, repoPath) {
20018
- const moduleDir = path4.resolve(root, repoPath);
20019
- if (fs5.existsSync(path4.join(moduleDir, "knowledge", "tech", "module", "module-context.md"))) {
20464
+ const moduleDir = path6.resolve(root, repoPath);
20465
+ if (fs7.existsSync(path6.join(moduleDir, "knowledge", "tech", "module", "module-context.md"))) {
20020
20466
  return "knowledge/tech/module/module-context.md";
20021
20467
  }
20022
20468
  return "knowledge/module/module-context.md";
@@ -20113,7 +20559,7 @@ function validateWorkItemModulesTool(root, args) {
20113
20559
  if (!wi) return fail2(`Work Item "${args.workItemId}" not found.`);
20114
20560
  const raw = readText(root, wi.path);
20115
20561
  if (!raw) return fail2(`Could not read Work Item file.`);
20116
- const parsed = matter14(raw);
20562
+ const parsed = matter16(raw);
20117
20563
  const data = parsed.data;
20118
20564
  const affectedModules = Array.isArray(data.affected_modules) ? data.affected_modules.map(String) : [];
20119
20565
  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 +20612,7 @@ function getWorkItemContextTool(root, args) {
20166
20612
  if (!wi) return fail2(`Work Item "${args.workItemId}" not found.`);
20167
20613
  const raw = readText(root, wi.path);
20168
20614
  if (!raw) return fail2(`Could not read Work Item file.`);
20169
- const parsed = matter14(raw);
20615
+ const parsed = matter16(raw);
20170
20616
  const data = parsed.data;
20171
20617
  const affectedModules = Array.isArray(data.affected_modules) ? data.affected_modules.map(String) : [];
20172
20618
  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 +20666,15 @@ function extractSection(content, heading) {
20220
20666
  return out;
20221
20667
  }
20222
20668
  function buildBranchStrategy(wi, affectedModules, codeEntries, descriptor) {
20223
- const slug5 = slugify4(wi.title);
20669
+ const slug6 = slugify4(wi.title);
20224
20670
  const wiLower = wi.id.toLowerCase();
20225
- const coreBranch = `feature/${wiLower}-${slug5}`;
20671
+ const coreBranch = `feature/${wiLower}-${slug6}`;
20226
20672
  const moduleBranches = {};
20227
20673
  const commitMessages = {};
20228
20674
  for (const modId of affectedModules) {
20229
20675
  const mod = (descriptor.modules ?? []).find((m2) => m2.id === modId);
20230
20676
  const repo = mod ? moduleRepoPath(mod) || modId : modId;
20231
- moduleBranches[repo] = `feature/${wiLower}-${modId}-${slug5.slice(0, 20)}`;
20677
+ moduleBranches[repo] = `feature/${wiLower}-${modId}-${slug6.slice(0, 20)}`;
20232
20678
  commitMessages[repo] = `fix(${wiLower}): update ${modId}`;
20233
20679
  }
20234
20680
  commitMessages["core"] = `docs(${wiLower}): capture cross-repo implementation summary`;
@@ -20252,7 +20698,7 @@ function suggestBranchStrategyTool(root, args) {
20252
20698
  if (!wi) return fail2(`Work Item "${args.workItemId}" not found.`);
20253
20699
  const raw = readText(root, wi.path);
20254
20700
  if (!raw) return fail2(`Could not read Work Item file.`);
20255
- const parsed = matter14(raw);
20701
+ const parsed = matter16(raw);
20256
20702
  const data = parsed.data;
20257
20703
  const affectedModules = Array.isArray(data.affected_modules) ? data.affected_modules.map(String) : [];
20258
20704
  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 +20847,31 @@ function modulesDiscoverTool(root, args) {
20401
20847
  const sysName = config.system?.name ?? config.project?.name ?? "";
20402
20848
  const workspaceRoots2 = config.multirepo?.workspace_roots ?? [".."];
20403
20849
  const IGNORED = /* @__PURE__ */ new Set([".git", "node_modules", "dist", "build", "coverage", "vendor", ".next", "out", "tmp", "temp", ".kaddo"]);
20404
- const coreAbs = path4.resolve(root);
20850
+ const coreAbs = path6.resolve(root);
20405
20851
  const discovered = [];
20406
20852
  for (const wr of workspaceRoots2) {
20407
- const rootAbs = path4.resolve(root, wr);
20408
- if (!fs5.existsSync(rootAbs) || !fs5.statSync(rootAbs).isDirectory()) continue;
20853
+ const rootAbs = path6.resolve(root, wr);
20854
+ if (!fs7.existsSync(rootAbs) || !fs7.statSync(rootAbs).isDirectory()) continue;
20409
20855
  let entries;
20410
20856
  try {
20411
- entries = fs5.readdirSync(rootAbs);
20857
+ entries = fs7.readdirSync(rootAbs);
20412
20858
  } catch {
20413
20859
  continue;
20414
20860
  }
20415
20861
  for (const entry of entries) {
20416
20862
  if (IGNORED.has(entry)) continue;
20417
- const candidateAbs = path4.resolve(rootAbs, entry);
20418
- if (!fs5.statSync(candidateAbs).isDirectory()) continue;
20863
+ const candidateAbs = path6.resolve(rootAbs, entry);
20864
+ if (!fs7.statSync(candidateAbs).isDirectory()) continue;
20419
20865
  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)) {
20866
+ const relPath = path6.relative(root, candidateAbs).replace(/\\/g, "/");
20867
+ const cfgPath = path6.join(candidateAbs, ".kaddo", "config.yml");
20868
+ if (!fs7.existsSync(cfgPath)) {
20423
20869
  discovered.push({ id: entry, path: relPath, status: "not_configured", eligibleForMapping: false });
20424
20870
  continue;
20425
20871
  }
20426
20872
  let parsed = null;
20427
20873
  try {
20428
- parsed = parseYaml11(fs5.readFileSync(cfgPath, "utf-8"));
20874
+ parsed = parseYaml11(fs7.readFileSync(cfgPath, "utf-8"));
20429
20875
  } catch {
20430
20876
  }
20431
20877
  const role = parsed?.project?.role ?? parsed?.multirepo?.role;
@@ -20452,7 +20898,7 @@ function modulesDiscoverTool(root, args) {
20452
20898
  descriptor.modules.push({ id: d3.id, name: d3.id, path: d3.path, parent_system: sysName, status: "configured" });
20453
20899
  }
20454
20900
  }
20455
- const ymlContent = stringifyYaml7({ version: descriptor.version ?? 1, system: sysName, workspace_roots: workspaceRoots2, modules: descriptor.modules ?? [] });
20901
+ const ymlContent = stringifyYaml8({ version: descriptor.version ?? 1, system: sysName, workspace_roots: workspaceRoots2, modules: descriptor.modules ?? [] });
20456
20902
  writeDerived(root, ".kaddo/modules.yml", ymlContent);
20457
20903
  return ok2({ discovered, applied: true });
20458
20904
  }
@@ -20498,9 +20944,9 @@ ${list3(data.owners)}
20498
20944
  }
20499
20945
 
20500
20946
  // ../cli/src/core/system-topology.ts
20501
- import { parse as parseYaml12, stringify as stringifyYaml8 } from "yaml";
20502
- import fs6 from "fs";
20503
- import path5 from "path";
20947
+ import { parse as parseYaml12, stringify as stringifyYaml9 } from "yaml";
20948
+ import fs8 from "fs";
20949
+ import path7 from "path";
20504
20950
  import crypto from "crypto";
20505
20951
  var TOPOLOGY_FILE2 = "knowledge/tech/system-topology.yml";
20506
20952
  var SYSTEM_ENTITY_KINDS2 = /* @__PURE__ */ new Set([
@@ -20872,16 +21318,16 @@ function findSystemPaths(dir, source, target, opts = {}) {
20872
21318
  out.get(r2.source).push(r2.target);
20873
21319
  }
20874
21320
  const paths = [];
20875
- const walk = (node, path7) => {
21321
+ const walk = (node, path9) => {
20876
21322
  if (paths.length >= 20) return;
20877
- if (node === target && path7.length > 1) {
20878
- paths.push([...path7]);
21323
+ if (node === target && path9.length > 1) {
21324
+ paths.push([...path9]);
20879
21325
  return;
20880
21326
  }
20881
- if (path7.length > maxDepth) return;
21327
+ if (path9.length > maxDepth) return;
20882
21328
  for (const nxt of out.get(node) ?? []) {
20883
- if (path7.includes(nxt)) continue;
20884
- walk(nxt, [...path7, nxt]);
21329
+ if (path9.includes(nxt)) continue;
21330
+ walk(nxt, [...path9, nxt]);
20885
21331
  }
20886
21332
  };
20887
21333
  walk(source, [source]);
@@ -20906,14 +21352,14 @@ function getImpactCandidates(dir, seedIds, opts = {}) {
20906
21352
  existing.reason.push(reason);
20907
21353
  continue;
20908
21354
  }
20909
- const path7 = findSystemPaths(dir, seed, n.id, { maxDepth: opts.maxDepth ?? DEFAULT_MAX_DEPTH })[0];
21355
+ const path9 = findSystemPaths(dir, seed, n.id, { maxDepth: opts.maxDepth ?? DEFAULT_MAX_DEPTH })[0];
20910
21356
  candidates.set(n.id, {
20911
21357
  nodeId: n.id,
20912
21358
  label: n.label,
20913
21359
  kind: n.type,
20914
21360
  ...n.moduleId ? { moduleId: n.moduleId } : {},
20915
21361
  reason: [reason],
20916
- ...path7 ? { graphPath: path7.map((id) => byId.get(id)?.label ?? id) } : {},
21362
+ ...path9 ? { graphPath: path9.map((id) => byId.get(id)?.label ?? id) } : {},
20917
21363
  ...n.implementationRefs?.length ? { implementationRefs: n.implementationRefs } : {},
20918
21364
  ...n.knowledgeRefs?.length ? { knowledgeRefs: n.knowledgeRefs } : {},
20919
21365
  status: "candidate",
@@ -21012,14 +21458,14 @@ function systemImpactCandidatesTool(root, args) {
21012
21458
  }
21013
21459
 
21014
21460
  // ../cli/src/services/integrations.ts
21015
- import matter16 from "gray-matter";
21016
- import { parse as parseYaml13, stringify as stringifyYaml9 } from "yaml";
21461
+ import matter18 from "gray-matter";
21462
+ import { parse as parseYaml13, stringify as stringifyYaml10 } from "yaml";
21017
21463
 
21018
21464
  // ../cli/src/core/work-item-write.ts
21019
- import fs7 from "fs";
21020
- import path6 from "path";
21465
+ import fs9 from "fs";
21466
+ import path8 from "path";
21021
21467
  import crypto4 from "crypto";
21022
- import matter15 from "gray-matter";
21468
+ import matter17 from "gray-matter";
21023
21469
  var SECTION_ORDER2 = [
21024
21470
  "Actor",
21025
21471
  "Outcome",
@@ -21687,8 +22133,8 @@ async function handleJiraResponse2(res) {
21687
22133
  }
21688
22134
  return await res.json();
21689
22135
  }
21690
- async function jiraFetch2(baseUrl, path7, auth, timeoutMs, params) {
21691
- const url = new URL(path7, baseUrl + "/");
22136
+ async function jiraFetch2(baseUrl, path9, auth, timeoutMs, params) {
22137
+ const url = new URL(path9, baseUrl + "/");
21692
22138
  if (params) for (const [k2, v] of Object.entries(params)) url.searchParams.set(k2, v);
21693
22139
  const controller = new AbortController();
21694
22140
  const timer = setTimeout(() => controller.abort(), timeoutMs);
@@ -21706,8 +22152,8 @@ async function jiraFetch2(baseUrl, path7, auth, timeoutMs, params) {
21706
22152
  clearTimeout(timer);
21707
22153
  }
21708
22154
  }
21709
- async function jiraPost2(baseUrl, path7, auth, timeoutMs, body) {
21710
- const url = new URL(path7, baseUrl + "/");
22155
+ async function jiraPost2(baseUrl, path9, auth, timeoutMs, body) {
22156
+ const url = new URL(path9, baseUrl + "/");
21711
22157
  const controller = new AbortController();
21712
22158
  const timer = setTimeout(() => controller.abort(), timeoutMs);
21713
22159
  try {
@@ -22067,7 +22513,7 @@ async function integrationsWorkItemTool(root, args) {
22067
22513
  }
22068
22514
 
22069
22515
  // ../cli/src/core/context-pack.ts
22070
- import matter17 from "gray-matter";
22516
+ import matter19 from "gray-matter";
22071
22517
  var CONTEXT_PACK_VERSION = "1";
22072
22518
  var ARCH_DIR4 = "knowledge";
22073
22519
  function readScanJson(dir) {
@@ -22080,7 +22526,7 @@ function readScanJson(dir) {
22080
22526
  }
22081
22527
  }
22082
22528
  function firstParagraph4(markdown) {
22083
- const body = matter17(markdown).content.trim();
22529
+ const body = matter19(markdown).content.trim();
22084
22530
  const para = body.split("\n\n").map((p) => p.trim()).find((p) => p && !p.startsWith("#"));
22085
22531
  return para ?? "";
22086
22532
  }
@@ -22186,6 +22632,7 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
22186
22632
  missing.push("No work items found.");
22187
22633
  }
22188
22634
  const state = config.project.state;
22635
+ const mode = projectMode(config);
22189
22636
  const roadmapPath = join(dir, ARCH_DIR4, "delivery", "roadmap.md");
22190
22637
  const materialized = allArtifacts.filter(
22191
22638
  (a2) => a2.filePath.replace(/\\/g, "/").includes("/delivery/work-items/") && Boolean(a2.type)
@@ -22278,6 +22725,7 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
22278
22725
  project: {
22279
22726
  name: config.project.name,
22280
22727
  state,
22728
+ mode,
22281
22729
  teamSize: config.team.size,
22282
22730
  structure: config.project.structure,
22283
22731
  language: languageLabel(projectLanguage(config))
@@ -22303,6 +22751,7 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
22303
22751
  allWorkItems: allWorkItemArtifacts.map(toContextWorkItem),
22304
22752
  artifacts: allArtifacts.filter((a2) => a2.codeGlobs.length > 0).map(toContextArtifact)
22305
22753
  },
22754
+ ...mode === "poc" ? { poc: readPocSummary(dir) } : {},
22306
22755
  layers,
22307
22756
  knowledgeQuality,
22308
22757
  roadmap,
@@ -22406,11 +22855,23 @@ function renderContextPack(pack) {
22406
22855
  [
22407
22856
  `- Name: ${project.name}`,
22408
22857
  `- State: ${project.state}`,
22858
+ `- Mode: ${project.mode}`,
22409
22859
  `- Team size: ${project.teamSize}`,
22410
22860
  `- Structure: ${project.structure}`,
22411
22861
  `- Language: ${project.language}`
22412
22862
  ].join("\n") + "\n"
22413
22863
  );
22864
+ if (pack.poc) {
22865
+ parts.push("## Proof of Concept\n");
22866
+ parts.push([
22867
+ `- Hypothesis: ${pack.poc.hypothesisDefined ? "defined" : "missing"}`,
22868
+ `- Success criteria: ${pack.poc.successCriteriaCount}`,
22869
+ `- Constraints: ${pack.poc.constraintsDefined ? "defined" : "missing"}`,
22870
+ `- Non-goals: ${pack.poc.nonGoalsDefined ? "defined" : "missing"}`,
22871
+ `- Conclusion: ${pack.poc.conclusion}`,
22872
+ "- Canonical artifact: `knowledge/delivery/poc.md`"
22873
+ ].join("\n") + "\n");
22874
+ }
22414
22875
  parts.push("## Current Phase\n");
22415
22876
  parts.push(`Phase: ${pack.phase.phase}
22416
22877
  `);
@@ -22494,27 +22955,29 @@ function renderContextPack(pack) {
22494
22955
  }
22495
22956
  parts.push("## Current Knowledge\n");
22496
22957
  parts.push((knowledge.summary || "No project knowledge summary found yet.") + "\n");
22497
- parts.push("## Roadmap Status\n");
22498
- if (pack.roadmap.present) {
22499
- parts.push(
22500
- [
22501
- `- Initiatives: ${pack.roadmap.initiatives}`,
22502
- `- Work Item candidates: ${pack.roadmap.work_item_candidates}`,
22503
- `- Materialized Work Items: ${pack.roadmap.materialized_work_items}`,
22504
- `- Remaining Work Item candidates: ${pack.roadmap.remaining_work_item_candidates}`
22505
- ].join("\n") + "\n"
22506
- );
22507
- if (pack.roadmap.remaining_work_item_candidates > 0) {
22958
+ if (project.mode !== "poc") {
22959
+ parts.push("## Roadmap Status\n");
22960
+ if (pack.roadmap.present) {
22508
22961
  parts.push(
22509
- "Work Item candidates are not yet Work Items. Materialize them with `kaddo create --from roadmap`.\n"
22962
+ [
22963
+ `- Initiatives: ${pack.roadmap.initiatives}`,
22964
+ `- Work Item candidates: ${pack.roadmap.work_item_candidates}`,
22965
+ `- Materialized Work Items: ${pack.roadmap.materialized_work_items}`,
22966
+ `- Remaining Work Item candidates: ${pack.roadmap.remaining_work_item_candidates}`
22967
+ ].join("\n") + "\n"
22510
22968
  );
22969
+ if (pack.roadmap.remaining_work_item_candidates > 0) {
22970
+ parts.push(
22971
+ "Work Item candidates are not yet Work Items. Materialize them with `kaddo create --from roadmap`.\n"
22972
+ );
22973
+ }
22511
22974
  }
22975
+ parts.push((knowledge.roadmapSummary || "No roadmap baseline found.") + "\n");
22512
22976
  }
22513
- parts.push((knowledge.roadmapSummary || "No roadmap baseline found.") + "\n");
22514
22977
  const rq = pack.roadmapQuality;
22515
22978
  const rqi = rq.initiatives;
22516
22979
  const rqw = rq.work_item_candidates;
22517
- if (rqi.total > 0 || rqw.total > 0) {
22980
+ if (project.mode !== "poc" && (rqi.total > 0 || rqw.total > 0)) {
22518
22981
  parts.push("## Roadmap Quality\n");
22519
22982
  if (rqi.total > 0) {
22520
22983
  parts.push(
@@ -22729,7 +23192,13 @@ function renderContextPack(pack) {
22729
23192
  function agentIsInstalled(dir, fileName) {
22730
23193
  return exists(join(dir, agentInstallPath(fileName))) || exists(join(dir, "knowledge", "agents", fileName));
22731
23194
  }
22732
- function flowForState(state) {
23195
+ function flowForState(state, mode) {
23196
+ if (mode === "poc") {
23197
+ return [
23198
+ { agent: "architecture-agent.md", output: "knowledge/tech/current-state.md" },
23199
+ { agent: "work-item-agent.md", output: "knowledge/delivery/poc.md" }
23200
+ ];
23201
+ }
22733
23202
  switch (state) {
22734
23203
  case "new":
22735
23204
  return [
@@ -22754,9 +23223,10 @@ function flowForState(state) {
22754
23223
  }
22755
23224
  function buildUnderstandPlan(dir, config) {
22756
23225
  const state = config.project.state;
23226
+ const mode = projectMode(config);
22757
23227
  if (isModule(config)) {
22758
23228
  return {
22759
- project: { name: config.project.name, state, teamSize: config.team.size, structure: config.project.structure, language: "English" },
23229
+ project: { name: config.project.name, state, mode, teamSize: config.team.size, structure: config.project.structure, language: "English" },
22760
23230
  steps: [],
22761
23231
  missingAgents: [],
22762
23232
  agentsInstalled: true,
@@ -22764,7 +23234,7 @@ function buildUnderstandPlan(dir, config) {
22764
23234
  contextPackPath: ".kaddo/context-pack.md"
22765
23235
  };
22766
23236
  }
22767
- const flow = flowForState(state).filter((s) => !exists(join(dir, s.output)));
23237
+ const flow = flowForState(state, mode).filter((s) => !exists(join(dir, s.output)));
22768
23238
  const steps = flow.map((s) => {
22769
23239
  const installed = agentIsInstalled(dir, s.agent);
22770
23240
  return { agent: s.agent, output: s.output, installed };
@@ -22775,6 +23245,7 @@ function buildUnderstandPlan(dir, config) {
22775
23245
  project: {
22776
23246
  name: config.project.name,
22777
23247
  state,
23248
+ mode,
22778
23249
  teamSize: config.team.size,
22779
23250
  structure: config.project.structure,
22780
23251
  language: "English"
@@ -23047,7 +23518,7 @@ function requireKnowledge(root) {
23047
23518
  throw new KaddoMcpError("Knowledge repository not found. Run `kaddo bootstrap` first.");
23048
23519
  }
23049
23520
  }
23050
- function slug4(s) {
23521
+ function slug5(s) {
23051
23522
  return s.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "project";
23052
23523
  }
23053
23524
  function generateContext(root) {
@@ -23180,7 +23651,7 @@ function generateCapsuleDraft(root) {
23180
23651
  const config = requireConfig(root);
23181
23652
  requireKnowledge(root);
23182
23653
  const capsule = buildCapsule(root, config);
23183
- const name = slug4(config.project.name);
23654
+ const name = slug5(config.project.name);
23184
23655
  const md = `.kaddo/exports/${name}.capsule.md`;
23185
23656
  const json = `.kaddo/exports/${name}.capsule.json`;
23186
23657
  writeDerived(root, md, renderCapsuleMarkdown(capsule));
@@ -23478,6 +23949,53 @@ function createServer(root) {
23478
23949
  { 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
23950
  async (args) => toolText(guarded(root, () => getResourceTool(root, args.id)))
23480
23951
  );
23952
+ const resourceFields = {
23953
+ title: z4.string().optional(),
23954
+ resource_type: z4.string().optional(),
23955
+ provider: z4.string().optional(),
23956
+ environments: z4.array(z4.string()).optional(),
23957
+ scope: z4.string().optional(),
23958
+ module: z4.string().optional(),
23959
+ modules: z4.array(z4.string()).optional(),
23960
+ purpose: z4.string().optional(),
23961
+ access_interfaces: z4.array(z4.object({
23962
+ type: z4.string(),
23963
+ tool: z4.string().optional(),
23964
+ provider: z4.string().optional(),
23965
+ purpose: z4.string().optional(),
23966
+ operations: z4.array(z4.string()).optional(),
23967
+ environments: z4.array(z4.string()).optional(),
23968
+ constraints: z4.string().optional()
23969
+ })).optional(),
23970
+ authentication: z4.object({ mode: z4.string().optional(), refs: z4.array(z4.string()).optional() }).optional()
23971
+ };
23972
+ server.registerTool(
23973
+ "kaddo_create_resource",
23974
+ {
23975
+ title: "Create Project Resource",
23976
+ 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.",
23977
+ inputSchema: { ...resourceFields, confirm: z4.boolean().optional() }
23978
+ },
23979
+ async (args) => toolText(guarded(root, () => createResourceTool(root, args, args.confirm)))
23980
+ );
23981
+ server.registerTool(
23982
+ "kaddo_update_resource",
23983
+ {
23984
+ title: "Update Project Resource",
23985
+ description: "Propose updating a Project Resource (no-lossy). Without confirm, returns a preview; with confirm=true, Core applies it. Never include credential values.",
23986
+ inputSchema: { id: z4.string(), ...resourceFields, confirm: z4.boolean().optional() }
23987
+ },
23988
+ async (args) => toolText(guarded(root, () => updateResourceTool(root, args.id, args, args.confirm)))
23989
+ );
23990
+ server.registerTool(
23991
+ "kaddo_delete_resource",
23992
+ {
23993
+ title: "Delete Project Resource",
23994
+ 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.",
23995
+ inputSchema: { id: z4.string(), confirm: z4.boolean().optional() }
23996
+ },
23997
+ async (args) => toolText(guarded(root, () => deleteResourceTool(root, args.id, args.confirm)))
23998
+ );
23481
23999
  server.registerTool(
23482
24000
  "kaddo_list_capsules",
23483
24001
  { title: "List Knowledge Capsules", description: "List registered external Knowledge Capsules.", inputSchema: {} },