@kaddo/mcp 3.118.0 → 3.119.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 +530 -89
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -389,7 +389,7 @@ function join(...parts) {
389
389
  }
390
390
 
391
391
  // ../cli/src/core/impact-report.ts
392
- import matter10 from "gray-matter";
392
+ import matter11 from "gray-matter";
393
393
 
394
394
  // ../cli/src/core/config.ts
395
395
  import { z } from "zod";
@@ -691,12 +691,12 @@ function classifyLayer(posixPath) {
691
691
  return "unknown";
692
692
  }
693
693
  function enrich(dir, a2) {
694
- const posix = toPosix2(a2.filePath);
694
+ const posix3 = toPosix2(a2.filePath);
695
695
  const isWi = isWorkItemArtifact(a2);
696
696
  return {
697
697
  ...a2,
698
698
  relPath: relativeTo(dir, a2.filePath),
699
- layer: classifyLayer(posix),
699
+ layer: classifyLayer(posix3),
700
700
  isWorkItem: isWi,
701
701
  lifecycle: isWi ? lifecycleStateOf({ status: a2.status, filePath: a2.filePath }) : void 0
702
702
  };
@@ -3914,8 +3914,8 @@ function buildOpenQuestionsReport(dir, now = /* @__PURE__ */ new Date()) {
3914
3914
  const project = loadConfig(dir)?.project.name ?? "unknown";
3915
3915
  const questions = [];
3916
3916
  let seq = 0;
3917
- for (const source of SOURCES) {
3918
- const p = join(dir, source);
3917
+ for (const source3 of SOURCES) {
3918
+ const p = join(dir, source3);
3919
3919
  if (!exists(p)) continue;
3920
3920
  let md;
3921
3921
  try {
@@ -3928,8 +3928,8 @@ function buildOpenQuestionsReport(dir, now = /* @__PURE__ */ new Date()) {
3928
3928
  const { classification, reason } = classifyQuestion(eq.text);
3929
3929
  questions.push({
3930
3930
  id: `OQ-${String(seq).padStart(3, "0")}`,
3931
- source,
3932
- sourcePath: source,
3931
+ source: source3,
3932
+ sourcePath: source3,
3933
3933
  line: eq.line,
3934
3934
  raw: eq.raw,
3935
3935
  section: "Open Questions",
@@ -4360,6 +4360,121 @@ function readPocSummary(dir) {
4360
4360
  };
4361
4361
  }
4362
4362
 
4363
+ // ../cli/src/core/poc-report.ts
4364
+ import { createHash } from "crypto";
4365
+ import matter8 from "gray-matter";
4366
+ var POC_REPORT_PREFIX = "poc-report-v";
4367
+ var SECRET_LINE = /(^|\s)(?:[A-Z][A-Z0-9_]*(?:TOKEN|SECRET|PASSWORD|API_KEY|PRIVATE_KEY)|(?:token|secret|password|api[_-]?key))\s*[:=]\s*\S+/i;
4368
+ var reportName = /^poc-report-v(\d{3,})\.md$/;
4369
+ function sha(value) {
4370
+ return createHash("sha256").update(value).digest("hex");
4371
+ }
4372
+ function posix(value) {
4373
+ return value.replace(/\\/g, "/");
4374
+ }
4375
+ function redactSecrets(value) {
4376
+ return value.split(/\r?\n/).map((line) => {
4377
+ if (!SECRET_LINE.test(line)) return line;
4378
+ return line.replace(/([:=]\s*)\S+.*/, "$1[redacted]");
4379
+ }).join("\n");
4380
+ }
4381
+ function source(dir, path9, kind) {
4382
+ const absolute = join(dir, path9);
4383
+ if (!exists(absolute)) return null;
4384
+ const raw = readFile(absolute);
4385
+ return { path: posix(path9), kind, digest: sha(raw), content: redactSecrets(raw) };
4386
+ }
4387
+ function reportFiles(dir) {
4388
+ const delivery = join(dir, "knowledge", "delivery");
4389
+ return readDir(delivery).flatMap((name) => {
4390
+ const match = name.match(reportName);
4391
+ if (!match) return [];
4392
+ try {
4393
+ const parsed = matter8(readFile(join(delivery, name)));
4394
+ const fingerprint = typeof parsed.data.source_fingerprint === "string" ? parsed.data.source_fingerprint : "";
4395
+ const manifest = parsed.data.source_manifest;
4396
+ const sourceManifest = manifest && typeof manifest === "object" && !Array.isArray(manifest) ? Object.fromEntries(Object.entries(manifest).filter(([, digest]) => typeof digest === "string")) : {};
4397
+ return [{ path: `knowledge/delivery/${name}`, version: Number(match[1]), sourceFingerprint: fingerprint, sourceManifest }];
4398
+ } catch {
4399
+ return [];
4400
+ }
4401
+ }).sort((a2, b2) => b2.version - a2.version);
4402
+ }
4403
+ function reportPath(version) {
4404
+ return `knowledge/delivery/${POC_REPORT_PREFIX}${String(version).padStart(3, "0")}.md`;
4405
+ }
4406
+ function buildPocReportContext(dir) {
4407
+ const poc = readPocSummary(dir);
4408
+ const reports = reportFiles(dir);
4409
+ const latestReport = reports[0] ?? null;
4410
+ const nextPath = reportPath((latestReport?.version ?? 0) + 1);
4411
+ if (!poc.exists || poc.conclusion === "pending") {
4412
+ return {
4413
+ eligible: false,
4414
+ conclusion: poc.conclusion,
4415
+ status: latestReport ? "stale" : "missing",
4416
+ latestReport,
4417
+ nextPath,
4418
+ sourceFingerprint: "",
4419
+ sources: [],
4420
+ changesSinceLatest: [],
4421
+ handoff: "POC conclusion is still pending. Record validated, rejected, or inconclusive before generating a final report."
4422
+ };
4423
+ }
4424
+ const sources = [];
4425
+ const add = (path9, kind) => {
4426
+ if (sources.some((item2) => item2.path === posix(path9))) return;
4427
+ const item = source(dir, path9, kind);
4428
+ if (item) sources.push(item);
4429
+ };
4430
+ add(POC_ARTIFACT_PATH, "poc");
4431
+ const pocText = readFile(join(dir, POC_ARTIFACT_PATH));
4432
+ const knowledge = discoverKnowledge(dir);
4433
+ const workItems = discoverWorkItems(dir);
4434
+ const referencedWorkItems = workItems.filter((wi) => wi.lifecycle === "completed" && wi.id && new RegExp(`\\b${wi.id}\\b`).test(pocText));
4435
+ const resourceIds = /* @__PURE__ */ new Set();
4436
+ const decisionIds = /* @__PURE__ */ new Set();
4437
+ for (const wi of referencedWorkItems) {
4438
+ add(wi.relPath, "work-item");
4439
+ for (const resource of parseWorkItemResources(wi.rawFrontmatter)) resourceIds.add(resource.id);
4440
+ const decisions = wi.rawFrontmatter.decisions;
4441
+ if (Array.isArray(decisions)) decisions.forEach((id) => decisionIds.add(String(id)));
4442
+ }
4443
+ for (const id of resourceIds) {
4444
+ const resource = getResource(dir, id);
4445
+ if (resource) add(resource.path, "resource");
4446
+ }
4447
+ for (const artifact of knowledge) {
4448
+ if (artifact.isWorkItem || !artifact.id) continue;
4449
+ if (decisionIds.has(artifact.id) || referencedWorkItems.some((wi) => new RegExp(`\\b${artifact.id}\\b`).test(readFile(wi.filePath)))) add(artifact.relPath, "knowledge");
4450
+ }
4451
+ sources.sort((a2, b2) => a2.path.localeCompare(b2.path));
4452
+ const sourceFingerprint = sha(sources.map((item) => `${item.path}\0${item.digest}`).join("\n"));
4453
+ const status = !latestReport ? "missing" : latestReport.sourceFingerprint === sourceFingerprint ? "current" : "stale";
4454
+ const currentManifest = Object.fromEntries(sources.map((item) => [item.path, item.digest]));
4455
+ const changesSinceLatest = status === "stale" ? [.../* @__PURE__ */ new Set([...Object.keys(currentManifest), ...Object.keys(latestReport?.sourceManifest ?? {})])].filter((path9) => currentManifest[path9] !== latestReport?.sourceManifest[path9]).sort() : [];
4456
+ const sourceList = sources.map((item) => `- ${item.path} (${item.kind})`).join("\n") || "- No sources selected.";
4457
+ return {
4458
+ eligible: true,
4459
+ conclusion: poc.conclusion,
4460
+ status,
4461
+ latestReport,
4462
+ nextPath,
4463
+ sourceFingerprint,
4464
+ sources,
4465
+ changesSinceLatest,
4466
+ handoff: [
4467
+ "Create a POC Final Report from the supplied sources only. Do not explore the repository.",
4468
+ `Canonical conclusion: ${poc.conclusion}. Do not contradict it.`,
4469
+ "Use the 15 canonical sections. Mark missing evidence as Not evaluated, Not applicable, or No evidence available.",
4470
+ "Distinguish measured, calculated, estimated, and projected values. Never include secret values.",
4471
+ "",
4472
+ "Selected sources:",
4473
+ sourceList
4474
+ ].join("\n")
4475
+ };
4476
+ }
4477
+
4363
4478
  // ../cli/src/core/next-step.ts
4364
4479
  function buildDeliveryState(dir) {
4365
4480
  const wis = discoverWorkItems(dir);
@@ -4476,7 +4591,11 @@ function resolveNextStep(dir, now = /* @__PURE__ */ new Date()) {
4476
4591
  if (poc.conclusion === "pending") {
4477
4592
  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." };
4478
4593
  }
4479
- 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." };
4594
+ const report = buildPocReportContext(dir);
4595
+ if (report.status !== "current") {
4596
+ return { id: "poc-final-report", phase: "Conclusion", label: `POC concluded: ${poc.conclusion}. Generate a final report if it would help communicate the result.`, command: "kaddo poc report", target: POC_ARTIFACT_PATH, reason: report.status === "stale" ? "The latest final report is stale after source changes." : "A final report is optional and has not been generated." };
4597
+ }
4598
+ 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 and final report are current." };
4480
4599
  }
4481
4600
  const q3 = (rel) => analyzeKnowledgeArtifact(dir, rel);
4482
4601
  const resolveAgent = (agent) => {
@@ -4914,7 +5033,7 @@ function deriveDeliveryCompletedStatus(dir) {
4914
5033
  }
4915
5034
 
4916
5035
  // ../cli/src/core/assets.ts
4917
- import matter8 from "gray-matter";
5036
+ import matter9 from "gray-matter";
4918
5037
 
4919
5038
  // ../cli/src/core/version.ts
4920
5039
  import { fileURLToPath } from "url";
@@ -5590,6 +5709,50 @@ their intersection with the affected areas, and recommended actions.
5590
5709
  - The assessment is proportional \u2014 a small change gets a brief assessment.
5591
5710
  `
5592
5711
  );
5712
+ var POC_REPORT_WRITING = skill(
5713
+ "poc-report-writing",
5714
+ "POC Final Report Writing Skill",
5715
+ "delivery",
5716
+ ["implementation-agent", "work-item-agent"],
5717
+ `
5718
+ # POC Final Report Writing Skill
5719
+
5720
+ ## Purpose
5721
+
5722
+ Turn a supplied deterministic POC report context into a concise, traceable final report.
5723
+
5724
+ ## When to use
5725
+
5726
+ After a POC has a canonical conclusion and Core has prepared its deterministic report context.
5727
+
5728
+ ## Rules
5729
+
5730
+ - Use only the supplied context; do not scan the repository or infer missing results.
5731
+ - Treat \`poc.md\` as canonical for the conclusion. Never contradict it.
5732
+ - Keep facts, measurements, calculations, estimates, and recommendations distinct.
5733
+ - Never include credential values, tokens, private keys, passwords, or connection strings.
5734
+ - Use \`Not evaluated in this POC.\`, \`Not applicable.\`, or \`No evidence available.\` when sources do not support a section.
5735
+ - Return a proposal for human review; Core persists it only after confirmation.
5736
+
5737
+ ## Required structure
5738
+
5739
+ 1. Executive Summary
5740
+ 2. Problem and Objective
5741
+ 3. Hypothesis and Success Criteria
5742
+ 4. Scope, Constraints and Non-goals
5743
+ 5. Technical Approach and Architecture
5744
+ 6. Infrastructure and Project Resources
5745
+ 7. Implementation and Relevant Technical Decisions
5746
+ 8. Experiments and Validation
5747
+ 9. Results and Measurements
5748
+ 10. Cost and Efficiency Analysis
5749
+ 11. Observability and Operational Findings
5750
+ 12. Findings, Learnings, Limitations and Risks
5751
+ 13. Conclusion
5752
+ 14. Recommendation and Next Steps
5753
+ 15. Traceability and Sources
5754
+ `
5755
+ );
5593
5756
  var SKILLS = [
5594
5757
  ADR_WRITING,
5595
5758
  WORK_ITEM_REFINEMENT,
@@ -5600,10 +5763,11 @@ var SKILLS = [
5600
5763
  IMPLEMENTATION_PLANNING,
5601
5764
  MODULE_CONTEXT_REFINEMENT,
5602
5765
  EVIDENCE_VERIFICATION,
5603
- LEGACY_RISK_ASSESSMENT
5766
+ LEGACY_RISK_ASSESSMENT,
5767
+ POC_REPORT_WRITING
5604
5768
  ];
5605
5769
  var SKILL_GROUPS = {
5606
- delivery: ["work-item-refinement", "implementation-planning", "learning-capture", "evidence-verification", "legacy-risk-assessment"],
5770
+ delivery: ["work-item-refinement", "implementation-planning", "learning-capture", "evidence-verification", "legacy-risk-assessment", "poc-report-writing"],
5607
5771
  tech: ["adr-writing", "ownership-suggestion", "graph-metadata-review", "module-context-refinement"],
5608
5772
  integration: ["capsule-writing"]
5609
5773
  };
@@ -8137,7 +8301,7 @@ function canonicalSkills() {
8137
8301
  }
8138
8302
  function versionOf(content) {
8139
8303
  try {
8140
- const v = matter8(content).data.version;
8304
+ const v = matter9(content).data.version;
8141
8305
  return v === void 0 || v === null ? null : String(v);
8142
8306
  } catch {
8143
8307
  return null;
@@ -8399,9 +8563,9 @@ function parseWorkItemSource(frontmatter) {
8399
8563
  reason: "No source metadata found."
8400
8564
  };
8401
8565
  }
8402
- function renderSourceCompact(source) {
8403
- const parts = [source.type];
8404
- if (source.id) parts.push(source.id);
8566
+ function renderSourceCompact(source3) {
8567
+ const parts = [source3.type];
8568
+ if (source3.id) parts.push(source3.id);
8405
8569
  return parts.join(" \xB7 ");
8406
8570
  }
8407
8571
  function optStr2(v) {
@@ -8778,7 +8942,17 @@ var concludePoc = {
8778
8942
  return conclusion === "pending" ? { status: "pending", reason: "Record validated, rejected, or inconclusive from the evidence." } : { status: "done", evidence: [POC_ARTIFACT_PATH], reason: `Conclusion: ${conclusion}.` };
8779
8943
  }
8780
8944
  };
8781
- var POC_STEPS = [enableKaddo, ensurePocArtifact, definePocHypothesis, definePocCriteria, describePocTech, identifyPocResources, createPocExperiment, capturePocEvidence, concludePoc];
8945
+ var finalPocReport = {
8946
+ id: "poc-final-report",
8947
+ label: "Generate final report (optional)",
8948
+ evaluate: (ctx) => {
8949
+ const report = buildPocReportContext(ctx.dir);
8950
+ if (!report.eligible) return { status: "optional", reason: "Available after recording a POC conclusion." };
8951
+ if (report.status === "current") return { status: "done", evidence: [report.latestReport.path] };
8952
+ return { status: "optional", command: "kaddo poc report", reason: report.status === "stale" ? "Latest report is stale." : "A final report is optional." };
8953
+ }
8954
+ };
8955
+ var POC_STEPS = [enableKaddo, ensurePocArtifact, definePocHypothesis, definePocCriteria, describePocTech, identifyPocResources, createPocExperiment, capturePocEvidence, concludePoc, finalPocReport];
8782
8956
  function stepsForState(state) {
8783
8957
  switch (state) {
8784
8958
  case "new":
@@ -8882,6 +9056,7 @@ function mapNextStepId(id) {
8882
9056
  "poc-resources": "poc-resources",
8883
9057
  "poc-create-experiment": "poc-create-experiment",
8884
9058
  "poc-evaluate": "poc-evaluate",
9059
+ "poc-final-report": "poc-final-report",
8885
9060
  "poc-complete": "poc-evaluate"
8886
9061
  };
8887
9062
  return MAP[id] ?? id;
@@ -8896,7 +9071,7 @@ function buildProjectRoute(dir) {
8896
9071
  return { id: d3.id, label: d3.label, ...result };
8897
9072
  });
8898
9073
  for (const s of steps) {
8899
- if (s.id === ctx.nextStepId && s.status !== "done") {
9074
+ if (s.id === ctx.nextStepId && s.status !== "done" && s.status !== "optional") {
8900
9075
  s.status = "current";
8901
9076
  }
8902
9077
  }
@@ -8908,8 +9083,8 @@ function buildProjectRoute(dir) {
8908
9083
  break;
8909
9084
  }
8910
9085
  }
8911
- const completed = steps.filter((s) => s.status === "done").length;
8912
- const total = steps.length;
9086
+ const completed = steps.filter((s) => s.status === "done" && s.status !== "optional").length;
9087
+ const total = steps.filter((s) => s.status !== "optional").length;
8913
9088
  const progressPercent = total > 0 ? Math.round(completed / total * 100) : 0;
8914
9089
  return {
8915
9090
  type: state,
@@ -8966,7 +9141,7 @@ function renderRouteCompact(route) {
8966
9141
  }
8967
9142
 
8968
9143
  // ../cli/src/core/metadata-health.ts
8969
- import matter9 from "gray-matter";
9144
+ import matter10 from "gray-matter";
8970
9145
  var KNOWLEDGE_FILES2 = [
8971
9146
  "knowledge/business/business.md",
8972
9147
  "knowledge/product/product.md",
@@ -8986,7 +9161,7 @@ function analyzeMetadataHealth(dir) {
8986
9161
  let data;
8987
9162
  try {
8988
9163
  const raw = readFile(p);
8989
- data = matter9(raw).data;
9164
+ data = matter10(raw).data;
8990
9165
  } catch {
8991
9166
  continue;
8992
9167
  }
@@ -9018,7 +9193,7 @@ function analyzeMetadataHealth(dir) {
9018
9193
  if (isFile(p) && entry.endsWith(".md")) {
9019
9194
  try {
9020
9195
  const raw = readFile(p);
9021
- const fm = matter9(raw).data;
9196
+ const fm = matter10(raw).data;
9022
9197
  if (fm.status === "done") {
9023
9198
  const rel = p.replace(dir + "/", "").replace(dir + "\\", "").replace(/\\/g, "/");
9024
9199
  findings.push({
@@ -9973,7 +10148,7 @@ function buildImpactReport(dir, opts = {}, now = /* @__PURE__ */ new Date()) {
9973
10148
  }
9974
10149
  }
9975
10150
  try {
9976
- const body = matter10(readFile(wi.filePath)).content;
10151
+ const body = matter11(readFile(wi.filePath)).content;
9977
10152
  if (hasSection(body, /acceptance|criterios de aceptaci/i)) withAcceptance++;
9978
10153
  else gaps.missing_acceptance_criteria.push(gapItem(wi, "Add an `## Acceptance Criteria` section."));
9979
10154
  if (hasSection(body, /definition of done|^#{1,6}\s*dod\b|definici[oó]n de (terminado|hecho)/i)) withDoD++;
@@ -10381,7 +10556,7 @@ function parseRatioNumerator(s) {
10381
10556
  }
10382
10557
  var SAVINGS_DISCLAIMER = "These are evidence-based estimates, not exact ROI. Adjust assumptions with real team data.";
10383
10558
  function buildSavingsReport(dir, opts = {}, now = /* @__PURE__ */ new Date()) {
10384
- const { assumptions: a2, source } = loadAssumptions(dir);
10559
+ const { assumptions: a2, source: source3 } = loadAssumptions(dir);
10385
10560
  const impact = buildImpactReport(dir, { scope: opts.scope, scopeSource: opts.scopeSource }, now);
10386
10561
  const completed = impact.traceability.completed_work_items;
10387
10562
  const wiWithOwnership = parseRatioNumerator(impact.traceability.work_items_connected_to_code);
@@ -10421,7 +10596,7 @@ function buildSavingsReport(dir, opts = {}, now = /* @__PURE__ */ new Date()) {
10421
10596
  if (impact.score === null || impact.score < 60 || graphQuality === "empty") {
10422
10597
  level = "Low";
10423
10598
  reasons.push("Impact score is low or the knowledge graph is empty.");
10424
- } else if (impact.score >= 85 && graphQuality === "good" && source === "file" && guard.available && guard.resolved > 0) {
10599
+ } else if (impact.score >= 85 && graphQuality === "good" && source3 === "file" && guard.available && guard.resolved > 0) {
10425
10600
  level = "High";
10426
10601
  reasons.push("Strong evidence, custom assumptions and recorded drift resolution.");
10427
10602
  } else {
@@ -10435,9 +10610,9 @@ function buildSavingsReport(dir, opts = {}, now = /* @__PURE__ */ new Date()) {
10435
10610
  } else {
10436
10611
  reasons.push("Guard history available, but no resolved drift warnings have been recorded yet.");
10437
10612
  }
10438
- reasons.push(source === "file" ? "Assumptions come from `.kaddo/savings.yml`." : "Savings assumptions are defaults and should be calibrated.");
10613
+ reasons.push(source3 === "file" ? "Assumptions come from `.kaddo/savings.yml`." : "Savings assumptions are defaults and should be calibrated.");
10439
10614
  const actions = [];
10440
- if (source === "default") actions.push("Run `kaddo savings init` to customize assumptions.");
10615
+ if (source3 === "default") actions.push("Run `kaddo savings init` to customize assumptions.");
10441
10616
  actions.push("Calibrate hourly cost and team assumptions with real team data.");
10442
10617
  actions.push("Run `kaddo impact` to verify the underlying evidence quality.");
10443
10618
  actions.push("Use this report as directional evidence, not accounting data.");
@@ -10455,7 +10630,7 @@ function buildSavingsReport(dir, opts = {}, now = /* @__PURE__ */ new Date()) {
10455
10630
  scope: impact.scope,
10456
10631
  currency: a2.currency,
10457
10632
  disclaimer: SAVINGS_DISCLAIMER,
10458
- assumptions_source: source,
10633
+ assumptions_source: source3,
10459
10634
  assumptions: {
10460
10635
  hourly_cost: a2.hourly_cost,
10461
10636
  context_preparation_minutes_saved_per_work_item: a2.context_preparation_minutes_saved_per_work_item,
@@ -10686,7 +10861,7 @@ function serializeDriftJson(r2) {
10686
10861
  }
10687
10862
 
10688
10863
  // src/workitems.ts
10689
- import matter11 from "gray-matter";
10864
+ import matter12 from "gray-matter";
10690
10865
  var VALID_SOURCES2 = /* @__PURE__ */ new Set(["manual", "roadmap", "jira", "github", "notion", "xlsx", "csv", "api", "external", "unknown"]);
10691
10866
  function optStr3(v) {
10692
10867
  return typeof v === "string" && v.trim() ? v.trim() : void 0;
@@ -10777,7 +10952,7 @@ function listWorkItems(root) {
10777
10952
  if (raw === null) continue;
10778
10953
  let parsed;
10779
10954
  try {
10780
- parsed = matter11(raw);
10955
+ parsed = matter12(raw);
10781
10956
  } catch {
10782
10957
  continue;
10783
10958
  }
@@ -10786,8 +10961,8 @@ function listWorkItems(root) {
10786
10961
  const status = lifecycleOf(data, rel);
10787
10962
  const code = strArray2(data.code);
10788
10963
  const domains = strArray2(data.domains);
10789
- const source = parseSource(data);
10790
- const readiness = status === "draft" ? assessReadiness(data, parsed.content, code, domains, source) : void 0;
10964
+ const source3 = parseSource(data);
10965
+ const readiness = status === "draft" ? assessReadiness(data, parsed.content, code, domains, source3) : void 0;
10791
10966
  items.push({
10792
10967
  id: String(data.id ?? "") || rel,
10793
10968
  title: String(data.title ?? ""),
@@ -10800,7 +10975,7 @@ function listWorkItems(root) {
10800
10975
  capabilities: strArray2(data.capabilities),
10801
10976
  decisions: strArray2(data.decisions),
10802
10977
  capsules: strArray2(data.capsules),
10803
- source,
10978
+ source: source3,
10804
10979
  domains,
10805
10980
  readiness,
10806
10981
  ...data.implementation_status ? { implementation_status: String(data.implementation_status) } : {},
@@ -10817,7 +10992,7 @@ function listWorkItems(root) {
10817
10992
  }
10818
10993
  return items.sort((a2, b2) => a2.id.localeCompare(b2.id));
10819
10994
  }
10820
- function assessReadiness(data, content, code, domains, source) {
10995
+ function assessReadiness(data, content, code, domains, source3) {
10821
10996
  const body = content.trim();
10822
10997
  const lower = body.toLowerCase();
10823
10998
  const warnings = [];
@@ -10974,6 +11149,25 @@ var RESOURCES = [
10974
11149
  "POC artifact not found. Initialize in POC mode or switch with `kaddo project mode poc`."
10975
11150
  )
10976
11151
  },
11152
+ {
11153
+ uri: "kaddo://poc-report",
11154
+ name: "Kaddo final POC report",
11155
+ description: "Latest final POC report, or deterministic report status and source selection.",
11156
+ mimeType: "text/markdown",
11157
+ read: (root) => {
11158
+ const context = buildPocReportContext(root);
11159
+ if (context.latestReport) {
11160
+ const saved = readText(root, context.latestReport.path);
11161
+ if (saved) return text2("kaddo://poc-report", saved);
11162
+ }
11163
+ const lines = [
11164
+ `Final POC report: ${context.status}`,
11165
+ `Conclusion: ${context.conclusion}`,
11166
+ context.handoff
11167
+ ];
11168
+ return text2("kaddo://poc-report", lines.join("\n\n"));
11169
+ }
11170
+ },
10977
11171
  {
10978
11172
  uri: "kaddo://capsules",
10979
11173
  name: "Kaddo knowledge capsules",
@@ -11257,14 +11451,14 @@ function getPrompt(root, name) {
11257
11451
  }
11258
11452
 
11259
11453
  // src/tools.ts
11260
- import matter15 from "gray-matter";
11454
+ import matter16 from "gray-matter";
11261
11455
 
11262
11456
  // ../cli/dist/core.js
11263
11457
  import fs3 from "fs";
11264
11458
  import path3 from "path";
11265
11459
  import { z as z3 } from "zod";
11266
11460
  import { parse as parseYaml10, stringify as stringifyYaml5 } from "yaml";
11267
- import matter12 from "gray-matter";
11461
+ import matter13 from "gray-matter";
11268
11462
  import matter22 from "gray-matter";
11269
11463
  import { parse as parseYaml62, stringify as stringifyYaml42 } from "yaml";
11270
11464
  import { parse as parseYaml22 } from "yaml";
@@ -11275,25 +11469,27 @@ import matter32 from "gray-matter";
11275
11469
  import matter42 from "gray-matter";
11276
11470
  import matter52 from "gray-matter";
11277
11471
  import { parse as parseYaml72 } from "yaml";
11472
+ import { createHash as createHash2 } from "crypto";
11278
11473
  import matter62 from "gray-matter";
11474
+ import matter72 from "gray-matter";
11279
11475
  import { fileURLToPath as fileURLToPath2 } from "url";
11280
11476
  import { dirname as dirname2, join as join22, parse as parse2 } from "path";
11281
11477
  import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
11282
- import matter72 from "gray-matter";
11283
11478
  import matter82 from "gray-matter";
11284
11479
  import matter92 from "gray-matter";
11480
+ import matter102 from "gray-matter";
11285
11481
  import { parse as parseYaml82, stringify as stringifyYaml52 } from "yaml";
11286
11482
  import fs4 from "fs";
11287
11483
  import path4 from "path";
11288
11484
  import crypto2 from "crypto";
11289
- import matter102 from "gray-matter";
11485
+ import matter112 from "gray-matter";
11290
11486
  import crypto3 from "crypto";
11291
11487
  import fs5 from "fs";
11292
- import matter112 from "gray-matter";
11293
11488
  import matter122 from "gray-matter";
11489
+ import matter132 from "gray-matter";
11294
11490
  import { parse as parseYaml92, stringify as stringifyYaml6 } from "yaml";
11295
11491
  import { parse as parseYaml102, stringify as stringifyYaml7 } from "yaml";
11296
- import matter13 from "gray-matter";
11492
+ import matter14 from "gray-matter";
11297
11493
  function exists2(p) {
11298
11494
  return fs3.existsSync(p);
11299
11495
  }
@@ -11469,7 +11665,7 @@ function parseImpactAnalysis2(data) {
11469
11665
  }
11470
11666
  function parseArtifact2(filePath, raw) {
11471
11667
  try {
11472
- const { data } = matter12(raw);
11668
+ const { data } = matter13(raw);
11473
11669
  const globs = Array.isArray(data.code) ? data.code.filter(Boolean) : [];
11474
11670
  return {
11475
11671
  filePath,
@@ -11583,12 +11779,12 @@ function classifyLayer2(posixPath) {
11583
11779
  return "unknown";
11584
11780
  }
11585
11781
  function enrich2(dir, a2) {
11586
- const posix = toPosix7(a2.filePath);
11782
+ const posix22 = toPosix7(a2.filePath);
11587
11783
  const isWi = isWorkItemArtifact2(a2);
11588
11784
  return {
11589
11785
  ...a2,
11590
11786
  relPath: relativeTo2(dir, a2.filePath),
11591
- layer: classifyLayer2(posix),
11787
+ layer: classifyLayer2(posix22),
11592
11788
  isWorkItem: isWi,
11593
11789
  lifecycle: isWi ? lifecycleStateOf2({ status: a2.status, filePath: a2.filePath }) : void 0
11594
11790
  };
@@ -13618,6 +13814,177 @@ function buildTechDecisions2(dir) {
13618
13814
  function hasUnmaterializedDecisions(td) {
13619
13815
  return td.candidates > 0 && td.adrs === 0;
13620
13816
  }
13817
+ var POC_ARTIFACT_PATH2 = "knowledge/delivery/poc.md";
13818
+ var PLACEHOLDER2 = /^[_<].*[_>]$|^(describe|list|document|what|how)\b/i;
13819
+ function section3(markdown, heading) {
13820
+ const match = markdown.match(new RegExp(`^##\\s+${heading}\\s*$([\\s\\S]*?)(?=^##\\s+|(?![\\s\\S]))`, "im"));
13821
+ return match?.[1] ?? "";
13822
+ }
13823
+ function meaningful2(text22) {
13824
+ return text22.split(/\r?\n/).some((line) => {
13825
+ const value = line.trim().replace(/^[-*]\s+(?:\[[^\]]+\]\s*)?/, "");
13826
+ return Boolean(value) && !PLACEHOLDER2.test(value) && !/^status:\s*(pending|tbd)$/i.test(value);
13827
+ });
13828
+ }
13829
+ function readPocSummary2(dir) {
13830
+ const path52 = join3(dir, POC_ARTIFACT_PATH2);
13831
+ if (!exists2(path52)) {
13832
+ return { exists: false, hypothesisDefined: false, successCriteriaCount: 0, constraintsDefined: false, nonGoalsDefined: false, conclusion: "pending" };
13833
+ }
13834
+ let markdown = "";
13835
+ try {
13836
+ markdown = readFile2(path52);
13837
+ } catch {
13838
+ return { exists: false, hypothesisDefined: false, successCriteriaCount: 0, constraintsDefined: false, nonGoalsDefined: false, conclusion: "pending" };
13839
+ }
13840
+ const criteria = section3(markdown, "Success Criteria").split(/\r?\n/).filter((line) => /^\s*[-*]\s+(?:\[[ xX]\]\s+)?/.test(line)).filter((line) => meaningful2(line)).length;
13841
+ const conclusionBody = section3(markdown, "Conclusion");
13842
+ const conclusionMatch = conclusionBody.match(/\b(?:status|outcome)\s*:\s*(validated|rejected|inconclusive)\b/i);
13843
+ return {
13844
+ exists: true,
13845
+ hypothesisDefined: meaningful2(section3(markdown, "Hypothesis")),
13846
+ successCriteriaCount: criteria,
13847
+ constraintsDefined: meaningful2(section3(markdown, "Constraints")),
13848
+ nonGoalsDefined: meaningful2(section3(markdown, "Non-goals")),
13849
+ conclusion: conclusionMatch ? conclusionMatch[1].toLowerCase() : "pending"
13850
+ };
13851
+ }
13852
+ var POC_REPORT_PREFIX2 = "poc-report-v";
13853
+ var SECRET_LINE2 = /(^|\s)(?:[A-Z][A-Z0-9_]*(?:TOKEN|SECRET|PASSWORD|API_KEY|PRIVATE_KEY)|(?:token|secret|password|api[_-]?key))\s*[:=]\s*\S+/i;
13854
+ var reportName2 = /^poc-report-v(\d{3,})\.md$/;
13855
+ function sha2(value) {
13856
+ return createHash2("sha256").update(value).digest("hex");
13857
+ }
13858
+ function posix2(value) {
13859
+ return value.replace(/\\/g, "/");
13860
+ }
13861
+ function redactSecrets2(value) {
13862
+ return value.split(/\r?\n/).map((line) => {
13863
+ if (!SECRET_LINE2.test(line)) return line;
13864
+ return line.replace(/([:=]\s*)\S+.*/, "$1[redacted]");
13865
+ }).join("\n");
13866
+ }
13867
+ function source2(dir, path52, kind) {
13868
+ const absolute = join3(dir, path52);
13869
+ if (!exists2(absolute)) return null;
13870
+ const raw = readFile2(absolute);
13871
+ return { path: posix2(path52), kind, digest: sha2(raw), content: redactSecrets2(raw) };
13872
+ }
13873
+ function reportFiles2(dir) {
13874
+ const delivery = join3(dir, "knowledge", "delivery");
13875
+ return readDir2(delivery).flatMap((name) => {
13876
+ const match = name.match(reportName2);
13877
+ if (!match) return [];
13878
+ try {
13879
+ const parsed = matter62(readFile2(join3(delivery, name)));
13880
+ const fingerprint = typeof parsed.data.source_fingerprint === "string" ? parsed.data.source_fingerprint : "";
13881
+ const manifest = parsed.data.source_manifest;
13882
+ const sourceManifest = manifest && typeof manifest === "object" && !Array.isArray(manifest) ? Object.fromEntries(Object.entries(manifest).filter(([, digest]) => typeof digest === "string")) : {};
13883
+ return [{ path: `knowledge/delivery/${name}`, version: Number(match[1]), sourceFingerprint: fingerprint, sourceManifest }];
13884
+ } catch {
13885
+ return [];
13886
+ }
13887
+ }).sort((a2, b2) => b2.version - a2.version);
13888
+ }
13889
+ function reportPath2(version) {
13890
+ return `knowledge/delivery/${POC_REPORT_PREFIX2}${String(version).padStart(3, "0")}.md`;
13891
+ }
13892
+ function buildPocReportContext2(dir) {
13893
+ const poc = readPocSummary2(dir);
13894
+ const reports = reportFiles2(dir);
13895
+ const latestReport = reports[0] ?? null;
13896
+ const nextPath = reportPath2((latestReport?.version ?? 0) + 1);
13897
+ if (!poc.exists || poc.conclusion === "pending") {
13898
+ return {
13899
+ eligible: false,
13900
+ conclusion: poc.conclusion,
13901
+ status: latestReport ? "stale" : "missing",
13902
+ latestReport,
13903
+ nextPath,
13904
+ sourceFingerprint: "",
13905
+ sources: [],
13906
+ changesSinceLatest: [],
13907
+ handoff: "POC conclusion is still pending. Record validated, rejected, or inconclusive before generating a final report."
13908
+ };
13909
+ }
13910
+ const sources = [];
13911
+ const add = (path52, kind) => {
13912
+ if (sources.some((item2) => item2.path === posix2(path52))) return;
13913
+ const item = source2(dir, path52, kind);
13914
+ if (item) sources.push(item);
13915
+ };
13916
+ add(POC_ARTIFACT_PATH2, "poc");
13917
+ const pocText = readFile2(join3(dir, POC_ARTIFACT_PATH2));
13918
+ const knowledge = discoverKnowledge2(dir);
13919
+ const workItems = discoverWorkItems2(dir);
13920
+ const referencedWorkItems = workItems.filter((wi) => wi.lifecycle === "completed" && wi.id && new RegExp(`\\b${wi.id}\\b`).test(pocText));
13921
+ const resourceIds = /* @__PURE__ */ new Set();
13922
+ const decisionIds = /* @__PURE__ */ new Set();
13923
+ for (const wi of referencedWorkItems) {
13924
+ add(wi.relPath, "work-item");
13925
+ for (const resource of parseWorkItemResources2(wi.rawFrontmatter)) resourceIds.add(resource.id);
13926
+ const decisions = wi.rawFrontmatter.decisions;
13927
+ if (Array.isArray(decisions)) decisions.forEach((id) => decisionIds.add(String(id)));
13928
+ }
13929
+ for (const id of resourceIds) {
13930
+ const resource = getResource2(dir, id);
13931
+ if (resource) add(resource.path, "resource");
13932
+ }
13933
+ for (const artifact of knowledge) {
13934
+ if (artifact.isWorkItem || !artifact.id) continue;
13935
+ if (decisionIds.has(artifact.id) || referencedWorkItems.some((wi) => new RegExp(`\\b${artifact.id}\\b`).test(readFile2(wi.filePath)))) add(artifact.relPath, "knowledge");
13936
+ }
13937
+ sources.sort((a2, b2) => a2.path.localeCompare(b2.path));
13938
+ const sourceFingerprint = sha2(sources.map((item) => `${item.path}\0${item.digest}`).join("\n"));
13939
+ const status = !latestReport ? "missing" : latestReport.sourceFingerprint === sourceFingerprint ? "current" : "stale";
13940
+ const currentManifest = Object.fromEntries(sources.map((item) => [item.path, item.digest]));
13941
+ const changesSinceLatest = status === "stale" ? [.../* @__PURE__ */ new Set([...Object.keys(currentManifest), ...Object.keys(latestReport?.sourceManifest ?? {})])].filter((path52) => currentManifest[path52] !== latestReport?.sourceManifest[path52]).sort() : [];
13942
+ const sourceList = sources.map((item) => `- ${item.path} (${item.kind})`).join("\n") || "- No sources selected.";
13943
+ return {
13944
+ eligible: true,
13945
+ conclusion: poc.conclusion,
13946
+ status,
13947
+ latestReport,
13948
+ nextPath,
13949
+ sourceFingerprint,
13950
+ sources,
13951
+ changesSinceLatest,
13952
+ handoff: [
13953
+ "Create a POC Final Report from the supplied sources only. Do not explore the repository.",
13954
+ `Canonical conclusion: ${poc.conclusion}. Do not contradict it.`,
13955
+ "Use the 15 canonical sections. Mark missing evidence as Not evaluated, Not applicable, or No evidence available.",
13956
+ "Distinguish measured, calculated, estimated, and projected values. Never include secret values.",
13957
+ "",
13958
+ "Selected sources:",
13959
+ sourceList
13960
+ ].join("\n")
13961
+ };
13962
+ }
13963
+ function canonicalReport(context, proposal) {
13964
+ const supersedes = context.latestReport?.path.split("/").pop();
13965
+ const data = {
13966
+ type: "poc-report",
13967
+ report_version: Number(context.nextPath.match(/v(\d+)\.md$/)?.[1] ?? 1),
13968
+ generated_at: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
13969
+ poc_conclusion: context.conclusion,
13970
+ source_fingerprint: context.sourceFingerprint,
13971
+ sources: context.sources.map((item) => item.path),
13972
+ source_manifest: Object.fromEntries(context.sources.map((item) => [item.path, item.digest]))
13973
+ };
13974
+ if (supersedes) data.supersedes = supersedes;
13975
+ return matter62.stringify(redactSecrets2(proposal).trimEnd() + "\n", data);
13976
+ }
13977
+ function persistPocReport(dir, proposal, opts = {}) {
13978
+ const context = buildPocReportContext2(dir);
13979
+ if (!context.eligible) throw new Error(context.handoff);
13980
+ if (context.status === "current" && !opts.force) return { status: "current", path: context.latestReport.path, message: "The current POC report already represents the latest evidence." };
13981
+ if (!opts.confirm) return { status: "needs_confirmation", path: context.nextPath, content: canonicalReport(context, proposal), message: "Review the proposed report and confirm persistence." };
13982
+ const output = join3(dir, context.nextPath);
13983
+ if (exists2(output)) throw new Error(`POC report already exists: ${context.nextPath}`);
13984
+ const content = canonicalReport(context, proposal);
13985
+ writeFile2(output, content);
13986
+ return { status: "persisted", path: context.nextPath, content };
13987
+ }
13621
13988
  function resolveVersion2() {
13622
13989
  try {
13623
13990
  let dir = dirname2(fileURLToPath2(import.meta.url));
@@ -14286,6 +14653,50 @@ their intersection with the affected areas, and recommended actions.
14286
14653
  - The assessment is proportional \u2014 a small change gets a brief assessment.
14287
14654
  `
14288
14655
  );
14656
+ var POC_REPORT_WRITING2 = skill2(
14657
+ "poc-report-writing",
14658
+ "POC Final Report Writing Skill",
14659
+ "delivery",
14660
+ ["implementation-agent", "work-item-agent"],
14661
+ `
14662
+ # POC Final Report Writing Skill
14663
+
14664
+ ## Purpose
14665
+
14666
+ Turn a supplied deterministic POC report context into a concise, traceable final report.
14667
+
14668
+ ## When to use
14669
+
14670
+ After a POC has a canonical conclusion and Core has prepared its deterministic report context.
14671
+
14672
+ ## Rules
14673
+
14674
+ - Use only the supplied context; do not scan the repository or infer missing results.
14675
+ - Treat \`poc.md\` as canonical for the conclusion. Never contradict it.
14676
+ - Keep facts, measurements, calculations, estimates, and recommendations distinct.
14677
+ - Never include credential values, tokens, private keys, passwords, or connection strings.
14678
+ - Use \`Not evaluated in this POC.\`, \`Not applicable.\`, or \`No evidence available.\` when sources do not support a section.
14679
+ - Return a proposal for human review; Core persists it only after confirmation.
14680
+
14681
+ ## Required structure
14682
+
14683
+ 1. Executive Summary
14684
+ 2. Problem and Objective
14685
+ 3. Hypothesis and Success Criteria
14686
+ 4. Scope, Constraints and Non-goals
14687
+ 5. Technical Approach and Architecture
14688
+ 6. Infrastructure and Project Resources
14689
+ 7. Implementation and Relevant Technical Decisions
14690
+ 8. Experiments and Validation
14691
+ 9. Results and Measurements
14692
+ 10. Cost and Efficiency Analysis
14693
+ 11. Observability and Operational Findings
14694
+ 12. Findings, Learnings, Limitations and Risks
14695
+ 13. Conclusion
14696
+ 14. Recommendation and Next Steps
14697
+ 15. Traceability and Sources
14698
+ `
14699
+ );
14289
14700
  var SKILLS2 = [
14290
14701
  ADR_WRITING2,
14291
14702
  WORK_ITEM_REFINEMENT2,
@@ -14296,10 +14707,11 @@ var SKILLS2 = [
14296
14707
  IMPLEMENTATION_PLANNING2,
14297
14708
  MODULE_CONTEXT_REFINEMENT2,
14298
14709
  EVIDENCE_VERIFICATION2,
14299
- LEGACY_RISK_ASSESSMENT2
14710
+ LEGACY_RISK_ASSESSMENT2,
14711
+ POC_REPORT_WRITING2
14300
14712
  ];
14301
14713
  var SKILL_GROUPS2 = {
14302
- delivery: ["work-item-refinement", "implementation-planning", "learning-capture", "evidence-verification", "legacy-risk-assessment"],
14714
+ delivery: ["work-item-refinement", "implementation-planning", "learning-capture", "evidence-verification", "legacy-risk-assessment", "poc-report-writing"],
14303
14715
  tech: ["adr-writing", "ownership-suggestion", "graph-metadata-review", "module-context-refinement"],
14304
14716
  integration: ["capsule-writing"]
14305
14717
  };
@@ -17547,7 +17959,7 @@ function parseOriginalSnapshot(fm) {
17547
17959
  function readBody(filePath) {
17548
17960
  try {
17549
17961
  const raw = readFile2(filePath);
17550
- return matter92(raw).content;
17962
+ return matter102(raw).content;
17551
17963
  } catch {
17552
17964
  return "";
17553
17965
  }
@@ -17832,7 +18244,7 @@ function ensureH1(body, title) {
17832
18244
  ${trimmed}`;
17833
18245
  }
17834
18246
  function serialize2(data, body) {
17835
- return matter102.stringify(`
18247
+ return matter112.stringify(`
17836
18248
  ${body.trim()}
17837
18249
  `, data);
17838
18250
  }
@@ -17867,7 +18279,7 @@ function createWorkItem(dir, opts) {
17867
18279
  const title = intent.split(/\r?\n/)[0].trim().slice(0, 120);
17868
18280
  const today22 = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
17869
18281
  const summary = opts.summary?.trim() || intent;
17870
- const source = opts.source ? { ...opts.source, inferred: false } : { type: "manual", inferred: false };
18282
+ const source22 = opts.source ? { ...opts.source, inferred: false } : { type: "manual", inferred: false };
17871
18283
  const data = {
17872
18284
  type,
17873
18285
  id,
@@ -17875,7 +18287,7 @@ function createWorkItem(dir, opts) {
17875
18287
  status: "draft",
17876
18288
  work_type: type,
17877
18289
  created_at: today22,
17878
- source,
18290
+ source: source22,
17879
18291
  generated_by: "kaddo-admin",
17880
18292
  affected_modules: [],
17881
18293
  summary
@@ -18623,7 +19035,7 @@ function detectFormat(raw) {
18623
19035
  if (!trimmed) throw new ImportError("EMPTY_INPUT", "Import content is empty.");
18624
19036
  if (trimmed.startsWith("---")) {
18625
19037
  try {
18626
- const { data } = matter112(trimmed);
19038
+ const { data } = matter122(trimmed);
18627
19039
  if (data && typeof data === "object" && Object.keys(data).length > 0) {
18628
19040
  const d3 = data;
18629
19041
  if (d3.type === "work-item" || d3.id && typeof d3.id === "string" && /^WI-\d+/.test(d3.id)) {
@@ -18645,7 +19057,7 @@ function parseContent(raw) {
18645
19057
  let bodyText = trimmed;
18646
19058
  if (format === "kaddo-frontmatter" || format === "markdown-frontmatter") {
18647
19059
  try {
18648
- const parsed = matter112(trimmed);
19060
+ const parsed = matter122(trimmed);
18649
19061
  frontmatterFields = parsed.data ?? {};
18650
19062
  bodyText = parsed.content.trim();
18651
19063
  } catch {
@@ -18717,12 +19129,12 @@ function normalizeImportFields(parsed, opts) {
18717
19129
  const intent = parsed.title;
18718
19130
  const body = hasRealBody ? parsed.body : void 0;
18719
19131
  const summary = hasRealBody ? deriveShortSummary(parsed) : parsed.title;
18720
- const source = {
19132
+ const source22 = {
18721
19133
  type: opts.importSource === "chat" ? "chat" : "external",
18722
19134
  imported_at: (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
18723
19135
  };
18724
- source.source_format = opts.sourceFormat;
18725
- source.source_hash = opts.sourceHash;
19136
+ source22.source_format = opts.sourceFormat;
19137
+ source22.source_hash = opts.sourceHash;
18726
19138
  const snapshot = { title: parsed.title };
18727
19139
  if (summary !== parsed.title) snapshot.description = summary;
18728
19140
  if (typeof parsed.candidateFields.type === "string") snapshot.type = parsed.candidateFields.type;
@@ -18731,7 +19143,7 @@ function normalizeImportFields(parsed, opts) {
18731
19143
  const labels = parsed.candidateFields.labels.filter((l2) => typeof l2 === "string");
18732
19144
  if (labels.length > 0) snapshot.labels = labels;
18733
19145
  }
18734
- return { type, intent, summary, body, source, snapshot, discardedFields };
19146
+ return { type, intent, summary, body, source: source22, snapshot, discardedFields };
18735
19147
  }
18736
19148
  function computeContentHash(raw) {
18737
19149
  return crypto3.createHash("sha256").update(raw.trim(), "utf-8").digest("hex");
@@ -18742,10 +19154,10 @@ function findDuplicateByHash(dir, hash) {
18742
19154
  const raw = readFile2(item.filePath);
18743
19155
  if (!raw) continue;
18744
19156
  try {
18745
- const { data } = matter112(raw);
18746
- const source = data?.source;
18747
- if (source && typeof source === "object" && !Array.isArray(source)) {
18748
- const s = source;
19157
+ const { data } = matter122(raw);
19158
+ const source22 = data?.source;
19159
+ if (source22 && typeof source22 === "object" && !Array.isArray(source22)) {
19160
+ const s = source22;
18749
19161
  if (s.source_hash === hash) {
18750
19162
  return { workItemId: data.id ?? item.id ?? item.title, path: item.relPath };
18751
19163
  }
@@ -19637,7 +20049,7 @@ function buildInitiativeContent(id, input) {
19637
20049
  if (input.priority) data.priority = input.priority;
19638
20050
  if (input.source) data.source = input.source;
19639
20051
  if (input.sourceId) data.source_id = input.sourceId;
19640
- return matter13.stringify("\n" + (input.body ?? defaultBody(input.title.trim())), data);
20052
+ return matter14.stringify("\n" + (input.body ?? defaultBody(input.title.trim())), data);
19641
20053
  }
19642
20054
  function createInitiative(dir, input) {
19643
20055
  if (!input.title?.trim()) throw new InitiativeWriteError("Initiative title is required.");
@@ -19661,10 +20073,10 @@ function transitionInitiative(dir, id, to) {
19661
20073
  throw new InitiativeWriteError(`Invalid transition ${ini.status} \u2192 ${to}.`);
19662
20074
  }
19663
20075
  const raw = readFile2(ini.filePath);
19664
- const { data, content } = matter13(raw, {});
20076
+ const { data, content } = matter14(raw, {});
19665
20077
  data.status = to;
19666
20078
  if (to === "completed") data.completed_at = today2();
19667
- const newContent = matter13.stringify(content, data);
20079
+ const newContent = matter14.stringify(content, data);
19668
20080
  writeFile2(ini.filePath, newContent);
19669
20081
  const updated = parseInitiative2(dir, ini.filePath, newContent);
19670
20082
  if (!updated) throw new InitiativeWriteError("Failed to parse the Initiative after transition.");
@@ -19674,7 +20086,7 @@ function addCandidate(dir, id, candidate) {
19674
20086
  const ini = getInitiative(dir, id);
19675
20087
  if (!ini) throw new InitiativeWriteError(`Initiative ${id} not found.`);
19676
20088
  const raw = readFile2(ini.filePath);
19677
- const { data, content } = matter13(raw, {});
20089
+ const { data, content } = matter14(raw, {});
19678
20090
  const existing = Array.isArray(data.candidates) ? data.candidates : [];
19679
20091
  const fallbackId = `WI-CANDIDATE-${String(existing.length + 1).padStart(3, "0")}`;
19680
20092
  const normalized = normalizeCandidate(candidate, fallbackId);
@@ -19682,7 +20094,7 @@ function addCandidate(dir, id, candidate) {
19682
20094
  throw new InitiativeWriteError(`Candidate ${normalized.id} already exists in ${id}.`);
19683
20095
  }
19684
20096
  data.candidates = [...existing, candidateToData(normalized)];
19685
- const newContent = matter13.stringify(content, data);
20097
+ const newContent = matter14.stringify(content, data);
19686
20098
  writeFile2(ini.filePath, newContent);
19687
20099
  const updated = parseInitiative2(dir, ini.filePath, newContent);
19688
20100
  if (!updated) throw new InitiativeWriteError("Failed to parse the Initiative after adding a candidate.");
@@ -19692,7 +20104,7 @@ function updateInitiative(dir, id, patch) {
19692
20104
  const ini = getInitiative(dir, id);
19693
20105
  if (!ini) throw new InitiativeWriteError(`Initiative ${id} not found.`);
19694
20106
  const raw = readFile2(ini.filePath);
19695
- const { data, content } = matter13(raw, {});
20107
+ const { data, content } = matter14(raw, {});
19696
20108
  if (patch.title !== void 0) {
19697
20109
  if (!patch.title.trim()) throw new InitiativeWriteError("Initiative title cannot be empty.");
19698
20110
  data.title = patch.title.trim();
@@ -19702,7 +20114,7 @@ function updateInitiative(dir, id, patch) {
19702
20114
  if (patch.knowledgeLevel !== void 0) data.knowledge_level = patch.knowledgeLevel;
19703
20115
  if (patch.domains !== void 0) data.domains = patch.domains;
19704
20116
  if (patch.relatedCapabilities !== void 0) data.related_capabilities = patch.relatedCapabilities;
19705
- const newContent = matter13.stringify(content, data);
20117
+ const newContent = matter14.stringify(content, data);
19706
20118
  writeFile2(ini.filePath, newContent);
19707
20119
  const updated = parseInitiative2(dir, ini.filePath, newContent);
19708
20120
  if (!updated) throw new InitiativeWriteError("Failed to parse the Initiative after update.");
@@ -19720,10 +20132,10 @@ function addExternalLink(dir, id, link) {
19720
20132
  throw new InitiativeWriteError(`External link ${link.integration}:${link.externalId} already exists on ${id}.`);
19721
20133
  }
19722
20134
  const raw = readFile2(ini.filePath);
19723
- const { data, content } = matter13(raw, {});
20135
+ const { data, content } = matter14(raw, {});
19724
20136
  const existing = Array.isArray(data.external_links) ? data.external_links : [];
19725
20137
  data.external_links = [...existing, externalLinkToData(link)];
19726
- const newContent = matter13.stringify(content, data);
20138
+ const newContent = matter14.stringify(content, data);
19727
20139
  writeFile2(ini.filePath, newContent);
19728
20140
  const updated = parseInitiative2(dir, ini.filePath, newContent);
19729
20141
  if (!updated) throw new InitiativeWriteError("Failed to parse the Initiative after adding an external link.");
@@ -19792,7 +20204,7 @@ function buildWorkItemContent(wiId, iniId, candidate) {
19792
20204
  "_What did we learn? Update after completion._",
19793
20205
  ""
19794
20206
  ].join("\n");
19795
- return matter13.stringify("\n" + body, data);
20207
+ return matter14.stringify("\n" + body, data);
19796
20208
  }
19797
20209
  function materializeCandidate(dir, id, candidateId) {
19798
20210
  const ini = getInitiative(dir, id);
@@ -19809,13 +20221,13 @@ function materializeCandidate(dir, id, candidateId) {
19809
20221
  ensureDir2(join3(dir, DRAFT_DIR));
19810
20222
  writeFile2(join3(dir, DRAFT_DIR, fileName), buildWorkItemContent(wiId, ini.id, candidate));
19811
20223
  const raw = readFile2(ini.filePath);
19812
- const { data, content } = matter13(raw, {});
20224
+ const { data, content } = matter14(raw, {});
19813
20225
  const cands = Array.isArray(data.candidates) ? data.candidates : [];
19814
20226
  for (const c of cands) {
19815
20227
  if (c && typeof c === "object" && String(c.id) === candidateId) c.materialized_as = wiId;
19816
20228
  }
19817
20229
  data.candidates = cands;
19818
- const newContent = matter13.stringify(content, data);
20230
+ const newContent = matter14.stringify(content, data);
19819
20231
  writeFile2(ini.filePath, newContent);
19820
20232
  const updated = parseInitiative2(dir, ini.filePath, newContent);
19821
20233
  if (!updated) throw new InitiativeWriteError("Failed to parse the Initiative after materialization.");
@@ -19929,7 +20341,7 @@ function evaluateInitiativeCompletion(dir, ini) {
19929
20341
  // ../cli/src/core/resource-write.ts
19930
20342
  import fs6 from "fs";
19931
20343
  import path5 from "path";
19932
- import matter14 from "gray-matter";
20344
+ import matter15 from "gray-matter";
19933
20345
  var RESOURCES_DIR = "knowledge/tech/resources";
19934
20346
  var ResourceWriteError = class extends Error {
19935
20347
  constructor(code, message) {
@@ -20009,7 +20421,7 @@ function buildFrontmatter(id, input, base = {}) {
20009
20421
  return data;
20010
20422
  }
20011
20423
  function serialize(data, body) {
20012
- return matter14.stringify(`
20424
+ return matter15.stringify(`
20013
20425
  ${body.trim()}
20014
20426
  `, data);
20015
20427
  }
@@ -20049,7 +20461,7 @@ function updateResource(dir, id, input) {
20049
20461
  throw new ResourceWriteError("VALIDATION_FAILED", findings.filter((f3) => f3.level === "error").map((f3) => f3.message).join("; "));
20050
20462
  }
20051
20463
  const raw = readFile(match.filePath);
20052
- const parsed = matter14(raw);
20464
+ const parsed = matter15(raw);
20053
20465
  const base = parsed.data ?? {};
20054
20466
  const data = buildFrontmatter(match.id || id, input, base);
20055
20467
  let body = parsed.content.trim();
@@ -20087,6 +20499,17 @@ function deleteResource(dir, id, opts = {}) {
20087
20499
  // src/tools.ts
20088
20500
  var ok = (data) => ({ ok: true, data });
20089
20501
  var fail = (message) => ({ ok: false, message });
20502
+ function preparePocReportTool(root) {
20503
+ const context = buildPocReportContext2(root);
20504
+ return context.eligible ? ok(context) : fail(context.handoff);
20505
+ }
20506
+ function persistPocReportTool(root, proposal, confirm2, force) {
20507
+ try {
20508
+ return ok(persistPocReport(root, proposal, { confirm: confirm2, force }));
20509
+ } catch (err) {
20510
+ return fail(err instanceof Error ? err.message : String(err));
20511
+ }
20512
+ }
20090
20513
  function projectStatus(root) {
20091
20514
  const explain = readJson(root, ".kaddo/explain.json");
20092
20515
  const hints = readJson(root, ".kaddo/graph-hints.json");
@@ -20179,12 +20602,12 @@ function markWorkItemReady(root, id, confirm2) {
20179
20602
  }
20180
20603
  const raw = readText(root, item.path);
20181
20604
  if (!raw) return fail(`Could not read Work Item file: ${item.path}`);
20182
- const parsed = matter15(raw);
20605
+ const parsed = matter16(raw);
20183
20606
  const data = { ...parsed.data };
20184
20607
  const today3 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
20185
20608
  data.status = "ready";
20186
20609
  data.ready_at = today3;
20187
- const newContent = matter15.stringify(parsed.content, data);
20610
+ const newContent = matter16.stringify(parsed.content, data);
20188
20611
  const posixPath = item.path.replace(/\\/g, "/");
20189
20612
  let newRelPath = item.path;
20190
20613
  if (posixPath.includes("/work-items/draft/")) {
@@ -20246,12 +20669,12 @@ function listGraphHints(root, filter = {}) {
20246
20669
  });
20247
20670
  }
20248
20671
  function importWorkItemTool(root, args) {
20249
- const source = args.source === "cli" || args.source === "admin" ? args.source : "chat";
20672
+ const source3 = args.source === "cli" || args.source === "admin" ? args.source : "chat";
20250
20673
  const onConflict = args.onConflict === "replace" || args.onConflict === "new-id" ? args.onConflict : void 0;
20251
20674
  try {
20252
20675
  const result = importWorkItem(root, {
20253
20676
  content: args.content,
20254
- source,
20677
+ source: source3,
20255
20678
  type: args.type,
20256
20679
  onConflict
20257
20680
  });
@@ -20412,7 +20835,7 @@ function deleteResourceTool(root, id, confirm2) {
20412
20835
  // src/multirepo.ts
20413
20836
  import fs7 from "fs";
20414
20837
  import path6 from "path";
20415
- import matter16 from "gray-matter";
20838
+ import matter17 from "gray-matter";
20416
20839
  import { parse as parseYaml11, stringify as stringifyYaml8 } from "yaml";
20417
20840
  var ok2 = (data) => ({ ok: true, data });
20418
20841
  var fail2 = (message) => ({ ok: false, message });
@@ -20564,7 +20987,7 @@ function validateWorkItemModulesTool(root, args) {
20564
20987
  if (!wi) return fail2(`Work Item "${args.workItemId}" not found.`);
20565
20988
  const raw = readText(root, wi.path);
20566
20989
  if (!raw) return fail2(`Could not read Work Item file.`);
20567
- const parsed = matter16(raw);
20990
+ const parsed = matter17(raw);
20568
20991
  const data = parsed.data;
20569
20992
  const affectedModules = Array.isArray(data.affected_modules) ? data.affected_modules.map(String) : [];
20570
20993
  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) : [] })) : [];
@@ -20617,7 +21040,7 @@ function getWorkItemContextTool(root, args) {
20617
21040
  if (!wi) return fail2(`Work Item "${args.workItemId}" not found.`);
20618
21041
  const raw = readText(root, wi.path);
20619
21042
  if (!raw) return fail2(`Could not read Work Item file.`);
20620
- const parsed = matter16(raw);
21043
+ const parsed = matter17(raw);
20621
21044
  const data = parsed.data;
20622
21045
  const affectedModules = Array.isArray(data.affected_modules) ? data.affected_modules.map(String) : [];
20623
21046
  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) : [] })) : [];
@@ -20703,7 +21126,7 @@ function suggestBranchStrategyTool(root, args) {
20703
21126
  if (!wi) return fail2(`Work Item "${args.workItemId}" not found.`);
20704
21127
  const raw = readText(root, wi.path);
20705
21128
  if (!raw) return fail2(`Could not read Work Item file.`);
20706
- const parsed = matter16(raw);
21129
+ const parsed = matter17(raw);
20707
21130
  const data = parsed.data;
20708
21131
  const affectedModules = Array.isArray(data.affected_modules) ? data.affected_modules.map(String) : [];
20709
21132
  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) : [] })) : [];
@@ -21312,10 +21735,10 @@ function getSystemNeighbors(dir, nodeId, opts = {}) {
21312
21735
  }
21313
21736
  return { seed: nodeId, nodes, relationships: rels, truncated };
21314
21737
  }
21315
- function findSystemPaths(dir, source, target, opts = {}) {
21738
+ function findSystemPaths(dir, source3, target, opts = {}) {
21316
21739
  const projection = getSystemMapProjection2(dir);
21317
21740
  const byId = new Map(projection.nodes.map((n) => [n.id, n]));
21318
- if (!byId.has(source) || !byId.has(target)) return [];
21741
+ if (!byId.has(source3) || !byId.has(target)) return [];
21319
21742
  const maxDepth = Math.max(1, opts.maxDepth ?? 4);
21320
21743
  const out = /* @__PURE__ */ new Map();
21321
21744
  for (const r2 of projection.relationships) {
@@ -21335,7 +21758,7 @@ function findSystemPaths(dir, source, target, opts = {}) {
21335
21758
  walk(nxt, [...path9, nxt]);
21336
21759
  }
21337
21760
  };
21338
- walk(source, [source]);
21761
+ walk(source3, [source3]);
21339
21762
  return paths;
21340
21763
  }
21341
21764
  function getImpactCandidates(dir, seedIds, opts = {}) {
@@ -21463,14 +21886,14 @@ function systemImpactCandidatesTool(root, args) {
21463
21886
  }
21464
21887
 
21465
21888
  // ../cli/src/services/integrations.ts
21466
- import matter18 from "gray-matter";
21889
+ import matter19 from "gray-matter";
21467
21890
  import { parse as parseYaml13, stringify as stringifyYaml10 } from "yaml";
21468
21891
 
21469
21892
  // ../cli/src/core/work-item-write.ts
21470
21893
  import fs9 from "fs";
21471
21894
  import path8 from "path";
21472
21895
  import crypto4 from "crypto";
21473
- import matter17 from "gray-matter";
21896
+ import matter18 from "gray-matter";
21474
21897
  var SECTION_ORDER2 = [
21475
21898
  "Actor",
21476
21899
  "Outcome",
@@ -22518,7 +22941,7 @@ async function integrationsWorkItemTool(root, args) {
22518
22941
  }
22519
22942
 
22520
22943
  // ../cli/src/core/context-pack.ts
22521
- import matter19 from "gray-matter";
22944
+ import matter20 from "gray-matter";
22522
22945
  var CONTEXT_PACK_VERSION = "1";
22523
22946
  var ARCH_DIR4 = "knowledge";
22524
22947
  function readScanJson(dir) {
@@ -22531,7 +22954,7 @@ function readScanJson(dir) {
22531
22954
  }
22532
22955
  }
22533
22956
  function firstParagraph4(markdown) {
22534
- const body = matter19(markdown).content.trim();
22957
+ const body = matter20(markdown).content.trim();
22535
22958
  const para = body.split("\n\n").map((p) => p.trim()).find((p) => p && !p.startsWith("#"));
22536
22959
  return para ?? "";
22537
22960
  }
@@ -23944,6 +24367,24 @@ function createServer(root) {
23944
24367
  },
23945
24368
  async (args) => toolText(guarded(root, () => markWorkItemReady(root, args.id, args.confirm)))
23946
24369
  );
24370
+ server.registerTool(
24371
+ "kaddo_prepare_poc_report",
24372
+ {
24373
+ title: "Prepare final POC report context",
24374
+ description: "Build a deterministic, source-attributed POC report context. Does not call an LLM or write a report.",
24375
+ inputSchema: {}
24376
+ },
24377
+ async () => toolText(guarded(root, () => preparePocReportTool(root)))
24378
+ );
24379
+ server.registerTool(
24380
+ "kaddo_persist_poc_report",
24381
+ {
24382
+ title: "Persist confirmed final POC report",
24383
+ description: "Preview a proposed final POC report by default. With confirm=true, persist the next immutable version. Never provide credential values.",
24384
+ inputSchema: { proposal: z4.string(), confirm: z4.boolean().optional(), force: z4.boolean().optional() }
24385
+ },
24386
+ async (args) => toolText(guarded(root, () => persistPocReportTool(root, args.proposal, args.confirm, args.force)))
24387
+ );
23947
24388
  server.registerTool(
23948
24389
  "kaddo_list_resources",
23949
24390
  { title: "List Project Resources", description: "List external systems the project uses (databases, cloud, APIs\u2026), read-only, no secrets.", inputSchema: {} },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaddo/mcp",
3
- "version": "3.118.0",
3
+ "version": "3.119.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": {