@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 +1 -0
- package/dist/index.js +73 -8
- package/package.json +1 -1
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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");
|