@kaddo/cli 3.21.0 → 3.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -508,6 +508,7 @@ create --from roadmap → owners → guard → explain`.
508
508
  | v3.19 | Read-only MCP server `@kaddo/mcp` (resources, tools, prompts over stdio) |
509
509
  | v3.20 | MCP derived tools: safe regeneration of context/explain/understand/graph/capsule-draft under `.kaddo/` |
510
510
  | v3.21 | Reusable Skills layer (`kaddo add skills`); skills in context/explain/understand, agent prompts and MCP |
511
+ | v3.22 | Guard & graph scope semantics: graph scope metadata, contextual-empty messaging, Guard ownership scope (active + completed; `--include-archived`) |
511
512
 
512
513
  **Optional modules (installed with `kaddo add`):**
513
514
 
package/dist/index.js CHANGED
@@ -5524,10 +5524,14 @@ async function offerIgnore(dir, match) {
5524
5524
  log2.success(`${id} added to .kaddo/ignores.yml`);
5525
5525
  return true;
5526
5526
  }
5527
- function printCIJson(dir, touchedFiles, activeMatches, ignoredCount, pluginSignals, affectedOwners, workspace) {
5527
+ function printCIJson(dir, touchedFiles, activeMatches, ignoredCount, pluginSignals, affectedOwners, workspace, includeArchived) {
5528
5528
  const output = {
5529
5529
  kaddo_guard: true,
5530
5530
  ci: true,
5531
+ ownership_scope: {
5532
+ included: includeArchived ? ["draft", "ready", "in-progress", "blocked", "completed", "archived"] : ["draft", "ready", "in-progress", "blocked", "completed"],
5533
+ excluded: includeArchived ? [] : ["archived"]
5534
+ },
5531
5535
  touched_files: touchedFiles.length,
5532
5536
  fyi_count: activeMatches.length,
5533
5537
  ignored_count: ignoredCount,
@@ -5556,6 +5560,12 @@ function printCIJson(dir, touchedFiles, activeMatches, ignoredCount, pluginSigna
5556
5560
  }
5557
5561
  console.log(JSON.stringify(output, null, 2));
5558
5562
  }
5563
+ function printOwnershipScope(includeArchived) {
5564
+ console.log("Ownership scope:");
5565
+ console.log("- Active and completed Work Items");
5566
+ console.log(includeArchived ? "- Archived Work Items included (--include-archived)" : "- Archived Work Items excluded");
5567
+ console.log("");
5568
+ }
5559
5569
  async function runGuard(opts = {}) {
5560
5570
  const dir = cwd();
5561
5571
  const interactive = opts.interactive !== false && !opts.ci && !opts.json;
@@ -5597,7 +5607,10 @@ async function runGuard(opts = {}) {
5597
5607
  console.log("kaddo guard: no knowledge/ directory found. Run `kaddo init` first.");
5598
5608
  return;
5599
5609
  }
5600
- const artifacts = discoverKnowledge(dir);
5610
+ const includeArchived = opts.includeArchived === true;
5611
+ const artifacts = discoverKnowledge(dir).filter(
5612
+ (a) => !(a.isWorkItem && a.lifecycle === "archived" && !includeArchived)
5613
+ );
5601
5614
  const result = analyzeGuard(touchedFiles, artifacts, silentWithoutOwnership);
5602
5615
  const pluginSignals = runPlugins(plugins, currentFiles, (filePath) => {
5603
5616
  const abs = join(dir, filePath);
@@ -5618,7 +5631,7 @@ async function runGuard(opts = {}) {
5618
5631
  const ownerMapCI = loadOwners(dir);
5619
5632
  const matchedDomainsCI = collectMatchedDomains(activeMatches.map((m) => m.artifact.domains));
5620
5633
  const affectedOwnersCI = resolveAffectedOwners(matchedDomainsCI, ownerMapCI);
5621
- printCIJson(dir, touchedFiles, activeMatches, alreadyIgnoredMatches.length, pluginSignals, affectedOwnersCI, workspaceScan);
5634
+ printCIJson(dir, touchedFiles, activeMatches, alreadyIgnoredMatches.length, pluginSignals, affectedOwnersCI, workspaceScan, includeArchived);
5622
5635
  return;
5623
5636
  }
5624
5637
  if (result.silenced && pluginSignals.length === 0) {
@@ -5628,12 +5641,18 @@ async function runGuard(opts = {}) {
5628
5641
  if (result.matches.length === 0 && pluginSignals.length === 0) {
5629
5642
  if (workspaceScan) printWorkspaceHeader(workspaceScan);
5630
5643
  printHeader(touchedFiles);
5644
+ printOwnershipScope(includeArchived);
5631
5645
  console.log(" No artifact ownership matches found.");
5646
+ console.log("");
5647
+ console.log("Note:");
5648
+ console.log("Guard checks ownership from active and completed Work Items.");
5649
+ console.log("Run `kaddo explain` to inspect ownership coverage.");
5632
5650
  printUntracked();
5633
5651
  return;
5634
5652
  }
5635
5653
  if (workspaceScan) printWorkspaceHeader(workspaceScan);
5636
5654
  printHeader(touchedFiles);
5655
+ printOwnershipScope(includeArchived);
5637
5656
  printUntracked();
5638
5657
  for (const match of activeMatches) {
5639
5658
  printFYI(match, dir);
@@ -6040,6 +6059,11 @@ function loadExternalCapsules(dir) {
6040
6059
 
6041
6060
  // src/core/graph.ts
6042
6061
  var KNOWLEDGE2 = "knowledge";
6062
+ var ACTIVE_STATUSES = ["draft", "ready", "in-progress", "blocked"];
6063
+ var ALL_STATUSES = ["draft", "ready", "in-progress", "blocked", "completed"];
6064
+ function scopeStatuses(scope) {
6065
+ return scope === "all" ? { included: ALL_STATUSES, excluded: ["archived"] } : { included: ACTIVE_STATUSES, excluded: ["completed", "archived"] };
6066
+ }
6043
6067
  function toPosix2(p2) {
6044
6068
  return p2.replace(/\\/g, "/");
6045
6069
  }
@@ -6082,7 +6106,10 @@ function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
6082
6106
  }
6083
6107
  const all = discoverKnowledge(dir);
6084
6108
  const workItems = all.filter((a) => a.isWorkItem);
6085
- const selectedWIs = scope === "active" ? workItems.filter((a) => a.lifecycle && isActiveState(a.lifecycle)) : workItems;
6109
+ const { included, excluded } = scopeStatuses(scope);
6110
+ const includedSet = new Set(included);
6111
+ const activeWICount = workItems.filter((a) => a.lifecycle && isActiveState(a.lifecycle)).length;
6112
+ const selectedWIs = workItems.filter((a) => a.lifecycle && includedSet.has(a.lifecycle));
6086
6113
  for (const wi of selectedWIs) {
6087
6114
  const id = wi.id || wi.title;
6088
6115
  const wiNodeId = `wi:${id}`;
@@ -6152,6 +6179,7 @@ function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
6152
6179
  }
6153
6180
  }
6154
6181
  }
6182
+ const scopeReason = scope === "all" ? "All supported Work Item statuses are included." : activeWICount === 0 ? "No active Work Items found. Completed Work Items are excluded from active scope." : "Active Work Items only; completed and archived are excluded.";
6155
6183
  return {
6156
6184
  generated_at: now.toISOString(),
6157
6185
  project: {
@@ -6159,6 +6187,10 @@ function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
6159
6187
  state: config.project.state,
6160
6188
  structure: config.project.structure
6161
6189
  },
6190
+ scope,
6191
+ scope_reason: scopeReason,
6192
+ included_statuses: included,
6193
+ excluded_statuses: excluded,
6162
6194
  nodes: [...nodes.values()],
6163
6195
  edges
6164
6196
  };
@@ -6206,8 +6238,13 @@ function loadGraphSummary(dir) {
6206
6238
  const connected = new Set(
6207
6239
  edges.filter((e) => e.type === "owns" && activeWiIds.has(e.from)).map((e) => e.from)
6208
6240
  );
6241
+ const scope = graph.scope === "all" ? "all" : "active";
6209
6242
  return {
6210
6243
  generatedAt: String(graph.generated_at ?? ""),
6244
+ scope,
6245
+ scopeReason: String(graph.scope_reason ?? ""),
6246
+ includedStatuses: Array.isArray(graph.included_statuses) ? graph.included_statuses : [],
6247
+ excludedStatuses: Array.isArray(graph.excluded_statuses) ? graph.excluded_statuses : [],
6211
6248
  nodes: nodes.length,
6212
6249
  edges: edges.length,
6213
6250
  activeWorkItemsConnectedToCode: connected.size
@@ -6359,6 +6396,8 @@ function buildGraphHints(dir, graph, now = /* @__PURE__ */ new Date()) {
6359
6396
  const quality = assessQuality(nodes, relationshipEdges, hints.length);
6360
6397
  return {
6361
6398
  generated_at: now.toISOString(),
6399
+ scope: graph.scope,
6400
+ scope_reason: graph.scope_reason,
6362
6401
  quality,
6363
6402
  summary: { nodes, edges: graph.edges.length, hints: hints.length },
6364
6403
  metrics,
@@ -6394,13 +6433,23 @@ function renderGraphHintsMarkdown(report) {
6394
6433
  lines.push("");
6395
6434
  lines.push("## Summary");
6396
6435
  lines.push("");
6436
+ lines.push(`- Scope: ${report.scope} \u2014 ${report.scope_reason}`);
6397
6437
  lines.push(`- Relationship quality: ${report.quality} \u2014 ${QUALITY_NOTE[report.quality]}`);
6398
6438
  lines.push(`- Nodes: ${report.summary.nodes}`);
6399
6439
  lines.push(`- Edges: ${report.summary.edges}`);
6400
6440
  lines.push(`- Hints: ${report.summary.hints}`);
6401
6441
  lines.push("");
6402
6442
  if (report.hints.length === 0) {
6403
- lines.push("No hints \u2014 the declared relationships look healthy. \u{1F389}");
6443
+ if (report.quality === "empty") {
6444
+ lines.push("No active relationship hints were generated.");
6445
+ lines.push("");
6446
+ lines.push(`The graph is ${report.quality} for the **${report.scope}** scope \u2014 ${report.scope_reason}`);
6447
+ if (report.scope !== "all") {
6448
+ lines.push("Run `kaddo graph export --scope all` to inspect completed Work Items and historical relationships.");
6449
+ }
6450
+ } else {
6451
+ lines.push("No hints \u2014 the declared relationships look healthy. \u{1F389}");
6452
+ }
6404
6453
  lines.push("");
6405
6454
  return lines.join("\n");
6406
6455
  }
@@ -6436,6 +6485,8 @@ function loadGraphHints(dir) {
6436
6485
  const report = JSON.parse(readFile(p2));
6437
6486
  const hints = Array.isArray(report.hints) ? report.hints : [];
6438
6487
  return {
6488
+ scope: String(report.scope ?? "active"),
6489
+ scopeReason: String(report.scope_reason ?? ""),
6439
6490
  quality: report.quality ?? "empty",
6440
6491
  totalHints: hints.length,
6441
6492
  activeWorkItemHints: hints.filter((h) => h.artifact_type === "work-item").length,
@@ -6959,13 +7010,18 @@ function renderExplanationHuman(exp) {
6959
7010
  }
6960
7011
  if (exp.graph) {
6961
7012
  lines.push("## Knowledge Graph");
7013
+ lines.push(`- Scope: ${exp.graph.scope}`);
6962
7014
  lines.push(`- Nodes: ${exp.graph.nodes}`);
6963
7015
  lines.push(`- Edges: ${exp.graph.edges}`);
6964
7016
  if (exp.graphHints) {
6965
7017
  lines.push(`- Quality: ${exp.graphHints.quality}`);
6966
7018
  lines.push(`- Hints: ${exp.graphHints.totalHints}`);
6967
7019
  }
7020
+ if (exp.graph.scopeReason) lines.push(`- Reason: ${exp.graph.scopeReason}`);
6968
7021
  if (exp.graph.generatedAt) lines.push(`- Last exported: ${exp.graph.generatedAt}`);
7022
+ if (exp.graph.scope === "active" && exp.graphHints?.quality === "empty") {
7023
+ lines.push("- Tip: Run `kaddo graph export --scope all` to include completed Work Items");
7024
+ }
6969
7025
  lines.push("");
6970
7026
  }
6971
7027
  if (exp.skills.total > 0) {
@@ -7557,9 +7613,12 @@ function renderContextPack(pack) {
7557
7613
  parts.push(
7558
7614
  [
7559
7615
  "- Available: yes",
7616
+ `- Scope: ${pack.graph.scope}`,
7560
7617
  `- Nodes: ${pack.graph.nodes}`,
7561
7618
  `- Edges: ${pack.graph.edges}`,
7562
- `- Active Work Items connected to code: ${pack.graph.activeWorkItemsConnectedToCode}`
7619
+ ...pack.graphHints ? [`- Quality: ${pack.graphHints.quality}`] : [],
7620
+ `- Active Work Items connected to code: ${pack.graph.activeWorkItemsConnectedToCode}`,
7621
+ ...pack.graph.scopeReason ? [`- Reason: ${pack.graph.scopeReason}`] : []
7563
7622
  ].join("\n") + "\n"
7564
7623
  );
7565
7624
  parts.push("Full graph: `.kaddo/graph.json` / `.kaddo/graph.mmd` (run `kaddo graph export` to refresh).\n");
@@ -10797,9 +10856,15 @@ function runGraphExport(opts = {}) {
10797
10856
  writeFile(join(dir, ".kaddo", "graph-hints.md"), renderGraphHintsMarkdown(hints));
10798
10857
  writeFile(join(dir, ".kaddo", "graph-hints.json"), serializeGraphHintsJson(hints));
10799
10858
  written.push(".kaddo/graph-hints.md", ".kaddo/graph-hints.json");
10800
- log2.success("Knowledge graph exported.");
10859
+ log2.success(`Knowledge graph exported with ${scope} scope.`);
10801
10860
  log2.info(`Scope: ${scope} \xB7 Nodes: ${graph.nodes.length} \xB7 Edges: ${graph.edges.length}`);
10802
10861
  log2.info(`Relationship quality: ${hints.quality}`);
10862
+ const hasWorkItemNodes = graph.nodes.some((n) => n.type === "work-item");
10863
+ if (scope === "active" && !hasWorkItemNodes) {
10864
+ log2.warn("No active Work Items found.");
10865
+ log2.info("The active graph only includes knowledge layers.");
10866
+ log2.info("Run `kaddo graph export --scope all` to include completed Work Items.");
10867
+ }
10803
10868
  if (hints.hints.length > 0) {
10804
10869
  const shown = hints.hints.slice(0, 5);
10805
10870
  log2.warn(`${hints.hints.length} metadata hint(s) available:`);
@@ -10841,7 +10906,7 @@ var graphCmd = program.command("graph").description("Export the lightweight, fil
10841
10906
  graphCmd.command("export").description("Write the knowledge graph to .kaddo/graph.json and .kaddo/graph.mmd").option("--scope <scope>", "Graph scope: active (default) or all").option("--format <format>", "Output format: json, mermaid (default: both)").action((opts) => {
10842
10907
  runGraphExport(opts);
10843
10908
  });
10844
- program.command("guard").description("Check if modified code has related artifacts that were not updated").option("--staged", "Check only staged files").option("--no-interactive", "Disable interactive ignore prompts").option("--ci", "CI mode: output JSON, no prompts, non-blocking").option("--json", "Output JSON (alias for --ci)").option("--workspace", "Also check local mapped module repos from .kaddo/modules.yml (opt-in)").action(async (opts) => {
10909
+ program.command("guard").description("Check if modified code has related artifacts that were not updated").option("--staged", "Check only staged files").option("--no-interactive", "Disable interactive ignore prompts").option("--ci", "CI mode: output JSON, no prompts, non-blocking").option("--json", "Output JSON (alias for --ci)").option("--workspace", "Also check local mapped module repos from .kaddo/modules.yml (opt-in)").option("--include-archived", "Include archived Work Items in ownership matching (excluded by default)").action(async (opts) => {
10845
10910
  await runGuard(opts);
10846
10911
  });
10847
10912
  var ignoreCmd = program.command("ignore").description("Manage guard ignore list");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaddo/cli",
3
- "version": "3.21.0",
3
+ "version": "3.22.0",
4
4
  "description": "Knowledge Driven Development toolkit",
5
5
  "license": "MIT",
6
6
  "repository": {