@kaddo/mcp 3.127.0 → 3.129.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 +1083 -68
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -169,6 +169,8 @@ var DERIVED_WRITE_FILES = /* @__PURE__ */ new Set([
169
169
  ".kaddo/graph.mmd",
170
170
  ".kaddo/graph-hints.md",
171
171
  ".kaddo/graph-hints.json",
172
+ ".kaddo/consolidation-plan.md",
173
+ ".kaddo/consolidation-plan.json",
172
174
  ".kaddo/modules.yml"
173
175
  ]);
174
176
  var DERIVED_EXPORTS_RE = /^\.kaddo\/exports\/[^/]+\.capsule\.(md|json)$/;
@@ -507,6 +509,21 @@ ${issues}`);
507
509
  }
508
510
  return parsed.data;
509
511
  }
512
+ function requireConfig(dir) {
513
+ let config;
514
+ try {
515
+ config = loadConfig(dir);
516
+ } catch (err) {
517
+ const message = err instanceof ConfigError ? err.message : String(err);
518
+ console.error(message);
519
+ process.exit(1);
520
+ }
521
+ if (!config) {
522
+ console.error("No .kaddo/config.yml found. Run `kaddo init` first.");
523
+ process.exit(1);
524
+ }
525
+ return config;
526
+ }
510
527
  function projectRole(config) {
511
528
  return config.project.role ?? config.multirepo?.role;
512
529
  }
@@ -2619,6 +2636,29 @@ var RESOURCE_TYPES = [
2619
2636
  "other"
2620
2637
  ];
2621
2638
  var INTERFACE_TYPES = ["cli", "mcp", "api", "sql", "sdk", "iac", "console", "other"];
2639
+ var REDACTED_COORDINATE = "\u2022\u2022\u2022\u2022 (hidden \u2014 store as an authentication reference)";
2640
+ var SECRET_KEY_RE = /(pass(word|phrase)?|secret|token|api[_-]?key|\bkey\b|credential|private|auth[_-]?token|access[_-]?key)/i;
2641
+ var SECRET_VALUE_PATTERNS = [
2642
+ /\b(AKIA|ASIA)[A-Z0-9]{8,}/,
2643
+ // AWS access key id
2644
+ /\b(ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{20,}/,
2645
+ // GitHub tokens
2646
+ /\bxox[baprs]-[A-Za-z0-9-]{10,}/,
2647
+ // Slack tokens
2648
+ /\bsk-[A-Za-z0-9]{16,}/,
2649
+ // OpenAI-style secret keys
2650
+ /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\./,
2651
+ // JWT
2652
+ /\w+=[^;\s]{6,}/,
2653
+ // key=value credential fragments
2654
+ /^[A-Za-z0-9+/]{40,}={0,2}$/
2655
+ // long opaque base64 blob
2656
+ ];
2657
+ function looksLikeSecretCoordinate(key, value) {
2658
+ if (SECRET_KEY_RE.test(key)) return true;
2659
+ const v = value.trim();
2660
+ return SECRET_VALUE_PATTERNS.some((re) => re.test(v));
2661
+ }
2622
2662
  function str(v) {
2623
2663
  return typeof v === "string" && v.trim() ? v.trim() : null;
2624
2664
  }
@@ -2647,6 +2687,29 @@ function parseInterfaces(v) {
2647
2687
  }
2648
2688
  return out;
2649
2689
  }
2690
+ function parseConnectionProfiles(v) {
2691
+ if (!Array.isArray(v)) return [];
2692
+ const out = [];
2693
+ for (const raw of v) {
2694
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) continue;
2695
+ const o2 = raw;
2696
+ const coords = [];
2697
+ const rawCoords = o2.coordinates;
2698
+ if (rawCoords && typeof rawCoords === "object" && !Array.isArray(rawCoords)) {
2699
+ for (const [key, val] of Object.entries(rawCoords)) {
2700
+ const k2 = key.trim();
2701
+ const value = val == null ? "" : String(val).trim();
2702
+ if (!k2 || !value) continue;
2703
+ coords.push({ key: k2, value: looksLikeSecretCoordinate(k2, value) ? REDACTED_COORDINATE : value });
2704
+ }
2705
+ }
2706
+ const environment = str(o2.environment);
2707
+ const iface = str(o2.interface);
2708
+ if (!environment && !iface && coords.length === 0) continue;
2709
+ out.push({ environment, interface: iface, coordinates: coords });
2710
+ }
2711
+ return out;
2712
+ }
2650
2713
  function parseBoundaries(v) {
2651
2714
  if (!v || typeof v !== "object" || Array.isArray(v)) return {};
2652
2715
  const out = {};
@@ -2695,6 +2758,7 @@ function toDetail(filePath, relPath, raw) {
2695
2758
  const a2 = parseAuth(fm.authentication);
2696
2759
  return { authRefs: a2.refs, authMode: a2.mode };
2697
2760
  })(),
2761
+ connectionProfiles: parseConnectionProfiles(fm.connection_profiles),
2698
2762
  markdownBody: body
2699
2763
  };
2700
2764
  }
@@ -4500,7 +4564,8 @@ var AGENT_GROUPS = {
4500
4564
  "work-item-agent.md",
4501
4565
  "implementation-agent.md",
4502
4566
  "ownership-agent.md",
4503
- "git-strategy-agent.md"
4567
+ "git-strategy-agent.md",
4568
+ "knowledge-consolidation-agent.md"
4504
4569
  ],
4505
4570
  utilities: ["legacy-agent.md"]
4506
4571
  };
@@ -5179,7 +5244,7 @@ var KNOWLEDGE_FILES = [
5179
5244
  function buildReadinessReport(dir, now = /* @__PURE__ */ new Date()) {
5180
5245
  const config = loadConfig(dir);
5181
5246
  const rec = resolveNextStep(dir, now);
5182
- const stub = (overall2, label, command) => ({
5247
+ const stub = (overall2, label2, command) => ({
5183
5248
  project_name: config?.project.name ?? "unknown",
5184
5249
  project_type: config?.project.state ?? "unknown",
5185
5250
  overall: overall2,
@@ -5202,7 +5267,7 @@ function buildReadinessReport(dir, now = /* @__PURE__ */ new Date()) {
5202
5267
  resolved_questions: 0,
5203
5268
  deferred_questions: 0
5204
5269
  },
5205
- recommended_next_step: { label, ...command ? { command } : {} },
5270
+ recommended_next_step: { label: label2, ...command ? { command } : {} },
5206
5271
  nextStepRecommendation: rec
5207
5272
  });
5208
5273
  if (!config) return stub("not-initialized", rec.label, rec.command);
@@ -6077,6 +6142,69 @@ After a POC has a canonical conclusion and Core has prepared its deterministic r
6077
6142
  15. Traceability and Sources
6078
6143
  `
6079
6144
  );
6145
+ var KNOWLEDGE_CONSOLIDATION = skill(
6146
+ "knowledge-consolidation",
6147
+ "Knowledge Consolidation Skill",
6148
+ "delivery",
6149
+ ["knowledge-consolidation-agent", "work-item-agent", "architecture-agent"],
6150
+ `
6151
+ # Knowledge Consolidation Skill
6152
+
6153
+ ## Purpose
6154
+
6155
+ Standardize the SEMANTIC review of a deterministic Consolidation Plan so canonical knowledge becomes
6156
+ more precise, connected, consistent and retrievable \u2014 without losing history, decisions or evidence.
6157
+
6158
+ ## When to use
6159
+
6160
+ After \`kaddo knowledge consolidate\` (or the \`kaddo_generate_consolidation_plan\` tool) has produced
6161
+ \`.kaddo/consolidation-plan.{md,json}\`, and a human or agent wants to review the opportunities and
6162
+ prepare refinements. Never as an autonomous rewrite of \`knowledge/\`.
6163
+
6164
+ ## Inputs
6165
+
6166
+ The Consolidation Plan (\`.kaddo/consolidation-plan.json\`), the referenced source artifacts, and the
6167
+ context pack. Each opportunity carries: category, classification, confidence, source/target, evidence,
6168
+ rationale, suggested action, and \`requires_semantic_review\`.
6169
+
6170
+ ## Output
6171
+
6172
+ For each opportunity the agent chooses to act on: a reviewable proposal that states what would change,
6173
+ what content is added/moved/summarized, what must be preserved, and which claims still need
6174
+ confirmation \u2014 with a diff or equivalent. No canonical file is edited by this skill; application goes
6175
+ through the existing controlled-edit flows after human approval.
6176
+
6177
+ ## Rules
6178
+
6179
+ - Distinguish \`observed\` (verifiable fact) from \`suggested\` (a candidate needing judgement). Never
6180
+ present a \`suggested\` candidate as a verified conclusion.
6181
+ - Classify every claim as Observed / Derived / Suggested / Unknown.
6182
+ - Do not merge Business/Product/Tech just because they share a topic \u2014 preserve layer responsibilities.
6183
+ - Do not move every historical learning into current knowledge; incorporate only live rules, WITH a
6184
+ reference back to the originating Work Item. Never summarize or delete the Work Item.
6185
+ - Do not invent relationships, decisions or business rules; a missing optional relationship is an
6186
+ opportunity, not an error.
6187
+ - For a potential inconsistency, present BOTH sources and say whether the conflict is verified or
6188
+ potential. Never pick a winner arbitrarily.
6189
+ - Preserve critical constraints, decisions and reasons, exceptions, known risks, technical contracts,
6190
+ implementation evidence, provenance and necessary history.
6191
+ - Never reveal secret values or resolve secret references; never connect to a Project Resource.
6192
+ - Do not commit, push or release automatically.
6193
+
6194
+ ## Quality checklist
6195
+
6196
+ - Every proposal cites its source artifact(s) and evidence.
6197
+ - Facts are separated from interpretations.
6198
+ - Preserved information is named explicitly.
6199
+ - Claims needing confirmation are flagged.
6200
+ - No canonical artifact was modified by the analysis or the review.
6201
+
6202
+ ## Example output
6203
+
6204
+ A short set of proposals, each with evidence, a before/after (or diff) and an explicit list of what
6205
+ must be preserved, ready for human approval.
6206
+ `
6207
+ );
6080
6208
  var SKILLS = [
6081
6209
  ADR_WRITING,
6082
6210
  WORK_ITEM_REFINEMENT,
@@ -6088,10 +6216,11 @@ var SKILLS = [
6088
6216
  MODULE_CONTEXT_REFINEMENT,
6089
6217
  EVIDENCE_VERIFICATION,
6090
6218
  LEGACY_RISK_ASSESSMENT,
6091
- POC_REPORT_WRITING
6219
+ POC_REPORT_WRITING,
6220
+ KNOWLEDGE_CONSOLIDATION
6092
6221
  ];
6093
6222
  var SKILL_GROUPS = {
6094
- delivery: ["work-item-refinement", "implementation-planning", "learning-capture", "evidence-verification", "legacy-risk-assessment", "poc-report-writing"],
6223
+ delivery: ["work-item-refinement", "implementation-planning", "learning-capture", "evidence-verification", "legacy-risk-assessment", "poc-report-writing", "knowledge-consolidation"],
6095
6224
  tech: ["adr-writing", "ownership-suggestion", "graph-metadata-review", "module-context-refinement"],
6096
6225
  integration: ["capsule-writing"]
6097
6226
  };
@@ -6287,6 +6416,24 @@ var RESPONSIBILITY_MATRIX = {
6287
6416
  ],
6288
6417
  next: ["kaddo graph export"]
6289
6418
  },
6419
+ "knowledge-consolidation-agent": {
6420
+ agent: "knowledge-consolidation-agent",
6421
+ responsibleFor: [
6422
+ "Semantic review of the deterministic Consolidation Plan",
6423
+ "Preparing reviewable knowledge refinements"
6424
+ ],
6425
+ produces: ["proposed refinements (before/after) for human approval"],
6426
+ canSuggest: ["kaddo knowledge consolidate"],
6427
+ cannotSuggest: [
6428
+ "code",
6429
+ "git",
6430
+ "commits",
6431
+ "modifying canonical knowledge without confirmation",
6432
+ "inventing facts or relationships",
6433
+ "deleting history"
6434
+ ],
6435
+ next: ["kaddo knowledge consolidate"]
6436
+ },
6290
6437
  "ownership-agent": {
6291
6438
  agent: "ownership-agent",
6292
6439
  responsibleFor: ["Precise code: ownership for Work Items and artifacts"],
@@ -8447,6 +8594,78 @@ ADRs) and re-runs \`kaddo graph export\`.
8447
8594
  - No files were modified; no Git was run.
8448
8595
  - The human is asked to confirm and re-export the graph.
8449
8596
  `;
8597
+ var KNOWLEDGE_CONSOLIDATION_AGENT = `# Knowledge Consolidation Agent
8598
+
8599
+ ## Role
8600
+
8601
+ You are the Kaddo Knowledge Consolidation Agent. Your job is to perform the **semantic review** of
8602
+ the deterministic **Consolidation Plan** produced by \`kaddo knowledge consolidate\`, and to prepare
8603
+ **reviewable refinements** that make canonical knowledge more precise, connected, consistent and
8604
+ retrievable \u2014 without losing history, decisions or evidence.
8605
+
8606
+ You do not write code, you do not modify canonical files, and you never run Git. You propose; a human
8607
+ confirms, and application goes through Kaddo's existing controlled-edit flows.
8608
+
8609
+ ## When to Use
8610
+
8611
+ Use this agent after \`kaddo knowledge consolidate\` (or the \`kaddo_generate_consolidation_plan\`
8612
+ tool) has produced \`.kaddo/consolidation-plan.md\` and \`.kaddo/consolidation-plan.json\`, and the
8613
+ plan contains opportunities flagged \`requires_semantic_review\`.
8614
+
8615
+ ## Input Required
8616
+
8617
+ Provide \`.kaddo/context-pack.md\` and \`.kaddo/consolidation-plan.json\` as the primary inputs, plus
8618
+ the source artifacts each opportunity references under \`knowledge/\` (and completed Work Items under
8619
+ \`knowledge/delivery/work-items/completed/\` for learning opportunities).
8620
+
8621
+ ## Expected Output
8622
+
8623
+ For each opportunity you choose to act on, a proposal that states what would change, what content is
8624
+ added / moved / summarized, what must be preserved, and which claims still need confirmation \u2014 with a
8625
+ diff or equivalent before/after.
8626
+
8627
+ ## Instructions
8628
+
8629
+ 1. Read the plan and work one opportunity at a time, highest-signal first.
8630
+ 2. Separate what you **observe** in the files from what you **suggest** \u2014 never present a suggested
8631
+ candidate as a verified conclusion.
8632
+ 3. For possible redundancy, compare the actual sections; propose a single reference location only if
8633
+ one genuinely restates the other. Preserve layer separation (Business / Product / Tech / Delivery).
8634
+ 4. For an unincorporated learning, decide whether it is a live rule; if so, propose incorporating it
8635
+ into the right Tech artifact **with a reference back to the originating Work Item**. Never summarize
8636
+ or delete the Work Item.
8637
+ 5. For a relationship opportunity, propose the relationship only when existing knowledge justifies it;
8638
+ a missing optional relationship is an opportunity, not an error.
8639
+ 6. For a potential inconsistency, present **both** sources and say whether the conflict is verified or
8640
+ potential. Do not pick a winner arbitrarily.
8641
+ 7. Classify every claim as Observed / Derived / Suggested / Unknown, and mark what needs confirmation.
8642
+
8643
+ ## Constraints
8644
+
8645
+ - Do **not** modify canonical files \u2014 propose changes for a human to apply.
8646
+ - Do **not** invent facts, relationships, decisions or business rules.
8647
+ - Do **not** delete historical information, change Work Item states, or alter verification results.
8648
+ - Do **not** reveal secret values or resolve secret references; do **not** connect to a Project Resource.
8649
+ - Do **not** run Git, commit, push or release.
8650
+
8651
+ ## Output Format
8652
+
8653
+ Per opportunity: the opportunity id, classification and confidence, the proposed before/after (or
8654
+ diff), the explicit list of what must be preserved, and the open questions that need confirmation.
8655
+
8656
+ ## Where to Save the Result
8657
+
8658
+ Nothing is saved automatically. The human reviews the proposals and applies the approved ones through
8659
+ Kaddo's existing controlled-edit flows. Re-run \`kaddo knowledge consolidate\` afterwards to confirm.
8660
+
8661
+ ## Quality Checklist
8662
+
8663
+ - Every proposal cites its source artifact(s) and evidence from the plan.
8664
+ - Facts are separated from interpretations; suggested candidates are never asserted as fact.
8665
+ - Preserved information (constraints, decisions, exceptions, risks, contracts, provenance) is named.
8666
+ - No canonical artifact was modified; no Git was run.
8667
+ - The human is asked to confirm before anything is applied.
8668
+ `;
8450
8669
  var AGENT_PROMPTS = [
8451
8670
  { fileName: "capability-agent.md", content: CAPABILITY_AGENT },
8452
8671
  { fileName: "architecture-agent.md", content: ARCHITECTURE_AGENT },
@@ -8473,7 +8692,9 @@ var AGENT_PROMPTS = [
8473
8692
  // Knowledge Capsule refinement (external context — VS-054)
8474
8693
  { fileName: "capsule-agent.md", content: CAPSULE_AGENT },
8475
8694
  // Graph relationship quality (metadata hints → precise front matter — VS-056)
8476
- { fileName: "graph-agent.md", content: GRAPH_AGENT }
8695
+ { fileName: "graph-agent.md", content: GRAPH_AGENT },
8696
+ // Knowledge Consolidation semantic review (deterministic plan → reviewable refinements — WI-070)
8697
+ { fileName: "knowledge-consolidation-agent.md", content: KNOWLEDGE_CONSOLIDATION_AGENT }
8477
8698
  // Every official prompt ends with its responsibility boundaries + Agent Trace footer.
8478
8699
  ].map((p) => ({ fileName: p.fileName, content: withResponsibilityTrace(p.fileName, p.content) }));
8479
8700
 
@@ -8680,9 +8901,9 @@ function installedAssetsSummary(dir) {
8680
8901
  // ../cli/src/core/roadmap-quality.ts
8681
8902
  var ROADMAP_PATH = "knowledge/delivery/roadmap.md";
8682
8903
  var INITIATIVE_RE2 = /^#{2,4}\s+(RM-[\w.-]+)\s*[:\-–]?\s*(.*)$/;
8683
- function fieldPresent(lines, label) {
8904
+ function fieldPresent(lines, label2) {
8684
8905
  for (let i = 0; i < lines.length; i++) {
8685
- const m2 = lines[i].match(new RegExp(`^\\s*[-*]?\\s*(?:\\*\\*)?\\s*(${label.source})\\s*(?:\\*\\*)?\\s*:\\s*(.*)$`, "i"));
8906
+ const m2 = lines[i].match(new RegExp(`^\\s*[-*]?\\s*(?:\\*\\*)?\\s*(${label2.source})\\s*(?:\\*\\*)?\\s*:\\s*(.*)$`, "i"));
8686
8907
  if (!m2) continue;
8687
8908
  const inline = m2[2].trim();
8688
8909
  if (inline) return true;
@@ -8784,13 +9005,13 @@ function renderSignalsCompact(signals) {
8784
9005
  ["external_integrations", "External integrations"],
8785
9006
  ["environment", "Environment"]
8786
9007
  ];
8787
- for (const [key, label] of CATEGORIES) {
9008
+ for (const [key, label2] of CATEGORIES) {
8788
9009
  const arr = signals[key];
8789
9010
  if (arr.length === 0) continue;
8790
9011
  if (arr.length === 1) {
8791
- lines.push(`- ${label}: ${arr[0].label}`);
9012
+ lines.push(`- ${label2}: ${arr[0].label}`);
8792
9013
  } else {
8793
- lines.push(`- ${label}: ${arr.length}`);
9014
+ lines.push(`- ${label2}: ${arr.length}`);
8794
9015
  }
8795
9016
  }
8796
9017
  return lines.length > 0 ? lines.join("\n") + "\n" : "No signals detected.\n";
@@ -10988,9 +11209,9 @@ function renderImpactMarkdown(r2, opts = {}) {
10988
11209
  ["validation section", gp.missing_validation]
10989
11210
  ];
10990
11211
  L.push("## Historical Documentation Completeness", "");
10991
- for (const [label, items] of historical) {
11212
+ for (const [label2, items] of historical) {
10992
11213
  const completed = items.filter((item) => item.status === "completed");
10993
- if (completed.length > 0) L.push(`- Completed Work Items without detected ${label}: ${completed.length}`);
11214
+ if (completed.length > 0) L.push(`- Completed Work Items without detected ${label2}: ${completed.length}`);
10994
11215
  }
10995
11216
  if (!historical.some(([, items]) => items.some((item) => item.status === "completed"))) {
10996
11217
  L.push("No historical documentation completeness gaps were detected.");
@@ -11765,6 +11986,27 @@ var RESOURCES = [
11765
11986
  return parts;
11766
11987
  }
11767
11988
  },
11989
+ {
11990
+ uri: "kaddo://consolidation-plan",
11991
+ name: "Kaddo consolidation plan",
11992
+ description: "Knowledge Consolidation Plan (.kaddo/consolidation-plan.md + .json) \u2014 reviewable opportunities to optimize, connect and refine canonical knowledge. Read-only.",
11993
+ mimeType: "text/markdown",
11994
+ read: (root) => {
11995
+ const md = readText(root, ".kaddo/consolidation-plan.md");
11996
+ const json = readText(root, ".kaddo/consolidation-plan.json");
11997
+ if (!md && !json) {
11998
+ return text2(
11999
+ "kaddo://consolidation-plan",
12000
+ "Consolidation Plan not found. Run `kaddo knowledge consolidate` (or the kaddo_generate_consolidation_plan tool) first.",
12001
+ "text/plain"
12002
+ );
12003
+ }
12004
+ const parts = [];
12005
+ if (md) parts.push({ uri: "kaddo://consolidation-plan", text: md, mimeType: "text/markdown" });
12006
+ if (json) parts.push({ uri: "kaddo://consolidation-plan.json", text: json, mimeType: "application/json" });
12007
+ return parts;
12008
+ }
12009
+ },
11768
12010
  {
11769
12011
  uri: "kaddo://build-contract",
11770
12012
  name: "Kaddo build contract",
@@ -12121,7 +12363,7 @@ function getPrompt(root, name) {
12121
12363
  }
12122
12364
 
12123
12365
  // src/tools.ts
12124
- import matter24 from "gray-matter";
12366
+ import matter25 from "gray-matter";
12125
12367
 
12126
12368
  // ../cli/dist/core.js
12127
12369
  import fs3 from "fs";
@@ -12151,22 +12393,23 @@ import matter102 from "gray-matter";
12151
12393
  import { parse as parseYaml82 } from "yaml";
12152
12394
  import matter112 from "gray-matter";
12153
12395
  import matter122 from "gray-matter";
12396
+ import matter132 from "gray-matter";
12154
12397
  import { parse as parseYaml92, stringify as stringifyYaml52 } from "yaml";
12155
12398
  import fs4 from "fs";
12156
12399
  import path4 from "path";
12157
12400
  import crypto2 from "crypto";
12158
- import matter132 from "gray-matter";
12401
+ import matter142 from "gray-matter";
12159
12402
  import crypto3 from "crypto";
12160
12403
  import fs5 from "fs";
12161
- import matter142 from "gray-matter";
12162
12404
  import matter15 from "gray-matter";
12405
+ import matter16 from "gray-matter";
12163
12406
  import { parse as parseYaml102, stringify as stringifyYaml6 } from "yaml";
12164
12407
  import { parse as parseYaml11, stringify as stringifyYaml7 } from "yaml";
12165
- import matter16 from "gray-matter";
12166
12408
  import matter17 from "gray-matter";
12167
12409
  import matter18 from "gray-matter";
12168
12410
  import matter19 from "gray-matter";
12169
12411
  import matter20 from "gray-matter";
12412
+ import matter21 from "gray-matter";
12170
12413
  function exists2(p) {
12171
12414
  return fs3.existsSync(p);
12172
12415
  }
@@ -13897,6 +14140,29 @@ function suggestInitiativesForExternalItem(dir, integration, externalId) {
13897
14140
  );
13898
14141
  }
13899
14142
  var RESOURCE_ARTIFACT_TYPE2 = "project-resource";
14143
+ var REDACTED_COORDINATE2 = "\u2022\u2022\u2022\u2022 (hidden \u2014 store as an authentication reference)";
14144
+ var SECRET_KEY_RE2 = /(pass(word|phrase)?|secret|token|api[_-]?key|\bkey\b|credential|private|auth[_-]?token|access[_-]?key)/i;
14145
+ var SECRET_VALUE_PATTERNS2 = [
14146
+ /\b(AKIA|ASIA)[A-Z0-9]{8,}/,
14147
+ // AWS access key id
14148
+ /\b(ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{20,}/,
14149
+ // GitHub tokens
14150
+ /\bxox[baprs]-[A-Za-z0-9-]{10,}/,
14151
+ // Slack tokens
14152
+ /\bsk-[A-Za-z0-9]{16,}/,
14153
+ // OpenAI-style secret keys
14154
+ /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\./,
14155
+ // JWT
14156
+ /\w+=[^;\s]{6,}/,
14157
+ // key=value credential fragments
14158
+ /^[A-Za-z0-9+/]{40,}={0,2}$/
14159
+ // long opaque base64 blob
14160
+ ];
14161
+ function looksLikeSecretCoordinate2(key, value) {
14162
+ if (SECRET_KEY_RE2.test(key)) return true;
14163
+ const v = value.trim();
14164
+ return SECRET_VALUE_PATTERNS2.some((re) => re.test(v));
14165
+ }
13900
14166
  function str3(v) {
13901
14167
  return typeof v === "string" && v.trim() ? v.trim() : null;
13902
14168
  }
@@ -13925,6 +14191,29 @@ function parseInterfaces2(v) {
13925
14191
  }
13926
14192
  return out;
13927
14193
  }
14194
+ function parseConnectionProfiles2(v) {
14195
+ if (!Array.isArray(v)) return [];
14196
+ const out = [];
14197
+ for (const raw of v) {
14198
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) continue;
14199
+ const o2 = raw;
14200
+ const coords = [];
14201
+ const rawCoords = o2.coordinates;
14202
+ if (rawCoords && typeof rawCoords === "object" && !Array.isArray(rawCoords)) {
14203
+ for (const [key, val] of Object.entries(rawCoords)) {
14204
+ const k2 = key.trim();
14205
+ const value = val == null ? "" : String(val).trim();
14206
+ if (!k2 || !value) continue;
14207
+ coords.push({ key: k2, value: looksLikeSecretCoordinate2(k2, value) ? REDACTED_COORDINATE2 : value });
14208
+ }
14209
+ }
14210
+ const environment = str3(o2.environment);
14211
+ const iface = str3(o2.interface);
14212
+ if (!environment && !iface && coords.length === 0) continue;
14213
+ out.push({ environment, interface: iface, coordinates: coords });
14214
+ }
14215
+ return out;
14216
+ }
13928
14217
  function parseBoundaries2(v) {
13929
14218
  if (!v || typeof v !== "object" || Array.isArray(v)) return {};
13930
14219
  const out = {};
@@ -13973,6 +14262,7 @@ function toDetail2(filePath, relPath, raw) {
13973
14262
  const a2 = parseAuth2(fm.authentication);
13974
14263
  return { authRefs: a2.refs, authMode: a2.mode };
13975
14264
  })(),
14265
+ connectionProfiles: parseConnectionProfiles2(fm.connection_profiles),
13976
14266
  markdownBody: body
13977
14267
  };
13978
14268
  }
@@ -14594,7 +14884,8 @@ var AGENT_GROUPS2 = {
14594
14884
  "work-item-agent.md",
14595
14885
  "implementation-agent.md",
14596
14886
  "ownership-agent.md",
14597
- "git-strategy-agent.md"
14887
+ "git-strategy-agent.md",
14888
+ "knowledge-consolidation-agent.md"
14598
14889
  ],
14599
14890
  utilities: ["legacy-agent.md"]
14600
14891
  };
@@ -15624,6 +15915,69 @@ After a POC has a canonical conclusion and Core has prepared its deterministic r
15624
15915
  15. Traceability and Sources
15625
15916
  `
15626
15917
  );
15918
+ var KNOWLEDGE_CONSOLIDATION2 = skill2(
15919
+ "knowledge-consolidation",
15920
+ "Knowledge Consolidation Skill",
15921
+ "delivery",
15922
+ ["knowledge-consolidation-agent", "work-item-agent", "architecture-agent"],
15923
+ `
15924
+ # Knowledge Consolidation Skill
15925
+
15926
+ ## Purpose
15927
+
15928
+ Standardize the SEMANTIC review of a deterministic Consolidation Plan so canonical knowledge becomes
15929
+ more precise, connected, consistent and retrievable \u2014 without losing history, decisions or evidence.
15930
+
15931
+ ## When to use
15932
+
15933
+ After \`kaddo knowledge consolidate\` (or the \`kaddo_generate_consolidation_plan\` tool) has produced
15934
+ \`.kaddo/consolidation-plan.{md,json}\`, and a human or agent wants to review the opportunities and
15935
+ prepare refinements. Never as an autonomous rewrite of \`knowledge/\`.
15936
+
15937
+ ## Inputs
15938
+
15939
+ The Consolidation Plan (\`.kaddo/consolidation-plan.json\`), the referenced source artifacts, and the
15940
+ context pack. Each opportunity carries: category, classification, confidence, source/target, evidence,
15941
+ rationale, suggested action, and \`requires_semantic_review\`.
15942
+
15943
+ ## Output
15944
+
15945
+ For each opportunity the agent chooses to act on: a reviewable proposal that states what would change,
15946
+ what content is added/moved/summarized, what must be preserved, and which claims still need
15947
+ confirmation \u2014 with a diff or equivalent. No canonical file is edited by this skill; application goes
15948
+ through the existing controlled-edit flows after human approval.
15949
+
15950
+ ## Rules
15951
+
15952
+ - Distinguish \`observed\` (verifiable fact) from \`suggested\` (a candidate needing judgement). Never
15953
+ present a \`suggested\` candidate as a verified conclusion.
15954
+ - Classify every claim as Observed / Derived / Suggested / Unknown.
15955
+ - Do not merge Business/Product/Tech just because they share a topic \u2014 preserve layer responsibilities.
15956
+ - Do not move every historical learning into current knowledge; incorporate only live rules, WITH a
15957
+ reference back to the originating Work Item. Never summarize or delete the Work Item.
15958
+ - Do not invent relationships, decisions or business rules; a missing optional relationship is an
15959
+ opportunity, not an error.
15960
+ - For a potential inconsistency, present BOTH sources and say whether the conflict is verified or
15961
+ potential. Never pick a winner arbitrarily.
15962
+ - Preserve critical constraints, decisions and reasons, exceptions, known risks, technical contracts,
15963
+ implementation evidence, provenance and necessary history.
15964
+ - Never reveal secret values or resolve secret references; never connect to a Project Resource.
15965
+ - Do not commit, push or release automatically.
15966
+
15967
+ ## Quality checklist
15968
+
15969
+ - Every proposal cites its source artifact(s) and evidence.
15970
+ - Facts are separated from interpretations.
15971
+ - Preserved information is named explicitly.
15972
+ - Claims needing confirmation are flagged.
15973
+ - No canonical artifact was modified by the analysis or the review.
15974
+
15975
+ ## Example output
15976
+
15977
+ A short set of proposals, each with evidence, a before/after (or diff) and an explicit list of what
15978
+ must be preserved, ready for human approval.
15979
+ `
15980
+ );
15627
15981
  var SKILLS2 = [
15628
15982
  ADR_WRITING2,
15629
15983
  WORK_ITEM_REFINEMENT2,
@@ -15635,10 +15989,11 @@ var SKILLS2 = [
15635
15989
  MODULE_CONTEXT_REFINEMENT2,
15636
15990
  EVIDENCE_VERIFICATION2,
15637
15991
  LEGACY_RISK_ASSESSMENT2,
15638
- POC_REPORT_WRITING2
15992
+ POC_REPORT_WRITING2,
15993
+ KNOWLEDGE_CONSOLIDATION2
15639
15994
  ];
15640
15995
  var SKILL_GROUPS2 = {
15641
- delivery: ["work-item-refinement", "implementation-planning", "learning-capture", "evidence-verification", "legacy-risk-assessment", "poc-report-writing"],
15996
+ delivery: ["work-item-refinement", "implementation-planning", "learning-capture", "evidence-verification", "legacy-risk-assessment", "poc-report-writing", "knowledge-consolidation"],
15642
15997
  tech: ["adr-writing", "ownership-suggestion", "graph-metadata-review", "module-context-refinement"],
15643
15998
  integration: ["capsule-writing"]
15644
15999
  };
@@ -15829,6 +16184,24 @@ var RESPONSIBILITY_MATRIX2 = {
15829
16184
  ],
15830
16185
  next: ["kaddo graph export"]
15831
16186
  },
16187
+ "knowledge-consolidation-agent": {
16188
+ agent: "knowledge-consolidation-agent",
16189
+ responsibleFor: [
16190
+ "Semantic review of the deterministic Consolidation Plan",
16191
+ "Preparing reviewable knowledge refinements"
16192
+ ],
16193
+ produces: ["proposed refinements (before/after) for human approval"],
16194
+ canSuggest: ["kaddo knowledge consolidate"],
16195
+ cannotSuggest: [
16196
+ "code",
16197
+ "git",
16198
+ "commits",
16199
+ "modifying canonical knowledge without confirmation",
16200
+ "inventing facts or relationships",
16201
+ "deleting history"
16202
+ ],
16203
+ next: ["kaddo knowledge consolidate"]
16204
+ },
15832
16205
  "ownership-agent": {
15833
16206
  agent: "ownership-agent",
15834
16207
  responsibleFor: ["Precise code: ownership for Work Items and artifacts"],
@@ -17987,6 +18360,78 @@ ADRs) and re-runs \`kaddo graph export\`.
17987
18360
  - No files were modified; no Git was run.
17988
18361
  - The human is asked to confirm and re-export the graph.
17989
18362
  `;
18363
+ var KNOWLEDGE_CONSOLIDATION_AGENT2 = `# Knowledge Consolidation Agent
18364
+
18365
+ ## Role
18366
+
18367
+ You are the Kaddo Knowledge Consolidation Agent. Your job is to perform the **semantic review** of
18368
+ the deterministic **Consolidation Plan** produced by \`kaddo knowledge consolidate\`, and to prepare
18369
+ **reviewable refinements** that make canonical knowledge more precise, connected, consistent and
18370
+ retrievable \u2014 without losing history, decisions or evidence.
18371
+
18372
+ You do not write code, you do not modify canonical files, and you never run Git. You propose; a human
18373
+ confirms, and application goes through Kaddo's existing controlled-edit flows.
18374
+
18375
+ ## When to Use
18376
+
18377
+ Use this agent after \`kaddo knowledge consolidate\` (or the \`kaddo_generate_consolidation_plan\`
18378
+ tool) has produced \`.kaddo/consolidation-plan.md\` and \`.kaddo/consolidation-plan.json\`, and the
18379
+ plan contains opportunities flagged \`requires_semantic_review\`.
18380
+
18381
+ ## Input Required
18382
+
18383
+ Provide \`.kaddo/context-pack.md\` and \`.kaddo/consolidation-plan.json\` as the primary inputs, plus
18384
+ the source artifacts each opportunity references under \`knowledge/\` (and completed Work Items under
18385
+ \`knowledge/delivery/work-items/completed/\` for learning opportunities).
18386
+
18387
+ ## Expected Output
18388
+
18389
+ For each opportunity you choose to act on, a proposal that states what would change, what content is
18390
+ added / moved / summarized, what must be preserved, and which claims still need confirmation \u2014 with a
18391
+ diff or equivalent before/after.
18392
+
18393
+ ## Instructions
18394
+
18395
+ 1. Read the plan and work one opportunity at a time, highest-signal first.
18396
+ 2. Separate what you **observe** in the files from what you **suggest** \u2014 never present a suggested
18397
+ candidate as a verified conclusion.
18398
+ 3. For possible redundancy, compare the actual sections; propose a single reference location only if
18399
+ one genuinely restates the other. Preserve layer separation (Business / Product / Tech / Delivery).
18400
+ 4. For an unincorporated learning, decide whether it is a live rule; if so, propose incorporating it
18401
+ into the right Tech artifact **with a reference back to the originating Work Item**. Never summarize
18402
+ or delete the Work Item.
18403
+ 5. For a relationship opportunity, propose the relationship only when existing knowledge justifies it;
18404
+ a missing optional relationship is an opportunity, not an error.
18405
+ 6. For a potential inconsistency, present **both** sources and say whether the conflict is verified or
18406
+ potential. Do not pick a winner arbitrarily.
18407
+ 7. Classify every claim as Observed / Derived / Suggested / Unknown, and mark what needs confirmation.
18408
+
18409
+ ## Constraints
18410
+
18411
+ - Do **not** modify canonical files \u2014 propose changes for a human to apply.
18412
+ - Do **not** invent facts, relationships, decisions or business rules.
18413
+ - Do **not** delete historical information, change Work Item states, or alter verification results.
18414
+ - Do **not** reveal secret values or resolve secret references; do **not** connect to a Project Resource.
18415
+ - Do **not** run Git, commit, push or release.
18416
+
18417
+ ## Output Format
18418
+
18419
+ Per opportunity: the opportunity id, classification and confidence, the proposed before/after (or
18420
+ diff), the explicit list of what must be preserved, and the open questions that need confirmation.
18421
+
18422
+ ## Where to Save the Result
18423
+
18424
+ Nothing is saved automatically. The human reviews the proposals and applies the approved ones through
18425
+ Kaddo's existing controlled-edit flows. Re-run \`kaddo knowledge consolidate\` afterwards to confirm.
18426
+
18427
+ ## Quality Checklist
18428
+
18429
+ - Every proposal cites its source artifact(s) and evidence from the plan.
18430
+ - Facts are separated from interpretations; suggested candidates are never asserted as fact.
18431
+ - Preserved information (constraints, decisions, exceptions, risks, contracts, provenance) is named.
18432
+ - No canonical artifact was modified; no Git was run.
18433
+ - The human is asked to confirm before anything is applied.
18434
+ `;
17990
18435
  var AGENT_PROMPTS2 = [
17991
18436
  { fileName: "capability-agent.md", content: CAPABILITY_AGENT2 },
17992
18437
  { fileName: "architecture-agent.md", content: ARCHITECTURE_AGENT2 },
@@ -18013,7 +18458,9 @@ var AGENT_PROMPTS2 = [
18013
18458
  // Knowledge Capsule refinement (external context — VS-054)
18014
18459
  { fileName: "capsule-agent.md", content: CAPSULE_AGENT2 },
18015
18460
  // Graph relationship quality (metadata hints → precise front matter — VS-056)
18016
- { fileName: "graph-agent.md", content: GRAPH_AGENT2 }
18461
+ { fileName: "graph-agent.md", content: GRAPH_AGENT2 },
18462
+ // Knowledge Consolidation semantic review (deterministic plan → reviewable refinements — WI-070)
18463
+ { fileName: "knowledge-consolidation-agent.md", content: KNOWLEDGE_CONSOLIDATION_AGENT2 }
18017
18464
  // Every official prompt ends with its responsibility boundaries + Agent Trace footer.
18018
18465
  ].map((p) => ({ fileName: p.fileName, content: withResponsibilityTrace2(p.fileName, p.content) }));
18019
18466
  function withAgentFrontMatter2(fileName, content) {
@@ -18891,7 +19338,7 @@ function parseOriginalSnapshot(fm) {
18891
19338
  function readBody(filePath) {
18892
19339
  try {
18893
19340
  const raw = readFile2(filePath);
18894
- return matter122(raw).content;
19341
+ return matter132(raw).content;
18895
19342
  } catch {
18896
19343
  return "";
18897
19344
  }
@@ -19176,7 +19623,7 @@ function ensureH1(body, title) {
19176
19623
  ${trimmed}`;
19177
19624
  }
19178
19625
  function serialize2(data, body) {
19179
- return matter132.stringify(`
19626
+ return matter142.stringify(`
19180
19627
  ${body.trim()}
19181
19628
  `, data);
19182
19629
  }
@@ -20090,7 +20537,7 @@ function detectFormat(raw) {
20090
20537
  if (!trimmed) throw new ImportError("EMPTY_INPUT", "Import content is empty.");
20091
20538
  if (trimmed.startsWith("---")) {
20092
20539
  try {
20093
- const { data } = matter142(trimmed);
20540
+ const { data } = matter15(trimmed);
20094
20541
  if (data && typeof data === "object" && Object.keys(data).length > 0) {
20095
20542
  const d3 = data;
20096
20543
  if (d3.type === "work-item" || d3.id && typeof d3.id === "string" && /^WI-\d+/.test(d3.id)) {
@@ -20112,7 +20559,7 @@ function parseContent(raw) {
20112
20559
  let bodyText = trimmed;
20113
20560
  if (format === "kaddo-frontmatter" || format === "markdown-frontmatter") {
20114
20561
  try {
20115
- const parsed = matter142(trimmed);
20562
+ const parsed = matter15(trimmed);
20116
20563
  frontmatterFields = parsed.data ?? {};
20117
20564
  bodyText = parsed.content.trim();
20118
20565
  } catch {
@@ -20209,7 +20656,7 @@ function findDuplicateByHash(dir, hash) {
20209
20656
  const raw = readFile2(item.filePath);
20210
20657
  if (!raw) continue;
20211
20658
  try {
20212
- const { data } = matter142(raw);
20659
+ const { data } = matter15(raw);
20213
20660
  const source22 = data?.source;
20214
20661
  if (source22 && typeof source22 === "object" && !Array.isArray(source22)) {
20215
20662
  const s = source22;
@@ -21104,7 +21551,7 @@ function buildInitiativeContent(id, input) {
21104
21551
  if (input.priority) data.priority = input.priority;
21105
21552
  if (input.source) data.source = input.source;
21106
21553
  if (input.sourceId) data.source_id = input.sourceId;
21107
- return matter16.stringify("\n" + (input.body ?? defaultBody(input.title.trim())), data);
21554
+ return matter17.stringify("\n" + (input.body ?? defaultBody(input.title.trim())), data);
21108
21555
  }
21109
21556
  function createInitiative(dir, input) {
21110
21557
  if (!input.title?.trim()) throw new InitiativeWriteError("Initiative title is required.");
@@ -21128,10 +21575,10 @@ function transitionInitiative(dir, id, to) {
21128
21575
  throw new InitiativeWriteError(`Invalid transition ${ini.status} \u2192 ${to}.`);
21129
21576
  }
21130
21577
  const raw = readFile2(ini.filePath);
21131
- const { data, content } = matter16(raw, {});
21578
+ const { data, content } = matter17(raw, {});
21132
21579
  data.status = to;
21133
21580
  if (to === "completed") data.completed_at = today2();
21134
- const newContent = matter16.stringify(content, data);
21581
+ const newContent = matter17.stringify(content, data);
21135
21582
  writeFile2(ini.filePath, newContent);
21136
21583
  const updated = parseInitiative2(dir, ini.filePath, newContent);
21137
21584
  if (!updated) throw new InitiativeWriteError("Failed to parse the Initiative after transition.");
@@ -21141,7 +21588,7 @@ function addCandidate(dir, id, candidate) {
21141
21588
  const ini = getInitiative(dir, id);
21142
21589
  if (!ini) throw new InitiativeWriteError(`Initiative ${id} not found.`);
21143
21590
  const raw = readFile2(ini.filePath);
21144
- const { data, content } = matter16(raw, {});
21591
+ const { data, content } = matter17(raw, {});
21145
21592
  const existing = Array.isArray(data.candidates) ? data.candidates : [];
21146
21593
  const fallbackId = `WI-CANDIDATE-${String(existing.length + 1).padStart(3, "0")}`;
21147
21594
  const normalized = normalizeCandidate(candidate, fallbackId);
@@ -21149,7 +21596,7 @@ function addCandidate(dir, id, candidate) {
21149
21596
  throw new InitiativeWriteError(`Candidate ${normalized.id} already exists in ${id}.`);
21150
21597
  }
21151
21598
  data.candidates = [...existing, candidateToData(normalized)];
21152
- const newContent = matter16.stringify(content, data);
21599
+ const newContent = matter17.stringify(content, data);
21153
21600
  writeFile2(ini.filePath, newContent);
21154
21601
  const updated = parseInitiative2(dir, ini.filePath, newContent);
21155
21602
  if (!updated) throw new InitiativeWriteError("Failed to parse the Initiative after adding a candidate.");
@@ -21159,7 +21606,7 @@ function updateInitiative(dir, id, patch) {
21159
21606
  const ini = getInitiative(dir, id);
21160
21607
  if (!ini) throw new InitiativeWriteError(`Initiative ${id} not found.`);
21161
21608
  const raw = readFile2(ini.filePath);
21162
- const { data, content } = matter16(raw, {});
21609
+ const { data, content } = matter17(raw, {});
21163
21610
  if (patch.title !== void 0) {
21164
21611
  if (!patch.title.trim()) throw new InitiativeWriteError("Initiative title cannot be empty.");
21165
21612
  data.title = patch.title.trim();
@@ -21169,7 +21616,7 @@ function updateInitiative(dir, id, patch) {
21169
21616
  if (patch.knowledgeLevel !== void 0) data.knowledge_level = patch.knowledgeLevel;
21170
21617
  if (patch.domains !== void 0) data.domains = patch.domains;
21171
21618
  if (patch.relatedCapabilities !== void 0) data.related_capabilities = patch.relatedCapabilities;
21172
- const newContent = matter16.stringify(content, data);
21619
+ const newContent = matter17.stringify(content, data);
21173
21620
  writeFile2(ini.filePath, newContent);
21174
21621
  const updated = parseInitiative2(dir, ini.filePath, newContent);
21175
21622
  if (!updated) throw new InitiativeWriteError("Failed to parse the Initiative after update.");
@@ -21187,10 +21634,10 @@ function addExternalLink(dir, id, link) {
21187
21634
  throw new InitiativeWriteError(`External link ${link.integration}:${link.externalId} already exists on ${id}.`);
21188
21635
  }
21189
21636
  const raw = readFile2(ini.filePath);
21190
- const { data, content } = matter16(raw, {});
21637
+ const { data, content } = matter17(raw, {});
21191
21638
  const existing = Array.isArray(data.external_links) ? data.external_links : [];
21192
21639
  data.external_links = [...existing, externalLinkToData(link)];
21193
- const newContent = matter16.stringify(content, data);
21640
+ const newContent = matter17.stringify(content, data);
21194
21641
  writeFile2(ini.filePath, newContent);
21195
21642
  const updated = parseInitiative2(dir, ini.filePath, newContent);
21196
21643
  if (!updated) throw new InitiativeWriteError("Failed to parse the Initiative after adding an external link.");
@@ -21259,7 +21706,7 @@ function buildWorkItemContent(wiId, iniId, candidate) {
21259
21706
  "_What did we learn? Update after completion._",
21260
21707
  ""
21261
21708
  ].join("\n");
21262
- return matter16.stringify("\n" + body, data);
21709
+ return matter17.stringify("\n" + body, data);
21263
21710
  }
21264
21711
  function materializeCandidate(dir, id, candidateId) {
21265
21712
  const ini = getInitiative(dir, id);
@@ -21276,13 +21723,13 @@ function materializeCandidate(dir, id, candidateId) {
21276
21723
  ensureDir2(join3(dir, DRAFT_DIR));
21277
21724
  writeFile2(join3(dir, DRAFT_DIR, fileName), buildWorkItemContent(wiId, ini.id, candidate));
21278
21725
  const raw = readFile2(ini.filePath);
21279
- const { data, content } = matter16(raw, {});
21726
+ const { data, content } = matter17(raw, {});
21280
21727
  const cands = Array.isArray(data.candidates) ? data.candidates : [];
21281
21728
  for (const c of cands) {
21282
21729
  if (c && typeof c === "object" && String(c.id) === candidateId) c.materialized_as = wiId;
21283
21730
  }
21284
21731
  data.candidates = cands;
21285
- const newContent = matter16.stringify(content, data);
21732
+ const newContent = matter17.stringify(content, data);
21286
21733
  writeFile2(ini.filePath, newContent);
21287
21734
  const updated = parseInitiative2(dir, ini.filePath, newContent);
21288
21735
  if (!updated) throw new InitiativeWriteError("Failed to parse the Initiative after materialization.");
@@ -21396,7 +21843,7 @@ function evaluateInitiativeCompletion(dir, ini) {
21396
21843
  // ../cli/src/core/resource-write.ts
21397
21844
  import fs6 from "fs";
21398
21845
  import path5 from "path";
21399
- import matter21 from "gray-matter";
21846
+ import matter23 from "gray-matter";
21400
21847
  var RESOURCES_DIR = "knowledge/tech/resources";
21401
21848
  var ResourceWriteError = class extends Error {
21402
21849
  constructor(code, message) {
@@ -21442,6 +21889,20 @@ function validateResource(dir, input) {
21442
21889
  findings.push({ level: "warning", message: `Unknown access interface type "${i.type}".` });
21443
21890
  }
21444
21891
  }
21892
+ for (const p of input.connectionProfiles ?? []) {
21893
+ const where = p.environment ? ` in environment "${p.environment}"` : "";
21894
+ for (const c of p.coordinates ?? []) {
21895
+ const key = (c?.key ?? "").trim();
21896
+ const value = (c?.value ?? "").trim();
21897
+ if (!key || !value) continue;
21898
+ if (looksLikeSecretCoordinate(key, value)) {
21899
+ findings.push({
21900
+ level: "error",
21901
+ message: `Connection coordinate "${key}"${where} looks like a secret value. Store credentials as authentication references, not connection coordinates.`
21902
+ });
21903
+ }
21904
+ }
21905
+ }
21445
21906
  const mapped = loadMappedModules(dir).map((m2) => m2.id);
21446
21907
  if (mapped.length > 0) {
21447
21908
  const refs = /* @__PURE__ */ new Set();
@@ -21468,6 +21929,22 @@ function buildFrontmatter(id, input, base = {}) {
21468
21929
  if (input.modules != null) data.modules = input.modules;
21469
21930
  if (input.accessInterfaces != null) data.access_interfaces = input.accessInterfaces;
21470
21931
  if (input.accessBoundaries != null) data.access_boundaries = input.accessBoundaries;
21932
+ if (input.connectionProfiles != null) {
21933
+ data.connection_profiles = input.connectionProfiles.map((p) => {
21934
+ const coordinates = {};
21935
+ for (const c of p.coordinates ?? []) {
21936
+ const key = (c?.key ?? "").trim();
21937
+ const value = (c?.value ?? "").trim();
21938
+ if (!key || !value || looksLikeSecretCoordinate(key, value)) continue;
21939
+ coordinates[key] = value;
21940
+ }
21941
+ const entry = {};
21942
+ if (p.environment?.trim()) entry.environment = p.environment.trim();
21943
+ if (p.interface?.trim()) entry.interface = p.interface.trim();
21944
+ if (Object.keys(coordinates).length) entry.coordinates = coordinates;
21945
+ return entry;
21946
+ }).filter((e) => Object.keys(e).length > 0);
21947
+ }
21471
21948
  if (input.authentication != null) {
21472
21949
  const mode = input.authentication.mode;
21473
21950
  const refs = sanitizeRefs(input.authentication.refs);
@@ -21476,7 +21953,7 @@ function buildFrontmatter(id, input, base = {}) {
21476
21953
  return data;
21477
21954
  }
21478
21955
  function serialize(data, body) {
21479
- return matter21.stringify(`
21956
+ return matter23.stringify(`
21480
21957
  ${body.trim()}
21481
21958
  `, data);
21482
21959
  }
@@ -21516,7 +21993,7 @@ function updateResource(dir, id, input) {
21516
21993
  throw new ResourceWriteError("VALIDATION_FAILED", findings.filter((f3) => f3.level === "error").map((f3) => f3.message).join("; "));
21517
21994
  }
21518
21995
  const raw = readFile(match.filePath);
21519
- const parsed = matter21(raw);
21996
+ const parsed = matter23(raw);
21520
21997
  const base = parsed.data ?? {};
21521
21998
  const data = buildFrontmatter(match.id || id, input, base);
21522
21999
  let body = parsed.content.trim();
@@ -21749,7 +22226,7 @@ var CANDIDATE_DISCOVERY_GUIDANCE2 = [
21749
22226
  // ../cli/src/core/deprecation-write.ts
21750
22227
  import fs7 from "fs";
21751
22228
  import path6 from "path";
21752
- import matter23 from "gray-matter";
22229
+ import matter24 from "gray-matter";
21753
22230
  var DeprecationWriteError = class extends Error {
21754
22231
  constructor(code, message) {
21755
22232
  super(message);
@@ -21767,7 +22244,7 @@ function atomicWrite2(filePath, content) {
21767
22244
  fs7.renameSync(tmp, filePath);
21768
22245
  }
21769
22246
  function serialize3(data, body) {
21770
- return matter23.stringify(`
22247
+ return matter24.stringify(`
21771
22248
  ${body.trim()}
21772
22249
  `, data);
21773
22250
  }
@@ -21833,7 +22310,7 @@ function validateTargetInput(dir, raw) {
21833
22310
  function loadPlanRaw(dir, planId) {
21834
22311
  const match = allPlanArtifacts(dir).find((a2) => a2.id === planId || a2.title === planId);
21835
22312
  if (!match) throw new DeprecationWriteError("NOT_FOUND", `Deprecation Plan "${planId}" not found.`);
21836
- const parsed = matter23(readFile(match.filePath));
22313
+ const parsed = matter24(readFile(match.filePath));
21837
22314
  const data = { ...parsed.data ?? {} };
21838
22315
  return { filePath: match.filePath, data, body: parsed.content.trim() };
21839
22316
  }
@@ -22021,12 +22498,12 @@ function markWorkItemReady(root, id, confirm2) {
22021
22498
  }
22022
22499
  const raw = readText(root, item.path);
22023
22500
  if (!raw) return fail(`Could not read Work Item file: ${item.path}`);
22024
- const parsed = matter24(raw);
22501
+ const parsed = matter25(raw);
22025
22502
  const data = { ...parsed.data };
22026
22503
  const today3 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
22027
22504
  data.status = "ready";
22028
22505
  data.ready_at = today3;
22029
- const newContent = matter24.stringify(parsed.content, data);
22506
+ const newContent = matter25.stringify(parsed.content, data);
22030
22507
  const posixPath = item.path.replace(/\\/g, "/");
22031
22508
  let newRelPath = item.path;
22032
22509
  if (posixPath.includes("/work-items/draft/")) {
@@ -22321,7 +22798,7 @@ function removeDeprecationTargetTool(root, planId, targetId, confirm2) {
22321
22798
  // src/multirepo.ts
22322
22799
  import fs8 from "fs";
22323
22800
  import path7 from "path";
22324
- import matter25 from "gray-matter";
22801
+ import matter26 from "gray-matter";
22325
22802
  import { parse as parseYaml12, stringify as stringifyYaml8 } from "yaml";
22326
22803
  var ok2 = (data) => ({ ok: true, data });
22327
22804
  var fail2 = (message) => ({ ok: false, message });
@@ -22473,7 +22950,7 @@ function validateWorkItemModulesTool(root, args) {
22473
22950
  if (!wi) return fail2(`Work Item "${args.workItemId}" not found.`);
22474
22951
  const raw = readText(root, wi.path);
22475
22952
  if (!raw) return fail2(`Could not read Work Item file.`);
22476
- const parsed = matter25(raw);
22953
+ const parsed = matter26(raw);
22477
22954
  const data = parsed.data;
22478
22955
  const affectedModules = Array.isArray(data.affected_modules) ? data.affected_modules.map(String) : [];
22479
22956
  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) : [] })) : [];
@@ -22526,7 +23003,7 @@ function getWorkItemContextTool(root, args) {
22526
23003
  if (!wi) return fail2(`Work Item "${args.workItemId}" not found.`);
22527
23004
  const raw = readText(root, wi.path);
22528
23005
  if (!raw) return fail2(`Could not read Work Item file.`);
22529
- const parsed = matter25(raw);
23006
+ const parsed = matter26(raw);
22530
23007
  const data = parsed.data;
22531
23008
  const affectedModules = Array.isArray(data.affected_modules) ? data.affected_modules.map(String) : [];
22532
23009
  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) : [] })) : [];
@@ -22612,7 +23089,7 @@ function suggestBranchStrategyTool(root, args) {
22612
23089
  if (!wi) return fail2(`Work Item "${args.workItemId}" not found.`);
22613
23090
  const raw = readText(root, wi.path);
22614
23091
  if (!raw) return fail2(`Could not read Work Item file.`);
22615
- const parsed = matter25(raw);
23092
+ const parsed = matter26(raw);
22616
23093
  const data = parsed.data;
22617
23094
  const affectedModules = Array.isArray(data.affected_modules) ? data.affected_modules.map(String) : [];
22618
23095
  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) : [] })) : [];
@@ -23372,14 +23849,14 @@ function systemImpactCandidatesTool(root, args) {
23372
23849
  }
23373
23850
 
23374
23851
  // ../cli/src/services/integrations.ts
23375
- import matter27 from "gray-matter";
23852
+ import matter28 from "gray-matter";
23376
23853
  import { parse as parseYaml14, stringify as stringifyYaml10 } from "yaml";
23377
23854
 
23378
23855
  // ../cli/src/core/work-item-write.ts
23379
23856
  import fs10 from "fs";
23380
23857
  import path9 from "path";
23381
23858
  import crypto4 from "crypto";
23382
- import matter26 from "gray-matter";
23859
+ import matter27 from "gray-matter";
23383
23860
  var SECTION_ORDER2 = [
23384
23861
  "Actor",
23385
23862
  "Outcome",
@@ -24427,7 +24904,7 @@ async function integrationsWorkItemTool(root, args) {
24427
24904
  }
24428
24905
 
24429
24906
  // ../cli/src/core/context-pack.ts
24430
- import matter28 from "gray-matter";
24907
+ import matter29 from "gray-matter";
24431
24908
  var CONTEXT_PACK_VERSION = "1";
24432
24909
  var ARCH_DIR4 = "knowledge";
24433
24910
  function readScanJson(dir) {
@@ -24440,7 +24917,7 @@ function readScanJson(dir) {
24440
24917
  }
24441
24918
  }
24442
24919
  function firstParagraph4(markdown) {
24443
- const body = matter28(markdown).content.trim();
24920
+ const body = matter29(markdown).content.trim();
24444
24921
  const para = body.split("\n\n").map((p) => p.trim()).find((p) => p && !p.startsWith("#"));
24445
24922
  return para ?? "";
24446
24923
  }
@@ -24941,11 +25418,11 @@ function renderContextPack(pack) {
24941
25418
  if (mixEntries.length > 0) {
24942
25419
  parts.push("## Delivery Mix\n");
24943
25420
  parts.push("Active Work Items by type:\n");
24944
- const label = (t) => {
25421
+ const label2 = (t) => {
24945
25422
  const c = t.charAt(0).toUpperCase() + t.slice(1);
24946
25423
  return c.endsWith("s") ? c : `${c}s`;
24947
25424
  };
24948
- parts.push(mixEntries.map(([t, n]) => `- ${label(t)}: ${n}`).join("\n") + "\n");
25425
+ parts.push(mixEntries.map(([t, n]) => `- ${label2(t)}: ${n}`).join("\n") + "\n");
24949
25426
  }
24950
25427
  parts.push("## Artifacts and Ownership\n");
24951
25428
  if (knowledge.artifacts.length > 0) {
@@ -25421,8 +25898,502 @@ function renderUnderstand(plan) {
25421
25898
  return parts.join("\n");
25422
25899
  }
25423
25900
 
25901
+ // ../cli/src/core/knowledge-consolidation.ts
25902
+ import { createHash as createHash3 } from "crypto";
25903
+ import matter30 from "gray-matter";
25904
+ var KNOWLEDGE7 = "knowledge";
25905
+ var CONSOLIDATION_CATEGORIES = [
25906
+ "knowledge_redundancy",
25907
+ "unincorporated_learning",
25908
+ "knowledge_fragmentation",
25909
+ "relationship_opportunity",
25910
+ "potentially_outdated",
25911
+ "knowledge_organization"
25912
+ ];
25913
+ function toPosix8(p) {
25914
+ return p.replace(/\\/g, "/");
25915
+ }
25916
+ function relFromDir(dir, filePath) {
25917
+ const d3 = toPosix8(dir).replace(/\/+$/, "");
25918
+ const f3 = toPosix8(filePath);
25919
+ return f3.startsWith(d3 + "/") ? f3.slice(d3.length + 1) : f3;
25920
+ }
25921
+ function sha256(content) {
25922
+ return createHash3("sha256").update(content, "utf-8").digest("hex");
25923
+ }
25924
+ var GENERIC_HEADINGS = /* @__PURE__ */ new Set([
25925
+ "purpose",
25926
+ "proposito",
25927
+ "prop\xF3sito",
25928
+ "intent",
25929
+ "goal",
25930
+ "objetivo",
25931
+ "summary",
25932
+ "resumen",
25933
+ "overview",
25934
+ "context",
25935
+ "contexto",
25936
+ "scope",
25937
+ "alcance",
25938
+ "out of scope",
25939
+ "fuera de alcance",
25940
+ "acceptance criteria",
25941
+ "criterios de aceptacion",
25942
+ "criterios de aceptaci\xF3n",
25943
+ "validation",
25944
+ "validation scenarios",
25945
+ "validacion",
25946
+ "validaci\xF3n",
25947
+ "escenarios de validacion",
25948
+ "escenarios de validaci\xF3n",
25949
+ "notes",
25950
+ "notas",
25951
+ "references",
25952
+ "referencias",
25953
+ "examples",
25954
+ "ejemplos",
25955
+ "learning",
25956
+ "learnings",
25957
+ "aprendizaje",
25958
+ "aprendizajes",
25959
+ // Work Item template sections
25960
+ "problem",
25961
+ "problema",
25962
+ "expected result",
25963
+ "resultado esperado",
25964
+ "refinement",
25965
+ "refinamiento",
25966
+ "implementation guidance",
25967
+ "guia de implementacion",
25968
+ "gu\xEDa de implementaci\xF3n",
25969
+ "definition of done",
25970
+ "definicion de hecho",
25971
+ "definici\xF3n de hecho",
25972
+ "commit gate",
25973
+ "future evolution",
25974
+ "evolucion futura",
25975
+ "evoluci\xF3n futura",
25976
+ "guiding principle",
25977
+ "principio rector",
25978
+ "open questions",
25979
+ "preguntas abiertas",
25980
+ "dependencies",
25981
+ "dependencias",
25982
+ "conceptual model",
25983
+ "goal"
25984
+ ]);
25985
+ var RULE_KEYWORDS = [
25986
+ "siempre",
25987
+ "nunca",
25988
+ "debe",
25989
+ "deben",
25990
+ "requiere",
25991
+ "requieren",
25992
+ "invariante",
25993
+ "restriccion",
25994
+ "restricci\xF3n",
25995
+ "regla",
25996
+ "obligatorio",
25997
+ "must",
25998
+ "always",
25999
+ "never",
26000
+ "require",
26001
+ "requires",
26002
+ "constraint",
26003
+ "invariant",
26004
+ "rule"
26005
+ ];
26006
+ function normalizeHeading(h2) {
26007
+ return h2.trim().toLowerCase().replace(/\s+/g, " ");
26008
+ }
26009
+ function distinctiveHeadings(body) {
26010
+ const out = [];
26011
+ for (const line of body.split(/\r?\n/)) {
26012
+ const m2 = line.match(/^#{2,3}\s+(.+?)\s*$/);
26013
+ if (!m2) continue;
26014
+ const norm = normalizeHeading(m2[1]);
26015
+ if (!norm || GENERIC_HEADINGS.has(norm)) continue;
26016
+ out.push(norm);
26017
+ }
26018
+ return [...new Set(out)];
26019
+ }
26020
+ function sectionBody(body, titles) {
26021
+ const lines = body.split(/\r?\n/);
26022
+ const re = new RegExp(`^#{1,3}\\s+(${titles.join("|")})\\s*$`, "i");
26023
+ const start = lines.findIndex((l2) => re.test(l2));
26024
+ if (start < 0) return null;
26025
+ const collected = [];
26026
+ for (let i = start + 1; i < lines.length; i++) {
26027
+ if (/^#{1,3}\s+/.test(lines[i])) break;
26028
+ collected.push(lines[i]);
26029
+ }
26030
+ const text3 = collected.join("\n").trim();
26031
+ return text3.length > 0 ? text3 : null;
26032
+ }
26033
+ function stableOpportunityId(category, sources) {
26034
+ const key = [category, ...[...sources].sort()].join("|");
26035
+ return `${category}:${sha256(key).slice(0, 12)}`;
26036
+ }
26037
+ function layerOf(a2) {
26038
+ const byType = layerForType(a2.type);
26039
+ if (byType) return byType;
26040
+ const p = toPosix8(a2.filePath);
26041
+ if (p.includes(`/${KNOWLEDGE7}/business/`)) return "Business";
26042
+ if (p.includes(`/${KNOWLEDGE7}/product/`)) return "Product";
26043
+ if (p.includes(`/${KNOWLEDGE7}/tech/`)) return "Tech";
26044
+ if (p.includes(`/${KNOWLEDGE7}/delivery/`)) return "Delivery";
26045
+ return null;
26046
+ }
26047
+ function inScope(la, scope) {
26048
+ if (scope.kind === "all") return true;
26049
+ if (scope.kind === "layer") return la.layer === scope.layer;
26050
+ return la.artifact.affectedModules.includes(scope.module);
26051
+ }
26052
+ function label(la) {
26053
+ return la.artifact.id || la.rel;
26054
+ }
26055
+ function buildConsolidationPlan(dir, opts = {}) {
26056
+ requireConfig(dir);
26057
+ const scope = opts.scope ?? { kind: "all" };
26058
+ const now = opts.now ?? /* @__PURE__ */ new Date();
26059
+ const archDir = join(dir, KNOWLEDGE7);
26060
+ const rawArtifacts = exists(archDir) ? readArtifacts(archDir) : [];
26061
+ const loaded = rawArtifacts.map((a2) => {
26062
+ let body = "";
26063
+ try {
26064
+ body = matter30(readFile(a2.filePath)).content;
26065
+ } catch {
26066
+ body = "";
26067
+ }
26068
+ return { artifact: a2, rel: relFromDir(dir, a2.filePath), body, layer: layerOf(a2) };
26069
+ });
26070
+ const scoped = loaded.filter((la) => inScope(la, scope));
26071
+ const scopedRels = new Set(scoped.map((la) => la.rel));
26072
+ const knownIds = new Set(loaded.map((la) => la.artifact.id).filter(Boolean));
26073
+ const opportunities = [];
26074
+ try {
26075
+ const config = requireConfig(dir);
26076
+ const graph = buildGraph(dir, config, { scope: "all" }, now);
26077
+ const hints = buildGraphHints(dir, graph, now);
26078
+ for (const hint of hints.hints) {
26079
+ if (!hintInScope(hint, scoped)) continue;
26080
+ opportunities.push(relationshipOpportunityFromHint(hint));
26081
+ }
26082
+ } catch {
26083
+ }
26084
+ for (const la of scoped) {
26085
+ const dangling = la.artifact.decisions.filter((d3) => d3 && !knownIds.has(d3) && !decisionFileExists(dir, d3));
26086
+ if (dangling.length === 0) continue;
26087
+ opportunities.push({
26088
+ id: stableOpportunityId("potentially_outdated", [label(la), ...dangling]),
26089
+ category: "potentially_outdated",
26090
+ classification: "observed",
26091
+ confidence: "requires-review",
26092
+ title: `${label(la)} references decision(s) that no longer exist`,
26093
+ source_artifacts: [label(la)],
26094
+ target_artifacts: [label(la)],
26095
+ evidence: dangling.map((d3) => `Declared decision \`${d3}\` has no matching artifact in the project.`),
26096
+ rationale: "A reference to a missing decision is a verifiable inconsistency: either the decision artifact was removed/renamed or the reference is stale.",
26097
+ possible_impact: "Broken traceability between a Work Item/artifact and its decisions.",
26098
+ suggested_action: "Review and update or remove the dangling decision reference.",
26099
+ requires_semantic_review: false
26100
+ });
26101
+ }
26102
+ const canonical = scoped.filter(
26103
+ (la) => la.layer !== null && !isWorkItem(la) && la.artifact.type !== "roadmap"
26104
+ );
26105
+ const byHeading = /* @__PURE__ */ new Map();
26106
+ for (const la of canonical) {
26107
+ for (const h2 of distinctiveHeadings(la.body)) {
26108
+ const arr = byHeading.get(h2) ?? [];
26109
+ arr.push(la);
26110
+ byHeading.set(h2, arr);
26111
+ }
26112
+ }
26113
+ const pairHeadings = /* @__PURE__ */ new Map();
26114
+ for (const [heading, arts] of byHeading) {
26115
+ if (arts.length < 2) continue;
26116
+ for (let i = 0; i < arts.length; i++) {
26117
+ for (let j2 = i + 1; j2 < arts.length; j2++) {
26118
+ if (arts[i].rel === arts[j2].rel) continue;
26119
+ const key = [arts[i].rel, arts[j2].rel].sort().join("||");
26120
+ const entry = pairHeadings.get(key) ?? { a: arts[i], b: arts[j2], shared: /* @__PURE__ */ new Set() };
26121
+ entry.shared.add(heading);
26122
+ pairHeadings.set(key, entry);
26123
+ }
26124
+ }
26125
+ }
26126
+ for (const { a: a2, b: b2, shared } of pairHeadings.values()) {
26127
+ if (shared.size < 2) continue;
26128
+ opportunities.push({
26129
+ id: stableOpportunityId("knowledge_redundancy", [label(a2), label(b2)]),
26130
+ category: "knowledge_redundancy",
26131
+ classification: "suggested",
26132
+ confidence: "requires-review",
26133
+ title: `Possible overlapping content between ${label(a2)} and ${label(b2)}`,
26134
+ source_artifacts: [label(a2), label(b2)],
26135
+ target_artifacts: [label(a2), label(b2)],
26136
+ evidence: [`Both artifacts declare the sections: ${[...shared].sort().join(", ")}.`],
26137
+ rationale: "Shared section titles are a hint of potential overlap, NOT proof of duplication. Two documents can legitimately address the same topic from different layers/responsibilities.",
26138
+ possible_impact: "If genuinely duplicated, the same fact can drift out of sync across files.",
26139
+ suggested_action: "Semantic review: compare the sections; if one restates the other, keep a single reference location and link the rest. Preserve layer separation.",
26140
+ requires_semantic_review: true
26141
+ });
26142
+ }
26143
+ for (const la of scoped) {
26144
+ if (!isCompletedWorkItem(la)) continue;
26145
+ const learning = sectionBody(la.body, ["Learning", "Aprendizaje", "Aprendizajes", "Learnings"]);
26146
+ if (!learning) continue;
26147
+ if (!looksLikeDurableRule(learning)) continue;
26148
+ opportunities.push({
26149
+ id: stableOpportunityId("unincorporated_learning", [label(la)]),
26150
+ category: "unincorporated_learning",
26151
+ classification: "suggested",
26152
+ confidence: "requires-review",
26153
+ title: `${label(la)} captured a learning that may belong in current knowledge`,
26154
+ source_artifacts: [label(la)],
26155
+ target_artifacts: ["knowledge/tech/codebase.md"],
26156
+ evidence: [`Completed Work Item records a rule-like learning: "${firstSentence(learning)}"`],
26157
+ rationale: "A learning phrased as a durable rule MAY represent live technical knowledge not yet reflected in Tech Knowledge. This is a candidate, not a confirmed gap.",
26158
+ possible_impact: "A relevant constraint stays buried in delivery history instead of informing current work.",
26159
+ suggested_action: "Semantic review: decide whether this learning is a live rule; if so, incorporate it into the right Tech artifact WITH a reference back to the Work Item. Do not delete or summarize the Work Item.",
26160
+ requires_semantic_review: true
26161
+ });
26162
+ }
26163
+ const weakByFolder = /* @__PURE__ */ new Map();
26164
+ for (const la of scoped) {
26165
+ const quality = safeQuality(dir, la.rel);
26166
+ if (quality === "placeholder" || quality === "weak") {
26167
+ opportunities.push({
26168
+ id: stableOpportunityId("knowledge_organization", [la.rel]),
26169
+ category: "knowledge_organization",
26170
+ classification: "observed",
26171
+ confidence: "requires-review",
26172
+ title: `${label(la)} is ${quality} and may need consolidation or expansion`,
26173
+ source_artifacts: [label(la)],
26174
+ target_artifacts: [label(la)],
26175
+ evidence: [`Artifact quality analysis classifies this artifact as "${quality}".`],
26176
+ rationale: "Low-signal artifacts reduce retrieval quality. This is an observed quality signal, not a judgement that the content is wrong.",
26177
+ possible_impact: "Readers must open more files to assemble a complete picture.",
26178
+ suggested_action: "Review whether to expand, merge into a stronger artifact, or cross-reference it.",
26179
+ requires_semantic_review: false
26180
+ });
26181
+ const folder = la.rel.split("/").slice(0, -1).join("/");
26182
+ const arr = weakByFolder.get(folder) ?? [];
26183
+ arr.push(la);
26184
+ weakByFolder.set(folder, arr);
26185
+ }
26186
+ }
26187
+ for (const [folder, arts] of weakByFolder) {
26188
+ if (arts.length < 3) continue;
26189
+ opportunities.push({
26190
+ id: stableOpportunityId("knowledge_fragmentation", arts.map((a2) => a2.rel)),
26191
+ category: "knowledge_fragmentation",
26192
+ classification: "suggested",
26193
+ confidence: "requires-review",
26194
+ title: `${folder}/ holds ${arts.length} low-signal artifacts \u2014 possible fragmentation`,
26195
+ source_artifacts: arts.map((a2) => label(a2)),
26196
+ target_artifacts: [folder],
26197
+ evidence: [`${arts.length} placeholder/weak artifacts in the same folder: ${arts.map((a2) => a2.rel.split("/").pop()).join(", ")}.`],
26198
+ rationale: "Several thin artifacts in one folder MAY be easier to consume if consolidated or cross-linked. Consolidation must preserve each distinct responsibility.",
26199
+ possible_impact: "Related knowledge is spread across too many files to follow.",
26200
+ suggested_action: "Semantic review: evaluate whether cross-references or a consolidation would add clarity. Do not merge by file count alone.",
26201
+ requires_semantic_review: true
26202
+ });
26203
+ }
26204
+ opportunities.sort((x2, y) => {
26205
+ const ci = CONSOLIDATION_CATEGORIES.indexOf(x2.category) - CONSOLIDATION_CATEGORIES.indexOf(y.category);
26206
+ return ci !== 0 ? ci : x2.id.localeCompare(y.id);
26207
+ });
26208
+ const seen = /* @__PURE__ */ new Set();
26209
+ const deduped = opportunities.filter((o2) => seen.has(o2.id) ? false : (seen.add(o2.id), true));
26210
+ const layers = discoverLayers(dir);
26211
+ const layersEvaluated = scope.kind === "layer" ? [scope.layer] : layers.map((l2) => l2.layer);
26212
+ const workItemsEvaluated = scoped.filter(isWorkItem).length;
26213
+ const sourceFiles = [...scopedRels].sort().map((rel) => ({ path: rel, hash: sha256(readFileSafe(join(dir, rel))) }));
26214
+ const byCategory = Object.fromEntries(
26215
+ CONSOLIDATION_CATEGORIES.map((c) => [c, deduped.filter((o2) => o2.category === c).length])
26216
+ );
26217
+ return {
26218
+ generated_at: now.toISOString(),
26219
+ scope: describeScope(scope),
26220
+ project_context: reportingProjectContext(dir),
26221
+ coverage: {
26222
+ scope: describeScope(scope),
26223
+ layers_evaluated: layersEvaluated,
26224
+ artifacts_evaluated: scoped.length,
26225
+ work_items_evaluated: workItemsEvaluated,
26226
+ excluded_sources: scope.kind === "all" ? [] : [...loaded.map((l2) => l2.rel)].filter((r2) => !scopedRels.has(r2)).sort()
26227
+ },
26228
+ source_files: sourceFiles,
26229
+ opportunities: deduped,
26230
+ summary: {
26231
+ total: deduped.length,
26232
+ by_category: byCategory,
26233
+ requires_semantic_review: deduped.filter((o2) => o2.requires_semantic_review).length
26234
+ }
26235
+ };
26236
+ }
26237
+ var CATEGORY_LABELS = {
26238
+ knowledge_redundancy: "Possible redundancy",
26239
+ unincorporated_learning: "Learning integration opportunity",
26240
+ knowledge_fragmentation: "Possible fragmentation",
26241
+ relationship_opportunity: "Relationship review hint",
26242
+ potentially_outdated: "Potential inconsistency",
26243
+ knowledge_organization: "Organization opportunity"
26244
+ };
26245
+ function renderConsolidationMarkdown(plan) {
26246
+ const lines = [];
26247
+ lines.push("# Knowledge Consolidation Plan");
26248
+ lines.push("");
26249
+ lines.push("> Derived, review-only projection. No canonical knowledge was modified by this analysis.");
26250
+ lines.push("");
26251
+ lines.push(`- Generated at: ${plan.generated_at}`);
26252
+ lines.push(`- Scope: ${plan.scope}`);
26253
+ lines.push(`- Project: state=${plan.project_context.state}, mode=${plan.project_context.mode}, structure=${plan.project_context.repository_structure}`);
26254
+ lines.push("");
26255
+ lines.push("## Coverage");
26256
+ lines.push("");
26257
+ lines.push(`- Layers evaluated: ${plan.coverage.layers_evaluated.join(", ") || "\u2014"}`);
26258
+ lines.push(`- Artifacts evaluated: ${plan.coverage.artifacts_evaluated}`);
26259
+ lines.push(`- Work Items evaluated: ${plan.coverage.work_items_evaluated}`);
26260
+ if (plan.coverage.excluded_sources.length > 0) {
26261
+ lines.push(`- Excluded from this scope: ${plan.coverage.excluded_sources.length} artifact(s)`);
26262
+ }
26263
+ lines.push("");
26264
+ lines.push("## Findings");
26265
+ lines.push("");
26266
+ if (plan.summary.total === 0) {
26267
+ lines.push("No consolidation opportunities were detected by the available deterministic analyses within this scope.");
26268
+ lines.push("");
26269
+ lines.push("This is not a claim that the knowledge is perfect \u2014 only that no opportunity was found with the current signals.");
26270
+ lines.push("");
26271
+ return lines.join("\n") + "\n";
26272
+ }
26273
+ for (const cat of CONSOLIDATION_CATEGORIES) {
26274
+ const n = plan.summary.by_category[cat];
26275
+ if (n > 0) lines.push(`- ${n} \xD7 ${CATEGORY_LABELS[cat]}`);
26276
+ }
26277
+ lines.push("");
26278
+ lines.push(`${plan.summary.requires_semantic_review} finding(s) require semantic (agent/human) review before acting.`);
26279
+ lines.push("");
26280
+ lines.push("## Opportunities");
26281
+ lines.push("");
26282
+ for (const o2 of plan.opportunities) {
26283
+ lines.push(`### ${o2.title}`);
26284
+ lines.push("");
26285
+ lines.push(`- Id: \`${o2.id}\``);
26286
+ lines.push(`- Category: ${o2.category}`);
26287
+ lines.push(`- Classification: **${o2.classification}**${o2.requires_semantic_review ? " (requires semantic review)" : ""}`);
26288
+ lines.push(`- Confidence: ${o2.confidence}`);
26289
+ lines.push(`- Source: ${o2.source_artifacts.join(", ") || "\u2014"}`);
26290
+ lines.push(`- Suggested target: ${o2.target_artifacts.join(", ") || "\u2014"}`);
26291
+ lines.push(`- Evidence:`);
26292
+ for (const e of o2.evidence) lines.push(` - ${e}`);
26293
+ lines.push(`- Rationale: ${o2.rationale}`);
26294
+ lines.push(`- Possible impact: ${o2.possible_impact}`);
26295
+ lines.push(`- Suggested action: ${o2.suggested_action}`);
26296
+ lines.push("");
26297
+ }
26298
+ return lines.join("\n") + "\n";
26299
+ }
26300
+ function serializeConsolidationJson(plan) {
26301
+ return JSON.stringify(plan, null, 2) + "\n";
26302
+ }
26303
+ function describeScope(scope) {
26304
+ if (scope.kind === "all") return "all";
26305
+ if (scope.kind === "layer") return `layer:${scope.layer}`;
26306
+ return `module:${scope.module}`;
26307
+ }
26308
+ function isWorkItem(la) {
26309
+ return toPosix8(la.artifact.filePath).includes("/delivery/work-items/");
26310
+ }
26311
+ function isCompletedWorkItem(la) {
26312
+ const p = toPosix8(la.artifact.filePath);
26313
+ return p.includes("/delivery/work-items/completed/") || isWorkItem(la) && la.artifact.status === "completed";
26314
+ }
26315
+ function looksLikeDurableRule(text3) {
26316
+ const lower = text3.toLowerCase();
26317
+ return RULE_KEYWORDS.some((k2) => new RegExp(`(^|[^a-z\xE1\xE9\xED\xF3\xFA\xF1])${k2}([^a-z\xE1\xE9\xED\xF3\xFA\xF1]|$)`, "i").test(lower));
26318
+ }
26319
+ function firstSentence(text3) {
26320
+ const flat = text3.replace(/\s+/g, " ").trim();
26321
+ const m2 = flat.match(/^(.{0,200}?[.!?])(\s|$)/);
26322
+ const s = (m2 ? m2[1] : flat).slice(0, 200);
26323
+ return redactSecrets3(s.trim());
26324
+ }
26325
+ var SECRET_TOKEN_PATTERNS = [
26326
+ /\bgh[pours]_[A-Za-z0-9]{16,}\b/g,
26327
+ // GitHub tokens
26328
+ /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/g,
26329
+ // AWS access key ids
26330
+ /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g,
26331
+ // Slack tokens
26332
+ /\bsk-[A-Za-z0-9]{16,}\b/g,
26333
+ // OpenAI-style keys
26334
+ /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g,
26335
+ // JWTs
26336
+ /\b[A-Za-z0-9+/]{40,}={0,2}\b/g
26337
+ // long base64 / opaque blobs
26338
+ ];
26339
+ function redactSecrets3(text3) {
26340
+ let out = text3;
26341
+ for (const re of SECRET_TOKEN_PATTERNS) out = out.replace(re, "[redacted]");
26342
+ return out;
26343
+ }
26344
+ function safeQuality(dir, rel) {
26345
+ try {
26346
+ return analyzeKnowledgeArtifact(dir, rel);
26347
+ } catch {
26348
+ return "useful";
26349
+ }
26350
+ }
26351
+ function readFileSafe(abs) {
26352
+ try {
26353
+ return readFile(abs);
26354
+ } catch {
26355
+ return "";
26356
+ }
26357
+ }
26358
+ function decisionFileExists(dir, decisionId) {
26359
+ const decisionsDir = join(dir, KNOWLEDGE7, "tech", "decisions");
26360
+ if (!exists(decisionsDir)) return false;
26361
+ try {
26362
+ return readDir(decisionsDir).some((e) => {
26363
+ const full = join(decisionsDir, e);
26364
+ if (!isFile(full) || !e.endsWith(".md")) return false;
26365
+ return e.toLowerCase().includes(decisionId.toLowerCase());
26366
+ });
26367
+ } catch {
26368
+ return false;
26369
+ }
26370
+ }
26371
+ function hintInScope(hint, scoped) {
26372
+ if (scoped.length === 0) return false;
26373
+ return scoped.some(
26374
+ (la) => la.artifact.id === hint.artifact_id || hint.path && toPosix8(la.artifact.filePath).endsWith(toPosix8(hint.path))
26375
+ );
26376
+ }
26377
+ function relationshipOpportunityFromHint(hint) {
26378
+ const finding = graphHintFinding(hint);
26379
+ return {
26380
+ id: stableOpportunityId("relationship_opportunity", [hint.artifact_id, ...hint.missing]),
26381
+ category: "relationship_opportunity",
26382
+ classification: "observed",
26383
+ confidence: "requires-review",
26384
+ title: hint.message,
26385
+ source_artifacts: [hint.artifact_id],
26386
+ target_artifacts: [hint.artifact_id],
26387
+ evidence: [finding.evidence],
26388
+ rationale: "A missing OPTIONAL relationship is an opportunity to review, not an error or a verified inconsistency. Kaddo never invents the relationship.",
26389
+ possible_impact: "Weaker traceability in the knowledge graph.",
26390
+ suggested_action: finding.suggested_action,
26391
+ requires_semantic_review: false
26392
+ };
26393
+ }
26394
+
25424
26395
  // src/generate.ts
25425
- function requireConfig(root) {
26396
+ function requireConfig2(root) {
25426
26397
  const config = loadConfig(root);
25427
26398
  if (!config) throw new KaddoMcpError("Kaddo project not found. Run `kaddo init` first.");
25428
26399
  return config;
@@ -25436,7 +26407,7 @@ function slug6(s) {
25436
26407
  return s.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "project";
25437
26408
  }
25438
26409
  function generateContext(root) {
25439
- const config = requireConfig(root);
26410
+ const config = requireConfig2(root);
25440
26411
  requireKnowledge(root);
25441
26412
  const pack = buildContextPack(root, config);
25442
26413
  writeDerived(root, ".kaddo/context-pack.md", renderContextPack(pack));
@@ -25450,7 +26421,7 @@ function generateContext(root) {
25450
26421
  };
25451
26422
  }
25452
26423
  function generateExplain(root) {
25453
- requireConfig(root);
26424
+ requireConfig2(root);
25454
26425
  requireKnowledge(root);
25455
26426
  const exp = buildProjectExplanation(root);
25456
26427
  writeDerived(root, ".kaddo/explain.md", renderExplanationHuman(exp));
@@ -25464,7 +26435,7 @@ function generateExplain(root) {
25464
26435
  };
25465
26436
  }
25466
26437
  function generateUnderstand(root) {
25467
- const config = requireConfig(root);
26438
+ const config = requireConfig2(root);
25468
26439
  requireKnowledge(root);
25469
26440
  const plan = buildUnderstandPlan(root, config);
25470
26441
  writeDerived(root, ".kaddo/understand.md", renderUnderstand(plan));
@@ -25477,7 +26448,7 @@ function generateUnderstand(root) {
25477
26448
  };
25478
26449
  }
25479
26450
  function generateGraph(root, scope = "active") {
25480
- const config = requireConfig(root);
26451
+ const config = requireConfig2(root);
25481
26452
  requireKnowledge(root);
25482
26453
  const graph = buildGraph(root, config, { scope });
25483
26454
  const hints = buildGraphHints(root, graph);
@@ -25499,8 +26470,39 @@ function generateGraph(root, scope = "active") {
25499
26470
  next_suggested_resources: ["kaddo://graph", "kaddo://graph-hints"]
25500
26471
  };
25501
26472
  }
26473
+ var LAYER_BY_FLAG = {
26474
+ business: "Business",
26475
+ product: "Product",
26476
+ tech: "Tech",
26477
+ delivery: "Delivery"
26478
+ };
26479
+ function generateConsolidationPlan(root, opts = {}) {
26480
+ requireConfig2(root);
26481
+ requireKnowledge(root);
26482
+ let scope = { kind: "all" };
26483
+ if (opts.module) {
26484
+ scope = { kind: "module", module: opts.module };
26485
+ } else if (opts.layer) {
26486
+ const layer2 = LAYER_BY_FLAG[opts.layer.toLowerCase()];
26487
+ if (!layer2) {
26488
+ throw new KaddoMcpError(`Unknown layer "${opts.layer}". Use one of: business, product, tech, delivery.`);
26489
+ }
26490
+ scope = { kind: "layer", layer: layer2 };
26491
+ }
26492
+ const plan = buildConsolidationPlan(root, { scope });
26493
+ writeDerived(root, ".kaddo/consolidation-plan.md", renderConsolidationMarkdown(plan));
26494
+ writeDerived(root, ".kaddo/consolidation-plan.json", serializeConsolidationJson(plan));
26495
+ const warnings = plan.summary.requires_semantic_review > 0 ? [`${plan.summary.requires_semantic_review} finding(s) require semantic review before acting \u2014 nothing was changed automatically.`] : [];
26496
+ return {
26497
+ status: "ok",
26498
+ files_written: [".kaddo/consolidation-plan.md", ".kaddo/consolidation-plan.json"],
26499
+ summary: `Consolidation Plan generated (scope: ${plan.scope}, ${plan.summary.total} opportunity/opportunities).`,
26500
+ warnings,
26501
+ next_suggested_resources: ["kaddo://consolidation-plan"]
26502
+ };
26503
+ }
25502
26504
  function generateImpactReport(root, opts = {}) {
25503
- requireConfig(root);
26505
+ requireConfig2(root);
25504
26506
  requireKnowledge(root);
25505
26507
  const format = opts.format ?? "markdown";
25506
26508
  const report = buildImpactReport(root, { scope: opts.scope ?? "all", scopeSource: opts.scope ? "explicit" : "default" });
@@ -25516,7 +26518,7 @@ function generateImpactReport(root, opts = {}) {
25516
26518
  };
25517
26519
  }
25518
26520
  function generateSavingsReport(root, opts = {}) {
25519
- requireConfig(root);
26521
+ requireConfig2(root);
25520
26522
  requireKnowledge(root);
25521
26523
  const format = opts.format ?? "markdown";
25522
26524
  const report = buildSavingsReport(root, { scope: opts.scope ?? "all", scopeSource: opts.scope ? "explicit" : "default" });
@@ -25532,7 +26534,7 @@ function generateSavingsReport(root, opts = {}) {
25532
26534
  };
25533
26535
  }
25534
26536
  function generateDriftReport(root, opts = {}) {
25535
- requireConfig(root);
26537
+ requireConfig2(root);
25536
26538
  const format = opts.format ?? "markdown";
25537
26539
  const report = buildDriftReport(root);
25538
26540
  const content = format === "json" ? serializeDriftJson(report) : renderDriftMarkdown(report);
@@ -25547,7 +26549,7 @@ function generateDriftReport(root, opts = {}) {
25547
26549
  };
25548
26550
  }
25549
26551
  function generateQuestionsReport(root, opts = {}) {
25550
- requireConfig(root);
26552
+ requireConfig2(root);
25551
26553
  const format = opts.format ?? "markdown";
25552
26554
  const report = buildOpenQuestionsReport(root);
25553
26555
  const content = format === "json" ? serializeOpenQuestionsJson(report) : renderOpenQuestionsMarkdown(report);
@@ -25562,7 +26564,7 @@ function generateQuestionsReport(root, opts = {}) {
25562
26564
  };
25563
26565
  }
25564
26566
  function generateCapsuleDraft(root) {
25565
- const config = requireConfig(root);
26567
+ const config = requireConfig2(root);
25566
26568
  requireKnowledge(root);
25567
26569
  const capsule = buildCapsule(root, config);
25568
26570
  const name = slug6(config.project.name);
@@ -25782,13 +26784,13 @@ async function guardedAsync(root, fn) {
25782
26784
  throw err;
25783
26785
  }
25784
26786
  }
25785
- function generated(root, label, fn) {
26787
+ function generated(root, label2, fn) {
25786
26788
  try {
25787
26789
  assertKaddoProject(root);
25788
26790
  const result = fn();
25789
26791
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
25790
26792
  } catch (err) {
25791
- const message = err instanceof KaddoMcpError ? err.message : `Could not ${label}. ${String(err)}`;
26793
+ const message = err instanceof KaddoMcpError ? err.message : `Could not ${label2}. ${String(err)}`;
25792
26794
  return { content: [{ type: "text", text: message }], isError: true };
25793
26795
  }
25794
26796
  }
@@ -26489,6 +27491,19 @@ function createServer(root) {
26489
27491
  },
26490
27492
  async (args) => generated(root, "generate knowledge graph", () => generateGraph(root, args.scope ?? "active"))
26491
27493
  );
27494
+ server.registerTool(
27495
+ "kaddo_generate_consolidation_plan",
27496
+ {
27497
+ title: "Generate knowledge consolidation plan",
27498
+ description: `Analyze canonical knowledge and write .kaddo/consolidation-plan.md/.json \u2014 a reviewable projection of consolidation opportunities (redundancy, unincorporated learning, fragmentation, relationships, potential inconsistencies, organization). Deterministic: facts are "observed"; anything needing judgement is a "suggested" candidate flagged for semantic review. Optional layer (business|product|tech|delivery) or module scope. ${DERIVED_NOTE}`,
27499
+ inputSchema: { layer: z4.string().optional(), module: z4.string().optional() }
27500
+ },
27501
+ async (args) => generated(
27502
+ root,
27503
+ "generate consolidation plan",
27504
+ () => generateConsolidationPlan(root, { layer: args.layer, module: args.module })
27505
+ )
27506
+ );
26492
27507
  server.registerTool(
26493
27508
  "kaddo_generate_impact_report",
26494
27509
  {